diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000..7222107 --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,25 @@ +# This workflow will build a golang project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go + +name: Go + +on: + push: + branches: ["master"] + pull_request: + branches: ["master"] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: install deps + run: sudo apt install -y libmagic-dev + + - uses: glimps-re/go-actions@v0.1.0 + with: + golangci-lint-version: v2.4.0 + go-private-repo: github.com/glimps-re/* + go-version: '1.25' diff --git a/README.md b/README.md index e2feb6a..4a0f44a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ -# rfsnotify v0.1.0 -recursive directory notifications built as a wrapper around fsnotify (golang) +# rfsnotify + +recursive directory notifications built as a wrapper around fsnotify (golang), forked from [this repo](https://github.com/farmergreg/rfsnotify) [![GoDoc](https://godoc.org/github.com/farmergreg/rfsnotify?status.svg)](https://godoc.org/github.com/farmergreg/rfsnotify) diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..0dd3078 --- /dev/null +++ b/go.mod @@ -0,0 +1,7 @@ +module github.com/glimps-re/rfsnotify + +go 1.25 + +require github.com/fsnotify/fsnotify v1.9.0 + +require golang.org/x/sys v0.37.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a4e255b --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= diff --git a/rfsnotify.go b/rfsnotify.go index 7ba95ac..19263b3 100644 --- a/rfsnotify.go +++ b/rfsnotify.go @@ -2,11 +2,13 @@ package rfsnotify import ( - "gopkg.in/fsnotify.v1" - "errors" + "log/slog" "os" "path/filepath" + "strings" + + "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 @@ -84,16 +86,23 @@ func (m *RWatcher) start() { 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) + if e.Has(fsnotify.Create) { + if ewr := m.watchRecursive(e.Name, false); ewr != nil { + slog.Error("an error occurred with m.watchRecursive", slog.String("path", e.Name), slog.String("error", ewr.Error())) + } } } - //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) + // 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.Has(fsnotify.Remove) { + if er := m.fsnotify.Remove(e.Name); er != nil { + if !strings.Contains(er.Error(), "non-existent watch") { + slog.Warn("an error occurred with fsnotify.Remove", slog.String("path", e.Name), slog.String("error", er.Error())) + } + } } m.Events <- e @@ -101,7 +110,9 @@ func (m *RWatcher) start() { m.Errors <- e case <-m.done: - m.fsnotify.Close() + if ec := m.fsnotify.Close(); ec != nil { + slog.Error("an error occurred with fsnotify.Close", slog.String("error", ec.Error())) + } close(m.Events) close(m.Errors) return diff --git a/rfsnotify_test.go b/rfsnotify_test.go new file mode 100644 index 0000000..52987f2 --- /dev/null +++ b/rfsnotify_test.go @@ -0,0 +1,178 @@ +package rfsnotify + +import ( + "fmt" + "os" + "path/filepath" + "sync" +) + +func ExampleRWatcher_AddRecursive() { + // create test folder + rd := filepath.Join(os.TempDir(), "testRWatcher_recursive") + err := os.Mkdir(rd, 0o750) + if err != nil { + fmt.Printf("could not create root test folder, error: %s", err) + return + } + defer func() { + if e := os.RemoveAll(rd); e != nil { + fmt.Printf("could not remove test folder %s, error: %s", rd, e) + } + }() + if err = os.MkdirAll(filepath.Join(rd, "a/b/c"), 0o750); err != nil { + fmt.Printf("could not create test folders, error: %s", err) + return + } + + // create new watcher + watcher, err := NewWatcher() + if err != nil { + fmt.Printf("could not create recursive watcher, error: %s", err) + return + } + + defer func() { + if e := watcher.Close(); e != nil { + fmt.Printf("could not close recursive watcher, error: %s", e) + } + }() + + // add test folder to be watched + err = watcher.AddRecursive(rd) + if err != nil { + fmt.Printf("could not add recursive folder %s, error: %s", rd, err) + return + } + + // create new monitored folder + if err = os.MkdirAll(filepath.Join(rd, "a/b/c/d"), 0o750); err != nil { + fmt.Printf("could not create test folders, error: %s", err) + return + } + + // dump folder creation event + + <-watcher.Events + + // add dummy event listener, real one must use a controlled loop + wg := sync.WaitGroup{} + wg.Go(func() { + event, ok := <-watcher.Events + if !ok { + fmt.Printf("ko event") + return + } + fmt.Printf("received event %s", event) + }) + + // create a test file + f, err := os.Create(filepath.Clean(filepath.Join(rd, "a/b/c/d", "test.txt"))) + if err != nil { + fmt.Printf("could not create test file %s, error: %s", rd, err) + return + } + defer func() { + if e := f.Close(); e != nil { + fmt.Printf("could not close file %s, error: %s", f.Name(), e) + } + }() + + // wait until dummy listener is informed of the file creation + wg.Wait() + + // cleanup + if err = watcher.RemoveRecursive(rd); err != nil { + fmt.Printf("could not remove test folder %s from notify, error: %s", rd, err) + return + } + + // output: + // received event CREATE "/tmp/testRWatcher_recursive/a/b/c/d/test.txt" +} + +func ExampleRWatcher_Add() { + // create test folder + rd := filepath.Join(os.TempDir(), "testRWatcher_add") + err := os.Mkdir(rd, 0o750) + if err != nil { + fmt.Printf("could not create root test folder, error: %s", err) + return + } + defer func() { + if e := os.RemoveAll(rd); e != nil { + fmt.Printf("could not remove test folder %s, error: %s", rd, e) + } + }() + if err = os.MkdirAll(filepath.Join(rd, "a/b/c"), 0o750); err != nil { + fmt.Printf("could not create test folders, error: %s", err) + return + } + + // create new watcher + watcher, err := NewWatcher() + if err != nil { + fmt.Printf("could not create recursive watcher, error: %s", err) + return + } + + defer func() { + if e := watcher.Close(); e != nil { + fmt.Printf("could not close recursive watcher, error: %s", e) + } + }() + + // add dummy event listener, real one must use a controlled loop + wg := sync.WaitGroup{} + wg.Go(func() { + event, ok := <-watcher.Events + if !ok { + fmt.Printf("ko event") + return + } + fmt.Printf("received event %s", event) + }) + + // add test folder to be watched + err = watcher.Add(rd) + if err != nil { + fmt.Printf("could not add recursive folder %s, error: %s", rd, err) + return + } + + // create a test file that will not trigger notify + f, err := os.Create(filepath.Clean(filepath.Join(rd, "a/b/c", "test.txt"))) + if err != nil { + fmt.Printf("could not create test file %s, error: %s", rd, err) + return + } + defer func() { + if e := f.Close(); e != nil { + fmt.Printf("could not close file %s, error: %s", f.Name(), e) + } + }() + + // create a test file that will trigger notify + f2, err := os.Create(filepath.Clean(filepath.Join(rd, "test2.txt"))) + if err != nil { + fmt.Printf("could not create test file2 %s, error: %s", rd, err) + return + } + defer func() { + if e := f2.Close(); e != nil { + fmt.Printf("could not close file %s, error: %s", f2.Name(), e) + } + }() + + // wait until dummy listener is informed of the file creation + wg.Wait() + + // cleanup + if err = watcher.Remove(rd); err != nil { + fmt.Printf("could not remove test folder %s from notify, error: %s", rd, err) + return + } + + // output: + // received event CREATE "/tmp/testRWatcher_add/test2.txt" +}