package watcher import ( "log" "sync" "verstak/internal/core/activity" "verstak/internal/core/files" "verstak/internal/core/nodes" ) // Service wraps both the snapshot scanner and the fsnotify watcher. // It provides a single entry point for the app to start/stop file watching. type Service struct { vaultRoot string nodes *nodes.Repository files *files.Service activity *activity.Service mu sync.Mutex watcher *Watcher enabled bool } // NewService creates a combined watcher service. // It does not start watching until Start is called. func NewService(vaultRoot string, nr *nodes.Repository, fs *files.Service, as *activity.Service) *Service { return &Service{ vaultRoot: vaultRoot, nodes: nr, files: fs, activity: as, } } // Start performs a snapshot scan and then starts the real-time watcher. // If enabled is false, only the snapshot scan runs (one-shot). func (s *Service) Start(enabled bool) (*SnapshotResult, error) { s.mu.Lock() defer s.mu.Unlock() s.enabled = enabled // Always run snapshot scan first. scanner := NewScanner(s.vaultRoot, s.nodes, s.files, s.activity) result, err := scanner.Run() if err != nil { log.Printf("[watcher] snapshot scan error: %v", err) result = &SnapshotResult{} } if result.MissingFiles > 0 || result.RestoredFiles > 0 || result.ModifiedFiles > 0 { log.Printf("[watcher] snapshot scan: %d missing, %d restored, %d modified, %d new, %d nodes", result.MissingFiles, result.RestoredFiles, result.ModifiedFiles, result.NewFiles, result.NodesScanned) } if enabled { w := NewWatcher(s.vaultRoot, s.nodes, s.files, s.activity) if err := w.Start(); err != nil { log.Printf("[watcher] failed to start real-time watcher: %v", err) return result, nil } s.watcher = w log.Printf("[watcher] real-time watcher started") } return result, nil } // Stop shuts down the real-time watcher if running. func (s *Service) Stop() { s.mu.Lock() defer s.mu.Unlock() if s.watcher != nil { s.watcher.Stop() s.watcher = nil } s.enabled = false } // IsWatching returns whether the real-time watcher is active. func (s *Service) IsWatching() bool { s.mu.Lock() defer s.mu.Unlock() return s.watcher != nil && s.watcher.IsWatching() } // RunScanner performs a one-shot snapshot scan (even if watcher is active). func (s *Service) RunScanner() (*SnapshotResult, error) { scanner := NewScanner(s.vaultRoot, s.nodes, s.files, s.activity) return scanner.Run() }