94 lines
2.4 KiB
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()
|
|
}
|