service.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. // File: pkg/discovery/service.go
  2. package discovery
  3. import (
  4. "context"
  5. "fmt"
  6. "sync"
  7. "time"
  8. "TengTuToolbox/localsend/logging"
  9. "TengTuToolbox/localsend/model"
  10. )
  11. // Service coordinates different discovery mechanisms
  12. type Service struct {
  13. config *ServiceConfig
  14. multicast MulticastDiscoverer
  15. devices map[string]*model.Device
  16. devicesMutex sync.RWMutex
  17. handlers []func(*model.Device)
  18. handlersMutex sync.RWMutex
  19. announceTimer *time.Timer
  20. peerCache *PeerCache
  21. stopCh chan struct{}
  22. stopOnce sync.Once
  23. logger *logging.Logger
  24. }
  25. // ServiceConfig contains settings for the discovery service
  26. type ServiceConfig struct {
  27. MulticastConfig *MulticastConfig
  28. AnnounceInterval time.Duration
  29. DeviceTimeout time.Duration
  30. EnableAnnouncement bool
  31. }
  32. // DefaultServiceConfig returns a default configuration for the discovery service
  33. func DefaultServiceConfig() *ServiceConfig {
  34. return &ServiceConfig{
  35. MulticastConfig: DefaultMulticastConfig(),
  36. AnnounceInterval: 30 * time.Second,
  37. DeviceTimeout: 2 * time.Minute,
  38. EnableAnnouncement: true,
  39. }
  40. }
  41. // NewService creates a new discovery service
  42. func NewService(config *ServiceConfig, multicast MulticastDiscoverer, logger *logging.Logger) *Service {
  43. if config == nil {
  44. config = DefaultServiceConfig()
  45. }
  46. if logger == nil {
  47. logger = logging.NewQuiet()
  48. }
  49. s := &Service{
  50. config: config,
  51. devices: make(map[string]*model.Device),
  52. multicast: multicast,
  53. stopCh: make(chan struct{}),
  54. logger: logger,
  55. }
  56. // ALWAYS ensure multicast propagates raw events upward to the Service state
  57. if s.multicast != nil {
  58. s.multicast.AddDeviceHandler(func(device *model.Device) {
  59. s.updateDevice(device)
  60. })
  61. }
  62. return s
  63. }
  64. // SetPeerCache sets the persistent peer cache for both service and multicast.
  65. func (s *Service) SetPeerCache(cache *PeerCache) {
  66. s.peerCache = cache
  67. if mc, ok := s.multicast.(interface{ SetPeerCache(*PeerCache) }); ok {
  68. mc.SetPeerCache(cache)
  69. }
  70. }
  71. // Start initializes and starts the discovery service for listening and periodic announcements
  72. func (s *Service) Start(ctx context.Context, dto model.MulticastDto) error {
  73. s.multicast.SetDto(dto)
  74. if err := s.multicast.StartListening(ctx); err != nil {
  75. return fmt.Errorf("failed to start multicast discovery: %w", err)
  76. }
  77. if s.config.EnableAnnouncement {
  78. s.startAnnouncementLoop(ctx)
  79. }
  80. if err := s.multicast.SendDiscoveryAnnouncement(); err != nil {
  81. s.logger.Errorf("Failed to send initial discovery announcement: %v", err)
  82. }
  83. // Probe cached peers in the background
  84. if s.peerCache != nil {
  85. probeCtx, cancelProbe := context.WithTimeout(ctx, 10*time.Second)
  86. go func() {
  87. defer cancelProbe()
  88. ProbeCached(probeCtx, s.peerCache, func(device *model.Device) {
  89. s.updateDevice(device)
  90. }, s.logger)
  91. }()
  92. }
  93. return nil
  94. }
  95. // Stop stops the discovery service
  96. func (s *Service) Stop() {
  97. s.logger.Debugf("Stopping discovery service...")
  98. s.stopOnce.Do(func() {
  99. close(s.stopCh)
  100. })
  101. if s.multicast != nil {
  102. s.multicast.Stop()
  103. }
  104. if s.announceTimer != nil {
  105. s.announceTimer.Stop()
  106. }
  107. s.logger.Debugf("Discovery service stopped.")
  108. }
  109. // Discover performs a discovery scan and returns found devices.
  110. func (s *Service) Discover(ctx context.Context, dto model.MulticastDto) ([]*model.Device, error) {
  111. s.logger.Debugf("Performing one-off discovery scan...")
  112. s.multicast.SetDto(dto)
  113. // MUST be listening to receive multicast responses
  114. if err := s.multicast.StartListening(ctx); err != nil {
  115. s.logger.Debugf("Failed to start multicast listener (responses may be missed): %v", err)
  116. }
  117. if err := s.multicast.SendDiscoveryAnnouncement(); err != nil {
  118. s.logger.Errorf("Failed to send initial discovery announcement: %v", err)
  119. }
  120. var lastDevices []*model.Device
  121. ticker := time.NewTicker(250 * time.Millisecond)
  122. defer ticker.Stop()
  123. for {
  124. select {
  125. case <-ctx.Done():
  126. lastDevices = s.GetDevices() // Perform one absolute final read to guarantee capture
  127. s.logger.Debugf("Discovery scan finished. %d device(s) found.", len(lastDevices))
  128. return lastDevices, nil
  129. case <-ticker.C:
  130. devices := s.GetDevices()
  131. lastDevices = devices
  132. }
  133. }
  134. }
  135. // GetDevices returns all currently known devices
  136. func (s *Service) GetDevices() []*model.Device {
  137. s.devicesMutex.RLock()
  138. defer s.devicesMutex.RUnlock()
  139. devices := make([]*model.Device, 0, len(s.devices))
  140. for _, device := range s.devices {
  141. if !device.IsStale(s.config.DeviceTimeout) {
  142. devices = append(devices, device)
  143. }
  144. }
  145. return devices
  146. }
  147. // GetDevice returns a specific device by ID
  148. func (s *Service) GetDevice(id string) *model.Device {
  149. s.devicesMutex.RLock()
  150. defer s.devicesMutex.RUnlock()
  151. if device, ok := s.devices[id]; ok {
  152. if !device.IsStale(s.config.DeviceTimeout) {
  153. return device
  154. }
  155. }
  156. return nil
  157. }
  158. // AddDeviceHandler adds a handler for device discovery events
  159. func (s *Service) AddDeviceHandler(handler func(*model.Device)) {
  160. s.handlersMutex.Lock()
  161. defer s.handlersMutex.Unlock()
  162. s.handlers = append(s.handlers, handler)
  163. }
  164. // updateDevice updates the device list with a newly discovered device
  165. func (s *Service) updateDevice(device *model.Device) {
  166. s.devicesMutex.Lock()
  167. defer s.devicesMutex.Unlock()
  168. existingDevice, exists := s.devices[device.Fingerprint]
  169. if exists {
  170. // Update last seen timestamp
  171. existingDevice.UpdateLastSeen()
  172. } else {
  173. // Add new device
  174. s.devices[device.Fingerprint] = device
  175. // Notify Service-level handlers about the new device ONLY ONCE (debounces the bursts)
  176. s.handlersMutex.RLock()
  177. handlers := make([]func(*model.Device), len(s.handlers))
  178. copy(handlers, s.handlers)
  179. s.handlersMutex.RUnlock()
  180. for _, handler := range handlers {
  181. go handler(device)
  182. }
  183. }
  184. }
  185. // startAnnouncementLoop starts a periodic announcement loop
  186. func (s *Service) startAnnouncementLoop(ctx context.Context) {
  187. s.announceTimer = time.NewTimer(s.config.AnnounceInterval)
  188. go func() {
  189. for {
  190. select {
  191. case <-ctx.Done():
  192. s.announceTimer.Stop()
  193. return
  194. case <-s.stopCh:
  195. s.announceTimer.Stop()
  196. return
  197. case <-s.announceTimer.C:
  198. if err := s.multicast.SendDiscoveryAnnouncement(); err != nil {
  199. s.logger.Errorf("Failed to send periodic announcement: %v", err)
  200. }
  201. s.announceTimer.Reset(s.config.AnnounceInterval)
  202. }
  203. }
  204. }()
  205. }