Skip to content

Commit

Permalink
Add recursive watcher for Windows backend (#540)
Browse files Browse the repository at this point in the history
Recursive watches can be added by using a "/..." parameter, similar to
the Go command:

	w.Add("dir")         // Behaves as before.
	w.Add("dir/...")     // Watch dir and all paths underneath it.

	w.Remove("dir")      // Remove the watch for dir and, if
	                     // recursive, all paths underneath it too

	w.Remove("dir/...")  // Behaves like just "dir" if the path was
	                     // recursive, error otherwise (probably
	                     // want to add recursive remove too at some
	                     // point).

The advantage of using "/..." vs. an option is that it can be easily
specified in configuration files and the like; for example from a TOML
file:

	[watches]
	dirs = ["/tmp/one", "/tmp/two/..."]

Options for this were previously discussed at:
#339 (comment)

This should be expanded to other backends too; I started with Windows
because the implementation is the both the easiest and has the least
amount of control (just setting a boolean parameter), and we can focus
mostly on writing tests and documentation and the for it, and we can
then match the inotify and kqueue behaviour to the Windows one.

Fixes #21

Co-authored-by: Milas Bowman <milasb@gmail.com>
  • Loading branch information
arp242 and milas committed Dec 20, 2022
1 parent 1a76583 commit 736c884
Show file tree
Hide file tree
Showing 11 changed files with 436 additions and 61 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Expand Up @@ -10,6 +10,8 @@ Unreleased
- all: add `AddWith()`, which is identical to `Add()` but allows passing
options. ([#521])

- all: support recursively watching paths with `Add("path/...")`. ([#540])

- windows: allow setting the buffer size with `fsnotify.WithBufferSize()`; the
default of 64K is the highest value that works on all platforms and is enough
for most purposes, but in some cases a highest buffer is needed. ([#521])
Expand Down Expand Up @@ -46,7 +48,7 @@ Unreleased

- other: use the backend_other.go no-op if the `appengine` build tag is set;
Google AppEngine forbids usage of the unsafe package so the inotify backend
won't work there.
won't compile there.


[#371]: https://github.com/fsnotify/fsnotify/pull/371
Expand All @@ -58,6 +60,7 @@ Unreleased
[#526]: https://github.com/fsnotify/fsnotify/pull/526
[#528]: https://github.com/fsnotify/fsnotify/pull/528
[#537]: https://github.com/fsnotify/fsnotify/pull/537
[#540]: https://github.com/fsnotify/fsnotify/pull/540

1.6.0 - 2022-10-13
-------------------
Expand Down
33 changes: 28 additions & 5 deletions backend_fen.go
@@ -1,6 +1,9 @@
//go:build solaris
// +build solaris

// Note: the documentation on the Watcher type and methods is generated from
// mkdoc.zsh

package fsnotify

import (
Expand Down Expand Up @@ -63,6 +66,16 @@ import (
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
// control the maximum number of open files, as well as /etc/login.conf on BSD
// systems.
//
// # Windows notes
//
// Paths can be added as "C:\path\to\dir", but forward slashes
// ("C:/path/to/dir") will also work.
//
// The default buffer size is 64K, which is the largest value that is guaranteed
// to work with SMB filesystems. If you have many events in quick succession
// this may not be enough, and you will have to use [WithBufferSize] to increase
// the value.
type Watcher struct {
// Events sends the filesystem change events.
//
Expand Down Expand Up @@ -92,6 +105,8 @@ type Watcher struct {
// you may get hundreds of Write events, so you
// probably want to wait until you've stopped receiving
// them (see the dedup example in cmd/fsnotify).
// Some systems may send Write event for directories
// when the directory content changes.
//
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
// when a file is removed (or more accurately, when a
Expand Down Expand Up @@ -184,7 +199,7 @@ func (w *Watcher) Close() error {
//
// A path can only be watched once; attempting to watch it more than once will
// return an error. Paths that do not yet exist on the filesystem cannot be
// added.
// watched.
//
// A watch will be automatically removed if the watched path is deleted or
// renamed. The exception is the Windows backend, which doesn't remove the
Expand All @@ -200,8 +215,9 @@ func (w *Watcher) Close() error {
// # Watching directories
//
// All files in a directory are monitored, including new files that are created
// after the watcher is started. Subdirectories are not watched (i.e. it's
// non-recursive).
// after the watcher is started. By default subdirectories are not watched (i.e.
// it's non-recursive), but if the path ends with "/..." all files and
// subdirectories are watched too.
//
// # Watching files
//
Expand Down Expand Up @@ -266,8 +282,15 @@ func (w *Watcher) AddWith(name string, opts ...addOpt) error {

// Remove stops monitoring the path for changes.
//
// Directories are always removed non-recursively. For example, if you added
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
// If the path was added as a recursive watch (e.g. as "/tmp/dir/...") then the
// entire recursive watch will be removed. You can use either "/tmp/dir" or
// "/tmp/dir/..." (they behave identically).
//
// You cannot remove individual files or subdirectories from recursive watches;
// e.g. Add("/tmp/path/...") and then Remove("/tmp/path/sub") will fail.
//
// For other watches directories are removed non-recursively. For example, if
// you added "/tmp/dir" and "/tmp/dir/subdir" then you will need to remove both.
//
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
//
Expand Down
33 changes: 28 additions & 5 deletions backend_inotify.go
@@ -1,6 +1,9 @@
//go:build linux && !appengine
// +build linux,!appengine

// Note: the documentation on the Watcher type and methods is generated from
// mkdoc.zsh

package fsnotify

import (
Expand Down Expand Up @@ -66,6 +69,16 @@ import (
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
// control the maximum number of open files, as well as /etc/login.conf on BSD
// systems.
//
// # Windows notes
//
// Paths can be added as "C:\path\to\dir", but forward slashes
// ("C:/path/to/dir") will also work.
//
// The default buffer size is 64K, which is the largest value that is guaranteed
// to work with SMB filesystems. If you have many events in quick succession
// this may not be enough, and you will have to use [WithBufferSize] to increase
// the value.
type Watcher struct {
// Events sends the filesystem change events.
//
Expand Down Expand Up @@ -95,6 +108,8 @@ type Watcher struct {
// you may get hundreds of Write events, so you
// probably want to wait until you've stopped receiving
// them (see the dedup example in cmd/fsnotify).
// Some systems may send Write event for directories
// when the directory content changes.
//
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
// when a file is removed (or more accurately, when a
Expand Down Expand Up @@ -206,7 +221,7 @@ func (w *Watcher) Close() error {
//
// A path can only be watched once; attempting to watch it more than once will
// return an error. Paths that do not yet exist on the filesystem cannot be
// added.
// watched.
//
// A watch will be automatically removed if the watched path is deleted or
// renamed. The exception is the Windows backend, which doesn't remove the
Expand All @@ -222,8 +237,9 @@ func (w *Watcher) Close() error {
// # Watching directories
//
// All files in a directory are monitored, including new files that are created
// after the watcher is started. Subdirectories are not watched (i.e. it's
// non-recursive).
// after the watcher is started. By default subdirectories are not watched (i.e.
// it's non-recursive), but if the path ends with "/..." all files and
// subdirectories are watched too.
//
// # Watching files
//
Expand Down Expand Up @@ -281,8 +297,15 @@ func (w *Watcher) AddWith(name string, opts ...addOpt) error {

// Remove stops monitoring the path for changes.
//
// Directories are always removed non-recursively. For example, if you added
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
// If the path was added as a recursive watch (e.g. as "/tmp/dir/...") then the
// entire recursive watch will be removed. You can use either "/tmp/dir" or
// "/tmp/dir/..." (they behave identically).
//
// You cannot remove individual files or subdirectories from recursive watches;
// e.g. Add("/tmp/path/...") and then Remove("/tmp/path/sub") will fail.
//
// For other watches directories are removed non-recursively. For example, if
// you added "/tmp/dir" and "/tmp/dir/subdir" then you will need to remove both.
//
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
//
Expand Down
1 change: 1 addition & 0 deletions backend_inotify_test.go
Expand Up @@ -93,6 +93,7 @@ func TestInotifyDeleteOpenFile(t *testing.T) {
w.collect(t)

rm(t, file)
eventSeparator()
e := w.events(t)
cmpEvents(t, tmp, e, newEvents(t, `chmod /file`))

Expand Down
33 changes: 28 additions & 5 deletions backend_kqueue.go
@@ -1,6 +1,9 @@
//go:build freebsd || openbsd || netbsd || dragonfly || darwin
// +build freebsd openbsd netbsd dragonfly darwin

// Note: the documentation on the Watcher type and methods is generated from
// mkdoc.zsh

package fsnotify

import (
Expand Down Expand Up @@ -63,6 +66,16 @@ import (
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
// control the maximum number of open files, as well as /etc/login.conf on BSD
// systems.
//
// # Windows notes
//
// Paths can be added as "C:\path\to\dir", but forward slashes
// ("C:/path/to/dir") will also work.
//
// The default buffer size is 64K, which is the largest value that is guaranteed
// to work with SMB filesystems. If you have many events in quick succession
// this may not be enough, and you will have to use [WithBufferSize] to increase
// the value.
type Watcher struct {
// Events sends the filesystem change events.
//
Expand Down Expand Up @@ -92,6 +105,8 @@ type Watcher struct {
// you may get hundreds of Write events, so you
// probably want to wait until you've stopped receiving
// them (see the dedup example in cmd/fsnotify).
// Some systems may send Write event for directories
// when the directory content changes.
//
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
// when a file is removed (or more accurately, when a
Expand Down Expand Up @@ -237,7 +252,7 @@ func (w *Watcher) Close() error {
//
// A path can only be watched once; attempting to watch it more than once will
// return an error. Paths that do not yet exist on the filesystem cannot be
// added.
// watched.
//
// A watch will be automatically removed if the watched path is deleted or
// renamed. The exception is the Windows backend, which doesn't remove the
Expand All @@ -253,8 +268,9 @@ func (w *Watcher) Close() error {
// # Watching directories
//
// All files in a directory are monitored, including new files that are created
// after the watcher is started. Subdirectories are not watched (i.e. it's
// non-recursive).
// after the watcher is started. By default subdirectories are not watched (i.e.
// it's non-recursive), but if the path ends with "/..." all files and
// subdirectories are watched too.
//
// # Watching files
//
Expand Down Expand Up @@ -288,8 +304,15 @@ func (w *Watcher) AddWith(name string, opts ...addOpt) error {

// Remove stops monitoring the path for changes.
//
// Directories are always removed non-recursively. For example, if you added
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
// If the path was added as a recursive watch (e.g. as "/tmp/dir/...") then the
// entire recursive watch will be removed. You can use either "/tmp/dir" or
// "/tmp/dir/..." (they behave identically).
//
// You cannot remove individual files or subdirectories from recursive watches;
// e.g. Add("/tmp/path/...") and then Remove("/tmp/path/sub") will fail.
//
// For other watches directories are removed non-recursively. For example, if
// you added "/tmp/dir" and "/tmp/dir/subdir" then you will need to remove both.
//
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
//
Expand Down
33 changes: 28 additions & 5 deletions backend_other.go
@@ -1,6 +1,9 @@
//go:build appengine || (!darwin && !dragonfly && !freebsd && !openbsd && !linux && !netbsd && !solaris && !windows)
// +build appengine !darwin,!dragonfly,!freebsd,!openbsd,!linux,!netbsd,!solaris,!windows

// Note: the documentation on the Watcher type and methods is generated from
// mkdoc.zsh

package fsnotify

import "errors"
Expand Down Expand Up @@ -55,6 +58,16 @@ import "errors"
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
// control the maximum number of open files, as well as /etc/login.conf on BSD
// systems.
//
// # Windows notes
//
// Paths can be added as "C:\path\to\dir", but forward slashes
// ("C:/path/to/dir") will also work.
//
// The default buffer size is 64K, which is the largest value that is guaranteed
// to work with SMB filesystems. If you have many events in quick succession
// this may not be enough, and you will have to use [WithBufferSize] to increase
// the value.
type Watcher struct {
// Events sends the filesystem change events.
//
Expand Down Expand Up @@ -84,6 +97,8 @@ type Watcher struct {
// you may get hundreds of Write events, so you
// probably want to wait until you've stopped receiving
// them (see the dedup example in cmd/fsnotify).
// Some systems may send Write event for directories
// when the directory content changes.
//
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
// when a file is removed (or more accurately, when a
Expand Down Expand Up @@ -119,7 +134,7 @@ func (w *Watcher) WatchList() []string { return nil }
//
// A path can only be watched once; attempting to watch it more than once will
// return an error. Paths that do not yet exist on the filesystem cannot be
// added.
// watched.
//
// A watch will be automatically removed if the watched path is deleted or
// renamed. The exception is the Windows backend, which doesn't remove the
Expand All @@ -135,8 +150,9 @@ func (w *Watcher) WatchList() []string { return nil }
// # Watching directories
//
// All files in a directory are monitored, including new files that are created
// after the watcher is started. Subdirectories are not watched (i.e. it's
// non-recursive).
// after the watcher is started. By default subdirectories are not watched (i.e.
// it's non-recursive), but if the path ends with "/..." all files and
// subdirectories are watched too.
//
// # Watching files
//
Expand All @@ -162,8 +178,15 @@ func (w *Watcher) AddWith(name string, opts ...addOpt) error { return nil }

// Remove stops monitoring the path for changes.
//
// Directories are always removed non-recursively. For example, if you added
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
// If the path was added as a recursive watch (e.g. as "/tmp/dir/...") then the
// entire recursive watch will be removed. You can use either "/tmp/dir" or
// "/tmp/dir/..." (they behave identically).
//
// You cannot remove individual files or subdirectories from recursive watches;
// e.g. Add("/tmp/path/...") and then Remove("/tmp/path/sub") will fail.
//
// For other watches directories are removed non-recursively. For example, if
// you added "/tmp/dir" and "/tmp/dir/subdir" then you will need to remove both.
//
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
//
Expand Down

0 comments on commit 736c884

Please sign in to comment.