Documentation ¶
Overview ¶
Package notify implements access to filesystem events.
Notify is a high-level abstraction over filesystem watchers like inotify, kqueue, FSEvents, FEN or ReadDirectoryChangesW. Watcher implementations are split into two groups: ones that natively support recursive notifications (FSEvents and ReadDirectoryChangesW) and ones that do not (inotify, kqueue, FEN). For more details see watcher and recursiveWatcher interfaces in watcher.go source file.
On top of filesystem watchers notify maintains a watchpoint tree, which provides a strategy for creating and closing filesystem watches and dispatching filesystem events to user channels.
An event set is just an event list joint using bitwise OR operator into a single event value. Both the platform-independent (see Constants) and specific events can be used. Refer to the event_*.go source files for information about the available events.
A filesystem watch or just a watch is platform-specific entity which represents a single path registered for notifications for specific event set. Setting a watch means using platform-specific API calls for creating / initializing said watch. For each watcher the API call is:
- FSEvents: FSEventStreamCreate
- inotify: notify_add_watch
- kqueue: kevent
- ReadDirectoryChangesW: CreateFile+ReadDirectoryChangesW
- FEN: port_get
To rewatch means to either shrink or expand an event set that was previously registered during watch operation for particular filesystem watch.
A watchpoint is a list of user channel and event set pairs for particular path (watchpoint tree's node). A single watchpoint can contain multiple different user channels registered to listen for one or more events. A single user channel can be registered in one or more watchpoints, recursive and non-recursive ones as well.
Index ¶
Examples ¶
Constants ¶
const ( Create = osSpecificCreate Remove = osSpecificRemove Write = osSpecificWrite Rename = osSpecificRename // All is handful alias for all platform-independent event values. All = Create | Remove | Write | Rename )
Create, Remove, Write and Rename are the only event values guaranteed to be present on all platforms.
const ( InAccess = Event(unix.IN_ACCESS) // File was accessed InModify = Event(unix.IN_MODIFY) // File was modified InAttrib = Event(unix.IN_ATTRIB) // Metadata changed InCloseWrite = Event(unix.IN_CLOSE_WRITE) // Writtable file was closed InCloseNowrite = Event(unix.IN_CLOSE_NOWRITE) // Unwrittable file closed InOpen = Event(unix.IN_OPEN) // File was opened InMovedFrom = Event(unix.IN_MOVED_FROM) // File was moved from X InMovedTo = Event(unix.IN_MOVED_TO) // File was moved to Y InCreate = Event(unix.IN_CREATE) // Subfile was created InDelete = Event(unix.IN_DELETE) // Subfile was deleted InDeleteSelf = Event(unix.IN_DELETE_SELF) // Self was deleted InMoveSelf = Event(unix.IN_MOVE_SELF) // Self was moved )
Inotify specific masks are legal, implemented events that are guaranteed to work with notify package on linux-based systems.
Variables ¶
This section is empty.
Functions ¶
func Stop ¶
func Stop(c chan<- EventInfo)
Stop removes all watchpoints registered for c. All underlying watches are also removed, for which c was the last channel listening for events.
Stop does not close c. When Stop returns, it is guaranteed that c will receive no more signals.
Example ¶
This example shows why it is important to not create leaks by stoping a channel when it's no longer being used.
package main import ( "log" "path/filepath" "time" "github.com/rjeczalik/notify" ) func main() { waitfor := func(path string, e notify.Event, timeout time.Duration) bool { dir, file := filepath.Split(path) c := make(chan notify.EventInfo, 1) if err := notify.Watch(dir, c, e); err != nil { log.Fatal(err) } // Clean up watchpoint associated with c. If Stop was not called upon // return the channel would be leaked as notify holds the only reference // to it and does not release it on its own. defer notify.Stop(c) t := time.After(timeout) for { select { case ei := <-c: if filepath.Base(ei.Path()) == file { return true } case <-t: return false } } } if waitfor("index.lock", notify.Create, 5*time.Second) { log.Println("The git repository was locked") } if waitfor("index.lock", notify.Remove, 5*time.Second) { log.Println("The git repository was unlocked") } }
Output:
func Watch ¶
Watch sets up a watchpoint on path listening for events given by the events argument.
File or directory given by the path must exist, otherwise Watch will fail with non-nil error. Notify resolves, for its internal purpose, any symlinks the provided path may contain, so it may fail if the symlinks form a cycle. It does so, since not all watcher implementations treat passed paths as-is. E.g. FSEvents reports a real path for every event, setting a watchpoint on /tmp will report events with paths rooted at /private/tmp etc.
The c almost always is a buffered channel. Watch will not block sending to c - the caller must ensure that c has sufficient buffer space to keep up with the expected event rate.
It is allowed to pass the same channel multiple times with different event list or different paths. Calling Watch with different event lists for a single watchpoint expands its event set. The only way to shrink it, is to call Stop on its channel.
Calling Watch with empty event list does expand nor shrink watchpoint's event set. If c is the first channel to listen for events on the given path, Watch will seamlessly create a watch on the filesystem.
Notify dispatches copies of single filesystem event to all channels registered for each path. If a single filesystem event contains multiple coalesced events, each of them is dispatched separately. E.g. the following filesystem change:
~ $ echo Hello > Notify.txt
dispatches two events - notify.Create and notify.Write. However, it may depend on the underlying watcher implementation whether OS reports both of them.
Windows and recursive watches ¶
If a directory which path was used to create recursive watch under Windows gets deleted, the OS will not report such event. It is advised to keep in mind this limitation while setting recursive watchpoints for your application, e.g. use persistent paths like %userprofile% or watch additionally parent directory of a recursive watchpoint in order to receive delete events for it.
Example ¶
This is a basic example showing how to work with notify.Watch function.
package main import ( "log" "github.com/rjeczalik/notify" ) func main() { // Make the channel buffered to ensure no event is dropped. Notify will drop // an event if the receiver is not able to keep up the sending pace. c := make(chan notify.EventInfo, 1) // Set up a watchpoint listening on events within current working directory. // Dispatch each create and remove events separately to c. if err := notify.Watch(".", c, notify.Create, notify.Remove); err != nil { log.Fatal(err) } defer notify.Stop(c) // Block until an event is received. ei := <-c log.Println("Got event:", ei) }
Output:
Example (Linux) ¶
This example shows how to watch changes made on file-system by text editor when saving a file. Usually, either InCloseWrite or InMovedTo (when swapping with a temporary file) event is created.
package main import ( "log" "github.com/rjeczalik/notify" ) func main() { // Make the channel buffered to ensure no event is dropped. Notify will drop // an event if the receiver is not able to keep up the sending pace. c := make(chan notify.EventInfo, 1) // Set up a watchpoint listening for inotify-specific events within a // current working directory. Dispatch each InCloseWrite and InMovedTo // events separately to c. if err := notify.Watch(".", c, notify.InCloseWrite, notify.InMovedTo); err != nil { log.Fatal(err) } defer notify.Stop(c) // Block until an event is received. switch ei := <-c; ei.Event() { case notify.InCloseWrite: log.Println("Editing of", ei.Path(), "file is done.") case notify.InMovedTo: log.Println("File", ei.Path(), "was swapped/moved into the watched directory.") } }
Output:
Example (LinuxMove) ¶
This example shows how to use Sys() method from EventInfo interface to tie two separate events generated by rename(2) function.
package main import ( "log" "golang.org/x/sys/unix" "github.com/rjeczalik/notify" ) func main() { // Make the channel buffered to ensure no event is dropped. Notify will drop // an event if the receiver is not able to keep up the sending pace. c := make(chan notify.EventInfo, 2) // Set up a watchpoint listening for inotify-specific events within a // current working directory. Dispatch each InMovedFrom and InMovedTo // events separately to c. if err := notify.Watch(".", c, notify.InMovedFrom, notify.InMovedTo); err != nil { log.Fatal(err) } defer notify.Stop(c) // Inotify reports move filesystem action by sending two events tied with // unique cookie value (uint32): one of the events is of InMovedFrom type // carrying move source path, while the second one is of InMoveTo type // carrying move destination path. moves := make(map[uint32]struct { From string To string }) // Wait for moves. for ei := range c { cookie := ei.Sys().(*unix.InotifyEvent).Cookie info := moves[cookie] switch ei.Event() { case notify.InMovedFrom: info.From = ei.Path() case notify.InMovedTo: info.To = ei.Path() } moves[cookie] = info if cookie != 0 && info.From != "" && info.To != "" { log.Println("File:", info.From, "was renamed to", info.To) delete(moves, cookie) } } }
Output:
Example (Recursive) ¶
This example shows how to set up a recursive watchpoint.
package main import ( "log" "github.com/rjeczalik/notify" ) func main() { // Make the channel buffered to ensure no event is dropped. Notify will drop // an event if the receiver is not able to keep up the sending pace. c := make(chan notify.EventInfo, 1) // Set up a watchpoint listening for events within a directory tree rooted // at current working directory. Dispatch remove events to c. if err := notify.Watch("./...", c, notify.Remove); err != nil { log.Fatal(err) } defer notify.Stop(c) // Block until an event is received. ei := <-c log.Println("Got event:", ei) }
Output:
Types ¶
type Event ¶
type Event uint32
Event represents the type of filesystem action.
Number of available event values is dependent on the target system or the watcher implmenetation used (e.g. it's possible to use either kqueue or FSEvents on Darwin).
Please consult documentation for your target platform to see list of all available events.
type EventInfo ¶
type EventInfo interface { Event() Event // event value for the filesystem action Path() string // real path of the file or directory Sys() interface{} // underlying data source (can return nil) }
EventInfo describes an event reported by the underlying filesystem notification subsystem.
It always describes single event, even if the OS reported a coalesced action. Reported path is absolute and clean.
For non-recursive watchpoints its base is always equal to the path passed to corresponding Watch call.
The value of Sys if system-dependent and can be nil.
Sys ¶
Under Darwin (FSEvents) Sys() always returns a non-nil *notify.FSEvent value, which is defined as:
type FSEvent struct { Path string // real path of the file or directory ID uint64 // ID of the event (FSEventStreamEventId) Flags uint32 // joint FSEvents* flags (FSEventStreamEventFlags) }
For possible values of Flags see Darwin godoc for notify or FSEvents documentation for FSEventStreamEventFlags constants:
https://developer.apple.com/library/mac/documentation/Darwin/Reference/FSEvents_Ref/index.html#//apple_ref/doc/constant_group/FSEventStreamEventFlags
Under Linux (inotify) Sys() always returns a non-nil *unix.InotifyEvent value, defined as:
type InotifyEvent struct { Wd int32 // Watch descriptor Mask uint32 // Mask describing event Cookie uint32 // Unique cookie associating related events (for rename(2)) Len uint32 // Size of name field Name [0]uint8 // Optional null-terminated name }
More information about inotify masks and the usage of inotify_event structure can be found at:
http://man7.org/linux/man-pages/man7/inotify.7.html
Under Darwin, DragonFlyBSD, FreeBSD, NetBSD, OpenBSD (kqueue) Sys() always returns a non-nil *notify.Kevent value, which is defined as:
type Kevent struct { Kevent *syscall.Kevent_t // Kevent is a kqueue specific structure FI os.FileInfo // FI describes file/dir }
More information about syscall.Kevent_t can be found at:
https://www.freebsd.org/cgi/man.cgi?query=kqueue
Under Windows (ReadDirectoryChangesW) Sys() always returns nil. The documentation of watcher's WinAPI function can be found at:
https://msdn.microsoft.com/en-us/library/windows/desktop/aa365465%28v=vs.85%29.aspx
Notes ¶
Bugs ¶
Notify does not collect watchpoints, when underlying watches were removed by their os-specific watcher implementations. Instead users are advised to listen on persistent paths to have guarantee they receive events for the whole lifetime of their applications (to discuss see #69).
Linux (inotify) does not support watcher behavior masks like InOneshot, InOnlydir etc. Instead users are advised to perform the filtering themselves (to discuss see #71).
Notify was not tested for short path name support under Windows (ReadDirectoryChangesW).
Windows (ReadDirectoryChangesW) cannot recognize which notification triggers FileActionModified event. (to discuss see #75).