blob: 5d527891d954109c090c48c78041c25f706afed4 [file] [log] [blame]
// This file is dual-licensed under the Artistic License 2.0 as per the
// LICENSE.ARTISTIC file, and the Creative Commons Zero 1.0 license.
//! The `Event` type and the hierarchical `EventKind` descriptor.
use std::{
fmt,
hash::{Hash, Hasher},
path::PathBuf,
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// An event describing open or close operations on files.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
pub enum AccessMode {
/// The catch-all case, to be used when the specific kind of event is unknown.
Any,
/// An event emitted when the file is executed, or the folder opened.
Execute,
/// An event emitted when the file is opened for reading.
Read,
/// An event emitted when the file is opened for writing.
Write,
/// An event which specific kind is known but cannot be represented otherwise.
Other,
}
/// An event describing non-mutating access operations on files.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "kind", content = "mode"))]
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
pub enum AccessKind {
/// The catch-all case, to be used when the specific kind of event is unknown.
Any,
/// An event emitted when the file is read.
Read,
/// An event emitted when the file, or a handle to the file, is opened.
Open(AccessMode),
/// An event emitted when the file, or a handle to the file, is closed.
Close(AccessMode),
/// An event which specific kind is known but cannot be represented otherwise.
Other,
}
/// An event describing creation operations on files.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "kind"))]
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
pub enum CreateKind {
/// The catch-all case, to be used when the specific kind of event is unknown.
Any,
/// An event which results in the creation of a file.
File,
/// An event which results in the creation of a folder.
Folder,
/// An event which specific kind is known but cannot be represented otherwise.
Other,
}
/// An event emitted when the data content of a file is changed.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
pub enum DataChange {
/// The catch-all case, to be used when the specific kind of event is unknown.
Any,
/// An event emitted when the size of the data is changed.
Size,
/// An event emitted when the content of the data is changed.
Content,
/// An event which specific kind is known but cannot be represented otherwise.
Other,
}
/// An event emitted when the metadata of a file or folder is changed.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
pub enum MetadataKind {
/// The catch-all case, to be used when the specific kind of event is unknown.
Any,
/// An event emitted when the access time of the file or folder is changed.
AccessTime,
/// An event emitted when the write or modify time of the file or folder is changed.
WriteTime,
/// An event emitted when the permissions of the file or folder are changed.
Permissions,
/// An event emitted when the ownership of the file or folder is changed.
Ownership,
/// An event emitted when an extended attribute of the file or folder is changed.
///
/// If the extended attribute's name or type is known, it should be provided in the
/// `Info` event attribute.
Extended,
/// An event which specific kind is known but cannot be represented otherwise.
Other,
}
/// An event emitted when the name of a file or folder is changed.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
pub enum RenameMode {
/// The catch-all case, to be used when the specific kind of event is unknown.
Any,
/// An event emitted on the file or folder resulting from a rename.
To,
/// An event emitted on the file or folder that was renamed.
From,
/// A single event emitted with both the `From` and `To` paths.
///
/// This event should be emitted when both source and target are known. The paths should be
/// provided in this exact order (from, to).
Both,
/// An event which specific kind is known but cannot be represented otherwise.
Other,
}
/// An event describing mutation of content, name, or metadata.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "kind", content = "mode"))]
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
pub enum ModifyKind {
/// The catch-all case, to be used when the specific kind of event is unknown.
Any,
/// An event emitted when the data content of a file is changed.
Data(DataChange),
/// An event emitted when the metadata of a file or folder is changed.
Metadata(MetadataKind),
/// An event emitted when the name of a file or folder is changed.
#[cfg_attr(feature = "serde", serde(rename = "rename"))]
Name(RenameMode),
/// An event which specific kind is known but cannot be represented otherwise.
Other,
}
/// An event describing removal operations on files.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "kind"))]
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
pub enum RemoveKind {
/// The catch-all case, to be used when the specific kind of event is unknown.
Any,
/// An event emitted when a file is removed.
File,
/// An event emitted when a folder is removed.
Folder,
/// An event which specific kind is known but cannot be represented otherwise.
Other,
}
/// Top-level event kind.
///
/// This is arguably the most important classification for events. All subkinds below this one
/// represent details that may or may not be available for any particular backend, but most tools
/// and Notify systems will only care about which of these four general kinds an event is about.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
pub enum EventKind {
/// The catch-all event kind, for unsupported/unknown events.
///
/// This variant should be used as the "else" case when mapping native kernel bitmasks or
/// bitmaps, such that if the mask is ever extended with new event types the backend will not
/// gain bugs due to not matching new unknown event types.
///
/// This variant is also the default variant used when Notify is in "imprecise" mode.
Any,
/// An event describing non-mutating access operations on files.
///
/// This event is about opening and closing file handles, as well as executing files, and any
/// other such event that is about accessing files, folders, or other structures rather than
/// mutating them.
///
/// Only some platforms are capable of generating these.
Access(AccessKind),
/// An event describing creation operations on files.
///
/// This event is about the creation of files, folders, or other structures but not about e.g.
/// writing new content into them.
Create(CreateKind),
/// An event describing mutation of content, name, or metadata.
///
/// This event is about the mutation of files', folders', or other structures' content, name
/// (path), or associated metadata (attributes).
Modify(ModifyKind),
/// An event describing removal operations on files.
///
/// This event is about the removal of files, folders, or other structures but not e.g. erasing
/// content from them. This may also be triggered for renames/moves that move files _out of the
/// watched subpath_.
///
/// Some editors also trigger Remove events when saving files as they may opt for removing (or
/// renaming) the original then creating a new file in-place.
Remove(RemoveKind),
/// An event not fitting in any of the above four categories.
///
/// This may be used for meta-events about the watch itself. In "imprecise" mode, it is, along
/// with `Any`, the only other event generated.
Other,
}
impl EventKind {
/// Indicates whether an event is an Access variant.
pub fn is_access(&self) -> bool {
matches!(self, EventKind::Access(_))
}
/// Indicates whether an event is a Create variant.
pub fn is_create(&self) -> bool {
matches!(self, EventKind::Create(_))
}
/// Indicates whether an event is a Modify variant.
pub fn is_modify(&self) -> bool {
matches!(self, EventKind::Modify(_))
}
/// Indicates whether an event is a Remove variant.
pub fn is_remove(&self) -> bool {
matches!(self, EventKind::Remove(_))
}
/// Indicates whether an event is an Other variant.
pub fn is_other(&self) -> bool {
matches!(self, EventKind::Other)
}
}
impl Default for EventKind {
fn default() -> Self {
EventKind::Any
}
}
/// Notify event.
#[derive(Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Event {
/// Kind or type of the event.
///
/// This is a hierarchy of enums describing the event as precisely as possible. All enums in
/// the hierarchy have two variants always present, `Any` and `Other`, accompanied by one or
/// more specific variants.
///
/// `Any` should be used when more detail about the event is not known beyond the variant
/// already selected. For example, `AccessMode::Any` means a file has been accessed, but that's
/// all we know.
///
/// `Other` should be used when more detail _is_ available, but cannot be encoded as one of the
/// defined variants. When specifying `Other`, the event attributes should contain an `Info`
/// entry with a short string identifying this detail. That string is to be considered part of
/// the interface of the backend (i.e. a change should probably be breaking).
///
/// For example, `CreateKind::Other` with an `Info("mount")` may indicate the binding of a
/// mount. The documentation of the particular backend should indicate if any `Other` events
/// are generated, and what their description means.
///
/// The `EventKind::Any` variant should be used as the "else" case when mapping native kernel
/// bitmasks or bitmaps, such that if the mask is ever extended with new event types the
/// backend will not gain bugs due to not matching new unknown event types.
#[cfg_attr(feature = "serde", serde(rename = "type"))]
pub kind: EventKind,
/// Paths the event is about, if known.
///
/// If an event concerns two or more paths, and the paths are known at the time of event
/// creation, they should all go in this `Vec`. Otherwise, using the `Tracker` attr may be more
/// appropriate.
///
/// The order of the paths is likely to be significant! For example, renames where both ends of
/// the name change are known will have the "source" path first, and the "target" path last.
pub paths: Vec<PathBuf>,
// "What should be in the struct" and "what can go in the attrs" is an interesting question.
//
// Technically, the paths could go in the attrs. That would reduce the type size to 4 pointer
// widths, instead of 7 like it is now. Anything 8 and below is probably good — on x64 that's
// the size of an L1 cache line. The entire kind classification fits in 3 bytes, and an AnyMap
// is 3 pointers. A Vec<PathBuf> is another 3 pointers.
//
// Type size aside, what's behind these structures? A Vec and a PathBuf is stored on the heap.
// An AnyMap is stored on the heap. But a Vec is directly there, requiring about one access to
// get, while retrieving anything in the AnyMap requires some accesses as overhead.
//
// So things that are used often should be on the struct, and things that are used more rarely
// should go in the attrs. Additionally, arbitrary data can _only_ go in the attrs.
//
// The kind and the paths vie for first place on this scale, depending on how downstream wishes
// to use the information. Everything else is secondary. So far, that's why paths live here.
//
// In the future, it might be possible to have more data and to benchmark things properly, so
// the performance can be actually quantified. Also, it might turn out that I have no idea what
// I was talking about, so the above may be discarded or reviewed. We'll see!
//
/// Additional attributes of the event.
///
/// Arbitrary data may be added to this field, without restriction beyond the `Sync` and
/// `Clone` properties. Some data added here is considered for comparing and hashing, but not
/// all: at this writing this is `Tracker`, `Flag`, `Info`, and `Source`.
#[cfg_attr(feature = "serde", serde(default))]
pub attrs: EventAttributes,
}
/// Additional attributes of the event.
#[derive(Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct EventAttributes {
#[cfg_attr(feature = "serde", serde(flatten))]
inner: Option<Box<EventAttributesInner>>,
}
#[derive(Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
struct EventAttributesInner {
/// Tracking ID for events that are related.
///
/// For events generated by backends with the `TrackRelated` capability. Those backends _may_
/// emit events that are related to each other, and tag those with an identical "tracking id"
/// or "cookie". The value is normalised to `usize`.
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
tracker: Option<usize>,
/// Special Notify flag on the event.
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
flag: Option<Flag>,
/// Additional information on the event.
///
/// This is to be used for all `Other` variants of the event kind hierarchy. The variant
/// indicates that a consumer should look into the `attrs` for an `Info` value; if that value
/// is missing it should be considered a backend bug.
///
/// This attribute may also be present for non-`Other` variants of the event kind, if doing so
/// provides useful precision. For example, the `Modify(Metadata(Extended))` kind suggests
/// using this attribute when information about _what_ extended metadata changed is available.
///
/// This should be a short string, and changes may be considered breaking.
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
info: Option<String>,
/// The source of the event.
///
/// In most cases this should be a short string, identifying the backend unambiguously. In some
/// cases this may be dynamically generated, but should contain a prefix to make it unambiguous
/// between backends.
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
source: Option<String>,
/// The process ID of the originator of the event.
///
/// This attribute is experimental and, while included in Notify itself, is not considered
/// stable or standard enough to be part of the serde, eq, hash, and debug representations.
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing, skip_deserializing)
)]
process_id: Option<u32>,
}
impl EventAttributes {
/// Creates a new `EventAttributes`.
pub fn new() -> Self {
Self { inner: None }
}
/// Retrieves the tracker ID for an event directly, if present.
pub fn tracker(&self) -> Option<usize> {
self.inner.as_ref().and_then(|inner| inner.tracker)
}
/// Retrieves the Notify flag for an event directly, if present.
pub fn flag(&self) -> Option<Flag> {
self.inner.as_ref().and_then(|inner| inner.flag.clone())
}
/// Retrieves the additional info for an event directly, if present.
pub fn info(&self) -> Option<&str> {
self.inner.as_ref().and_then(|inner| inner.info.as_deref())
}
/// Retrieves the source for an event directly, if present.
pub fn source(&self) -> Option<&str> {
self.inner
.as_ref()
.and_then(|inner| inner.source.as_deref())
}
/// The process ID of the originator of the event.
///
/// This attribute is experimental and, while included in Notify itself, is not considered
/// stable or standard enough to be part of the serde, eq, hash, and debug representations.
pub fn process_id(&self) -> Option<u32> {
self.inner.as_ref().and_then(|inner| inner.process_id)
}
/// Sets the tracker.
pub fn set_tracker(&mut self, tracker: usize) {
self.inner_mut().tracker = Some(tracker);
}
/// Sets the Notify flag onto the event.
pub fn set_flag(&mut self, flag: Flag) {
self.inner_mut().flag = Some(flag);
}
/// Sets additional info onto the event.
pub fn set_info(&mut self, info: &str) {
self.inner_mut().info = Some(info.to_string());
}
/// Sets the process id onto the event.
pub fn set_process_id(&mut self, process_id: u32) {
self.inner_mut().process_id = Some(process_id)
}
fn inner_mut(&mut self) -> &mut EventAttributesInner {
self.inner
.get_or_insert_with(|| Box::new(Default::default()))
}
}
/// Special Notify flag on the event.
///
/// This attribute is used to flag certain kinds of events that Notify either marks or generates in
/// particular ways.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum Flag {
/*
/// Event notices are emitted by debounced watchers immediately after the _first_ event of that
/// kind is received on a path to indicate activity to a path within the interval of a debounce.
///
/// Event notices are a runtime option and are disabled by default. (TODO)
Notice,
/// Ongoing event notices are emitted by debounced watchers on a higher frequency than the
/// debouncing delay to indicate ongoing activity to a path within the interval of a debounce.
///
/// Ongoing event notices are a runtime option and are disabled by default.
Ongoing,
*/
/// Rescan notices are emitted by some platforms (and may also be emitted by Notify itself).
/// They indicate either a lapse in the events or a change in the filesystem such that events
/// received so far can no longer be relied on to represent the state of the filesystem now.
///
/// An application that simply reacts to file changes may not care about this. An application
/// that keeps an in-memory representation of the filesystem will need to care, and will need
/// to refresh that representation directly from the filesystem.
Rescan,
}
impl Event {
/// Retrieves the tracker ID for an event directly, if present.
pub fn tracker(&self) -> Option<usize> {
self.attrs.tracker()
}
/// Retrieves the Notify flag for an event directly, if present.
pub fn flag(&self) -> Option<Flag> {
self.attrs.flag()
}
/// Retrieves the additional info for an event directly, if present.
pub fn info(&self) -> Option<&str> {
self.attrs.info()
}
/// Retrieves the source for an event directly, if present.
pub fn source(&self) -> Option<&str> {
self.attrs.source()
}
/// Creates a new `Event` given a kind.
pub fn new(kind: EventKind) -> Self {
Self {
kind,
paths: Vec::new(),
attrs: EventAttributes::new(),
}
}
/// Sets the kind.
pub fn set_kind(mut self, kind: EventKind) -> Self {
self.kind = kind;
self
}
/// Adds a path to the event.
pub fn add_path(mut self, path: PathBuf) -> Self {
self.paths.push(path);
self
}
/// Adds a path to the event if the argument is Some.
pub fn add_some_path(self, path: Option<PathBuf>) -> Self {
if let Some(path) = path {
self.add_path(path)
} else {
self
}
}
/// Sets the tracker.
pub fn set_tracker(mut self, tracker: usize) -> Self {
self.attrs.set_tracker(tracker);
self
}
/// Sets additional info onto the event.
pub fn set_info(mut self, info: &str) -> Self {
self.attrs.set_info(info);
self
}
/// Sets the Notify flag onto the event.
pub fn set_flag(mut self, flag: Flag) -> Self {
self.attrs.set_flag(flag);
self
}
/// Sets the process id onto the event.
pub fn set_process_id(mut self, process_id: u32) -> Self {
self.attrs.set_process_id(process_id);
self
}
}
impl fmt::Debug for Event {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Event")
.field("kind", &self.kind)
.field("paths", &self.paths)
.field("attr:tracker", &self.tracker())
.field("attr:flag", &self.flag())
.field("attr:info", &self.info())
.field("attr:source", &self.source())
.finish()
}
}
impl Default for Event {
fn default() -> Self {
Self {
kind: EventKind::default(),
paths: Vec::new(),
attrs: EventAttributes::new(),
}
}
}
impl Eq for Event {}
impl PartialEq for Event {
fn eq(&self, other: &Self) -> bool {
self.kind.eq(&other.kind)
&& self.paths.eq(&other.paths)
&& self.tracker().eq(&other.tracker())
&& self.flag().eq(&other.flag())
&& self.info().eq(&other.info())
&& self.source().eq(&other.source())
}
}
impl Hash for Event {
fn hash<H: Hasher>(&self, state: &mut H) {
self.kind.hash(state);
self.paths.hash(state);
self.tracker().hash(state);
self.flag().hash(state);
self.info().hash(state);
self.source().hash(state);
}
}