blob: 84c8312e9f01669c1257281cf9fbf6d3a128f87e [file] [log] [blame] [edit]
//! Cross-platform file system notification library
//!
//! # Installation
//!
//! ```toml
//! [dependencies]
//! notify = "6.1.1"
//! ```
//!
//! If you want debounced events (or don't need them in-order), see [notify-debouncer-mini](https://docs.rs/notify-debouncer-mini/latest/notify_debouncer_mini/)
//! or [notify-debouncer-full](https://docs.rs/notify-debouncer-full/latest/notify_debouncer_full/).
//!
//! ## 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 serializable via [serde](https://serde.rs) if the `serde` feature is enabled:
//!
//! ```toml
//! notify = { version = "6.1.1", features = ["serde"] }
//! ```
//!
//! ### Crossbeam-Channel & Tokio
//!
//! By default crossbeam-channel is used internally by notify. Which also allows the [Watcher] to be sync.
//! 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 = "6.1.1", default-features = false, features = ["macos_kqueue"] }
//! // Alternatively macos_fsevent instead of macos_kqueue
//! ```
//! Note the `macos_kqueue` requirement here, otherwise no native backend is available on macos.
//!
//! # Known Problems
//!
//! ### Network filesystems
//!
//! Network mounted filesystems like NFS may not emit any events for notify to listen to.
//! This applies especially to WSL programs watching windows paths ([issue #254](https://github.com/notify-rs/notify/issues/254)).
//!
//! A workaround is the [PollWatcher] backend.
//!
//! ### 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.
//!
//! ### Watching large directories
//!
//! When watching a very large amount of files, notify may fail to receive all events.
//! For example the linux backend is documented to not be a 100% reliable source. See also issue [#412](https://github.com/notify-rs/notify/issues/412).
//!
//! # Examples
//!
//! For more examples visit the [examples folder](https://github.com/notify-rs/notify/tree/main/examples) in the repository.
//!
//! ```rust
//! # 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.
//! # #[cfg(not(any(
//! # target_os = "freebsd",
//! # target_os = "openbsd",
//! # target_os = "dragonflybsd",
//! # target_os = "netbsd")))]
//! # { // "." doesn't exist on BSD for some reason in CI
//! 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.
//!
//! ```rust
//! # use notify::{RecommendedWatcher, RecursiveMode, Result, Watcher};
//! # use std::path::Path;
//! #
//! # fn main() -> Result<()> {
//! fn event_fn(res: Result<notify::Event>) {
//! 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)?;
//! # #[cfg(not(any(
//! # target_os = "freebsd",
//! # target_os = "openbsd",
//! # target_os = "dragonflybsd",
//! # target_os = "netbsd")))]
//! # { // "." doesn't exist on BSD for some reason in CI
//! # watcher1.watch(Path::new("."), RecursiveMode::Recursive)?;
//! # watcher2.watch(Path::new("."), RecursiveMode::Recursive)?;
//! # }
//! // dropping the watcher1/2 here (no loop etc) will end the program
//! #
//! # 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<T> = crossbeam_channel::Receiver<T>;
#[allow(dead_code)]
#[cfg(not(feature = "crossbeam-channel"))]
pub(crate) type Receiver<T> = std::sync::mpsc::Receiver<T>;
#[allow(dead_code)]
#[cfg(feature = "crossbeam-channel")]
pub(crate) type Sender<T> = crossbeam_channel::Sender<T>;
#[allow(dead_code)]
#[cfg(not(feature = "crossbeam-channel"))]
pub(crate) type Sender<T> = std::sync::mpsc::Sender<T>;
// std limitation
#[allow(dead_code)]
#[cfg(feature = "crossbeam-channel")]
pub(crate) type BoundSender<T> = crossbeam_channel::Sender<T>;
#[allow(dead_code)]
#[cfg(not(feature = "crossbeam-channel"))]
pub(crate) type BoundSender<T> = std::sync::mpsc::SyncSender<T>;
#[allow(dead_code)]
#[inline]
pub(crate) fn unbounded<T>() -> (Sender<T>, Receiver<T>) {
#[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<T>(cap: usize) -> (BoundSender<T>, Receiver<T>) {
#[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<Event>) {
/// if let Ok(event) = event {
/// println!("Event: {:?}", event);
/// }
/// }
/// }
/// ```
pub trait EventHandler: Send + 'static {
/// Handles an event.
fn handle_event(&mut self, event: Result<Event>);
}
impl<F> EventHandler for F
where
F: FnMut(Result<Event>) + Send + 'static,
{
fn handle_event(&mut self, event: Result<Event>) {
(self)(event);
}
}
#[cfg(feature = "crossbeam-channel")]
impl EventHandler for crossbeam_channel::Sender<Result<Event>> {
fn handle_event(&mut self, event: Result<Event>) {
let _ = self.send(event);
}
}
impl EventHandler for std::sync::mpsc::Sender<Result<Event>> {
fn handle_event(&mut self, event: Result<Event>) {
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<F: EventHandler>(event_handler: F, config: config::Config) -> Result<Self>
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<bool> {
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<F>(event_handler: F) -> Result<RecommendedWatcher>
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);
}
}