notify: github.com/syncthing/notify Index | Examples | Files

package notify

import "github.com/syncthing/notify"

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

Package Files

debug.go debug_nodebug.go doc.go event.go event_inotify.go node.go notify.go tree.go tree_nonrecursive.go tree_recursive.go util.go watcher.go watcher_inotify.go watcher_stub.go watchpoint.go watchpoint_other.go

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.

func Stop Uses

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.

This example shows why it is important to not create leaks by stoping a channel when it's no longer being used.

Code:

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")
}

func Watch Uses

func Watch(path string, c chan<- EventInfo, events ...Event) error

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.

This is a basic example showing how to work with notify.Watch function.

Code:

// 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)

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.

Code:

// 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.")
}

This example shows how to use Sys() method from EventInfo interface to tie two separate events generated by rename(2) function.

Code:

// 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)
    }
}

This example shows how to set up a recursive watchpoint.

Code:

// 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)

func WatchWithFilter Uses

func WatchWithFilter(path string, c chan<- EventInfo,
    doNotWatch func(string) bool, events ...Event) error

This function works the same way as Watch. In addition it does not watch files or directories based on the return value of the argument function doNotWatch. Given a path as argument doNotWatch should return true if the file or directory should not be watched.

type DoNotWatchFn Uses

type DoNotWatchFn func(string) bool

type Event Uses

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.

func (Event) String Uses

func (e Event) String() string

String implements fmt.Stringer interface.

type EventInfo Uses

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

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).

Package notify imports 12 packages (graph) and is imported by 1 packages. Updated 2018-11-09. Refresh now. Tools for package owners.