package watcher import ( "iter" "log" "os" "runtime" "strings" "github.com/rjeczalik/notify" ) type WatchEventKind int const ( Update WatchEventKind = iota Clean ) // WatchEvent represents a change to a watched folder. It notes which file // changed and what change happened to it. type WatchEvent struct { File string Kind WatchEventKind } func updated(f string) *WatchEvent { return &WatchEvent{ File: f, Kind: Update, } } func cleaned(f string) *WatchEvent { return &WatchEvent{ File: f, Kind: Clean, } } // Watcher classifies notify.Events into updates and deletes, and calls the // respective functions for a file when those events happen to that file. func Watch(folder string, up Updater[string]) { cwd, err := os.Getwd() if err != nil { log.Fatal("could not get current working directory for listener!") } cwd = cwd + "/" c := make(chan notify.EventInfo, 1) var events []notify.Event // inotify events prevent double-firing of // certain events in Linux. if runtime.GOOS == "linux" { events = []notify.Event{ notify.InCloseWrite, notify.InMovedFrom, notify.InMovedTo, notify.InDelete, } } else { events = []notify.Event{ notify.Create, notify.Remove, notify.Rename, notify.Write, } } err = notify.Watch(folder, c, events...) if err != nil { log.Fatalf("could not setup watcher for folder %s: %s", folder, err) } defer notify.Stop(c) for { ei := <-c log.Printf("event: %s", ei.Event()) switch ei.Event() { case notify.InCloseWrite, notify.InMovedTo, notify.Create, notify.Rename, notify.Write: filePath := strings.TrimPrefix(ei.Path(), cwd) log.Printf("updating file %s", filePath) err := up.Fetch(strings.TrimPrefix(filePath, folder)) if err != nil { log.Printf("up.Fetch(%q): %v", filePath, err) } case notify.InMovedFrom, notify.InDelete, notify.Remove: filePath := strings.TrimPrefix(ei.Path(), cwd) log.Printf("cleaning file %s", filePath) err := up.Delete(strings.TrimPrefix(filePath, folder)) if err != nil { log.Printf("up.Delete(%q): %v", filePath, err) } } } } // Updater is a key-value store which can be informed when to recompute values // for a particular key. Updaters are normally also AutoMaps. type Updater[K any] interface { Fetch(K) error Delete(K) error } // AutoMap is a key-value store where the values are automatically computed by // the store itself, based on the key. type AutoMap[K, V any] interface { Get(K) (V, bool) } // OrderedAutoMap is an AutoMap that provides an iterator over its // currently-existing keys in a known order. type OrderedAutoMap[K, V any] interface { AutoMap[K, V] All() iter.Seq2[K, V] }