The Event Guide - notify-rs/notify GitHub Wiki

By default, Notify offers little detail on events. In the configuration guide, you can find how to turn precise events on, which makes two important changes to the way Notify delivers events:

  1. The kind of an event is populated with the full tree classification, described below, and
  2. Events are debounced based both on path and event type (as a result you may get more of them).

Notify does its best to smooth out platform differences, but event kind is one place where it cannot do that fully. The "imprecise" default behaviour is a safe view ("something changed at this path") of a messy reality. You should consider whether you need that sort of precision and whether it's worth dealing with the complicated nature of platform event classification.

The event system

Notify uses a nested, or tree-based, event classification system. At the top level, there are six main categories, two of which are special, and then every subcategory and subsub(sub...)category drills down further into what exactly the event is about.

Every level has two special categories:

  • Any, which indicates that the event may be any of the possibilities of this level, and it is not known which;
  • Other, which indicates that the event is not any of the defined possibilities at this level, but it is known which. In that case, the event will have an Info(String) attribute with a short description.

The top-level Other is even more special in that it indicates events about the watching itself, i.e. meta-events. These are described later and in most cases should either be entirely ignored or handled on a case-by-case basis. In imprecise mode, EventKind::Any and EventKind::Other are the only two event kinds available: Any to indicate a filesystem change, Other to indicate a meta change.

The event classification may change, and it is best to refer to the API docs (TODO: link), but at time of writing it looks like this (note that the Any/Other categories at each level are left out):

  • Access
    • Read
    • Open
      • Execute
      • Read
      • Write
    • Close
      • Execute
      • Read
      • Write
  • Create
    • File
    • Folder
  • Modify
    • Data
      • Size
      • Content
    • Metadata
      • AccessTime
      • WriteTime
      • Permissions
      • Ownership
      • Extended
    • Name
      • To
      • From
      • Both
  • Remove
    • File
    • Folder

It is not guaranteed that all event kinds are emitted by Notify at any point in time. Some categories were included in anticipation, some only for completeness. Furthermore, the classification can change. For these reasons, you should not attempt to write exhaustive matches. Rather, the best practice is to match as precisely as you care for, and wildcard the rest:

match event.kind {
    EventKind::Modify(ModifyKind::Metadata(_)) => { /* deal with metadata */ }
    EventKind::Create(CreateKind::File) => { /* deal with new files */ }
    EventKind::Other(_) => { /* ignore meta events */ }
    _ => { /* something else changed */ }
}

For simple cases, helpers are available to match one of the top level categories:

if event.kind.is_access() {}
if event.kind.is_create() {}
if event.kind.is_modify() {}
if event.kind.is_remove() {}
if event.kind.is_other() {}

For full detail about what each variant is meant for, refer to [the API docs].

Beyond their kind, events refer to zero or more paths (order may be significant), and have zero or more attributes. These attributes can be arbitrary data, of which some types are "well-known", and standardised in Notify. Of these, the Info and Flag types are relevant here:

  • Info may contain a free-form string offering more detail on the particular event. This is especially relevant for Other kinds.
  • Flag denotes some specific mutually-exclusive (events cannot have two flags) meta-information. This may have expanded (refer to [the API docs]!) but as of writing:
    • Flag::Notice is used for events emitted as part of the NoticeEvents option.
    • Flag::Ongoing is used for events emitted as part of the OngoingEvents option.
    • Flag::Rescan is used to indicate that some events may have been missed (see [details on the API docs]).

Note especially that an Other event may have a Flag and no Info, or even nothing at all (though that is likely a bug): write your code carefully to cope.

Platform-specific behaviour

The rest of this page is dedicated to documenting the behaviour of specific platforms in regards to event classification. If you deal with precise events, I strongly encourage you to regard this as required reading.

Within each section, behaviours are numbered to be easily referenced, and not for ordering (please do not change their order! strike out entries that need removal instead of deleting them).

What is not documented here is standard behaviour (described in [the API docs]).

Some common terminology:

  • A "watch" refers to a Watcher::watch call, and the abstract entity that it creates, which monitors an object or subtree.
  • A watch is "ended" or "destroyed" either by a Watcher::unwatch call or an interior event. Generally, no new events related to a watch are generated once that watch is ended.
  • A "watched $object" refers to the object pointed to by the path passed to a Watcher::watch call, and not to an arbitrary object somewhere inside a watched subtree.

Windows

  1. There is no distinction between data writes or metadata writes. Both of these are represented by Modify(Any).
  2. If a watched directory is removed, the watch is ended.
  3. If a watched file is removed, the path of that file will continue to be watched until either the watch is manually ended or the parent folder is removed.
  4. A rename with both the source and destination paths inside a watched directory produces a Modify(Name(From)) and a Modify(Name(To)) in immediate mode, and a single Modify(Name(Both)) in debounced mode. Both events have a Tracker attribute set to the same value.
  5. A rename with only the source inside a watched directory produces a Remove event.
  6. A rename with only the destination inside a watched directory produces a Create event.
  7. If a watched directory is renamed, no event is produced, but the directory continues to be watched. New events will be prefixed by the old path.
  8. If a watched file is renamed, a Modify(Name) is produced, but the old path continues to be watched.
  9. If the parent of a watched object is renamed, the watch will continue. New events will be prefixed by the old path.

macOS — FSEvents

  1. If a watched object is removed, a Remove is produced, and the watch continues for that object's path. If a new object appears at that path at a latter time before the watch is manually ended, events for that object will be produced.
  2. Any rename is produced as Modify(Name(Any)) in immediate mode, and only some of these are caught and produced as Modify(Name(Both)) in debounced mode. The Tracker may or may not be set.
  3. If a watched object is renamed, a Modify(Name) is produced, and then the behaviour is as in 1.
  4. If the parent of a watched object is renamed, no event is produced, and then the behaviour is as in 1.
  5. If the platform indicates that some events were coalesced, a meta-event with the Rescan flag is produced.

Linux — inotify

  1. This platform emits some access events.
  2. If a watched object is removed, its watch is ended.
  3. A rename with both the source and destination paths inside a watched directory produces a Modify(Name(From)) and a Modify(Name(To)) in immediate mode, and a single Modify(Name(Both)) in debounced mode. Both events have a Tracker attribute set to the same value.
  4. A rename with only the source inside a watched directory produces a Remove event.
  5. A rename with only the destination inside a watched directory produces a Create event.
  6. If a watched object is renamed, a Modify(Name) is produced, and the directory continues to be watched. New events will be prefixed by the old path.
  7. If the parent of a watched object is renamed, the watch will continue. New events will be prefixed by the old path.
  8. If the platform indicates that the kernel queue overflowed, a meta-event with the Rescan flag is produced.