1//! Cross-platform file system notification library
2//!
3//! # Installation
4//!
5//! ```toml
6//! [dependencies]
7//! notify = "6.1.1"
8//! ```
9//!
10//! 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/)
11//! or [notify-debouncer-full](https://docs.rs/notify-debouncer-full/latest/notify_debouncer_full/).
12//!
13//! ## Features
14//!
15//! List of compilation features, see below for details
16//!
17//! - `serde` for serialization of events
18//! - `macos_fsevent` enabled by default, for fsevent backend on macos
19//! - `macos_kqueue` for kqueue backend on macos
20//! - `crossbeam-channel` enabled by default, see below
21//!
22//! ### Serde
23//!
24//! Events are serializable via [serde](https://serde.rs) if the `serde` feature is enabled:
25//!
26//! ```toml
27//! notify = { version = "6.1.1", features = ["serde"] }
28//! ```
29//!
30//! ### Crossbeam-Channel & Tokio
31//!
32//! By default crossbeam-channel is used internally by notify. Which also allows the [Watcher] to be sync.
33//! This can [cause issues](https://github.com/notify-rs/notify/issues/380) when used inside tokio.
34//!
35//! You can disable crossbeam-channel, letting notify fallback to std channels via
36//!
37//! ```toml
38//! notify = { version = "6.1.1", default-features = false, features = ["macos_kqueue"] }
39//! // Alternatively macos_fsevent instead of macos_kqueue
40//! ```
41//! Note the `macos_kqueue` requirement here, otherwise no native backend is available on macos.
42//!
43//! # Known Problems
44//!
45//! ### Network filesystems
46//!
47//! Network mounted filesystems like NFS may not emit any events for notify to listen to.
48//! This applies especially to WSL programs watching windows paths ([issue #254](https://github.com/notify-rs/notify/issues/254)).
49//!
50//! A workaround is the [PollWatcher] backend.
51//!
52//! ### Docker with Linux on MacOS M1
53//!
54//! Docker on macos M1 [throws](https://github.com/notify-rs/notify/issues/423) `Function not implemented (os error 38)`.
55//! You have to manually use the [PollWatcher], as the native backend isn't available inside the emulation.
56//!
57//! ### MacOS, FSEvents and unowned files
58//!
59//! Due to the inner security model of FSEvents (see [FileSystemEventSecurity](https://developer.apple.com/library/mac/documentation/Darwin/Conceptual/FSEvents_ProgGuide/FileSystemEventSecurity/FileSystemEventSecurity.html)),
60//! some events cannot be observed easily when trying to follow files that do not
61//! belong to you. In this case, reverting to the pollwatcher can fix the issue,
62//! with a slight performance cost.
63//!
64//! ### Editor Behaviour
65//!
66//! If you rely on precise events (Write/Delete/Create..), you will notice that the actual events
67//! can differ a lot between file editors. Some truncate the file on save, some create a new one and replace the old one.
68//! 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.
69//!
70//! ### Parent folder deletion
71//!
72//! 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`.
73//! See [here](https://github.com/notify-rs/notify/issues/403) for more details.
74//!
75//! ### Pseudo Filesystems like /proc, /sys
76//!
77//! Some filesystems like `/proc` and `/sys` on *nix do not emit change events or use correct file change dates.
78//! To circumvent that problem you can use the [PollWatcher] with the `compare_contents` option.
79//!
80//! ### Linux: Bad File Descriptor / No space left on device
81//!
82//! This may be the case of running into the max-files watched limits of your user or system.
83//! (Files also includes folders.) Note that for recursive watched folders each file and folder inside counts towards the limit.
84//!
85//! You may increase this limit in linux via
86//! ```sh
87//! sudo sysctl fs.inotify.max_user_instances=8192 # example number
88//! sudo sysctl fs.inotify.max_user_watches=524288 # example number
89//! sudo sysctl -p
90//! ```
91//!
92//! Note that the [PollWatcher] is not restricted by this limitation, so it may be an alternative if your users can't increase the limit.
93//!
94//! ### Watching large directories
95//!
96//! When watching a very large amount of files, notify may fail to receive all events.
97//! 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).
98//!
99//! # Examples
100//!
101//! For more examples visit the [examples folder](https://github.com/notify-rs/notify/tree/main/examples) in the repository.
102//!
103//! ```rust
104//! # use std::path::Path;
105//! use notify::{Watcher, RecommendedWatcher, RecursiveMode, Result};
106//!
107//! fn main() -> Result<()> {
108//! // Automatically select the best implementation for your platform.
109//! let mut watcher = notify::recommended_watcher(|res| {
110//! match res {
111//! Ok(event) => println!("event: {:?}", event),
112//! Err(e) => println!("watch error: {:?}", e),
113//! }
114//! })?;
115//!
116//! // Add a path to be watched. All files and directories at that path and
117//! // below will be monitored for changes.
118//! # #[cfg(not(any(
119//! # target_os = "freebsd",
120//! # target_os = "openbsd",
121//! # target_os = "dragonflybsd",
122//! # target_os = "netbsd")))]
123//! # { // "." doesn't exist on BSD for some reason in CI
124//! watcher.watch(Path::new("."), RecursiveMode::Recursive)?;
125//! # }
126//!
127//! Ok(())
128//! }
129//! ```
130//!
131//! ## With different configurations
132//!
133//! It is possible to create several watchers with different configurations or implementations that
134//! all call the same event function. This can accommodate advanced behaviour or work around limits.
135//!
136//! ```rust
137//! # use notify::{RecommendedWatcher, RecursiveMode, Result, Watcher};
138//! # use std::path::Path;
139//! #
140//! # fn main() -> Result<()> {
141//! fn event_fn(res: Result<notify::Event>) {
142//! match res {
143//! Ok(event) => println!("event: {:?}", event),
144//! Err(e) => println!("watch error: {:?}", e),
145//! }
146//! }
147//!
148//! let mut watcher1 = notify::recommended_watcher(event_fn)?;
149//! // we will just use the same watcher kind again here
150//! let mut watcher2 = notify::recommended_watcher(event_fn)?;
151//! # #[cfg(not(any(
152//! # target_os = "freebsd",
153//! # target_os = "openbsd",
154//! # target_os = "dragonflybsd",
155//! # target_os = "netbsd")))]
156//! # { // "." doesn't exist on BSD for some reason in CI
157//! # watcher1.watch(Path::new("."), RecursiveMode::Recursive)?;
158//! # watcher2.watch(Path::new("."), RecursiveMode::Recursive)?;
159//! # }
160//! // dropping the watcher1/2 here (no loop etc) will end the program
161//! #
162//! # Ok(())
163//! # }
164//! ```
165
166#![deny(missing_docs)]
167
168pub use config::{Config, RecursiveMode};
169pub use error::{Error, ErrorKind, Result};
170pub use event::{Event, EventKind};
171use std::path::Path;
172
173#[allow(dead_code)]
174#[cfg(feature = "crossbeam-channel")]
175pub(crate) type Receiver<T> = crossbeam_channel::Receiver<T>;
176#[allow(dead_code)]
177#[cfg(not(feature = "crossbeam-channel"))]
178pub(crate) type Receiver<T> = std::sync::mpsc::Receiver<T>;
179
180#[allow(dead_code)]
181#[cfg(feature = "crossbeam-channel")]
182pub(crate) type Sender<T> = crossbeam_channel::Sender<T>;
183#[allow(dead_code)]
184#[cfg(not(feature = "crossbeam-channel"))]
185pub(crate) type Sender<T> = std::sync::mpsc::Sender<T>;
186
187// std limitation
188#[allow(dead_code)]
189#[cfg(feature = "crossbeam-channel")]
190pub(crate) type BoundSender<T> = crossbeam_channel::Sender<T>;
191#[allow(dead_code)]
192#[cfg(not(feature = "crossbeam-channel"))]
193pub(crate) type BoundSender<T> = std::sync::mpsc::SyncSender<T>;
194
195#[allow(dead_code)]
196#[inline]
197pub(crate) fn unbounded<T>() -> (Sender<T>, Receiver<T>) {
198 #[cfg(feature = "crossbeam-channel")]
199 return crossbeam_channel::unbounded();
200 #[cfg(not(feature = "crossbeam-channel"))]
201 return std::sync::mpsc::channel();
202}
203
204#[allow(dead_code)]
205#[inline]
206pub(crate) fn bounded<T>(cap: usize) -> (BoundSender<T>, Receiver<T>) {
207 #[cfg(feature = "crossbeam-channel")]
208 return crossbeam_channel::bounded(cap);
209 #[cfg(not(feature = "crossbeam-channel"))]
210 return std::sync::mpsc::sync_channel(bound:cap);
211}
212
213#[cfg(all(target_os = "macos", not(feature = "macos_kqueue")))]
214pub use crate::fsevent::FsEventWatcher;
215#[cfg(any(target_os = "linux", target_os = "android"))]
216pub use crate::inotify::INotifyWatcher;
217#[cfg(any(
218 target_os = "freebsd",
219 target_os = "openbsd",
220 target_os = "netbsd",
221 target_os = "dragonflybsd",
222 all(target_os = "macos", feature = "macos_kqueue")
223))]
224pub use crate::kqueue::KqueueWatcher;
225pub use null::NullWatcher;
226pub use poll::PollWatcher;
227#[cfg(target_os = "windows")]
228pub use windows::ReadDirectoryChangesWatcher;
229
230#[cfg(all(target_os = "macos", not(feature = "macos_kqueue")))]
231pub mod fsevent;
232#[cfg(any(target_os = "linux", target_os = "android"))]
233pub mod inotify;
234#[cfg(any(
235 target_os = "freebsd",
236 target_os = "openbsd",
237 target_os = "dragonflybsd",
238 target_os = "netbsd",
239 all(target_os = "macos", feature = "macos_kqueue")
240))]
241pub mod kqueue;
242#[cfg(target_os = "windows")]
243pub mod windows;
244
245pub mod event;
246pub mod null;
247pub mod poll;
248
249mod config;
250mod error;
251
252/// The set of requirements for watcher event handling functions.
253///
254/// # Example implementation
255///
256/// ```no_run
257/// use notify::{Event, Result, EventHandler};
258///
259/// /// Prints received events
260/// struct EventPrinter;
261///
262/// impl EventHandler for EventPrinter {
263/// fn handle_event(&mut self, event: Result<Event>) {
264/// if let Ok(event) = event {
265/// println!("Event: {:?}", event);
266/// }
267/// }
268/// }
269/// ```
270pub trait EventHandler: Send + 'static {
271 /// Handles an event.
272 fn handle_event(&mut self, event: Result<Event>);
273}
274
275impl<F> EventHandler for F
276where
277 F: FnMut(Result<Event>) + Send + 'static,
278{
279 fn handle_event(&mut self, event: Result<Event>) {
280 (self)(event);
281 }
282}
283
284#[cfg(feature = "crossbeam-channel")]
285impl EventHandler for crossbeam_channel::Sender<Result<Event>> {
286 fn handle_event(&mut self, event: Result<Event>) {
287 let _ = self.send(event);
288 }
289}
290
291impl EventHandler for std::sync::mpsc::Sender<Result<Event>> {
292 fn handle_event(&mut self, event: Result<Event>) {
293 let _ = self.send(event);
294 }
295}
296
297/// Watcher kind enumeration
298#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
299#[non_exhaustive]
300pub enum WatcherKind {
301 /// inotify backend (linux)
302 Inotify,
303 /// FS-Event backend (mac)
304 Fsevent,
305 /// KQueue backend (bsd,optionally mac)
306 Kqueue,
307 /// Polling based backend (fallback)
308 PollWatcher,
309 /// Windows backend
310 ReadDirectoryChangesWatcher,
311 /// Fake watcher for testing
312 NullWatcher,
313}
314
315/// Type that can deliver file activity notifications
316///
317/// Watcher is implemented per platform using the best implementation available on that platform.
318/// In addition to such event driven implementations, a polling implementation is also provided
319/// that should work on any platform.
320pub trait Watcher {
321 /// Create a new watcher with an initial Config.
322 fn new<F: EventHandler>(event_handler: F, config: config::Config) -> Result<Self>
323 where
324 Self: Sized;
325 /// Begin watching a new path.
326 ///
327 /// If the `path` is a directory, `recursive_mode` will be evaluated. If `recursive_mode` is
328 /// `RecursiveMode::Recursive` events will be delivered for all files in that tree. Otherwise
329 /// only the directory and its immediate children will be watched.
330 ///
331 /// If the `path` is a file, `recursive_mode` will be ignored and events will be delivered only
332 /// for the file.
333 ///
334 /// On some platforms, if the `path` is renamed or removed while being watched, behaviour may
335 /// be unexpected. See discussions in [#165] and [#166]. If less surprising behaviour is wanted
336 /// one may non-recursively watch the _parent_ directory as well and manage related events.
337 ///
338 /// [#165]: https://github.com/notify-rs/notify/issues/165
339 /// [#166]: https://github.com/notify-rs/notify/issues/166
340 fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()>;
341
342 /// Stop watching a path.
343 ///
344 /// # Errors
345 ///
346 /// Returns an error in the case that `path` has not been watched or if removing the watch
347 /// fails.
348 fn unwatch(&mut self, path: &Path) -> Result<()>;
349
350 /// Configure the watcher at runtime.
351 ///
352 /// See the [`Config`](config/enum.Config.html) enum for all configuration options.
353 ///
354 /// # Returns
355 ///
356 /// - `Ok(true)` on success.
357 /// - `Ok(false)` if the watcher does not support or implement the option.
358 /// - `Err(notify::Error)` on failure.
359 fn configure(&mut self, _option: Config) -> Result<bool> {
360 Ok(false)
361 }
362
363 /// Returns the watcher kind, allowing to perform backend-specific tasks
364 fn kind() -> WatcherKind
365 where
366 Self: Sized;
367}
368
369/// The recommended `Watcher` implementation for the current platform
370#[cfg(any(target_os = "linux", target_os = "android"))]
371pub type RecommendedWatcher = INotifyWatcher;
372/// The recommended `Watcher` implementation for the current platform
373#[cfg(all(target_os = "macos", not(feature = "macos_kqueue")))]
374pub type RecommendedWatcher = FsEventWatcher;
375/// The recommended `Watcher` implementation for the current platform
376#[cfg(target_os = "windows")]
377pub type RecommendedWatcher = ReadDirectoryChangesWatcher;
378/// The recommended `Watcher` implementation for the current platform
379#[cfg(any(
380 target_os = "freebsd",
381 target_os = "openbsd",
382 target_os = "netbsd",
383 target_os = "dragonflybsd",
384 all(target_os = "macos", feature = "macos_kqueue")
385))]
386pub type RecommendedWatcher = KqueueWatcher;
387/// The recommended `Watcher` implementation for the current platform
388#[cfg(not(any(
389 target_os = "linux",
390 target_os = "android",
391 target_os = "macos",
392 target_os = "windows",
393 target_os = "freebsd",
394 target_os = "openbsd",
395 target_os = "netbsd",
396 target_os = "dragonflybsd"
397)))]
398pub type RecommendedWatcher = PollWatcher;
399
400/// Convenience method for creating the `RecommendedWatcher` for the current platform in
401/// _immediate_ mode.
402///
403/// See [`Watcher::new_immediate`](trait.Watcher.html#tymethod.new_immediate).
404pub fn recommended_watcher<F>(event_handler: F) -> Result<RecommendedWatcher>
405where
406 F: EventHandler,
407{
408 // All recommended watchers currently implement `new`, so just call that.
409 RecommendedWatcher::new(event_handler, Config::default())
410}
411
412#[cfg(test)]
413mod tests {
414 use super::*;
415
416 #[test]
417 fn test_object_safe() {
418 let _watcher: &dyn Watcher = &NullWatcher;
419 }
420
421 #[test]
422 fn test_debug_impl() {
423 macro_rules! assert_debug_impl {
424 ($t:ty) => {{
425 trait NeedsDebug: std::fmt::Debug {}
426 impl NeedsDebug for $t {}
427 }};
428 }
429
430 assert_debug_impl!(Config);
431 assert_debug_impl!(Error);
432 assert_debug_impl!(ErrorKind);
433 assert_debug_impl!(event::AccessKind);
434 assert_debug_impl!(event::AccessMode);
435 assert_debug_impl!(event::CreateKind);
436 assert_debug_impl!(event::DataChange);
437 assert_debug_impl!(event::EventAttributes);
438 assert_debug_impl!(event::Flag);
439 assert_debug_impl!(event::MetadataKind);
440 assert_debug_impl!(event::ModifyKind);
441 assert_debug_impl!(event::RemoveKind);
442 assert_debug_impl!(event::RenameMode);
443 assert_debug_impl!(Event);
444 assert_debug_impl!(EventKind);
445 assert_debug_impl!(NullWatcher);
446 assert_debug_impl!(PollWatcher);
447 assert_debug_impl!(RecommendedWatcher);
448 assert_debug_impl!(RecursiveMode);
449 assert_debug_impl!(WatcherKind);
450 }
451}
452