package gonotify import ( "context" "path/filepath" "sync" ) // FileWatcher waits for events generated by filesystem for a specific list of file paths, including // IN_CREATE for not yet existing files and IN_DELETE for removed. type FileWatcher struct { C chan FileEvent done chan struct{} } // NewFileWatcher creates FileWatcher with provided inotify mask and list of files to wait events for. func NewFileWatcher(ctx context.Context, mask uint32, files ...string) (*FileWatcher, error) { f := &FileWatcher{ C: make(chan FileEvent), done: make(chan struct{}), } inotify, err := NewInotify(ctx) if err != nil { return nil, err } expectedPaths := make(map[string]bool) for _, file := range files { _, err := inotify.AddWatch(filepath.Dir(file), mask) if err != nil { return nil, err } expectedPaths[file] = true } events := make(chan FileEvent) wg := sync.WaitGroup{} wg.Add(1) go func() { defer wg.Done() for { select { case <-ctx.Done(): close(events) return default: } raw, err := inotify.Read() if err != nil { close(events) return } for _, event := range raw { select { case <-ctx.Done(): return case events <- FileEvent{ InotifyEvent: event, }: //noop } } } }() wg.Add(1) go func() { defer wg.Done() defer close(f.C) for { select { case <-ctx.Done(): return case event, ok := <-events: if !ok { select { case <-ctx.Done(): case f.C <- FileEvent{Eof: true}: } return } if !expectedPaths[event.Name] { continue } select { case <-ctx.Done(): return case f.C <- event: } } } }() go func() { <-inotify.Done() wg.Wait() close(f.done) }() return f, nil } // Done returns a channel that is closed when the FileWatcher is done. func (f *FileWatcher) Done() <-chan struct{} { return f.done }