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 |
|
168 | pub use config::{Config, RecursiveMode};
|
169 | pub use error::{Error, ErrorKind, Result};
|
170 | pub use event::{Event, EventKind};
|
171 | use std::path::Path;
|
172 |
|
173 | #[allow (dead_code)]
|
174 | #[cfg (feature = "crossbeam-channel" )]
|
175 | pub(crate) type Receiver<T> = crossbeam_channel::Receiver<T>;
|
176 | #[allow (dead_code)]
|
177 | #[cfg (not(feature = "crossbeam-channel" ))]
|
178 | pub(crate) type Receiver<T> = std::sync::mpsc::Receiver<T>;
|
179 |
|
180 | #[allow (dead_code)]
|
181 | #[cfg (feature = "crossbeam-channel" )]
|
182 | pub(crate) type Sender<T> = crossbeam_channel::Sender<T>;
|
183 | #[allow (dead_code)]
|
184 | #[cfg (not(feature = "crossbeam-channel" ))]
|
185 | pub(crate) type Sender<T> = std::sync::mpsc::Sender<T>;
|
186 |
|
187 | // std limitation
|
188 | #[allow (dead_code)]
|
189 | #[cfg (feature = "crossbeam-channel" )]
|
190 | pub(crate) type BoundSender<T> = crossbeam_channel::Sender<T>;
|
191 | #[allow (dead_code)]
|
192 | #[cfg (not(feature = "crossbeam-channel" ))]
|
193 | pub(crate) type BoundSender<T> = std::sync::mpsc::SyncSender<T>;
|
194 |
|
195 | #[allow (dead_code)]
|
196 | #[inline ]
|
197 | pub(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 ]
|
206 | pub(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" )))]
|
214 | pub use crate::fsevent::FsEventWatcher;
|
215 | #[cfg (any(target_os = "linux" , target_os = "android" ))]
|
216 | pub 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 | ))]
|
224 | pub use crate::kqueue::KqueueWatcher;
|
225 | pub use null::NullWatcher;
|
226 | pub use poll::PollWatcher;
|
227 | #[cfg (target_os = "windows" )]
|
228 | pub use windows::ReadDirectoryChangesWatcher;
|
229 |
|
230 | #[cfg (all(target_os = "macos" , not(feature = "macos_kqueue" )))]
|
231 | pub mod fsevent;
|
232 | #[cfg (any(target_os = "linux" , target_os = "android" ))]
|
233 | pub 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 | ))]
|
241 | pub mod kqueue;
|
242 | #[cfg (target_os = "windows" )]
|
243 | pub mod windows;
|
244 |
|
245 | pub mod event;
|
246 | pub mod null;
|
247 | pub mod poll;
|
248 |
|
249 | mod config;
|
250 | mod 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 | /// ```
|
270 | pub trait EventHandler: Send + 'static {
|
271 | /// Handles an event.
|
272 | fn handle_event(&mut self, event: Result<Event>);
|
273 | }
|
274 |
|
275 | impl<F> EventHandler for F
|
276 | where
|
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" )]
|
285 | impl EventHandler for crossbeam_channel::Sender<Result<Event>> {
|
286 | fn handle_event(&mut self, event: Result<Event>) {
|
287 | let _ = self.send(event);
|
288 | }
|
289 | }
|
290 |
|
291 | impl 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 ]
|
300 | pub 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.
|
320 | pub 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" ))]
|
371 | pub type RecommendedWatcher = INotifyWatcher;
|
372 | /// The recommended `Watcher` implementation for the current platform
|
373 | #[cfg (all(target_os = "macos" , not(feature = "macos_kqueue" )))]
|
374 | pub type RecommendedWatcher = FsEventWatcher;
|
375 | /// The recommended `Watcher` implementation for the current platform
|
376 | #[cfg (target_os = "windows" )]
|
377 | pub 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 | ))]
|
386 | pub 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 | )))]
|
398 | pub 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).
|
404 | pub fn recommended_watcher<F>(event_handler: F) -> Result<RecommendedWatcher>
|
405 | where
|
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)]
|
413 | mod 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 | |