notify-5.2.0/.cargo_vcs_info.json0000644000000001440000000000100123470ustar { "git": { "sha1": "d7df0d9d8c2c55b6d79d923ccd3ec467dd284131" }, "path_in_vcs": "notify" }notify-5.2.0/.gitignore000064400000000000000000000001140072674642500131540ustar 00000000000000/target /Cargo.lock .*.sw* tests/last-fails tests/last-run.log .cargo notify-5.2.0/Cargo.toml0000644000000053530000000000100103540ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.56" name = "notify" version = "5.2.0" authors = [ "Félix Saparelli ", "Daniel Faust ", ] description = "Cross-platform filesystem notification library" homepage = "https://github.com/notify-rs/notify" documentation = "https://docs.rs/notify" readme = "README.md" keywords = [ "events", "filesystem", "notify", "watch", ] categories = ["filesystem"] license = "CC0-1.0 OR Artistic-2.0" repository = "https://github.com/notify-rs/notify.git" resolver = "1" [dependencies.bitflags] version = "1.0.4" [dependencies.crossbeam-channel] version = "0.5.0" optional = true [dependencies.filetime] version = "0.2.6" [dependencies.libc] version = "0.2.4" [dependencies.serde] version = "1.0.89" features = ["derive"] optional = true [dependencies.walkdir] version = "2.2.2" [dev-dependencies.nix] version = "0.23.1" [dev-dependencies.serde_json] version = "1.0.39" [dev-dependencies.tempfile] version = "3.2.0" [features] default = [ "macos_fsevent", "crossbeam-channel", ] macos_fsevent = ["fsevent-sys"] macos_kqueue = [ "kqueue", "mio", ] manual_tests = [] timing_tests = [] [target."cfg(any(target_os=\"freebsd\", target_os=\"openbsd\", target_os = \"netbsd\", target_os = \"dragonflybsd\"))".dependencies.kqueue] version = "^1.0.4" [target."cfg(any(target_os=\"freebsd\", target_os=\"openbsd\", target_os = \"netbsd\", target_os = \"dragonflybsd\"))".dependencies.mio] version = "0.8" features = ["os-ext"] [target."cfg(any(target_os=\"linux\", target_os=\"android\"))".dependencies.inotify] version = "0.9" default-features = false [target."cfg(any(target_os=\"linux\", target_os=\"android\"))".dependencies.mio] version = "0.8" features = ["os-ext"] [target."cfg(target_os=\"macos\")".dependencies.fsevent-sys] version = "4" optional = true [target."cfg(target_os=\"macos\")".dependencies.kqueue] version = "1.0" optional = true [target."cfg(target_os=\"macos\")".dependencies.mio] version = "0.8" features = ["os-ext"] optional = true [target."cfg(windows)".dependencies.windows-sys] version = "0.45.0" features = [ "Win32_System_Threading", "Win32_Foundation", "Win32_Storage_FileSystem", "Win32_Security", "Win32_System_WindowsProgramming", "Win32_System_IO", ] notify-5.2.0/Cargo.toml.orig000064400000000000000000000035130072674642500140610ustar 00000000000000[package] name = "notify" version = "5.2.0" rust-version = "1.56" description = "Cross-platform filesystem notification library" documentation = "https://docs.rs/notify" homepage = "https://github.com/notify-rs/notify" repository = "https://github.com/notify-rs/notify.git" readme = "../README.md" license = "CC0-1.0 OR Artistic-2.0" keywords = ["events", "filesystem", "notify", "watch"] categories = ["filesystem"] authors = [ "Félix Saparelli ", "Daniel Faust " ] edition = "2021" [dependencies] bitflags = "1.0.4" crossbeam-channel = { version = "0.5.0", optional = true } filetime = "0.2.6" libc = "0.2.4" serde = { version = "1.0.89", features = ["derive"], optional = true } walkdir = "2.2.2" [target.'cfg(any(target_os="linux", target_os="android"))'.dependencies] inotify = { version = "0.9", default-features = false } mio = { version = "0.8", features = ["os-ext"] } [target.'cfg(target_os="macos")'.dependencies] fsevent-sys = { version = "4", optional = true } kqueue = { version = "1.0", optional = true } mio = { version = "0.8", features = ["os-ext"], optional = true } [target.'cfg(windows)'.dependencies] windows-sys = { version = "0.45.0", features = ["Win32_System_Threading", "Win32_Foundation", "Win32_Storage_FileSystem", "Win32_Security", "Win32_System_WindowsProgramming", "Win32_System_IO"] } [target.'cfg(any(target_os="freebsd", target_os="openbsd", target_os = "netbsd", target_os = "dragonflybsd"))'.dependencies] kqueue = "^1.0.4" # fix for #344 mio = { version = "0.8", features = ["os-ext"] } [dev-dependencies] serde_json = "1.0.39" tempfile = "3.2.0" nix = "0.23.1" [features] default = ["macos_fsevent","crossbeam-channel"] timing_tests = [] manual_tests = [] macos_kqueue = ["kqueue", "mio"] macos_fsevent = ["fsevent-sys"] notify-5.2.0/LICENSE000064400000000000000000000000120072674642500121660ustar 00000000000000../LICENSEnotify-5.2.0/LICENSE.ARTISTIC000064400000000000000000000000230072674642500134110ustar 00000000000000../LICENSE.ARTISTICnotify-5.2.0/README.md000064400000000000000000000077700072674642500124620ustar 00000000000000# Notify [![» Crate](https://flat.badgen.net/crates/v/notify)][crate] [![» Docs](https://flat.badgen.net/badge/api/docs.rs/df3600)][docs] [![» CI](https://flat.badgen.net/github/checks/notify-rs/notify/main)][build] [![» Downloads](https://flat.badgen.net/crates/d/notify)][crate] [![» Conduct](https://flat.badgen.net/badge/contributor/covenant/5e0d73)][coc] [![» Public Domain](https://flat.badgen.net/badge/license/CC0-1.0/purple)][cc0] _Cross-platform filesystem notification library for Rust._ (Looking for desktop notifications instead? Have a look at [notify-rust] or [alert-after]!) - [API Documentation][docs] - [Debouncer Documentation][debouncer] - [Examples][examples] - [Crate page][crate] - [Changelog][changelog] - [Upgrading from v4](UPGRADING_V4_TO_V5.md) - Earliest supported Rust version: **1.56** - **incomplete [Guides and in-depth docs][wiki]** As used by: [alacritty], [cargo watch], [cobalt], [docket], [mdBook], [pax], [rdiff], [rust-analyzer], [timetrack], [watchexec], [xi-editor], [watchfiles], and others. ## Platforms - Linux / Android: inotify - macOS: FSEvents or kqueue, see features - Windows: ReadDirectoryChangesW - FreeBSD / NetBSD / OpenBSD / DragonflyBSD: kqueue - All platforms: polling ### FSEvents Due to the inner security model of FSEvents (see [FileSystemEventSecurity]), some events cannot be observed easily when trying to follow files that do not belong to you. In this case, reverting to the pollwatcher can fix the issue, with a slight performance cost. ## License Notify was undergoing a transition to using the [Artistic License 2.0][artistic] from [CC Zero 1.0][cc0]. A part of the code is only under CC0, and another part, including _all new code_ since commit [`3378ac5a`], is under _both_ CC0 and Artistic. When the project was to be entirely free of CC0 code, the license would be formally changed (and that would have incurred a major version bump). As part of this, contributions to Notify since would agree to release under both. [`3378ac5a`]: https://github.com/notify-rs/notify/commit/3378ac5ad5f174dfeacce6edadd7ded1a08d384e ## Origins Inspired by Go's [fsnotify] and Node.js's [Chokidar], born out of need for [cargo watch], and general frustration at the non-existence of C/Rust cross-platform notify libraries. Originally created by [Félix Saparelli] and awesome [contributors]. [Chokidar]: https://github.com/paulmillr/chokidar [FileSystemEventSecurity]: https://developer.apple.com/library/mac/documentation/Darwin/Conceptual/FSEvents_ProgGuide/FileSystemEventSecurity/FileSystemEventSecurity.html [debouncer]: https://github.com/notify-rs/notify/tree/main/notify-debouncer-mini [Félix Saparelli]: https://passcod.name [alacritty]: https://github.com/jwilm/alacritty [alert-after]: https://github.com/frewsxcv/alert-after [artistic]: ./LICENSE.ARTISTIC [build]: https://github.com/notify-rs/notify/actions [cargo watch]: https://github.com/passcod/cargo-watch [cc0]: ./LICENSE [changelog]: ./CHANGELOG.md [cobalt]: https://github.com/cobalt-org/cobalt.rs [coc]: http://contributor-covenant.org/version/1/4/ [contributors]: https://github.com/notify-rs/notify/graphs/contributors [crate]: https://crates.io/crates/notify [docket]: https://iwillspeak.github.io/docket/ [docs]: https://docs.rs/notify/5.2.0/notify/ [fsnotify]: https://github.com/go-fsnotify/fsnotify [handlebars-iron]: https://github.com/sunng87/handlebars-iron [hotwatch]: https://github.com/francesca64/hotwatch [mdBook]: https://github.com/rust-lang-nursery/mdBook [notify-rust]: https://github.com/hoodie/notify-rust [pax]: https://pax.js.org/ [rdiff]: https://github.com/dyule/rdiff [rust-analyzer]: https://github.com/rust-analyzer/rust-analyzer [serde]: https://serde.rs/ [timetrack]: https://github.com/joshmcguigan/timetrack [watchexec]: https://github.com/mattgreen/watchexec [wiki]: https://github.com/notify-rs/notify/wiki [xi-editor]: https://xi-editor.io/ [watchfiles]: https://watchfiles.helpmanual.io/ [examples]: examples/notify-5.2.0/src/config.rs000064400000000000000000000060000072674642500135660ustar 00000000000000//! Configuration types use std::time::Duration; /// Indicates whether only the provided directory or its sub-directories as well should be watched #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] pub enum RecursiveMode { /// Watch all sub-directories as well, including directories created after installing the watch Recursive, /// Watch only the provided directory NonRecursive, } impl RecursiveMode { pub(crate) fn is_recursive(&self) -> bool { match *self { RecursiveMode::Recursive => true, RecursiveMode::NonRecursive => false, } } } /// Watcher Backend configuration /// /// This contains multiple settings that may relate to only one specific backend, /// such as to correctly configure each backend regardless of what is selected during runtime. /// /// ```rust /// # use std::time::Duration; /// # use notify::Config; /// let config = Config::default() /// .with_poll_interval(Duration::from_secs(2)) /// .with_compare_contents(true); /// ``` /// /// Some options can be changed during runtime, others have to be set when creating the watcher backend. #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub struct Config { /// See [BackendConfig::with_poll_interval] poll_interval: Duration, /// See [BackendConfig::with_compare_contents] compare_contents: bool, } impl Config { /// For [crate::PollWatcher] /// /// Interval between each rescan attempt. This can be extremely expensive for large /// file trees so it is recommended to measure and tune accordingly. /// /// The default poll frequency is 30 seconds. pub fn with_poll_interval(mut self, dur: Duration) -> Self { self.poll_interval = dur; self } /// Returns current setting pub fn poll_interval(&self) -> Duration { self.poll_interval } /// For [crate::PollWatcher] /// /// Optional feature that will evaluate the contents of changed files to determine if /// they have indeed changed using a fast hashing algorithm. This is especially important /// for pseudo filesystems like those on Linux under /sys and /proc which are not obligated /// to respect any other filesystem norms such as modification timestamps, file sizes, etc. /// By enabling this feature, performance will be significantly impacted as all files will /// need to be read and hashed at each `poll_interval`. /// /// This can't be changed during runtime. Off by default. pub fn with_compare_contents(mut self, compare_contents: bool) -> Self { self.compare_contents = compare_contents; self } /// Returns current setting pub fn compare_contents(&self) -> bool { self.compare_contents } } impl Default for Config { fn default() -> Self { Self { poll_interval: Duration::from_secs(30), compare_contents: false } } }notify-5.2.0/src/error.rs000064400000000000000000000121770072674642500134660ustar 00000000000000//! Error types use crate::Config; use std::error::Error as StdError; use std::path::PathBuf; use std::result::Result as StdResult; use std::{self, fmt, io}; /// Type alias to use this library's `Error` type in a Result pub type Result = StdResult; /// Error kinds #[derive(Debug)] pub enum ErrorKind { /// Generic error /// /// May be used in cases where a platform specific error is mapped to this type, or for opaque /// internal errors. Generic(String), /// I/O errors. Io(io::Error), /// A path does not exist. PathNotFound, /// Attempted to remove a watch that does not exist. WatchNotFound, /// An invalid value was passed as runtime configuration. InvalidConfig(Config), /// Can't watch (more) files, limit on the total number of inotify watches reached MaxFilesWatch, } /// Notify error type. /// /// Errors are emitted either at creation time of a `Watcher`, or during the event stream. They /// range from kernel errors to filesystem errors to argument errors. /// /// Errors can be general, or they can be about specific paths or subtrees. In that later case, the /// error's `paths` field will be populated. #[derive(Debug)] pub struct Error { /// Kind of the error. pub kind: ErrorKind, /// Relevant paths to the error, if any. pub paths: Vec, } impl Error { /// Adds a path to the error. pub fn add_path(mut self, path: PathBuf) -> Self { self.paths.push(path); self } /// Replaces the paths for the error. pub fn set_paths(mut self, paths: Vec) -> Self { self.paths = paths; self } /// Creates a new Error with empty paths given its kind. pub fn new(kind: ErrorKind) -> Self { Self { kind, paths: Vec::new(), } } /// Creates a new generic Error from a message. pub fn generic(msg: &str) -> Self { Self::new(ErrorKind::Generic(msg.into())) } /// Creates a new i/o Error from a stdlib `io::Error`. pub fn io(err: io::Error) -> Self { Self::new(ErrorKind::Io(err)) } /// Creates a new "path not found" error. pub fn path_not_found() -> Self { Self::new(ErrorKind::PathNotFound) } /// Creates a new "watch not found" error. pub fn watch_not_found() -> Self { Self::new(ErrorKind::WatchNotFound) } /// Creates a new "invalid config" error from the given `Config`. pub fn invalid_config(config: &Config) -> Self { Self::new(ErrorKind::InvalidConfig(config.clone())) } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let error = match self.kind { ErrorKind::PathNotFound => "No path was found.".into(), ErrorKind::WatchNotFound => "No watch was found.".into(), ErrorKind::InvalidConfig(ref config) => format!("Invalid configuration: {:?}", config), ErrorKind::Generic(ref err) => err.clone(), ErrorKind::Io(ref err) => err.to_string(), ErrorKind::MaxFilesWatch => "OS file watch limit reached.".into(), }; if self.paths.is_empty() { write!(f, "{}", error) } else { write!(f, "{} about {:?}", error, self.paths) } } } impl StdError for Error { fn cause(&self) -> Option<&dyn StdError> { match self.kind { ErrorKind::Io(ref cause) => Some(cause), _ => None, } } } impl From for Error { fn from(err: io::Error) -> Self { Error::io(err) } } #[cfg(feature = "crossbeam-channel")] impl From> for Error { fn from(err: crossbeam_channel::SendError) -> Self { Error::generic(&format!("internal channel disconnect: {:?}", err)) } } #[cfg(not(feature = "crossbeam-channel"))] impl From> for Error { fn from(err: std::sync::mpsc::SendError) -> Self { Error::generic(&format!("internal channel disconnect: {:?}", err)) } } #[cfg(feature = "crossbeam-channel")] impl From for Error { fn from(err: crossbeam_channel::RecvError) -> Self { Error::generic(&format!("internal channel disconnect: {:?}", err)) } } #[cfg(not(feature = "crossbeam-channel"))] impl From for Error { fn from(err: std::sync::mpsc::RecvError) -> Self { Error::generic(&format!("internal channel disconnect: {:?}", err)) } } impl From> for Error { fn from(err: std::sync::PoisonError) -> Self { Error::generic(&format!("internal mutex poisoned: {:?}", err)) } } #[test] fn display_formatted_errors() { let expected = "Some error"; assert_eq!(expected, format!("{}", Error::generic(expected))); assert_eq!( expected, format!( "{}", Error::io(io::Error::new(io::ErrorKind::Other, expected)) ) ); } notify-5.2.0/src/event.rs000064400000000000000000000572000072674642500134520ustar 00000000000000// 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, Copy, 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, Copy, 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, Copy, 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, Copy, 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, Copy, 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, Copy, 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, Copy, 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, Copy, 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, Copy, 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. 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. /// /// You might want to check [`Event::need_rescan`] to make sure no event was missed before you /// received this one. #[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, // "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 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, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct EventAttributes { #[cfg_attr(feature = "serde", serde(flatten))] inner: Option>, } #[derive(Clone, Default, Debug)] #[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, /// Special Notify flag on the event. #[cfg_attr( feature = "serde", serde(default, skip_serializing_if = "Option::is_none") )] flag: Option, /// 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, /// 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, /// 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, } 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 { self.inner.as_ref().and_then(|inner| inner.tracker) } /// Retrieves the Notify flag for an event directly, if present. pub fn flag(&self) -> Option { self.inner.as_ref().and_then(|inner| inner.flag) } /// 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 { 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, Copy, 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 { /// Returns whether some events may have been missed. If true, you should assume any file or /// folder might have been modified. /// /// See [`Flag::Rescan`] for more information. pub fn need_rescan(&self) -> bool { matches!(self.flag(), Some(Flag::Rescan)) } /// Retrieves the tracker ID for an event directly, if present. pub fn tracker(&self) -> Option { self.attrs.tracker() } /// Retrieves the Notify flag for an event directly, if present. pub fn flag(&self) -> Option { 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) -> 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(&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); } } notify-5.2.0/src/fsevent.rs000064400000000000000000000546640072674642500140160ustar 00000000000000//! Watcher implementation for Darwin's FSEvents API //! //! The FSEvents API provides a mechanism to notify clients about directories they ought to re-scan //! in order to keep their internal data structures up-to-date with respect to the true state of //! the file system. (For example, when files or directories are created, modified, or removed.) It //! sends these notifications "in bulk", possibly notifying the client of changes to several //! directories in a single callback. //! //! For more information see the [FSEvents API reference][ref]. //! //! TODO: document event translation //! //! [ref]: https://developer.apple.com/library/mac/documentation/Darwin/Reference/FSEvents_Ref/ #![allow(non_upper_case_globals, dead_code)] use crate::event::*; use crate::{unbounded, Config, Error, EventHandler, RecursiveMode, Result, Sender, Watcher}; use fsevent_sys as fs; use fsevent_sys::core_foundation as cf; use std::collections::HashMap; use std::ffi::CStr; use std::fmt; use std::os::raw; use std::path::{Path, PathBuf}; use std::ptr; use std::sync::{Arc, Mutex}; use std::thread; bitflags::bitflags! { #[repr(C)] struct StreamFlags: u32 { const NONE = fs::kFSEventStreamEventFlagNone; const MUST_SCAN_SUBDIRS = fs::kFSEventStreamEventFlagMustScanSubDirs; const USER_DROPPED = fs::kFSEventStreamEventFlagUserDropped; const KERNEL_DROPPED = fs::kFSEventStreamEventFlagKernelDropped; const IDS_WRAPPED = fs::kFSEventStreamEventFlagEventIdsWrapped; const HISTORY_DONE = fs::kFSEventStreamEventFlagHistoryDone; const ROOT_CHANGED = fs::kFSEventStreamEventFlagRootChanged; const MOUNT = fs::kFSEventStreamEventFlagMount; const UNMOUNT = fs::kFSEventStreamEventFlagUnmount; const ITEM_CREATED = fs::kFSEventStreamEventFlagItemCreated; const ITEM_REMOVED = fs::kFSEventStreamEventFlagItemRemoved; const INODE_META_MOD = fs::kFSEventStreamEventFlagItemInodeMetaMod; const ITEM_RENAMED = fs::kFSEventStreamEventFlagItemRenamed; const ITEM_MODIFIED = fs::kFSEventStreamEventFlagItemModified; const FINDER_INFO_MOD = fs::kFSEventStreamEventFlagItemFinderInfoMod; const ITEM_CHANGE_OWNER = fs::kFSEventStreamEventFlagItemChangeOwner; const ITEM_XATTR_MOD = fs::kFSEventStreamEventFlagItemXattrMod; const IS_FILE = fs::kFSEventStreamEventFlagItemIsFile; const IS_DIR = fs::kFSEventStreamEventFlagItemIsDir; const IS_SYMLINK = fs::kFSEventStreamEventFlagItemIsSymlink; const OWN_EVENT = fs::kFSEventStreamEventFlagOwnEvent; const IS_HARDLINK = fs::kFSEventStreamEventFlagItemIsHardlink; const IS_LAST_HARDLINK = fs::kFSEventStreamEventFlagItemIsLastHardlink; const ITEM_CLONED = fs::kFSEventStreamEventFlagItemCloned; } } /// FSEvents-based `Watcher` implementation pub struct FsEventWatcher { paths: cf::CFMutableArrayRef, since_when: fs::FSEventStreamEventId, latency: cf::CFTimeInterval, flags: fs::FSEventStreamCreateFlags, event_handler: Arc>, runloop: Option<(cf::CFRunLoopRef, thread::JoinHandle<()>)>, recursive_info: HashMap, } impl fmt::Debug for FsEventWatcher { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("FsEventWatcher") .field("paths", &self.paths) .field("since_when", &self.since_when) .field("latency", &self.latency) .field("flags", &self.flags) .field("event_handler", &Arc::as_ptr(&self.event_handler)) .field("runloop", &self.runloop) .field("recursive_info", &self.recursive_info) .finish() } } // CFMutableArrayRef is a type alias to *mut libc::c_void, so FsEventWatcher is not Send/Sync // automatically. It's Send because the pointer is not used in other threads. unsafe impl Send for FsEventWatcher {} // It's Sync because all methods that change the mutable state use `&mut self`. unsafe impl Sync for FsEventWatcher {} fn translate_flags(flags: StreamFlags, precise: bool) -> Vec { let mut evs = Vec::new(); // «Denotes a sentinel event sent to mark the end of the "historical" events // sent as a result of specifying a `sinceWhen` value in the FSEvents.Create // call that created this event stream. After invoking the client's callback // with all the "historical" events that occurred before now, the client's // callback will be invoked with an event where the HistoryDone flag is set. // The client should ignore the path supplied in this callback.» // — https://www.mbsplugins.eu/FSEventsNextEvent.shtml // // As a result, we just stop processing here and return an empty vec, which // will ignore this completely and not emit any Events whatsoever. if flags.contains(StreamFlags::HISTORY_DONE) { return evs; } // FSEvents provides two possible hints as to why events were dropped, // however documentation on what those mean is scant, so we just pass them // through in the info attr field. The intent is clear enough, and the // additional information is provided if the user wants it. if flags.contains(StreamFlags::MUST_SCAN_SUBDIRS) { let e = Event::new(EventKind::Other).set_flag(Flag::Rescan); evs.push(if flags.contains(StreamFlags::USER_DROPPED) { e.set_info("rescan: user dropped") } else if flags.contains(StreamFlags::KERNEL_DROPPED) { e.set_info("rescan: kernel dropped") } else { e }); } // In imprecise mode, let's not even bother parsing the kind of the event // except for the above very special events. if !precise { evs.push(Event::new(EventKind::Any)); return evs; } // This is most likely a rename or a removal. We assume rename but may want // to figure out if it was a removal some way later (TODO). To denote the // special nature of the event, we add an info string. if flags.contains(StreamFlags::ROOT_CHANGED) { evs.push( Event::new(EventKind::Modify(ModifyKind::Name(RenameMode::From))) .set_info("root changed"), ); } // A path was mounted at the event path; we treat that as a create. if flags.contains(StreamFlags::MOUNT) { evs.push(Event::new(EventKind::Create(CreateKind::Other)).set_info("mount")); } // A path was unmounted at the event path; we treat that as a remove. if flags.contains(StreamFlags::UNMOUNT) { evs.push(Event::new(EventKind::Remove(RemoveKind::Other)).set_info("mount")); } if flags.contains(StreamFlags::ITEM_CREATED) { evs.push(if flags.contains(StreamFlags::IS_DIR) { Event::new(EventKind::Create(CreateKind::Folder)) } else if flags.contains(StreamFlags::IS_FILE) { Event::new(EventKind::Create(CreateKind::File)) } else { let e = Event::new(EventKind::Create(CreateKind::Other)); if flags.contains(StreamFlags::IS_SYMLINK) { e.set_info("is: symlink") } else if flags.contains(StreamFlags::IS_HARDLINK) { e.set_info("is: hardlink") } else if flags.contains(StreamFlags::ITEM_CLONED) { e.set_info("is: clone") } else { Event::new(EventKind::Create(CreateKind::Any)) } }); } if flags.contains(StreamFlags::ITEM_REMOVED) { evs.push(if flags.contains(StreamFlags::IS_DIR) { Event::new(EventKind::Remove(RemoveKind::Folder)) } else if flags.contains(StreamFlags::IS_FILE) { Event::new(EventKind::Remove(RemoveKind::File)) } else { let e = Event::new(EventKind::Remove(RemoveKind::Other)); if flags.contains(StreamFlags::IS_SYMLINK) { e.set_info("is: symlink") } else if flags.contains(StreamFlags::IS_HARDLINK) { e.set_info("is: hardlink") } else if flags.contains(StreamFlags::ITEM_CLONED) { e.set_info("is: clone") } else { Event::new(EventKind::Remove(RemoveKind::Any)) } }); } // FSEvents provides no mechanism to associate the old and new sides of a // rename event. if flags.contains(StreamFlags::ITEM_RENAMED) { evs.push(Event::new(EventKind::Modify(ModifyKind::Name( RenameMode::Any, )))); } // This is only described as "metadata changed", but it may be that it's // only emitted for some more precise subset of events... if so, will need // amending, but for now we have an Any-shaped bucket to put it in. if flags.contains(StreamFlags::INODE_META_MOD) { evs.push(Event::new(EventKind::Modify(ModifyKind::Metadata( MetadataKind::Any, )))); } if flags.contains(StreamFlags::FINDER_INFO_MOD) { evs.push( Event::new(EventKind::Modify(ModifyKind::Metadata(MetadataKind::Other))) .set_info("meta: finder info"), ); } if flags.contains(StreamFlags::ITEM_CHANGE_OWNER) { evs.push(Event::new(EventKind::Modify(ModifyKind::Metadata( MetadataKind::Ownership, )))); } if flags.contains(StreamFlags::ITEM_XATTR_MOD) { evs.push(Event::new(EventKind::Modify(ModifyKind::Metadata( MetadataKind::Extended, )))); } // This is specifically described as a data change, which we take to mean // is a content change. if flags.contains(StreamFlags::ITEM_MODIFIED) { evs.push(Event::new(EventKind::Modify(ModifyKind::Data( DataChange::Content, )))); } if flags.contains(StreamFlags::OWN_EVENT) { for ev in &mut evs { *ev = std::mem::take(ev).set_process_id(std::process::id()); } } evs } struct StreamContextInfo { event_handler: Arc>, recursive_info: HashMap, } // Free the context when the stream created by `FSEventStreamCreate` is released. extern "C" fn release_context(info: *const libc::c_void) { // Safety: // - The [documentation] for `FSEventStreamContext` states that `release` is only // called when the stream is deallocated, so it is safe to convert `info` back into a // box and drop it. // // [docs]: https://developer.apple.com/documentation/coreservices/fseventstreamcontext?language=objc unsafe { drop(Box::from_raw( info as *const StreamContextInfo as *mut StreamContextInfo, )); } } extern "C" { /// Indicates whether the run loop is waiting for an event. fn CFRunLoopIsWaiting(runloop: cf::CFRunLoopRef) -> cf::Boolean; } impl FsEventWatcher { fn from_event_handler(event_handler: Arc>) -> Result { Ok(FsEventWatcher { paths: unsafe { cf::CFArrayCreateMutable(cf::kCFAllocatorDefault, 0, &cf::kCFTypeArrayCallBacks) }, since_when: fs::kFSEventStreamEventIdSinceNow, latency: 0.0, flags: fs::kFSEventStreamCreateFlagFileEvents | fs::kFSEventStreamCreateFlagNoDefer, event_handler, runloop: None, recursive_info: HashMap::new(), }) } fn watch_inner(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> { self.stop(); let result = self.append_path(path, recursive_mode); // ignore return error: may be empty path list let _ = self.run(); result } fn unwatch_inner(&mut self, path: &Path) -> Result<()> { self.stop(); let result = self.remove_path(path); // ignore return error: may be empty path list let _ = self.run(); result } #[inline] fn is_running(&self) -> bool { self.runloop.is_some() } fn stop(&mut self) { if !self.is_running() { return; } if let Some((runloop, thread_handle)) = self.runloop.take() { unsafe { let runloop = runloop as *mut raw::c_void; while CFRunLoopIsWaiting(runloop) == 0 { thread::yield_now(); } cf::CFRunLoopStop(runloop); } // Wait for the thread to shut down. thread_handle.join().expect("thread to shut down"); } } fn remove_path(&mut self, path: &Path) -> Result<()> { let str_path = path.to_str().unwrap(); unsafe { let mut err: cf::CFErrorRef = ptr::null_mut(); let cf_path = cf::str_path_to_cfstring_ref(str_path, &mut err); if cf_path.is_null() { cf::CFRelease(err as cf::CFRef); return Err(Error::watch_not_found().add_path(path.into())); } let mut to_remove = Vec::new(); for idx in 0..cf::CFArrayGetCount(self.paths) { let item = cf::CFArrayGetValueAtIndex(self.paths, idx); if cf::CFStringCompare(item, cf_path, cf::kCFCompareCaseInsensitive) == cf::kCFCompareEqualTo { to_remove.push(idx); } } cf::CFRelease(cf_path); for idx in to_remove.iter().rev() { cf::CFArrayRemoveValueAtIndex(self.paths, *idx); } } let p = if let Ok(canonicalized_path) = path.canonicalize() { canonicalized_path } else { path.to_owned() }; match self.recursive_info.remove(&p) { Some(_) => Ok(()), None => Err(Error::watch_not_found()), } } // https://github.com/thibaudgg/rb-fsevent/blob/master/ext/fsevent_watch/main.c fn append_path(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> { if !path.exists() { return Err(Error::path_not_found().add_path(path.into())); } let canonical_path = path.to_path_buf().canonicalize()?; let str_path = path.to_str().unwrap(); unsafe { let mut err: cf::CFErrorRef = ptr::null_mut(); let cf_path = cf::str_path_to_cfstring_ref(str_path, &mut err); if cf_path.is_null() { // Most likely the directory was deleted, or permissions changed, // while the above code was running. cf::CFRelease(err as cf::CFRef); return Err(Error::path_not_found().add_path(path.into())); } cf::CFArrayAppendValue(self.paths, cf_path); cf::CFRelease(cf_path); } self.recursive_info .insert(canonical_path, recursive_mode.is_recursive()); Ok(()) } fn run(&mut self) -> Result<()> { if unsafe { cf::CFArrayGetCount(self.paths) } == 0 { // TODO: Reconstruct and add paths to error return Err(Error::path_not_found()); } // We need to associate the stream context with our callback in order to propagate events // to the rest of the system. This will be owned by the stream, and will be freed when the // stream is closed. This means we will leak the context if we panic before reacing // `FSEventStreamRelease`. let context = Box::into_raw(Box::new(StreamContextInfo { event_handler: self.event_handler.clone(), recursive_info: self.recursive_info.clone(), })); let stream_context = fs::FSEventStreamContext { version: 0, info: context as *mut libc::c_void, retain: None, release: Some(release_context), copy_description: None, }; let stream = unsafe { fs::FSEventStreamCreate( cf::kCFAllocatorDefault, callback, &stream_context, self.paths, self.since_when, self.latency, self.flags, ) }; // Wrapper to help send CFRef types across threads. struct CFSendWrapper(cf::CFRef); // Safety: // - According to the Apple documentation, it's safe to move `CFRef`s across threads. // https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html unsafe impl Send for CFSendWrapper {} // move into thread let stream = CFSendWrapper(stream); // channel to pass runloop around let (rl_tx, rl_rx) = unbounded(); let thread_handle = thread::Builder::new() .name("notify-rs fsevents loop".to_string()) .spawn(move || { let _ = &stream; let stream = stream.0; unsafe { let cur_runloop = cf::CFRunLoopGetCurrent(); fs::FSEventStreamScheduleWithRunLoop( stream, cur_runloop, cf::kCFRunLoopDefaultMode, ); fs::FSEventStreamStart(stream); // the calling to CFRunLoopRun will be terminated by CFRunLoopStop call in drop() rl_tx .send(CFSendWrapper(cur_runloop)) .expect("Unable to send runloop to watcher"); cf::CFRunLoopRun(); fs::FSEventStreamStop(stream); fs::FSEventStreamInvalidate(stream); fs::FSEventStreamRelease(stream); } })?; // block until runloop has been sent self.runloop = Some((rl_rx.recv().unwrap().0, thread_handle)); Ok(()) } fn configure_raw_mode(&mut self, _config: Config, tx: Sender>) { tx.send(Ok(false)) .expect("configuration channel disconnect"); } } extern "C" fn callback( stream_ref: fs::FSEventStreamRef, info: *mut libc::c_void, num_events: libc::size_t, // size_t numEvents event_paths: *mut libc::c_void, // void *eventPaths event_flags: *const fs::FSEventStreamEventFlags, // const FSEventStreamEventFlags eventFlags[] event_ids: *const fs::FSEventStreamEventId, // const FSEventStreamEventId eventIds[] ) { unsafe { callback_impl( stream_ref, info, num_events, event_paths, event_flags, event_ids, ) } } unsafe fn callback_impl( _stream_ref: fs::FSEventStreamRef, info: *mut libc::c_void, num_events: libc::size_t, // size_t numEvents event_paths: *mut libc::c_void, // void *eventPaths event_flags: *const fs::FSEventStreamEventFlags, // const FSEventStreamEventFlags eventFlags[] _event_ids: *const fs::FSEventStreamEventId, // const FSEventStreamEventId eventIds[] ) { let event_paths = event_paths as *const *const libc::c_char; let info = info as *const StreamContextInfo; let event_handler = &(*info).event_handler; for p in 0..num_events { let path = CStr::from_ptr(*event_paths.add(p)) .to_str() .expect("Invalid UTF8 string."); let path = PathBuf::from(path); let flag = *event_flags.add(p); let flag = StreamFlags::from_bits(flag).unwrap_or_else(|| { panic!("Unable to decode StreamFlags: {}", flag); }); let mut handle_event = false; for (p, r) in &(*info).recursive_info { if path.starts_with(p) { if *r || &path == p { handle_event = true; break; } else if let Some(parent_path) = path.parent() { if parent_path == p { handle_event = true; break; } } } } if !handle_event { continue; } for ev in translate_flags(flag, true).into_iter() { // TODO: precise let ev = ev.add_path(path.clone()); let mut event_handler = event_handler.lock().expect("lock not to be poisoned"); event_handler.handle_event(Ok(ev)); } } } impl Watcher for FsEventWatcher { /// Create a new watcher. fn new(event_handler: F, _config: Config) -> Result { Self::from_event_handler(Arc::new(Mutex::new(event_handler))) } fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> { self.watch_inner(path, recursive_mode) } fn unwatch(&mut self, path: &Path) -> Result<()> { self.unwatch_inner(path) } fn configure(&mut self, config: Config) -> Result { let (tx, rx) = unbounded(); self.configure_raw_mode(config, tx); rx.recv()? } fn kind() -> crate::WatcherKind { crate::WatcherKind::Fsevent } } impl Drop for FsEventWatcher { fn drop(&mut self) { self.stop(); unsafe { cf::CFRelease(self.paths); } } } #[test] fn test_fsevent_watcher_drop() { use super::*; use std::time::Duration; let dir = tempfile::tempdir().unwrap(); let (tx, rx) = std::sync::mpsc::channel(); { let mut watcher = FsEventWatcher::new(tx, Default::default()).unwrap(); watcher.watch(dir.path(), RecursiveMode::Recursive).unwrap(); thread::sleep(Duration::from_millis(2000)); println!("is running -> {}", watcher.is_running()); thread::sleep(Duration::from_millis(1000)); watcher.unwatch(dir.path()).unwrap(); println!("is running -> {}", watcher.is_running()); } thread::sleep(Duration::from_millis(1000)); for res in rx { let e = res.unwrap(); println!("debug => {:?} {:?}", e.kind, e.paths); } println!("in test: {} works", file!()); } #[test] fn test_steam_context_info_send_and_sync() { fn check_send() {} check_send::(); } notify-5.2.0/src/inotify.rs000064400000000000000000000664020072674642500140160ustar 00000000000000//! Watcher implementation for the inotify Linux API //! //! The inotify API provides a mechanism for monitoring filesystem events. Inotify can be used to //! monitor individual files, or to monitor directories. When a directory is monitored, inotify //! will return events for the directory itself, and for files inside the directory. use super::event::*; use super::{Config, Error, ErrorKind, EventHandler, RecursiveMode, Result, Watcher}; use crate::{bounded, unbounded, BoundSender, Receiver, Sender}; use inotify as inotify_sys; use inotify_sys::{EventMask, Inotify, WatchDescriptor, WatchMask}; use std::collections::HashMap; use std::env; use std::ffi::OsStr; use std::fs::metadata; use std::os::unix::io::AsRawFd; use std::path::{Path, PathBuf}; use std::sync::Arc; use std::thread; use std::time::Duration; use walkdir::WalkDir; const INOTIFY: mio::Token = mio::Token(0); const MESSAGE: mio::Token = mio::Token(1); // The EventLoop will set up a mio::Poll and use it to wait for the following: // // - messages telling it what to do // // - events telling it that something has happened on one of the watched files. struct EventLoop { running: bool, poll: mio::Poll, event_loop_waker: Arc, event_loop_tx: Sender, event_loop_rx: Receiver, inotify: Option, event_handler: Box, watches: HashMap, paths: HashMap, rename_event: Option, } /// Watcher implementation based on inotify #[derive(Debug)] pub struct INotifyWatcher { channel: Sender, waker: Arc, } enum EventLoopMsg { AddWatch(PathBuf, RecursiveMode, Sender>), RemoveWatch(PathBuf, Sender>), Shutdown, RenameTimeout(usize), Configure(Config, BoundSender>), } #[inline] fn send_pending_rename_event( rename_event: &mut Option, event_handler: &mut dyn EventHandler, ) { if let Some(e) = rename_event.take() { event_handler.handle_event(Ok(e)); } } #[inline] fn add_watch_by_event( path: &Option, event: &inotify_sys::Event<&OsStr>, watches: &HashMap, add_watches: &mut Vec, ) { if let Some(ref path) = *path { if event.mask.contains(EventMask::ISDIR) { if let Some(parent_path) = path.parent() { if let Some(&(_, _, is_recursive)) = watches.get(parent_path) { if is_recursive { add_watches.push(path.to_owned()); } } } } } } #[inline] fn remove_watch_by_event( path: &Option, watches: &HashMap, remove_watches: &mut Vec, ) { if let Some(ref path) = *path { if watches.contains_key(path) { remove_watches.push(path.to_owned()); } } } impl EventLoop { pub fn new(inotify: Inotify, event_handler: Box) -> Result { let (event_loop_tx, event_loop_rx) = unbounded::(); let poll = mio::Poll::new()?; let event_loop_waker = Arc::new(mio::Waker::new(poll.registry(), MESSAGE)?); let inotify_fd = inotify.as_raw_fd(); let mut evented_inotify = mio::unix::SourceFd(&inotify_fd); poll.registry() .register(&mut evented_inotify, INOTIFY, mio::Interest::READABLE)?; let event_loop = EventLoop { running: true, poll, event_loop_waker, event_loop_tx, event_loop_rx, inotify: Some(inotify), event_handler, watches: HashMap::new(), paths: HashMap::new(), rename_event: None, }; Ok(event_loop) } // Run the event loop. pub fn run(self) { let _ = thread::Builder::new() .name("notify-rs inotify loop".to_string()) .spawn(|| self.event_loop_thread()); } fn event_loop_thread(mut self) { let mut events = mio::Events::with_capacity(16); loop { // Wait for something to happen. match self.poll.poll(&mut events, None) { Err(ref e) if matches!(e.kind(), std::io::ErrorKind::Interrupted) => { // System call was interrupted, we will retry // TODO: Not covered by tests (to reproduce likely need to setup signal handlers) } Err(e) => panic!("poll failed: {}", e), Ok(()) => {} } // Process whatever happened. for event in &events { self.handle_event(event); } // Stop, if we're done. if !self.running { break; } } } // Handle a single event. fn handle_event(&mut self, event: &mio::event::Event) { match event.token() { MESSAGE => { // The channel is readable - handle messages. self.handle_messages() } INOTIFY => { // inotify has something to tell us. self.handle_inotify() } _ => unreachable!(), } } fn handle_messages(&mut self) { while let Ok(msg) = self.event_loop_rx.try_recv() { match msg { EventLoopMsg::AddWatch(path, recursive_mode, tx) => { let _ = tx.send(self.add_watch(path, recursive_mode.is_recursive(), true)); } EventLoopMsg::RemoveWatch(path, tx) => { let _ = tx.send(self.remove_watch(path, false)); } EventLoopMsg::Shutdown => { let _ = self.remove_all_watches(); if let Some(inotify) = self.inotify.take() { let _ = inotify.close(); } self.running = false; break; } EventLoopMsg::RenameTimeout(cookie) => { let current_cookie = self.rename_event.as_ref().and_then(|e| e.tracker()); // send pending rename event only if the rename event for which the timer has been created hasn't been handled already; otherwise ignore this timeout if current_cookie == Some(cookie) { send_pending_rename_event(&mut self.rename_event, &mut *self.event_handler); } } EventLoopMsg::Configure(config, tx) => { self.configure_raw_mode(config, tx); } } } } fn configure_raw_mode(&mut self, _config: Config, tx: BoundSender>) { tx.send(Ok(false)) .expect("configuration channel disconnected"); } fn handle_inotify(&mut self) { let mut add_watches = Vec::new(); let mut remove_watches = Vec::new(); if let Some(ref mut inotify) = self.inotify { let mut buffer = [0; 1024]; // Read all buffers available. loop { match inotify.read_events(&mut buffer) { Ok(events) => { let mut num_events = 0; for event in events { num_events += 1; if event.mask.contains(EventMask::Q_OVERFLOW) { let ev = Ok(Event::new(EventKind::Other).set_flag(Flag::Rescan)); self.event_handler.handle_event(ev); } let path = match event.name { Some(name) => { self.paths.get(&event.wd).map(|root| root.join(&name)) } None => self.paths.get(&event.wd).cloned(), }; if event.mask.contains(EventMask::MOVED_FROM) { send_pending_rename_event( &mut self.rename_event, &mut *self.event_handler, ); remove_watch_by_event(&path, &self.watches, &mut remove_watches); self.rename_event = Some( Event::new(EventKind::Modify(ModifyKind::Name( RenameMode::From, ))) .add_some_path(path.clone()) .set_tracker(event.cookie as usize), ); } else { let mut evs = Vec::new(); if event.mask.contains(EventMask::MOVED_TO) { if let Some(e) = self.rename_event.take() { if e.tracker() == Some(event.cookie as usize) { self.event_handler.handle_event(Ok(e.clone())); evs.push( Event::new(EventKind::Modify(ModifyKind::Name( RenameMode::To, ))) .set_tracker(event.cookie as usize) .add_some_path(path.clone()), ); evs.push( Event::new(EventKind::Modify(ModifyKind::Name( RenameMode::Both, ))) .set_tracker(event.cookie as usize) .add_some_path(e.paths.first().cloned()) .add_some_path(path.clone()), ); } else { // TODO should it be rename? evs.push( Event::new(EventKind::Create( if event.mask.contains(EventMask::ISDIR) { CreateKind::Folder } else { CreateKind::File }, )) .add_some_path(path.clone()), ); } } else { // TODO should it be rename? evs.push( Event::new(EventKind::Create( if event.mask.contains(EventMask::ISDIR) { CreateKind::Folder } else { CreateKind::File }, )) .add_some_path(path.clone()), ); } add_watch_by_event( &path, &event, &self.watches, &mut add_watches, ); } if event.mask.contains(EventMask::MOVE_SELF) { evs.push( Event::new(EventKind::Modify(ModifyKind::Name( RenameMode::From, ))) .add_some_path(path.clone()), ); // TODO stat the path and get to new path // - emit To and Both events // - change prefix for further events } if event.mask.contains(EventMask::CREATE) { evs.push( Event::new(EventKind::Create( if event.mask.contains(EventMask::ISDIR) { CreateKind::Folder } else { CreateKind::File }, )) .add_some_path(path.clone()), ); add_watch_by_event( &path, &event, &self.watches, &mut add_watches, ); } if event.mask.contains(EventMask::DELETE_SELF) || event.mask.contains(EventMask::DELETE) { evs.push( Event::new(EventKind::Remove( if event.mask.contains(EventMask::ISDIR) { RemoveKind::Folder } else { RemoveKind::File }, )) .add_some_path(path.clone()), ); remove_watch_by_event( &path, &self.watches, &mut remove_watches, ); } if event.mask.contains(EventMask::MODIFY) { evs.push( Event::new(EventKind::Modify(ModifyKind::Data( DataChange::Any, ))) .add_some_path(path.clone()), ); } if event.mask.contains(EventMask::CLOSE_WRITE) { evs.push( Event::new(EventKind::Access(AccessKind::Close( AccessMode::Write, ))) .add_some_path(path.clone()), ); } if event.mask.contains(EventMask::CLOSE_NOWRITE) { evs.push( Event::new(EventKind::Access(AccessKind::Close( AccessMode::Read, ))) .add_some_path(path.clone()), ); } if event.mask.contains(EventMask::ATTRIB) { evs.push( Event::new(EventKind::Modify(ModifyKind::Metadata( MetadataKind::Any, ))) .add_some_path(path.clone()), ); } if event.mask.contains(EventMask::OPEN) { evs.push( Event::new(EventKind::Access(AccessKind::Open( AccessMode::Any, ))) .add_some_path(path.clone()), ); } if !evs.is_empty() { send_pending_rename_event( &mut self.rename_event, &mut *self.event_handler, ); } for ev in evs { self.event_handler.handle_event(Ok(ev)); } } } // All events read. Break out. if num_events == 0 { break; } // When receiving only the first part of a move event (IN_MOVED_FROM) it is unclear // whether the second part (IN_MOVED_TO) will arrive because the file or directory // could just have been moved out of the watched directory. So it's necessary to wait // for possible subsequent events in case it's a complete move event but also to make sure // that the first part of the event is handled in a timely manner in case no subsequent events arrive. // TODO: don't do this here, instead leave it entirely to the debounce // -> related to some rename events being reported as creates. if let Some(ref rename_event) = self.rename_event { let event_loop_tx = self.event_loop_tx.clone(); let waker = self.event_loop_waker.clone(); let cookie = rename_event.tracker().unwrap(); // unwrap is safe because rename_event is always set with some cookie let _ = thread::Builder::new() .name("notify-rs inotify rename".to_string()) .spawn(move || { thread::sleep(Duration::from_millis(10)); // wait up to 10 ms for a subsequent event // An error here means the other end of the channel was closed, a thing that can // happen normally. let _ = event_loop_tx.send(EventLoopMsg::RenameTimeout(cookie)); let _ = waker.wake(); }); } } Err(e) => { self.event_handler.handle_event(Err(Error::io(e))); } } } } for path in remove_watches { self.remove_watch(path, true).ok(); } for path in add_watches { self.add_watch(path, true, false).ok(); } } fn add_watch(&mut self, path: PathBuf, is_recursive: bool, mut watch_self: bool) -> Result<()> { // If the watch is not recursive, or if we determine (by stat'ing the path to get its // metadata) that the watched path is not a directory, add a single path watch. if !is_recursive || !metadata(&path).map_err(Error::io)?.is_dir() { return self.add_single_watch(path, false, true); } for entry in WalkDir::new(path) .follow_links(true) .into_iter() .filter_map(filter_dir) { self.add_single_watch(entry.path().to_path_buf(), is_recursive, watch_self)?; watch_self = false; } Ok(()) } fn add_single_watch( &mut self, path: PathBuf, is_recursive: bool, watch_self: bool, ) -> Result<()> { let mut watchmask = WatchMask::ATTRIB | WatchMask::CREATE | WatchMask::DELETE | WatchMask::CLOSE_WRITE | WatchMask::MODIFY | WatchMask::MOVED_FROM | WatchMask::MOVED_TO; if watch_self { watchmask.insert(WatchMask::DELETE_SELF); watchmask.insert(WatchMask::MOVE_SELF); } if let Some(&(_, old_watchmask, _)) = self.watches.get(&path) { watchmask.insert(old_watchmask); watchmask.insert(WatchMask::MASK_ADD); } if let Some(ref mut inotify) = self.inotify { match inotify.add_watch(&path, watchmask) { Err(e) => { Err(if e.raw_os_error() == Some(libc::ENOSPC) { // do not report inotify limits as "no more space" on linux #266 Error::new(ErrorKind::MaxFilesWatch) } else { Error::io(e) } .add_path(path)) } Ok(w) => { watchmask.remove(WatchMask::MASK_ADD); self.watches .insert(path.clone(), (w.clone(), watchmask, is_recursive)); self.paths.insert(w, path); Ok(()) } } } else { Ok(()) } } fn remove_watch(&mut self, path: PathBuf, remove_recursive: bool) -> Result<()> { match self.watches.remove(&path) { None => return Err(Error::watch_not_found().add_path(path)), Some((w, _, is_recursive)) => { if let Some(ref mut inotify) = self.inotify { inotify .rm_watch(w.clone()) .map_err(|e| Error::io(e).add_path(path.clone()))?; self.paths.remove(&w); if is_recursive || remove_recursive { let mut remove_list = Vec::new(); for (w, p) in &self.paths { if p.starts_with(&path) { inotify .rm_watch(w.clone()) .map_err(|e| Error::io(e).add_path(p.into()))?; self.watches.remove(p); remove_list.push(w.clone()); } } for w in remove_list { self.paths.remove(&w); } } } } } Ok(()) } fn remove_all_watches(&mut self) -> Result<()> { if let Some(ref mut inotify) = self.inotify { for (w, p) in &self.paths { inotify .rm_watch(w.clone()) .map_err(|e| Error::io(e).add_path(p.into()))?; } self.watches.clear(); self.paths.clear(); } Ok(()) } } /// return `DirEntry` when it is a directory fn filter_dir(e: walkdir::Result) -> Option { if let Ok(e) = e { if let Ok(metadata) = e.metadata() { if metadata.is_dir() { return Some(e); } } } None } impl INotifyWatcher { fn from_event_handler(event_handler: Box) -> Result { let inotify = Inotify::init()?; let event_loop = EventLoop::new(inotify, event_handler)?; let channel = event_loop.event_loop_tx.clone(); let waker = event_loop.event_loop_waker.clone(); event_loop.run(); Ok(INotifyWatcher { channel, waker }) } fn watch_inner(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> { let pb = if path.is_absolute() { path.to_owned() } else { let p = env::current_dir().map_err(Error::io)?; p.join(path) }; let (tx, rx) = unbounded(); let msg = EventLoopMsg::AddWatch(pb, recursive_mode, tx); // we expect the event loop to live and reply => unwraps must not panic self.channel.send(msg).unwrap(); self.waker.wake().unwrap(); rx.recv().unwrap() } fn unwatch_inner(&mut self, path: &Path) -> Result<()> { let pb = if path.is_absolute() { path.to_owned() } else { let p = env::current_dir().map_err(Error::io)?; p.join(path) }; let (tx, rx) = unbounded(); let msg = EventLoopMsg::RemoveWatch(pb, tx); // we expect the event loop to live and reply => unwraps must not panic self.channel.send(msg).unwrap(); self.waker.wake().unwrap(); rx.recv().unwrap() } } impl Watcher for INotifyWatcher { /// Create a new watcher. fn new(event_handler: F, _config: Config) -> Result { Self::from_event_handler(Box::new(event_handler)) } fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> { self.watch_inner(path, recursive_mode) } fn unwatch(&mut self, path: &Path) -> Result<()> { self.unwatch_inner(path) } fn configure(&mut self, config: Config) -> Result { let (tx, rx) = bounded(1); self.channel.send(EventLoopMsg::Configure(config, tx))?; self.waker.wake()?; rx.recv()? } fn kind() -> crate::WatcherKind { crate::WatcherKind::Inotify } } impl Drop for INotifyWatcher { fn drop(&mut self) { // we expect the event loop to live => unwrap must not panic self.channel.send(EventLoopMsg::Shutdown).unwrap(); self.waker.wake().unwrap(); } } #[test] fn inotify_watcher_is_send_and_sync() { fn check() {} check::(); } notify-5.2.0/src/kqueue.rs000064400000000000000000000423770072674642500136410ustar 00000000000000//! Watcher implementation for the kqueue API //! //! The kqueue() system call provides a generic method of notifying the user //! when an event happens or a condition holds, based on the results of small //! pieces of kernel code termed filters. use super::event::*; use super::{Config, Error, EventHandler, RecursiveMode, Result, Watcher}; use crate::{unbounded, Receiver, Sender}; use kqueue::{EventData, EventFilter, FilterFlag, Ident}; use std::collections::HashMap; use std::env; use std::fs::metadata; use std::os::unix::io::AsRawFd; use std::path::{Path, PathBuf}; use std::sync::Arc; use std::thread; use walkdir::WalkDir; const KQUEUE: mio::Token = mio::Token(0); const MESSAGE: mio::Token = mio::Token(1); // The EventLoop will set up a mio::Poll and use it to wait for the following: // // - messages telling it what to do // // - events telling it that something has happened on one of the watched files. struct EventLoop { running: bool, poll: mio::Poll, event_loop_waker: Arc, event_loop_tx: Sender, event_loop_rx: Receiver, kqueue: kqueue::Watcher, event_handler: Box, watches: HashMap, } /// Watcher implementation based on inotify #[derive(Debug)] pub struct KqueueWatcher { channel: Sender, waker: Arc, } enum EventLoopMsg { AddWatch(PathBuf, RecursiveMode, Sender>), RemoveWatch(PathBuf, Sender>), Shutdown, } impl EventLoop { pub fn new(kqueue: kqueue::Watcher, event_handler: Box) -> Result { let (event_loop_tx, event_loop_rx) = unbounded::(); let poll = mio::Poll::new()?; let event_loop_waker = Arc::new(mio::Waker::new(poll.registry(), MESSAGE)?); let kqueue_fd = kqueue.as_raw_fd(); let mut evented_kqueue = mio::unix::SourceFd(&kqueue_fd); poll.registry() .register(&mut evented_kqueue, KQUEUE, mio::Interest::READABLE)?; let event_loop = EventLoop { running: true, poll, event_loop_waker, event_loop_tx, event_loop_rx, kqueue, event_handler, watches: HashMap::new(), }; Ok(event_loop) } // Run the event loop. pub fn run(self) { let _ = thread::Builder::new() .name("notify-rs kqueue loop".to_string()) .spawn(|| self.event_loop_thread()); } fn event_loop_thread(mut self) { let mut events = mio::Events::with_capacity(16); loop { // Wait for something to happen. match self.poll.poll(&mut events, None) { Err(ref e) if matches!(e.kind(), std::io::ErrorKind::Interrupted) => { // System call was interrupted, we will retry // TODO: Not covered by tests (to reproduce likely need to setup signal handlers) } Err(e) => panic!("poll failed: {}", e), Ok(()) => {} } // Process whatever happened. for event in &events { self.handle_event(event); } // Stop, if we're done. if !self.running { break; } } } // Handle a single event. fn handle_event(&mut self, event: &mio::event::Event) { match event.token() { MESSAGE => { // The channel is readable - handle messages. self.handle_messages() } KQUEUE => { // inotify has something to tell us. self.handle_kqueue() } _ => unreachable!(), } } fn handle_messages(&mut self) { while let Ok(msg) = self.event_loop_rx.try_recv() { match msg { EventLoopMsg::AddWatch(path, recursive_mode, tx) => { let _ = tx.send(self.add_watch(path, recursive_mode.is_recursive())); } EventLoopMsg::RemoveWatch(path, tx) => { let _ = tx.send(self.remove_watch(path, false)); } EventLoopMsg::Shutdown => { self.running = false; break; } } } } fn handle_kqueue(&mut self) { let mut add_watches = Vec::new(); let mut remove_watches = Vec::new(); while let Some(event) = self.kqueue.poll(None) { match event { kqueue::Event { data: EventData::Vnode(data), ident: Ident::Filename(_, path), } => { let path = PathBuf::from(path); let event = match data { /* TODO: Differenciate folders and files kqueue dosen't tell us if this was a file or a dir, so we could only emulate this inotify behavior if we keep track of all files and directories internally and then perform a lookup. */ kqueue::Vnode::Delete => { remove_watches.push(path.clone()); Ok(Event::new(EventKind::Remove(RemoveKind::Any)).add_path(path)) } // a write to a directory means that a new file was created in it, let's // figure out which file this was kqueue::Vnode::Write if path.is_dir() => { // find which file is new in the directory by comparing it with our // list of known watches std::fs::read_dir(&path) .map(|dir| { dir.filter_map(std::result::Result::ok) .map(|f| f.path()) .find(|f| !self.watches.contains_key(f)) }) .map(|file| { if let Some(file) = file { // watch this new file add_watches.push(file.clone()); Event::new(EventKind::Create(if file.is_dir() { CreateKind::Folder } else if file.is_file() { CreateKind::File } else { CreateKind::Other })) .add_path(file) } else { Event::new(EventKind::Modify(ModifyKind::Data( DataChange::Any, ))) .add_path(path) } }) .map_err(Into::into) } // data was written to this file kqueue::Vnode::Write => Ok(Event::new(EventKind::Modify( ModifyKind::Data(DataChange::Any), )) .add_path(path)), /* Extend and Truncate are just different names for the same operation, extend is only used on FreeBSD, truncate everwhere else */ kqueue::Vnode::Extend | kqueue::Vnode::Truncate => Ok(Event::new( EventKind::Modify(ModifyKind::Data(DataChange::Size)), ) .add_path(path)), /* this kevent has the same problem as the delete kevent. The only way i can think of providing "better" event with more information is to do the diff our self, while this maybe do able of delete. In this case it would somewhat expensive to keep track and compare ever peace of metadata for every file */ kqueue::Vnode::Attrib => Ok(Event::new(EventKind::Modify( ModifyKind::Metadata(MetadataKind::Any), )) .add_path(path)), /* The link count on a file changed => subdirectory created or delete. */ kqueue::Vnode::Link => { // As we currently don't have a solution that whould allow us // to only add/remove the new/delete directory and that dosn't include a // possible race condition. On possible solution would be to // create a `HashMap>` which would // include every directory and this content add the time of // adding it to kqueue. While this sould allow us to do the // diff and only add/remove the files nessesary. This whould // also introduce a race condition, where multiple files could // all ready be remove from the directory, and we could get out // of sync. // So for now, until we find a better solution, let remove and // readd the whole directory. // This is a expensive operation, as we recursive through all // subdirectories. remove_watches.push(path.clone()); add_watches.push(path.clone()); Ok(Event::new(EventKind::Modify(ModifyKind::Any)).add_path(path)) } // Kqueue not provide us with the infomation nessesary to provide // the new file name to the event. kqueue::Vnode::Rename => { remove_watches.push(path.clone()); Ok( Event::new(EventKind::Modify(ModifyKind::Name(RenameMode::Any))) .add_path(path), ) } // Access to the file was revoked via revoke(2) or the underlying file system was unmounted. kqueue::Vnode::Revoke => { remove_watches.push(path.clone()); Ok(Event::new(EventKind::Remove(RemoveKind::Any)).add_path(path)) } // On different BSD variants, different extra events may be present #[allow(unreachable_patterns)] _ => Ok(Event::new(EventKind::Other)), }; self.event_handler.handle_event(event); } // as we don't add any other EVFILTER to kqueue we should never get here kqueue::Event { ident: _, data: _ } => unreachable!(), } } for path in remove_watches { self.remove_watch(path, true).ok(); } for path in add_watches { self.add_watch(path, true).ok(); } } fn add_watch(&mut self, path: PathBuf, is_recursive: bool) -> Result<()> { // If the watch is not recursive, or if we determine (by stat'ing the path to get its // metadata) that the watched path is not a directory, add a single path watch. if !is_recursive || !metadata(&path).map_err(Error::io)?.is_dir() { self.add_single_watch(path, false)?; } else { for entry in WalkDir::new(path).follow_links(true).into_iter() { let entry = entry.map_err(map_walkdir_error)?; self.add_single_watch(entry.path().to_path_buf(), is_recursive)?; } } // Only make a single `kevent` syscall to add all the watches. self.kqueue.watch()?; Ok(()) } /// Adds a single watch to the kqueue. /// /// The caller of this function must call `self.kqueue.watch()` afterwards to register the new watch. fn add_single_watch(&mut self, path: PathBuf, is_recursive: bool) -> Result<()> { let event_filter = EventFilter::EVFILT_VNODE; let filter_flags = FilterFlag::NOTE_DELETE | FilterFlag::NOTE_WRITE | FilterFlag::NOTE_EXTEND | FilterFlag::NOTE_ATTRIB | FilterFlag::NOTE_LINK | FilterFlag::NOTE_RENAME | FilterFlag::NOTE_REVOKE; self.kqueue .add_filename(&path, event_filter, filter_flags) .map_err(|e| Error::io(e).add_path(path.clone()))?; self.watches.insert(path, is_recursive); Ok(()) } fn remove_watch(&mut self, path: PathBuf, remove_recursive: bool) -> Result<()> { match self.watches.remove(&path) { None => return Err(Error::watch_not_found()), Some(is_recursive) => { self.kqueue .remove_filename(&path, EventFilter::EVFILT_VNODE) .map_err(|e| Error::io(e).add_path(path.clone()))?; if is_recursive || remove_recursive { for entry in WalkDir::new(path).follow_links(true).into_iter() { let p = entry.map_err(map_walkdir_error)?.path().to_path_buf(); self.kqueue .remove_filename(&p, EventFilter::EVFILT_VNODE) .map_err(|e| Error::io(e).add_path(p))?; } } self.kqueue.watch()?; } } Ok(()) } } fn map_walkdir_error(e: walkdir::Error) -> Error { if e.io_error().is_some() { // save to unwrap otherwise we whouldn't be in this branch Error::io(e.into_io_error().unwrap()) } else { Error::generic(&e.to_string()) } } impl KqueueWatcher { fn from_event_handler(event_handler: Box) -> Result { let kqueue = kqueue::Watcher::new()?; let event_loop = EventLoop::new(kqueue, event_handler)?; let channel = event_loop.event_loop_tx.clone(); let waker = event_loop.event_loop_waker.clone(); event_loop.run(); Ok(KqueueWatcher { channel, waker }) } fn watch_inner(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> { let pb = if path.is_absolute() { path.to_owned() } else { let p = env::current_dir().map_err(Error::io)?; p.join(path) }; let (tx, rx) = unbounded(); let msg = EventLoopMsg::AddWatch(pb, recursive_mode, tx); self.channel .send(msg) .map_err(|e| Error::generic(&e.to_string()))?; self.waker .wake() .map_err(|e| Error::generic(&e.to_string()))?; rx.recv() .unwrap() .map_err(|e| Error::generic(&e.to_string())) } fn unwatch_inner(&mut self, path: &Path) -> Result<()> { let pb = if path.is_absolute() { path.to_owned() } else { let p = env::current_dir().map_err(Error::io)?; p.join(path) }; let (tx, rx) = unbounded(); let msg = EventLoopMsg::RemoveWatch(pb, tx); self.channel .send(msg) .map_err(|e| Error::generic(&e.to_string()))?; self.waker .wake() .map_err(|e| Error::generic(&e.to_string()))?; rx.recv() .unwrap() .map_err(|e| Error::generic(&e.to_string())) } } impl Watcher for KqueueWatcher { /// Create a new watcher. fn new(event_handler: F, _config: Config) -> Result { Self::from_event_handler(Box::new(event_handler)) } fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> { self.watch_inner(path, recursive_mode) } fn unwatch(&mut self, path: &Path) -> Result<()> { self.unwatch_inner(path) } fn kind() -> crate::WatcherKind { crate::WatcherKind::Kqueue } } impl Drop for KqueueWatcher { fn drop(&mut self) { // we expect the event loop to live => unwrap must not panic self.channel.send(EventLoopMsg::Shutdown).unwrap(); self.waker.wake().unwrap(); } } notify-5.2.0/src/lib.rs000064400000000000000000000353460072674642500131060ustar 00000000000000//! Cross-platform file system notification library //! //! # Installation //! //! ```toml //! [dependencies] //! notify = "5.2.0" //! ``` //! //! If you want debounced events, see [notify-debouncer-mini](https://github.com/notify-rs/notify/tree/main/notify-debouncer-mini) //! //! ## Features //! //! List of compilation features, see below for details //! //! - `serde` for serialization of events //! - `macos_fsevent` enabled by default, for fsevent backend on macos //! - `macos_kqueue` for kqueue backend on macos //! - `crossbeam-channel` enabled by default, see below //! //! ### Serde //! //! Events are serialisable via [serde](https://serde.rs) if the `serde` feature is enabled: //! //! ```toml //! notify = { version = "5.2.0", features = ["serde"] } //! ``` //! //! ### Crossbeam-Channel & Tokio //! //! By default crossbeam-channel is used internally by notify. //! This can [cause issues](https://github.com/notify-rs/notify/issues/380) when used inside tokio. //! //! You can disable crossbeam-channel, letting notify fallback to std channels via //! //! ```toml //! notify = { version = "5.2.0", default-features = false, features = ["macos_kqueue"] } //! // Alternatively macos_fsevent instead of macos_kqueue //! ``` //! Note the `macos_kqueue` requirement here, otherwise no backend is available on macos. //! //! # Known Problems //! //! ### Docker with Linux on MacOS M1 //! //! Docker on macos M1 [throws](https://github.com/notify-rs/notify/issues/423) `Function not implemented (os error 38)`. //! You have to manually use the [PollWatcher], as the native backend isn't available inside the emulation. //! //! ### MacOS, FSEvents and unowned files //! //! Due to the inner security model of FSEvents (see [FileSystemEventSecurity](https://developer.apple.com/library/mac/documentation/Darwin/Conceptual/FSEvents_ProgGuide/FileSystemEventSecurity/FileSystemEventSecurity.html)), //! some events cannot be observed easily when trying to follow files that do not //! belong to you. In this case, reverting to the pollwatcher can fix the issue, //! with a slight performance cost. //! //! ### Editor Behaviour //! //! If you rely on precise events (Write/Delete/Create..), you will notice that the actual events //! can differ a lot between file editors. Some truncate the file on save, some create a new one and replace the old one. //! See also [this](https://github.com/notify-rs/notify/issues/247) and [this](https://github.com/notify-rs/notify/issues/113#issuecomment-281836995) issues for example. //! //! ### Parent folder deletion //! //! If you want to receive an event for a deletion of folder `b` for the path `/a/b/..`, you will have to watch its parent `/a`. //! See [here](https://github.com/notify-rs/notify/issues/403) for more details. //! //! ### Pseudo Filesystems like /proc,/sys //! //! Some filesystems like `/proc` and `/sys` on *nix do not emit change events or use correct file change dates. //! To circumvent that problem you can use the [PollWatcher] with the `compare_contents` option. //! //! ### Linux: Bad File Descriptor / No space left on device //! //! This may be the case of running into the max-files watched limits of your user or system. //! (Files also includes folders.) Note that for recursive watched folders each file and folder inside counts towards the limit. //! //! You may increase this limit in linux via //! ```sh //! sudo sysctl fs.inotify.max_user_instances=8192 # example number //! sudo sysctl fs.inotify.max_user_watches=524288 # example number //! sudo sysctl -p //! ``` //! //! Note that the [PollWatcher] is not restricted by this limitation, so it may be an alternative if your users can't increase the limit. //! //! # Examples //! //! For more examples visit the [examples folder](https://github.com/notify-rs/notify/tree/main/examples) in the repository. //! //! ```rust,no_exec //! # use std::path::Path; //! use notify::{Watcher, RecommendedWatcher, RecursiveMode, Result}; //! //! fn main() -> Result<()> { //! // Automatically select the best implementation for your platform. //! let mut watcher = notify::recommended_watcher(|res| { //! match res { //! Ok(event) => println!("event: {:?}", event), //! Err(e) => println!("watch error: {:?}", e), //! } //! })?; //! //! // Add a path to be watched. All files and directories at that path and //! // below will be monitored for changes. //! watcher.watch(Path::new("."), RecursiveMode::Recursive)?; //! //! Ok(()) //! } //! ``` //! //! ## With different configurations //! //! It is possible to create several watchers with different configurations or implementations that //! all call the same event function. This can accommodate advanced behaviour or work around limits. //! //! ``` //! # use notify::{RecommendedWatcher, RecursiveMode, Result, Watcher}; //! # use std::path::Path; //! # //! # fn main() -> Result<()> { //! fn event_fn(res: Result) { //! match res { //! Ok(event) => println!("event: {:?}", event), //! Err(e) => println!("watch error: {:?}", e), //! } //! } //! //! let mut watcher1 = notify::recommended_watcher(event_fn)?; //! // we will just use the same watcher kind again here //! let mut watcher2 = notify::recommended_watcher(event_fn)?; //! # watcher1.watch(Path::new("."), RecursiveMode::Recursive)?; //! # watcher2.watch(Path::new("."), RecursiveMode::Recursive)?; //! # //! # Ok(()) //! # } //! ``` #![deny(missing_docs)] pub use config::{Config, RecursiveMode}; pub use error::{Error, ErrorKind, Result}; pub use event::{Event, EventKind}; use std::path::Path; #[allow(dead_code)] #[cfg(feature = "crossbeam-channel")] pub(crate) type Receiver = crossbeam_channel::Receiver; #[allow(dead_code)] #[cfg(not(feature = "crossbeam-channel"))] pub(crate) type Receiver = std::sync::mpsc::Receiver; #[allow(dead_code)] #[cfg(feature = "crossbeam-channel")] pub(crate) type Sender = crossbeam_channel::Sender; #[allow(dead_code)] #[cfg(not(feature = "crossbeam-channel"))] pub(crate) type Sender = std::sync::mpsc::Sender; // std limitation #[allow(dead_code)] #[cfg(feature = "crossbeam-channel")] pub(crate) type BoundSender = crossbeam_channel::Sender; #[allow(dead_code)] #[cfg(not(feature = "crossbeam-channel"))] pub(crate) type BoundSender = std::sync::mpsc::SyncSender; #[allow(dead_code)] #[inline] pub(crate) fn unbounded() -> (Sender, Receiver) { #[cfg(feature = "crossbeam-channel")] return crossbeam_channel::unbounded(); #[cfg(not(feature = "crossbeam-channel"))] return std::sync::mpsc::channel(); } #[allow(dead_code)] #[inline] pub(crate) fn bounded(cap: usize) -> (BoundSender, Receiver) { #[cfg(feature = "crossbeam-channel")] return crossbeam_channel::bounded(cap); #[cfg(not(feature = "crossbeam-channel"))] return std::sync::mpsc::sync_channel(cap); } #[cfg(all(target_os = "macos", not(feature = "macos_kqueue")))] pub use crate::fsevent::FsEventWatcher; #[cfg(any(target_os = "linux", target_os = "android"))] pub use crate::inotify::INotifyWatcher; #[cfg(any( target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonflybsd", all(target_os = "macos", feature = "macos_kqueue") ))] pub use crate::kqueue::KqueueWatcher; pub use null::NullWatcher; pub use poll::PollWatcher; #[cfg(target_os = "windows")] pub use windows::ReadDirectoryChangesWatcher; #[cfg(all(target_os = "macos", not(feature = "macos_kqueue")))] pub mod fsevent; #[cfg(any(target_os = "linux", target_os = "android"))] pub mod inotify; #[cfg(any( target_os = "freebsd", target_os = "openbsd", target_os = "dragonflybsd", target_os = "netbsd", all(target_os = "macos", feature = "macos_kqueue") ))] pub mod kqueue; #[cfg(target_os = "windows")] pub mod windows; pub mod event; pub mod null; pub mod poll; mod config; mod error; /// The set of requirements for watcher event handling functions. /// /// # Example implementation /// /// ```no_run /// use notify::{Event, Result, EventHandler}; /// /// /// Prints received events /// struct EventPrinter; /// /// impl EventHandler for EventPrinter { /// fn handle_event(&mut self, event: Result) { /// if let Ok(event) = event { /// println!("Event: {:?}", event); /// } /// } /// } /// ``` pub trait EventHandler: Send + 'static { /// Handles an event. fn handle_event(&mut self, event: Result); } impl EventHandler for F where F: FnMut(Result) + Send + 'static, { fn handle_event(&mut self, event: Result) { (self)(event); } } #[cfg(feature = "crossbeam-channel")] impl EventHandler for crossbeam_channel::Sender> { fn handle_event(&mut self, event: Result) { let _ = self.send(event); } } impl EventHandler for std::sync::mpsc::Sender> { fn handle_event(&mut self, event: Result) { let _ = self.send(event); } } /// Watcher kind enumeration #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[non_exhaustive] pub enum WatcherKind { /// inotify backend (linux) Inotify, /// FS-Event backend (mac) Fsevent, /// KQueue backend (bsd,optionally mac) Kqueue, /// Polling based backend (fallback) PollWatcher, /// Windows backend ReadDirectoryChangesWatcher, /// Fake watcher for testing NullWatcher, } /// Type that can deliver file activity notifications /// /// Watcher is implemented per platform using the best implementation available on that platform. /// In addition to such event driven implementations, a polling implementation is also provided /// that should work on any platform. pub trait Watcher { /// Create a new watcher with an initial Config. fn new(event_handler: F, config: config::Config) -> Result where Self: Sized; /// Begin watching a new path. /// /// If the `path` is a directory, `recursive_mode` will be evaluated. If `recursive_mode` is /// `RecursiveMode::Recursive` events will be delivered for all files in that tree. Otherwise /// only the directory and its immediate children will be watched. /// /// If the `path` is a file, `recursive_mode` will be ignored and events will be delivered only /// for the file. /// /// On some platforms, if the `path` is renamed or removed while being watched, behaviour may /// be unexpected. See discussions in [#165] and [#166]. If less surprising behaviour is wanted /// one may non-recursively watch the _parent_ directory as well and manage related events. /// /// [#165]: https://github.com/notify-rs/notify/issues/165 /// [#166]: https://github.com/notify-rs/notify/issues/166 fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()>; /// Stop watching a path. /// /// # Errors /// /// Returns an error in the case that `path` has not been watched or if removing the watch /// fails. fn unwatch(&mut self, path: &Path) -> Result<()>; /// Configure the watcher at runtime. /// /// See the [`Config`](config/enum.Config.html) enum for all configuration options. /// /// # Returns /// /// - `Ok(true)` on success. /// - `Ok(false)` if the watcher does not support or implement the option. /// - `Err(notify::Error)` on failure. fn configure(&mut self, _option: Config) -> Result { Ok(false) } /// Returns the watcher kind, allowing to perform backend-specific tasks fn kind() -> WatcherKind where Self: Sized; } /// The recommended `Watcher` implementation for the current platform #[cfg(any(target_os = "linux", target_os = "android"))] pub type RecommendedWatcher = INotifyWatcher; /// The recommended `Watcher` implementation for the current platform #[cfg(all(target_os = "macos", not(feature = "macos_kqueue")))] pub type RecommendedWatcher = FsEventWatcher; /// The recommended `Watcher` implementation for the current platform #[cfg(target_os = "windows")] pub type RecommendedWatcher = ReadDirectoryChangesWatcher; /// The recommended `Watcher` implementation for the current platform #[cfg(any( target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonflybsd", all(target_os = "macos", feature = "macos_kqueue") ))] pub type RecommendedWatcher = KqueueWatcher; /// The recommended `Watcher` implementation for the current platform #[cfg(not(any( target_os = "linux", target_os = "android", target_os = "macos", target_os = "windows", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonflybsd" )))] pub type RecommendedWatcher = PollWatcher; /// Convenience method for creating the `RecommendedWatcher` for the current platform in /// _immediate_ mode. /// /// See [`Watcher::new_immediate`](trait.Watcher.html#tymethod.new_immediate). pub fn recommended_watcher(event_handler: F) -> Result where F: EventHandler, { // All recommended watchers currently implement `new`, so just call that. RecommendedWatcher::new(event_handler, Config::default()) } #[cfg(test)] mod tests { use super::*; #[test] fn test_object_safe() { let _watcher: &dyn Watcher = &NullWatcher; } #[test] fn test_debug_impl() { macro_rules! assert_debug_impl { ($t:ty) => {{ trait NeedsDebug: std::fmt::Debug {} impl NeedsDebug for $t {} }}; } assert_debug_impl!(Config); assert_debug_impl!(Error); assert_debug_impl!(ErrorKind); assert_debug_impl!(event::AccessKind); assert_debug_impl!(event::AccessMode); assert_debug_impl!(event::CreateKind); assert_debug_impl!(event::DataChange); assert_debug_impl!(event::EventAttributes); assert_debug_impl!(event::Flag); assert_debug_impl!(event::MetadataKind); assert_debug_impl!(event::ModifyKind); assert_debug_impl!(event::RemoveKind); assert_debug_impl!(event::RenameMode); assert_debug_impl!(Event); assert_debug_impl!(EventKind); assert_debug_impl!(NullWatcher); assert_debug_impl!(PollWatcher); assert_debug_impl!(RecommendedWatcher); assert_debug_impl!(RecursiveMode); assert_debug_impl!(WatcherKind); } } notify-5.2.0/src/null.rs000064400000000000000000000015320072674642500133000ustar 00000000000000//! Stub Watcher implementation #![allow(unused_variables)] use crate::Config; use super::{RecursiveMode, Result, Watcher}; use std::path::Path; /// Stub `Watcher` implementation /// /// Events are never delivered from this watcher. #[derive(Debug)] pub struct NullWatcher; impl Watcher for NullWatcher { fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> { Ok(()) } fn unwatch(&mut self, path: &Path) -> Result<()> { Ok(()) } fn new(event_handler: F, config: Config) -> Result where Self: Sized, { Ok(NullWatcher) } fn configure(&mut self, config: Config) -> Result { Ok(false) } fn kind() -> crate::WatcherKind { crate::WatcherKind::NullWatcher } } notify-5.2.0/src/poll.rs000064400000000000000000000442510072674642500133010ustar 00000000000000//! Generic Watcher implementation based on polling //! //! Checks the `watch`ed paths periodically to detect changes. This implementation only uses //! Rust stdlib APIs and should work on all of the platforms it supports. use crate::{EventHandler, RecursiveMode, Watcher, Config}; use std::{ collections::HashMap, path::{Path, PathBuf}, sync::{ atomic::{AtomicBool, Ordering}, Arc, Mutex, }, thread, time::Duration, }; use data::{DataBuilder, WatchData}; mod data { use crate::{ event::{CreateKind, DataChange, Event, EventKind, MetadataKind, ModifyKind, RemoveKind}, EventHandler, }; use filetime::FileTime; use std::{ cell::RefCell, collections::{hash_map::RandomState, HashMap}, fmt::{self, Debug}, fs::{self, File, Metadata}, hash::{BuildHasher, Hasher}, io::{self, Read}, path::{Path, PathBuf}, time::Instant, }; use walkdir::WalkDir; /// Builder for [`WatchData`] & [`PathData`]. pub(super) struct DataBuilder { emitter: EventEmitter, // TODO: May allow user setup their custom BuildHasher / BuildHasherDefault // in future. build_hasher: Option, // current timestamp for building Data. now: Instant, } impl DataBuilder { pub(super) fn new(event_handler: F, compare_content: bool) -> Self where F: EventHandler, { Self { emitter: EventEmitter::new(event_handler), build_hasher: compare_content.then(RandomState::default), now: Instant::now(), } } /// Update internal timestamp. pub(super) fn update_timestamp(&mut self) { self.now = Instant::now(); } /// Create [`WatchData`]. /// /// This function will return `Err(_)` if can not retrieve metadata from /// the path location. (e.g., not found). pub(super) fn build_watch_data( &self, root: PathBuf, is_recursive: bool, ) -> Option { WatchData::new(self, root, is_recursive) } /// Create [`PathData`]. fn build_path_data(&self, meta_path: &MetaPath) -> PathData { PathData::new(self, meta_path) } } impl Debug for DataBuilder { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("DataBuilder") .field("build_hasher", &self.build_hasher) .field("now", &self.now) .finish() } } #[derive(Debug)] pub(super) struct WatchData { // config part, won't change. root: PathBuf, is_recursive: bool, // current status part. all_path_data: HashMap, } impl WatchData { /// Scan filesystem and create a new `WatchData`. /// /// # Side effect /// /// This function may send event by `data_builder.emitter`. fn new(data_builder: &DataBuilder, root: PathBuf, is_recursive: bool) -> Option { // If metadata read error at `root` path, it will emit // a error event and stop to create the whole `WatchData`. // // QUESTION: inconsistent? // // When user try to *CREATE* a watch by `poll_watcher.watch(root, ..)`, // if `root` path hit an io error, then watcher will reject to // create this new watch. // // This may inconsistent with *POLLING* a watch. When watcher // continue polling, io error at root path will not delete // a existing watch. polling still working. // // So, consider a config file may not exists at first time but may // create after a while, developer cannot watch it. // // FIXME: Can we always allow to watch a path, even file not // found at this path? if let Err(e) = fs::metadata(&root) { data_builder.emitter.emit_io_err(e, &root); return None; } let all_path_data = Self::scan_all_path_data(data_builder, root.clone(), is_recursive).collect(); Some(Self { root, is_recursive, all_path_data, }) } /// Rescan filesystem and update this `WatchData`. /// /// # Side effect /// /// This function may emit event by `data_builder.emitter`. pub(super) fn rescan(&mut self, data_builder: &mut DataBuilder) { // scan current filesystem. for (path, new_path_data) in Self::scan_all_path_data(data_builder, self.root.clone(), self.is_recursive) { let old_path_data = self .all_path_data .insert(path.clone(), new_path_data.clone()); // emit event let event = PathData::compare_to_event(path, old_path_data.as_ref(), Some(&new_path_data)); if let Some(event) = event { data_builder.emitter.emit_ok(event); } } // scan for disappeared paths. let mut disappeared_paths = Vec::new(); for (path, path_data) in self.all_path_data.iter() { if path_data.last_check < data_builder.now { disappeared_paths.push(path.clone()); } } // remove disappeared paths for path in disappeared_paths { let old_path_data = self.all_path_data.remove(&path); // emit event let event = PathData::compare_to_event(path, old_path_data.as_ref(), None); if let Some(event) = event { data_builder.emitter.emit_ok(event); } } } /// Get all `PathData` by given configuration. /// /// # Side Effect /// /// This function may emit some IO Error events by `data_builder.emitter`. fn scan_all_path_data( data_builder: &'_ DataBuilder, root: PathBuf, is_recursive: bool, ) -> impl Iterator + '_ { // WalkDir return only one entry if root is a file (not a folder), // so we can use single logic to do the both file & dir's jobs. // // See: https://docs.rs/walkdir/2.0.1/walkdir/struct.WalkDir.html#method.new WalkDir::new(root) .follow_links(true) .max_depth(Self::dir_scan_depth(is_recursive)) .into_iter() // // QUESTION: should we ignore IO Error? // // current implementation ignore some IO error, e.g., // // - `.filter_map(|entry| entry.ok())` // - all read error when hashing // // but the code also interest with `fs::metadata()` error and // propagate to event handler. It may not consistent. // // FIXME: Should we emit all IO error events? Or ignore them all? .filter_map(|entry| entry.ok()) .filter_map(|entry| match entry.metadata() { Ok(metadata) => { let path = entry.into_path(); let meta_path = MetaPath::from_parts_unchecked(path, metadata); let data_path = data_builder.build_path_data(&meta_path); Some((meta_path.into_path(), data_path)) } Err(e) => { // emit event. let path = entry.into_path(); data_builder.emitter.emit_io_err(e, path); None } }) } fn dir_scan_depth(is_recursive: bool) -> usize { if is_recursive { usize::max_value() } else { 1 } } } /// Stored data for a one path locations. /// /// See [`WatchData`] for more detail. #[derive(Debug, Clone)] struct PathData { /// File updated time. mtime: i64, /// Content's hash value, only available if user request compare file /// contents and read successful. hash: Option, /// Checked time. last_check: Instant, } impl PathData { /// Create a new `PathData`. fn new(data_builder: &DataBuilder, meta_path: &MetaPath) -> PathData { let metadata = meta_path.metadata(); PathData { mtime: FileTime::from_last_modification_time(metadata).seconds(), hash: data_builder .build_hasher .as_ref() .filter(|_| metadata.is_file()) .and_then(|build_hasher| { Self::get_content_hash(build_hasher, meta_path.path()).ok() }), last_check: data_builder.now, } } /// Get hash value for the data content in given file `path`. fn get_content_hash(build_hasher: &RandomState, path: &Path) -> io::Result { let mut hasher = build_hasher.build_hasher(); let mut file = File::open(path)?; let mut buf = [0; 512]; loop { let n = match file.read(&mut buf) { Ok(0) => break, Ok(len) => len, Err(e) if e.kind() == io::ErrorKind::Interrupted => continue, Err(e) => return Err(e), }; hasher.write(&buf[..n]); } Ok(hasher.finish()) } /// Get [`Event`] by compare two optional [`PathData`]. fn compare_to_event

( path: P, old: Option<&PathData>, new: Option<&PathData>, ) -> Option where P: Into, { match (old, new) { (Some(old), Some(new)) => { if new.mtime > old.mtime { Some(EventKind::Modify(ModifyKind::Metadata( MetadataKind::WriteTime, ))) } else if new.hash != old.hash { Some(EventKind::Modify(ModifyKind::Data(DataChange::Any))) } else { None } } (None, Some(_new)) => Some(EventKind::Create(CreateKind::Any)), (Some(_old), None) => Some(EventKind::Remove(RemoveKind::Any)), (None, None) => None, } .map(|event_kind| Event::new(event_kind).add_path(path.into())) } } /// Compose path and its metadata. /// /// This data structure designed for make sure path and its metadata can be /// transferred in consistent way, and may avoid some duplicated /// `fs::metadata()` function call in some situations. #[derive(Debug)] pub(super) struct MetaPath { path: PathBuf, metadata: Metadata, } impl MetaPath { /// Create `MetaPath` by given parts. /// /// # Invariant /// /// User must make sure the input `metadata` are associated with `path`. fn from_parts_unchecked(path: PathBuf, metadata: Metadata) -> Self { Self { path, metadata } } fn path(&self) -> &Path { &self.path } fn metadata(&self) -> &Metadata { &self.metadata } fn into_path(self) -> PathBuf { self.path } } /// Thin wrapper for outer event handler, for easy to use. struct EventEmitter( // Use `RefCell` to make sure `emit()` only need shared borrow of self (&self). // Use `Box` to make sure EventEmitter is Sized. Box>, ); impl EventEmitter { fn new(event_handler: F) -> Self { Self(Box::new(RefCell::new(event_handler))) } /// Emit single event. fn emit(&self, event: crate::Result) { self.0.borrow_mut().handle_event(event); } /// Emit event. fn emit_ok(&self, event: Event) { self.emit(Ok(event)) } /// Emit io error event. fn emit_io_err(&self, err: E, path: P) where E: Into, P: Into, { self.emit(Err(crate::Error::io(err.into()).add_path(path.into()))) } } } /// Polling based `Watcher` implementation. /// /// By default scans through all files and checks for changed entries based on their change date. /// Can also be changed to perform file content change checks. /// /// See [Config] for more details. #[derive(Debug)] pub struct PollWatcher { watches: Arc>>, data_builder: Arc>, want_to_stop: Arc, delay: Duration, } impl PollWatcher { /// Create a new [PollWatcher], configured as needed. pub fn new( event_handler: F, config: Config, ) -> crate::Result { let data_builder = DataBuilder::new(event_handler, config.compare_contents()); let poll_watcher = PollWatcher { watches: Default::default(), data_builder: Arc::new(Mutex::new(data_builder)), want_to_stop: Arc::new(AtomicBool::new(false)), delay: config.poll_interval(), }; poll_watcher.run(); Ok(poll_watcher) } fn run(&self) { let watches = Arc::clone(&self.watches); let data_builder = Arc::clone(&self.data_builder); let want_to_stop = Arc::clone(&self.want_to_stop); let delay = self.delay; let _ = thread::Builder::new() .name("notify-rs poll loop".to_string()) .spawn(move || { loop { if want_to_stop.load(Ordering::SeqCst) { break; } // HINT: Make sure always lock in the same order to avoid deadlock. // // FIXME: inconsistent: some place mutex poison cause panic, // some place just ignore. if let (Ok(mut watches), Ok(mut data_builder)) = (watches.lock(), data_builder.lock()) { data_builder.update_timestamp(); let vals = watches.values_mut(); for watch_data in vals { watch_data.rescan(&mut data_builder); } } // QUESTION: `actual_delay == process_time + delay`. Is it intended to? // // If not, consider fix it to: // // ```rust // let still_need_to_delay = delay.checked_sub(data_builder.now.elapsed()); // if let Some(delay) = still_need_to_delay { // thread::sleep(delay); // } // ``` thread::sleep(delay); } }); } /// Watch a path location. /// /// QUESTION: this function never return an Error, is it as intend? /// Please also consider the IO Error event problem. fn watch_inner(&mut self, path: &Path, recursive_mode: RecursiveMode) { // HINT: Make sure always lock in the same order to avoid deadlock. // // FIXME: inconsistent: some place mutex poison cause panic, some place just ignore. if let (Ok(mut watches), Ok(mut data_builder)) = (self.watches.lock(), self.data_builder.lock()) { data_builder.update_timestamp(); let watch_data = data_builder.build_watch_data(path.to_path_buf(), recursive_mode.is_recursive()); // if create watch_data successful, add it to watching list. if let Some(watch_data) = watch_data { watches.insert(path.to_path_buf(), watch_data); } } } /// Unwatch a path. /// /// Return `Err(_)` if given path has't be monitored. fn unwatch_inner(&mut self, path: &Path) -> crate::Result<()> { // FIXME: inconsistent: some place mutex poison cause panic, some place just ignore. self.watches .lock() .unwrap() .remove(path) .map(|_| ()) .ok_or_else(crate::Error::watch_not_found) } } impl Watcher for PollWatcher { /// Create a new [PollWatcher]. fn new(event_handler: F, config: Config) -> crate::Result { Self::new(event_handler, config) } fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> crate::Result<()> { self.watch_inner(path, recursive_mode); Ok(()) } fn unwatch(&mut self, path: &Path) -> crate::Result<()> { self.unwatch_inner(path) } fn kind() -> crate::WatcherKind { crate::WatcherKind::PollWatcher } } impl Drop for PollWatcher { fn drop(&mut self) { self.want_to_stop.store(true, Ordering::Relaxed); } } #[test] fn poll_watcher_is_send_and_sync() { fn check() {} check::(); } notify-5.2.0/src/windows.rs000064400000000000000000000462030072674642500140240ustar 00000000000000#![allow(missing_docs)] //! Watcher implementation for Windows' directory management APIs //! //! For more information see the [ReadDirectoryChangesW reference][ref]. //! //! [ref]: https://msdn.microsoft.com/en-us/library/windows/desktop/aa363950(v=vs.85).aspx use crate::{bounded, unbounded, BoundSender, Config, Receiver, Sender}; use crate::{event::*, WatcherKind}; use crate::{Error, EventHandler, RecursiveMode, Result, Watcher}; use std::collections::HashMap; use std::env; use std::ffi::OsString; use std::mem; use std::os::raw::c_void; use std::os::windows::ffi::{OsStrExt, OsStringExt}; use std::path::{Path, PathBuf}; use std::ptr; use std::slice; use std::sync::{Arc, Mutex}; use std::thread; use windows_sys::Win32::Foundation::{ CloseHandle, ERROR_OPERATION_ABORTED, HANDLE, INVALID_HANDLE_VALUE, WAIT_OBJECT_0, }; use windows_sys::Win32::Storage::FileSystem::{ CreateFileW, ReadDirectoryChangesW, FILE_ACTION_ADDED, FILE_ACTION_MODIFIED, FILE_ACTION_REMOVED, FILE_ACTION_RENAMED_NEW_NAME, FILE_ACTION_RENAMED_OLD_NAME, FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OVERLAPPED, FILE_LIST_DIRECTORY, FILE_NOTIFY_CHANGE_ATTRIBUTES, FILE_NOTIFY_CHANGE_CREATION, FILE_NOTIFY_CHANGE_DIR_NAME, FILE_NOTIFY_CHANGE_FILE_NAME, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_NOTIFY_CHANGE_SECURITY, FILE_NOTIFY_CHANGE_SIZE, FILE_NOTIFY_INFORMATION, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING, }; use windows_sys::Win32::System::Threading::{ CreateSemaphoreW, ReleaseSemaphore, WaitForSingleObjectEx, }; use windows_sys::Win32::System::WindowsProgramming::INFINITE; use windows_sys::Win32::System::IO::{CancelIo, OVERLAPPED}; const BUF_SIZE: u32 = 16384; #[derive(Clone)] struct ReadData { dir: PathBuf, // directory that is being watched file: Option, // if a file is being watched, this is its full path complete_sem: HANDLE, is_recursive: bool, } struct ReadDirectoryRequest { event_handler: Arc>, buffer: [u8; BUF_SIZE as usize], handle: HANDLE, data: ReadData, } enum Action { Watch(PathBuf, RecursiveMode), Unwatch(PathBuf), Stop, Configure(Config, BoundSender>), } #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum MetaEvent { SingleWatchComplete, WatcherAwakened, } struct WatchState { dir_handle: HANDLE, complete_sem: HANDLE, } struct ReadDirectoryChangesServer { rx: Receiver, event_handler: Arc>, meta_tx: Sender, cmd_tx: Sender>, watches: HashMap, wakeup_sem: HANDLE, } impl ReadDirectoryChangesServer { fn start( event_handler: Arc>, meta_tx: Sender, cmd_tx: Sender>, wakeup_sem: HANDLE, ) -> Sender { let (action_tx, action_rx) = unbounded(); // it is, in fact, ok to send the semaphore across threads let sem_temp = wakeup_sem as u64; let _ = thread::Builder::new() .name("notify-rs windows loop".to_string()) .spawn(move || { let wakeup_sem = sem_temp as HANDLE; let server = ReadDirectoryChangesServer { rx: action_rx, event_handler, meta_tx, cmd_tx, watches: HashMap::new(), wakeup_sem, }; server.run(); }); action_tx } fn run(mut self) { loop { // process all available actions first let mut stopped = false; while let Ok(action) = self.rx.try_recv() { match action { Action::Watch(path, recursive_mode) => { let res = self.add_watch(path, recursive_mode.is_recursive()); let _ = self.cmd_tx.send(res); } Action::Unwatch(path) => self.remove_watch(path), Action::Stop => { stopped = true; for ws in self.watches.values() { stop_watch(ws, &self.meta_tx); } break; } Action::Configure(config, tx) => { self.configure_raw_mode(config, tx); } } } if stopped { break; } unsafe { // wait with alertable flag so that the completion routine fires let waitres = WaitForSingleObjectEx(self.wakeup_sem, 100, 1); if waitres == WAIT_OBJECT_0 { let _ = self.meta_tx.send(MetaEvent::WatcherAwakened); } } } // we have to clean this up, since the watcher may be long gone unsafe { CloseHandle(self.wakeup_sem); } } fn add_watch(&mut self, path: PathBuf, is_recursive: bool) -> Result { // path must exist and be either a file or directory if !path.is_dir() && !path.is_file() { return Err( Error::generic("Input watch path is neither a file nor a directory.") .add_path(path), ); } let (watching_file, dir_target) = { if path.is_dir() { (false, path.clone()) } else { // emulate file watching by watching the parent directory (true, path.parent().unwrap().to_path_buf()) } }; let encoded_path: Vec = dir_target .as_os_str() .encode_wide() .chain(Some(0)) .collect(); let handle; unsafe { handle = CreateFileW( encoded_path.as_ptr(), FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE, ptr::null_mut(), OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, 0, ); if handle == INVALID_HANDLE_VALUE { return Err(if watching_file { Error::generic( "You attempted to watch a single file, but parent \ directory could not be opened.", ) .add_path(path) } else { // TODO: Call GetLastError for better error info? Error::path_not_found().add_path(path) }); } } let wf = if watching_file { Some(path.clone()) } else { None }; // every watcher gets its own semaphore to signal completion let semaphore = unsafe { CreateSemaphoreW(ptr::null_mut(), 0, 1, ptr::null_mut()) }; if semaphore == 0 || semaphore == INVALID_HANDLE_VALUE { unsafe { CloseHandle(handle); } return Err(Error::generic("Failed to create semaphore for watch.").add_path(path)); } let rd = ReadData { dir: dir_target, file: wf, complete_sem: semaphore, is_recursive, }; let ws = WatchState { dir_handle: handle, complete_sem: semaphore, }; self.watches.insert(path.clone(), ws); start_read(&rd, self.event_handler.clone(), handle); Ok(path) } fn remove_watch(&mut self, path: PathBuf) { if let Some(ws) = self.watches.remove(&path) { stop_watch(&ws, &self.meta_tx); } } fn configure_raw_mode(&mut self, _config: Config, tx: BoundSender>) { tx.send(Ok(false)) .expect("configuration channel disconnect"); } } fn stop_watch(ws: &WatchState, meta_tx: &Sender) { unsafe { let cio = CancelIo(ws.dir_handle); let ch = CloseHandle(ws.dir_handle); // have to wait for it, otherwise we leak the memory allocated for there read request if cio != 0 && ch != 0 { while WaitForSingleObjectEx(ws.complete_sem, INFINITE, 1) != WAIT_OBJECT_0 { // drain the apc queue, fix for https://github.com/notify-rs/notify/issues/287#issuecomment-801465550 } } CloseHandle(ws.complete_sem); } let _ = meta_tx.send(MetaEvent::SingleWatchComplete); } fn start_read(rd: &ReadData, event_handler: Arc>, handle: HANDLE) { let mut request = Box::new(ReadDirectoryRequest { event_handler, handle, buffer: [0u8; BUF_SIZE as usize], data: rd.clone(), }); let flags = FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_SECURITY; let monitor_subdir = if (&request.data.file).is_none() && request.data.is_recursive { 1 } else { 0 }; unsafe { let mut overlapped: Box = Box::new(mem::zeroed()); // When using callback based async requests, we are allowed to use the hEvent member // for our own purposes let req_buf = request.buffer.as_mut_ptr() as *mut c_void; let request_p = Box::into_raw(request) as isize; overlapped.hEvent = request_p; // This is using an asynchronous call with a completion routine for receiving notifications // An I/O completion port would probably be more performant let ret = ReadDirectoryChangesW( handle, req_buf, BUF_SIZE, monitor_subdir, flags, &mut 0u32 as *mut u32, // not used for async reqs &mut *overlapped as *mut OVERLAPPED, Some(handle_event), ); if ret == 0 { // error reading. retransmute request memory to allow drop. // allow overlapped to drop by omitting forget() let request: Box = mem::transmute(request_p); ReleaseSemaphore(request.data.complete_sem, 1, ptr::null_mut()); } else { // read ok. forget overlapped to let the completion routine handle memory mem::forget(overlapped); } } } unsafe extern "system" fn handle_event( error_code: u32, _bytes_written: u32, overlapped: *mut OVERLAPPED, ) { let overlapped: Box = Box::from_raw(overlapped); let request: Box = Box::from_raw(overlapped.hEvent as *mut _); if error_code == ERROR_OPERATION_ABORTED { // received when dir is unwatched or watcher is shutdown; return and let overlapped/request // get drop-cleaned ReleaseSemaphore(request.data.complete_sem, 1, ptr::null_mut()); return; } // Get the next request queued up as soon as possible start_read(&request.data, request.event_handler.clone(), request.handle); // The FILE_NOTIFY_INFORMATION struct has a variable length due to the variable length // string as its last member. Each struct contains an offset for getting the next entry in // the buffer. let mut cur_offset: *const u8 = request.buffer.as_ptr(); let mut cur_entry = cur_offset as *const FILE_NOTIFY_INFORMATION; loop { // filename length is size in bytes, so / 2 let len = (*cur_entry).FileNameLength as usize / 2; let encoded_path: &[u16] = slice::from_raw_parts((*cur_entry).FileName.as_ptr(), len); // prepend root to get a full path let path = request .data .dir .join(PathBuf::from(OsString::from_wide(encoded_path))); // if we are watching a single file, ignore the event unless the path is exactly // the watched file let skip = match request.data.file { None => false, Some(ref watch_path) => *watch_path != path, }; if !skip { let newe = Event::new(EventKind::Any).add_path(path); fn emit_event(event_handler: &Mutex, res: Result) { if let Ok(mut guard) = event_handler.lock() { let f: &mut dyn EventHandler = &mut *guard; f.handle_event(res); } } let event_handler = |res| emit_event(&request.event_handler, res); if (*cur_entry).Action == FILE_ACTION_RENAMED_OLD_NAME { let mode = RenameMode::From; let kind = ModifyKind::Name(mode); let kind = EventKind::Modify(kind); let ev = newe.set_kind(kind); event_handler(Ok(ev)) } else { match (*cur_entry).Action { FILE_ACTION_RENAMED_NEW_NAME => { let kind = EventKind::Modify(ModifyKind::Name(RenameMode::To)); let ev = newe.set_kind(kind); event_handler(Ok(ev)); } FILE_ACTION_ADDED => { let kind = EventKind::Create(CreateKind::Any); let ev = newe.set_kind(kind); event_handler(Ok(ev)); } FILE_ACTION_REMOVED => { let kind = EventKind::Remove(RemoveKind::Any); let ev = newe.set_kind(kind); event_handler(Ok(ev)); } FILE_ACTION_MODIFIED => { let kind = EventKind::Modify(ModifyKind::Any); let ev = newe.set_kind(kind); event_handler(Ok(ev)); } _ => (), }; } } if (*cur_entry).NextEntryOffset == 0 { break; } cur_offset = cur_offset.offset((*cur_entry).NextEntryOffset as isize); cur_entry = cur_offset as *const FILE_NOTIFY_INFORMATION; } } /// Watcher implementation based on ReadDirectoryChanges #[derive(Debug)] pub struct ReadDirectoryChangesWatcher { tx: Sender, cmd_rx: Receiver>, wakeup_sem: HANDLE, } impl ReadDirectoryChangesWatcher { pub fn create( event_handler: Arc>, meta_tx: Sender, ) -> Result { let (cmd_tx, cmd_rx) = unbounded(); let wakeup_sem = unsafe { CreateSemaphoreW(ptr::null_mut(), 0, 1, ptr::null_mut()) }; if wakeup_sem == 0 || wakeup_sem == INVALID_HANDLE_VALUE { return Err(Error::generic("Failed to create wakeup semaphore.")); } let action_tx = ReadDirectoryChangesServer::start(event_handler, meta_tx, cmd_tx, wakeup_sem); Ok(ReadDirectoryChangesWatcher { tx: action_tx, cmd_rx, wakeup_sem, }) } fn wakeup_server(&mut self) { // breaks the server out of its wait state. right now this is really just an optimization, // so that if you add a watch you don't block for 100ms in watch() while the // server sleeps. unsafe { ReleaseSemaphore(self.wakeup_sem, 1, ptr::null_mut()); } } fn send_action_require_ack(&mut self, action: Action, pb: &PathBuf) -> Result<()> { self.tx .send(action) .map_err(|_| Error::generic("Error sending to internal channel"))?; // wake 'em up, we don't want to wait around for the ack self.wakeup_server(); let ack_pb = self .cmd_rx .recv() .map_err(|_| Error::generic("Error receiving from command channel"))? .map_err(|e| Error::generic(&format!("Error in watcher: {:?}", e)))?; if pb.as_path() != ack_pb.as_path() { Err(Error::generic(&format!( "Expected ack for {:?} but got \ ack for {:?}", pb, ack_pb ))) } else { Ok(()) } } fn watch_inner(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> { let pb = if path.is_absolute() { path.to_owned() } else { let p = env::current_dir().map_err(Error::io)?; p.join(path) }; // path must exist and be either a file or directory if !pb.is_dir() && !pb.is_file() { return Err(Error::generic( "Input watch path is neither a file nor a directory.", )); } self.send_action_require_ack(Action::Watch(pb.clone(), recursive_mode), &pb) } fn unwatch_inner(&mut self, path: &Path) -> Result<()> { let pb = if path.is_absolute() { path.to_owned() } else { let p = env::current_dir().map_err(Error::io)?; p.join(path) }; let res = self .tx .send(Action::Unwatch(pb)) .map_err(|_| Error::generic("Error sending to internal channel")); self.wakeup_server(); res } } impl Watcher for ReadDirectoryChangesWatcher { fn new(event_handler: F, config: Config) -> Result { // create dummy channel for meta event // TODO: determine the original purpose of this - can we remove it? let (meta_tx, _) = unbounded(); let event_handler = Arc::new(Mutex::new(event_handler)); Self::create(event_handler, meta_tx) } fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> { self.watch_inner(path, recursive_mode) } fn unwatch(&mut self, path: &Path) -> Result<()> { self.unwatch_inner(path) } fn configure(&mut self, config: Config) -> Result { let (tx, rx) = bounded(1); self.tx.send(Action::Configure(config, tx))?; rx.recv()? } fn kind() -> crate::WatcherKind { WatcherKind::ReadDirectoryChangesWatcher } } impl Drop for ReadDirectoryChangesWatcher { fn drop(&mut self) { let _ = self.tx.send(Action::Stop); // better wake it up self.wakeup_server(); } } // `ReadDirectoryChangesWatcher` is not Send/Sync because of the semaphore Handle. // As said elsewhere it's perfectly safe to send it across threads. unsafe impl Send for ReadDirectoryChangesWatcher {} // Because all public methods are `&mut self` it's also perfectly safe to share references. unsafe impl Sync for ReadDirectoryChangesWatcher {}