package main import ( "os" "strings" "sync" "github.com/fsnotify/fsnotify" "github.com/fxamacker/cbor/v2" ) type WatcherHandler struct { watcher *fsnotify.Watcher mutex sync.Mutex watchList map[int]string watchRefs map[string]int } func NewWatcherHandler() *WatcherHandler { watcher, err := fsnotify.NewWatcher() if err != nil { panic(err) } return &WatcherHandler{ watcher: watcher, watchList: make(map[int]string), watchRefs: make(map[string]int), } } type watchevent struct { Type string Id int Path string EventType int } func handleEvent(event fsnotify.Event, watchHandler *WatcherHandler, out chan<- []byte) { watchHandler.mutex.Lock() defer watchHandler.mutex.Unlock() // Find which watchList entries correspond to the event for id, path := range watchHandler.watchList { // See if the event path is a subpath of the watch path if strings.HasPrefix(event.Name, path) { data, _ := cbor.Marshal(watchevent{ Type: "watchEvent", Id: id, Path: event.Name, EventType: int(event.Op), }) if event.Op == fsnotify.Remove && event.Name == path { // Check if file exists _, err := os.Stat(event.Name) if err == nil { // File exists, so lets add it again err := watchHandler.watcher.Add(event.Name) if err != nil { sendError(out, command{Type: "watchEvent", Id: id}, err) } } } out <- data } } } func (watchHandler *WatcherHandler) start(out chan<- []byte) { for { select { case event, ok := <-watchHandler.watcher.Events: if !ok { return } handleEvent(event, watchHandler, out) case err, ok := <-watchHandler.watcher.Errors: if !ok { return } panic(err) } } } type addwatchresult struct { Type string Id int Result bool } func (watchHandler *WatcherHandler) processAdd(cmd command, out chan<- []byte) { // TODO: Resolve links err := watchHandler.watcher.Add(cmd.Path) if err != nil { sendError(out, cmd, err) return } watchHandler.mutex.Lock() defer watchHandler.mutex.Unlock() watchHandler.watchList[cmd.Id] = cmd.Path watchHandler.watchRefs[cmd.Path]++ data, _ := cbor.Marshal(addwatchresult{ Type: "addwatchresult", Id: cmd.Id, Result: true, }) out <- data } func (watchHandler *WatcherHandler) processStop(cmd command, out chan<- []byte) { watchHandler.mutex.Lock() defer watchHandler.mutex.Unlock() path, ok := watchHandler.watchList[cmd.Id] if !ok { sendError(out, cmd, &watchnotfounderror{}) return } if watchHandler.watchRefs[path] <= 0 { sendError(out, cmd, &watchnotfounderror{}) } watchHandler.watchRefs[path]-- if watchHandler.watchRefs[path] == 0 { err := watchHandler.watcher.Remove(path) if err != nil { sendError(out, cmd, err) return } delete(watchHandler.watchRefs, path) } delete(watchHandler.watchList, cmd.Id) data, _ := cbor.Marshal(removewatchresult{ Type: "removewatchresult", Id: cmd.Id, Result: true, }) out <- data } type removewatchresult struct { Type string Id int Result bool } type watchnotfounderror struct { } func (e *watchnotfounderror) Error() string { return "Watch not found" }