new flat KV structure

This commit is contained in:
Pierre
2026-02-08 21:53:42 +01:00
parent 4c3a0df4b4
commit 79d86b62d3
20 changed files with 2335 additions and 110 deletions
+143 -9
View File
@@ -13,10 +13,11 @@ import (
// Watcher provides file scanning and event dispatching
type Watcher struct {
config *config.Config
debouncer *Debouncer
dispatcher *Dispatcher
filesPath string
config *config.Config
debouncer *Debouncer
dispatcher *Dispatcher
filesPath string
stateRegistry *StateRegistry
mu sync.RWMutex
eventBuffer []FileEvent
@@ -27,11 +28,12 @@ func NewWatcher(cfg *config.Config, dispatcher *Dispatcher) (*Watcher, error) {
debouncer := NewDebouncer(0) // No debouncing for polling-based scanning
w := &Watcher{
config: cfg,
debouncer: debouncer,
dispatcher: dispatcher,
filesPath: cfg.FilesPath,
eventBuffer: make([]FileEvent, 0, 1000),
config: cfg,
debouncer: debouncer,
dispatcher: dispatcher,
filesPath: cfg.FilesPath,
stateRegistry: NewStateRegistry(),
eventBuffer: make([]FileEvent, 0, 1000),
}
return w, nil
@@ -110,6 +112,138 @@ func (w *Watcher) ScanDirectory(root string) int {
return count
}
// ScanDirectoryDiff scans a directory and only emits events for differences
// Returns the count of added, changed, and deleted files
func (w *Watcher) ScanDirectoryDiff(root string, watcherID string) DiffScanResult {
result := DiffScanResult{}
state := w.stateRegistry.GetOrCreate(watcherID)
// Get snapshot of known files
knownFiles := state.GetAll()
seenFiles := make(map[string]bool)
filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
// Skip directories
if info.IsDir() {
return nil
}
// Skip hidden files
if strings.HasPrefix(filepath.Base(path), ".") {
return nil
}
// Get relative path
relPath, err := filepath.Rel(w.filesPath, path)
if err != nil {
relPath = path
}
relPath = filepath.ToSlash(relPath)
seenFiles[relPath] = true
// Check current file state
currentMtime := info.ModTime().UnixNano()
currentSize := info.Size()
prevState := knownFiles[relPath]
if prevState == nil {
// New file - emit add event
event := FileEvent{
Type: EventTypeAdd,
Path: relPath,
Size: currentSize,
Timestamp: NowMS(),
}
// Compute midhash256
if hash, err := ComputeMidHash256(path); err == nil {
event.MidHash256 = hash
}
// Dispatch event
w.dispatcher.Dispatch(event)
// Update state
state.Set(relPath, &FileState{
Size: currentSize,
MtimeNano: currentMtime,
MidHash: event.MidHash256,
})
result.Added++
} else if prevState.Size != currentSize || prevState.MtimeNano != currentMtime {
// File changed - emit change event
event := FileEvent{
Type: EventTypeChange,
Path: relPath,
Size: currentSize,
Timestamp: NowMS(),
}
// Compute midhash256
if hash, err := ComputeMidHash256(path); err == nil {
event.MidHash256 = hash
}
// Dispatch event
w.dispatcher.Dispatch(event)
// Update state
state.Set(relPath, &FileState{
Size: currentSize,
MtimeNano: currentMtime,
MidHash: event.MidHash256,
})
result.Changed++
}
// If file exists and hasn't changed, do nothing
return nil
})
// Check for deleted files (in known but not seen)
for relPath := range knownFiles {
if !seenFiles[relPath] {
// File deleted - emit delete event
event := FileEvent{
Type: EventTypeDelete,
Path: relPath,
Timestamp: NowMS(),
}
w.dispatcher.Dispatch(event)
state.Delete(relPath)
result.Deleted++
}
}
result.Total = state.Count()
return result
}
// ClearState clears the tracked state for a watcher
func (w *Watcher) ClearState(watcherID string) {
w.stateRegistry.Clear(watcherID)
}
// ClearEventBuffer clears the in-memory event buffer
func (w *Watcher) ClearEventBuffer() {
w.mu.Lock()
defer w.mu.Unlock()
w.eventBuffer = make([]FileEvent, 0, 1000)
}
// GetStateRegistry returns the state registry for external access
func (w *Watcher) GetStateRegistry() *StateRegistry {
return w.stateRegistry
}
// ScanMountPath scans a specific mount path and returns file count
// This is used by the mount poller to scan individual mounts
func (w *Watcher) ScanMountPath(mountPath string) (int, error) {