service.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. package sitecheck
  2. import (
  3. "context"
  4. "sync"
  5. "time"
  6. "github.com/0xJacky/Nginx-UI/internal/cache"
  7. "github.com/uozi-tech/cosy/logger"
  8. )
  9. // Service manages site checking operations
  10. type Service struct {
  11. checker *SiteChecker
  12. ctx context.Context
  13. cancel context.CancelFunc
  14. ticker *time.Ticker
  15. mu sync.RWMutex
  16. running bool
  17. }
  18. var (
  19. globalService *Service
  20. )
  21. // Init initializes the site checking service
  22. func Init(ctx context.Context) {
  23. globalService = NewService(ctx, DefaultCheckOptions())
  24. globalService.Start()
  25. }
  26. // GetService returns the singleton service instance
  27. func GetService() *Service {
  28. return globalService
  29. }
  30. // waitForSiteCollection waits for the cache scanner to collect sites with progressive backoff
  31. func (s *Service) waitForSiteCollection(ctx context.Context) {
  32. startTime := time.Now()
  33. logger.Debug("Waiting for site collection to complete...")
  34. // First, wait for the initial cache scan to complete
  35. // This is much more efficient than polling
  36. logger.Info("Waiting for initial cache scan to complete before site collection...")
  37. cache.WaitForInitialScanComplete()
  38. logger.Infof("Initial cache scan completed after %v, now collecting sites", time.Since(startTime))
  39. // Now collect sites - the cache scanning should have populated IndexedSites
  40. s.checker.CollectSites()
  41. siteCount := s.checker.GetSiteCount()
  42. logger.Infof("Site collection completed: found %d sites after %v", siteCount, time.Since(startTime))
  43. // If no sites found after cache scan, do a brief fallback check
  44. if siteCount == 0 {
  45. logger.Debug("No sites found after cache scan completion, doing fallback check...")
  46. maxWaitTime := 10 * time.Second // Reduced from 30s since cache scan already completed
  47. checkInterval := 2 * time.Second // Reduced interval
  48. for {
  49. // Check if context is cancelled
  50. select {
  51. case <-ctx.Done():
  52. logger.Debug("Site collection fallback wait cancelled")
  53. return
  54. default:
  55. }
  56. // Re-check for sites
  57. s.checker.CollectSites()
  58. siteCount = s.checker.GetSiteCount()
  59. logger.Debugf("Fallback site collection check: found %d sites (total waited %v)",
  60. siteCount, time.Since(startTime))
  61. if siteCount > 0 {
  62. logger.Warnf("Site collection completed via fallback: found %d sites", siteCount)
  63. return
  64. }
  65. // Check if we've exceeded max fallback wait time
  66. if time.Since(startTime) >= maxWaitTime {
  67. logger.Warnf("Site collection fallback timeout after %v - proceeding with empty site list",
  68. time.Since(startTime))
  69. return
  70. }
  71. // Wait before next check
  72. select {
  73. case <-ctx.Done():
  74. return
  75. case <-time.After(checkInterval):
  76. // Continue to next iteration
  77. }
  78. }
  79. }
  80. }
  81. // NewService creates a new site checking service
  82. func NewService(ctx context.Context, options CheckOptions) *Service {
  83. return NewServiceWithContext(ctx, options)
  84. }
  85. // NewServiceWithContext creates a new site checking service with a parent context
  86. func NewServiceWithContext(parentCtx context.Context, options CheckOptions) *Service {
  87. ctx, cancel := context.WithCancel(parentCtx)
  88. return &Service{
  89. checker: NewSiteChecker(options),
  90. ctx: ctx,
  91. cancel: cancel,
  92. }
  93. }
  94. // SetUpdateCallback sets the callback function for site updates
  95. func (s *Service) SetUpdateCallback(callback func([]*SiteInfo)) {
  96. s.checker.SetUpdateCallback(callback)
  97. }
  98. // Start begins the site checking service
  99. func (s *Service) Start() {
  100. s.mu.Lock()
  101. defer s.mu.Unlock()
  102. if s.running {
  103. return
  104. }
  105. s.running = true
  106. logger.Info("Starting site checking service")
  107. // Initial collection and check with delay to allow cache scanner to complete
  108. go func() {
  109. logger.Info("Started sitecheck initial collection goroutine")
  110. // Give cache scanner more time to start up before checking
  111. time.Sleep(5 * time.Second)
  112. // Wait for cache scanner to collect sites with progressive backoff
  113. s.waitForSiteCollection(s.ctx)
  114. s.checker.CheckAllSites(s.ctx)
  115. logger.Info("Sitecheck initial collection goroutine completed")
  116. }()
  117. // Start periodic checking (every 5 minutes)
  118. s.ticker = time.NewTicker(5 * time.Minute)
  119. go func() {
  120. logger.Info("Started sitecheck periodicCheck goroutine")
  121. s.periodicCheck()
  122. logger.Info("Sitecheck periodicCheck goroutine completed")
  123. }()
  124. }
  125. // Stop stops the site checking service
  126. func (s *Service) Stop() {
  127. s.mu.Lock()
  128. defer s.mu.Unlock()
  129. if !s.running {
  130. return
  131. }
  132. s.running = false
  133. logger.Info("Stopping site checking service")
  134. if s.ticker != nil {
  135. s.ticker.Stop()
  136. }
  137. s.cancel()
  138. }
  139. // Restart restarts the site checking service
  140. func (s *Service) Restart() {
  141. s.Stop()
  142. time.Sleep(100 * time.Millisecond) // Brief pause
  143. s.Start()
  144. }
  145. // periodicCheck runs periodic site checks
  146. func (s *Service) periodicCheck() {
  147. for {
  148. select {
  149. case <-s.ctx.Done():
  150. return
  151. case <-s.ticker.C:
  152. logger.Debug("Starting periodic site check")
  153. s.checker.CollectSites() // Re-collect in case sites changed
  154. s.checker.CheckAllSites(s.ctx)
  155. }
  156. }
  157. }
  158. // RefreshSites manually triggers a site collection and check
  159. func (s *Service) RefreshSites() {
  160. go func() {
  161. logger.Info("Started sitecheck manual refresh goroutine")
  162. logger.Info("Manually refreshing sites")
  163. s.checker.CollectSites()
  164. s.checker.CheckAllSites(s.ctx)
  165. logger.Info("Sitecheck manual refresh goroutine completed")
  166. }()
  167. }
  168. // GetSites returns all checked sites with custom ordering applied
  169. func (s *Service) GetSites() []*SiteInfo {
  170. sites := s.checker.GetSitesList()
  171. // Apply custom ordering from database
  172. return s.applySiteOrdering(sites)
  173. }
  174. // GetSiteByURL returns a specific site by URL
  175. func (s *Service) GetSiteByURL(url string) *SiteInfo {
  176. sites := s.checker.GetSites()
  177. if site, exists := sites[url]; exists {
  178. return site
  179. }
  180. return nil
  181. }
  182. // IsRunning returns whether the service is currently running
  183. func (s *Service) IsRunning() bool {
  184. s.mu.RLock()
  185. defer s.mu.RUnlock()
  186. return s.running
  187. }
  188. // applySiteOrdering applies custom ordering from database to sites
  189. func (s *Service) applySiteOrdering(sites []*SiteInfo) []*SiteInfo {
  190. return applyCustomOrdering(sites)
  191. }