Skip to content

Instantly share code, notes, and snippets.

@lbe
Created June 23, 2024 16:29
Show Gist options
  • Save lbe/0cdafec787901baf882e63f1d9de25d2 to your computer and use it in GitHub Desktop.
Save lbe/0cdafec787901baf882e63f1d9de25d2 to your computer and use it in GitHub Desktop.
//Originally github.com/dietsche/rfsnotify
//Modified to use Walkdir instead of Walk
// Package rfsnotify implements recursive folder monitoring by wrapping the excellent fsnotify library
package rfsnotify
import (
"errors"
"io/fs"
"os"
"path/filepath"
"time"
"gopkg.in/fsnotify.v1"
//"github.com/fsnotify/fsnotify"
)
// RWatcher wraps fsnotify.Watcher. When fsnotify adds recursive watches, you should be able to switch your code to use fsnotify.Watcher
type RWatcher struct {
Events chan fsnotify.Event
Errors chan error
done chan struct{}
fsnotify *fsnotify.Watcher
isClosed bool
}
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
func NewWatcher() (*RWatcher, error) {
fsWatch, err := fsnotify.NewWatcher()
if err != nil {
return nil, err
}
m := &RWatcher{}
m.fsnotify = fsWatch
m.Events = make(chan fsnotify.Event)
m.Errors = make(chan error)
m.done = make(chan struct{})
go m.start()
return m, nil
}
// Add starts watching the named file or directory (non-recursively).
func (m *RWatcher) Add(name string) error {
if m.isClosed {
return errors.New("rfsnotify instance already closed")
}
return m.fsnotify.Add(name)
}
// AddRecursive starts watching the named directory and all sub-directories.
func (m *RWatcher) AddRecursive(name string) error {
if m.isClosed {
return errors.New("rfsnotify instance already closed")
}
if err := m.watchRecursive(name, false); err != nil {
return err
}
return nil
}
// Remove stops watching the the named file or directory (non-recursively).
func (m *RWatcher) Remove(name string) error {
return m.fsnotify.Remove(name)
}
// RemoveRecursive stops watching the named directory and all sub-directories.
func (m *RWatcher) RemoveRecursive(name string) error {
if err := m.watchRecursive(name, true); err != nil {
return err
}
return nil
}
// Close removes all watches and closes the events channel.
func (m *RWatcher) Close() error {
if m.isClosed {
return nil
}
close(m.done)
m.isClosed = true
return nil
}
func (m *RWatcher) start() {
for {
select {
case e := <-m.fsnotify.Events:
s, err := os.Stat(e.Name)
if err == nil && s != nil && s.IsDir() {
if e.Op&fsnotify.Create != 0 {
m.watchRecursive(e.Name, false)
}
}
// Can't stat a deleted directory, so just pretend that it's always a directory and
// try to remove from the watch list... we really have no clue if it's a directory or not...
if e.Op&fsnotify.Remove != 0 {
m.fsnotify.Remove(e.Name)
}
m.Events <- e
case e := <-m.fsnotify.Errors:
m.Errors <- e
case <-m.done:
m.fsnotify.Close()
close(m.Events)
close(m.Errors)
return
default:
time.Sleep(time.Second)
}
}
}
// watchRecursive adds all directories under the given one to the watch list.
// this is probably a very racey process. What if a file is added to a folder before we get the watch added?
func (m *RWatcher) watchRecursive(path string, unWatch bool) error {
err := filepath.WalkDir(path, func(walkPath string, fi fs.DirEntry, err error) error {
if err != nil {
return err
}
if fi.IsDir() {
if unWatch {
if err = m.fsnotify.Remove(walkPath); err != nil {
return err
}
} else {
if err = m.fsnotify.Add(walkPath); err != nil {
return err
}
}
}
return nil
})
return err
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment