verstak/internal/core/watcher/service.go

94 lines
2.4 KiB
Go

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()
}