service.go 6.2 KB

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