feat(server): targeted ScanFiles + fsnotify library watcher
Add Scanner.ScanFiles (watcher-driven targeted scan returning changed album IDs) and a recursive fsnotify Watcher that debounces filesystem events and enriches just the affected albums inline. Pure classifyEvent/drainPending seams unit-tested; ScanFiles covered in the scanner integration test.
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
package library
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
)
|
||||
|
||||
func TestClassifyEvent(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
op fsnotify.Op
|
||||
isDir bool
|
||||
isAudio bool
|
||||
want watchAction
|
||||
}{
|
||||
{"create audio file enqueues", fsnotify.Create, false, true, watchEnqueueFile},
|
||||
{"write audio file enqueues", fsnotify.Write, false, true, watchEnqueueFile},
|
||||
{"create non-audio file ignored", fsnotify.Create, false, false, watchIgnore},
|
||||
{"create directory scans it", fsnotify.Create, true, false, watchScanDir},
|
||||
{"write on directory ignored", fsnotify.Write, true, false, watchIgnore},
|
||||
{"remove audio file ignored (no pruning)", fsnotify.Remove, false, true, watchIgnore},
|
||||
{"rename audio file ignored", fsnotify.Rename, false, true, watchIgnore},
|
||||
{"chmod audio file ignored", fsnotify.Chmod, false, true, watchIgnore},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := classifyEvent(tt.op, tt.isDir, tt.isAudio); got != tt.want {
|
||||
t.Fatalf("classifyEvent(%v, dir=%v, audio=%v) = %v, want %v",
|
||||
tt.op, tt.isDir, tt.isAudio, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDrainPending(t *testing.T) {
|
||||
t.Run("empty set returns nil", func(t *testing.T) {
|
||||
if got := drainPending(map[string]struct{}{}); got != nil {
|
||||
t.Fatalf("drainPending(empty) = %v, want nil", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("returns paths and clears the set", func(t *testing.T) {
|
||||
pending := map[string]struct{}{
|
||||
"/music/a.mp3": {},
|
||||
"/music/b.flac": {},
|
||||
}
|
||||
got := drainPending(pending)
|
||||
sort.Strings(got)
|
||||
want := []string{"/music/a.mp3", "/music/b.flac"}
|
||||
if len(got) != len(want) || got[0] != want[0] || got[1] != want[1] {
|
||||
t.Fatalf("drainPending = %v, want %v", got, want)
|
||||
}
|
||||
if len(pending) != 0 {
|
||||
t.Fatalf("pending not cleared: %v", pending)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user