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