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 | |
166 | pub use config::{Config, RecursiveMode}; |
167 | pub use error::{Error, ErrorKind, Result}; |
168 | pub use notify_types::event::{self, Event, EventKind}; |
169 | use std::path::Path; |
170 | |
171 | pub(crate) type Receiver<T> = std::sync::mpsc::Receiver<T>; |
172 | pub(crate) type Sender<T> = std::sync::mpsc::Sender<T>; |
173 | #[cfg (any(target_os = "linux" , target_os = "android" , target_os = "windows" ))] |
174 | pub(crate) type BoundSender<T> = std::sync::mpsc::SyncSender<T>; |
175 | |
176 | #[inline ] |
177 | pub(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 ] |
183 | pub(crate) fn bounded<T>(cap: usize) -> (BoundSender<T>, Receiver<T>) { |
184 | std::sync::mpsc::sync_channel(bound:cap) |
185 | } |
186 | |
187 | #[cfg (all(target_os = "macos" , not(feature = "macos_kqueue" )))] |
188 | pub use crate::fsevent::FsEventWatcher; |
189 | #[cfg (any(target_os = "linux" , target_os = "android" ))] |
190 | pub 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 | ))] |
199 | pub use crate::kqueue::KqueueWatcher; |
200 | pub use null::NullWatcher; |
201 | pub use poll::PollWatcher; |
202 | #[cfg (target_os = "windows" )] |
203 | pub use windows::ReadDirectoryChangesWatcher; |
204 | |
205 | #[cfg (all(target_os = "macos" , not(feature = "macos_kqueue" )))] |
206 | pub mod fsevent; |
207 | #[cfg (any(target_os = "linux" , target_os = "android" ))] |
208 | pub 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 | ))] |
217 | pub mod kqueue; |
218 | #[cfg (target_os = "windows" )] |
219 | pub mod windows; |
220 | |
221 | pub mod null; |
222 | pub mod poll; |
223 | |
224 | mod config; |
225 | mod 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 | /// ``` |
245 | pub trait EventHandler: Send + 'static { |
246 | /// Handles an event. |
247 | fn handle_event(&mut self, event: Result<Event>); |
248 | } |
249 | |
250 | impl<F> EventHandler for F |
251 | where |
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" )] |
260 | impl EventHandler for crossbeam_channel::Sender<Result<Event>> { |
261 | fn handle_event(&mut self, event: Result<Event>) { |
262 | let _ = self.send(event); |
263 | } |
264 | } |
265 | |
266 | impl 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 ] |
275 | pub 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. |
295 | pub 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" ))] |
346 | pub type RecommendedWatcher = INotifyWatcher; |
347 | /// The recommended [`Watcher`] implementation for the current platform |
348 | #[cfg (all(target_os = "macos" , not(feature = "macos_kqueue" )))] |
349 | pub type RecommendedWatcher = FsEventWatcher; |
350 | /// The recommended [`Watcher`] implementation for the current platform |
351 | #[cfg (target_os = "windows" )] |
352 | pub 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 | ))] |
362 | pub 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 | )))] |
375 | pub type RecommendedWatcher = PollWatcher; |
376 | |
377 | /// Convenience method for creating the [`RecommendedWatcher`] for the current platform. |
378 | pub fn recommended_watcher<F>(event_handler: F) -> Result<RecommendedWatcher> |
379 | where |
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)] |
387 | mod 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 | } |
441 | |