notify_forked/lib.rs
1//! Cross-platform file system notification library
2//!
3//! Source is on GitHub: https://github.com/passcod/notify
4//!
5//! # Installation
6//!
7//! ```toml
8//! [dependencies]
9//! notify = "4.0.12"
10//! ```
11//!
12//! # Examples
13//!
14//! Notify provides two APIs. The default API _debounces_ events (if the backend reports two
15//! similar events in close succession, Notify will only report one). The raw API emits file
16//! changes as soon as they happen. For more details, see
17//! [`Watcher::new_raw`](trait.Watcher.html#tymethod.new_raw) and
18//! [`Watcher::new`](trait.Watcher.html#tymethod.new).
19//!
20//! ## Default (debounced) API
21//!
22//! ```no_run
23//! extern crate notify;
24//!
25//! use notify::{Watcher, RecursiveMode, watcher};
26//! use std::sync::mpsc::channel;
27//! use std::time::Duration;
28//!
29//! fn main() {
30//! // Create a channel to receive the events.
31//! let (tx, rx) = channel();
32//!
33//! // Create a watcher object, delivering debounced events.
34//! // The notification back-end is selected based on the platform.
35//! let mut watcher = watcher(tx, Duration::from_secs(10)).unwrap();
36//!
37//! // Add a path to be watched. All files and directories at that path and
38//! // below will be monitored for changes.
39//! watcher.watch("/home/test/notify", RecursiveMode::Recursive).unwrap();
40//!
41//! loop {
42//! match rx.recv() {
43//! Ok(event) => println!("{:?}", event),
44//! Err(e) => println!("watch error: {:?}", e),
45//! }
46//! }
47//! }
48//! ```
49//!
50//! Using the default API is easy, all possible events are described in the
51//! [`DebouncedEvent`](enum.DebouncedEvent.html) documentation. But in order to understand the
52//! subtleties of the event delivery, you should read the [`op`](op/index.html) documentation as
53//! well.
54//!
55//! ## Raw API
56//!
57//! ```no_run
58//! extern crate notify;
59//!
60//! use notify::{Watcher, RecursiveMode, RawEvent, raw_watcher};
61//! use std::sync::mpsc::channel;
62//!
63//! fn main() {
64//! // Create a channel to receive the events.
65//! let (tx, rx) = channel();
66//!
67//! // Create a watcher object, delivering raw events.
68//! // The notification back-end is selected based on the platform.
69//! let mut watcher = raw_watcher(tx).unwrap();
70//!
71//! // Add a path to be watched. All files and directories at that path and
72//! // below will be monitored for changes.
73//! watcher.watch("/home/test/notify", RecursiveMode::Recursive).unwrap();
74//!
75//! loop {
76//! match rx.recv() {
77//! Ok(RawEvent{path: Some(path), op: Ok(op), cookie}) => {
78//! println!("{:?} {:?} ({:?})", op, path, cookie)
79//! },
80//! Ok(event) => println!("broken event: {:?}", event),
81//! Err(e) => println!("watch error: {:?}", e),
82//! }
83//! }
84//! }
85//! ```
86//!
87//! The event structure is described in the [`RawEvent`](struct.RawEvent.html) documentation,
88//! all possible operations delivered in an event are described in the [`op`](op/index.html)
89//! documentation.
90
91#![deny(missing_docs)]
92
93#[macro_use]
94extern crate bitflags;
95extern crate filetime;
96#[cfg(target_os = "macos")]
97extern crate fsevent_sys;
98extern crate libc;
99#[cfg(target_os = "linux")]
100extern crate mio;
101#[cfg(target_os = "linux")]
102extern crate mio_extras;
103#[cfg(target_os = "windows")]
104extern crate winapi;
105
106pub use self::op::Op;
107use std::convert::AsRef;
108use std::error::Error as StdError;
109use std::fmt;
110use std::io;
111use std::path::{Path, PathBuf};
112use std::result::Result as StdResult;
113use std::sync::mpsc::Sender;
114use std::time::Duration;
115
116#[cfg(target_os = "macos")]
117pub use self::fsevent::FsEventWatcher;
118#[cfg(target_os = "linux")]
119pub use self::inotify::INotifyWatcher;
120pub use self::null::NullWatcher;
121pub use self::poll::PollWatcher;
122#[cfg(target_os = "windows")]
123pub use self::windows::ReadDirectoryChangesWatcher;
124
125#[cfg(target_os = "macos")]
126pub mod fsevent;
127#[cfg(target_os = "linux")]
128pub mod inotify;
129#[cfg(target_os = "windows")]
130pub mod windows;
131
132pub mod null;
133pub mod poll;
134
135mod debounce;
136
137/// Contains the `Op` type which describes the actions for an event.
138///
139/// `notify` aims to provide unified behavior across platforms. This however is not always possible
140/// due to the underlying technology of the various operating systems. So there are some issues
141/// `notify`-API users will have to take care of themselves, depending on their needs.
142///
143///
144/// # Chmod
145///
146/// __Linux, macOS__
147///
148/// On Linux and macOS the `CHMOD` event is emitted whenever attributes or extended attributes
149/// change.
150///
151/// __Windows__
152///
153/// On Windows a `WRITE` event is emitted when attributes change. This makes it impossible to
154/// distinguish between writes to a file or its meta data.
155///
156///
157/// # Close-Write
158///
159/// A `CLOSE_WRITE` event is emitted whenever a file that was opened for writing has been closed.
160///
161/// __This event is only available on Linux__.
162///
163///
164/// # Create
165///
166/// A `CREATE` event is emitted whenever a new file or directory is created.
167///
168/// Upon receiving a `Create` event for a directory, it is necessary to scan the newly created
169/// directory for contents. The directory can contain files or directories if those contents were
170/// created before the directory could be watched, or if the directory was moved into the watched
171/// directory.
172///
173/// # Remove
174///
175/// ## Remove file or directory within a watched directory
176///
177/// A `REMOVE` event is emitted whenever a file or directory is removed.
178///
179/// ## Remove watched file or directory itself
180///
181/// With the exception of Windows a `REMOVE` event is emitted whenever the watched file or
182/// directory itself is removed. The behavior after the remove differs between platforms though.
183///
184/// __Linux__
185///
186/// When a watched file or directory is removed, its watch gets destroyed and no new events will be
187/// sent.
188///
189/// __Windows__
190///
191/// If a watched directory is removed, an empty event is emitted.
192///
193/// When watching a single file on Windows, the file path will continue to be watched until either
194/// the watch is removed by the API user or the parent directory gets removed.
195///
196/// When watching a directory on Windows, the watch will get destroyed and no new events will be
197/// sent.
198///
199/// __macOS__
200///
201/// While Linux and Windows monitor "inodes", macOS monitors "paths". So a watch stays active even
202/// after the watched file or directory has been removed and it will emit events in case a new file
203/// or directory is created in its place.
204///
205///
206/// # Rename
207///
208/// A `RENAME` event is emitted whenever a file or directory has been renamed or moved to a
209/// different directory.
210///
211/// ## Rename file or directory within a watched directory
212///
213/// __Linux, Windows__
214///
215/// A rename with both the source and the destination path inside a watched directory produces two
216/// `RENAME` events. The first event contains the source path, the second contains the destination
217/// path. Both events share the same cookie.
218///
219/// A rename that originates inside of a watched directory but ends outside of a watched directory
220/// produces a `DELETE` event.
221///
222/// A rename that originates outside of a watched directory and ends inside of a watched directory
223/// produces a `CREATE` event.
224///
225/// __macOS__
226///
227/// A `RENAME` event is produced whenever a file or directory is moved. This includes moves within
228/// the watched directory as well as moves into or out of the watched directory. It is up to the
229/// API user to determine what exactly happened. Usually when a move within a watched directory
230/// occurs, the cookie is set for both connected events. This can however fail eg. if a file gets
231/// renamed multiple times without a delay (test `fsevents_rename_rename_file_0`). So in some cases
232/// rename cannot be caught properly but would be interpreted as a sequence of events where a file
233/// or directory is moved out of the watched directory and a different file or directory is moved
234/// in.
235///
236/// ## Rename watched file or directory itself
237///
238/// With the exception of Windows a `RENAME` event is emitted whenever the watched file or
239/// directory itself is renamed. The behavior after the rename differs between platforms though.
240/// Depending on the platform either the moved file or directory will continue to be watched or the
241/// old path. If the moved file or directory will continue to be watched, the paths of emitted
242/// events will still be prefixed with the old path though.
243///
244/// __Linux__
245///
246/// Linux will continue to watch the moved file or directory. Events will contain paths prefixed
247/// with the old path.
248///
249/// __Windows__
250///
251/// Currently there is no event emitted when a watched directory is renamed. But the directory will
252/// continue to be watched and events will contain paths prefixed with the old path.
253///
254/// When renaming a watched file, a `RENAME` event is emitted but the old path will continue to be
255/// watched.
256///
257/// __macOS__
258///
259/// macOS will continue to watch the (now non-existing) path.
260///
261/// ## Rename parent directory of watched file or directory
262///
263/// Currently no event will be emitted when any parent directory of the watched file or directory
264/// is renamed. Depending on the platform either the moved file or directory will continue to be
265/// watched or the old path. If the moved file or directory will continue to be watched, the paths
266/// of emitted events will still be prefixed with the old path though.
267///
268/// __Linux, Windows__
269///
270/// Linux and Windows will continue to watch the moved file or directory. Events will contain paths
271/// prefixed with the old path.
272///
273/// __macOS__
274///
275/// macOS will continue to watch the (now non-existing) path.
276///
277///
278/// # Rescan
279///
280/// A `RESCAN` event indicates that an error occurred and the watched directories need to be
281/// rescanned. This can happen if the internal event queue has overflown and some events were
282/// dropped. Or with FSEvents if events were coalesced hierarchically.
283///
284/// __Windows__
285///
286/// At the moment `RESCAN` events aren't emitted on Windows.
287///
288/// __Queue size__
289///
290/// Linux: `/proc/sys/fs/inotify/max_queued_events`
291///
292/// Windows: 16384 Bytes. The actual amount of events that fit into the queue depends on the length
293/// of the paths.
294///
295///
296/// # Write
297///
298/// A `WRITE` event is emitted whenever a file has been written to.
299///
300/// __Windows__
301///
302/// On Windows a `WRITE` event is emitted when attributes change.
303#[allow(missing_docs)]
304pub mod op {
305 bitflags! {
306 /// Holds a set of bit flags representing the actions for the event.
307 ///
308 /// For a list of possible values, have a look at the [notify::op](index.html) documentation.
309 ///
310 /// Multiple actions may be delivered in a single event.
311 pub struct Op: u32 {
312 /// Attributes changed
313 const CHMOD = 0b0000001;
314 /// Created
315 const CREATE = 0b0000010;
316 /// Removed
317 const REMOVE = 0b0000100;
318 /// Renamed
319 const RENAME = 0b0001000;
320 /// Written
321 const WRITE = 0b0010000;
322 /// File opened for writing was closed
323 const CLOSE_WRITE = 0b0100000;
324 /// Directories need to be rescanned
325 const RESCAN = 0b1000000;
326 }
327 }
328
329 pub const CHMOD: Op = Op::CHMOD;
330 pub const CREATE: Op = Op::CREATE;
331 pub const REMOVE: Op = Op::REMOVE;
332 pub const RENAME: Op = Op::RENAME;
333 pub const WRITE: Op = Op::WRITE;
334 pub const CLOSE_WRITE: Op = Op::CLOSE_WRITE;
335 pub const RESCAN: Op = Op::RESCAN;
336}
337
338#[cfg(test)]
339mod op_test {
340 #[test]
341 fn mixed_bitflags_form() {
342 let op = super::op::Op::CHMOD | super::op::WRITE;
343 assert!(op.contains(super::op::CHMOD));
344 assert!(op.contains(super::op::Op::WRITE));
345 }
346
347 #[test]
348 fn new_bitflags_form() {
349 let op = super::op::Op::CHMOD | super::op::Op::WRITE;
350 assert!(op.contains(super::op::Op::WRITE));
351 }
352
353 #[test]
354 fn old_bitflags_form() {
355 let op = super::op::CHMOD | super::op::WRITE;
356 assert!(op.contains(super::op::WRITE));
357 }
358}
359
360/// Event delivered when action occurs on a watched path in _raw_ mode
361#[derive(Debug)]
362pub struct RawEvent {
363 /// Path where the event originated.
364 ///
365 /// `path` is always absolute, even if a relative path is used to watch a file or directory.
366 ///
367 /// On **macOS** the path is always canonicalized.
368 ///
369 /// Keep in mind that the path may be false if the watched file or directory or any parent
370 /// directory is renamed. (See: [notify::op](op/index.html#rename))
371 pub path: Option<PathBuf>,
372
373 /// Operation detected on that path.
374 ///
375 /// When using the `PollWatcher`, `op` may be `Err` if reading meta data for the path fails.
376 ///
377 /// When using the `INotifyWatcher`, `op` may be `Err` if activity is detected on the file and
378 /// there is an error reading from inotify.
379 pub op: Result<Op>,
380
381 /// Unique cookie associating related events (for `RENAME` events).
382 ///
383 /// If two consecutive `RENAME` events share the same cookie, it means that the first event
384 /// holds the old path, and the second event holds the new path of the renamed file or
385 /// directory.
386 ///
387 /// For details on handling `RENAME` events with the `FsEventWatcher` have a look at the
388 /// [notify::op](op/index.html) documentation.
389 pub cookie: Option<u32>,
390}
391
392unsafe impl Send for RawEvent {}
393
394#[derive(Debug)]
395/// Event delivered when action occurs on a watched path in debounced mode
396pub enum DebouncedEvent {
397 /// `NoticeWrite` is emitted immediately after the first write event for the path.
398 ///
399 /// If you are reading from that file, you should probably close it immediately and discard all
400 /// data you read from it.
401 NoticeWrite(PathBuf),
402
403 /// `NoticeRemove` is emitted immediately after a remove or rename event for the path.
404 ///
405 /// The file will continue to exist until its last file handle is closed.
406 NoticeRemove(PathBuf),
407
408 /// `Create` is emitted when a file or directory has been created and no events were detected
409 /// for the path within the specified time frame.
410 ///
411 /// `Create` events have a higher priority than `Write` and `Chmod`. These events will not be
412 /// emitted if they are detected before the `Create` event has been emitted.
413 Create(PathBuf),
414
415 /// `Write` is emitted when a file has been written to and no events were detected for the path
416 /// within the specified time frame.
417 ///
418 /// `Write` events have a higher priority than `Chmod`. `Chmod` will not be emitted if it's
419 /// detected before the `Write` event has been emitted.
420 ///
421 /// Upon receiving a `Create` event for a directory, it is necessary to scan the newly created
422 /// directory for contents. The directory can contain files or directories if those contents
423 /// were created before the directory could be watched, or if the directory was moved into the
424 /// watched directory.
425 Write(PathBuf),
426
427 /// `Chmod` is emitted when attributes have been changed and no events were detected for the
428 /// path within the specified time frame.
429 Chmod(PathBuf),
430
431 /// `Remove` is emitted when a file or directory has been removed and no events were detected
432 /// for the path within the specified time frame.
433 Remove(PathBuf),
434
435 /// `Rename` is emitted when a file or directory has been moved within a watched directory and
436 /// no events were detected for the new path within the specified time frame.
437 ///
438 /// The first path contains the source, the second path the destination.
439 Rename(PathBuf, PathBuf),
440
441 /// `Rescan` is emitted immediately after a problem has been detected that makes it necessary
442 /// to re-scan the watched directories.
443 Rescan,
444
445 /// `Error` is emitted immediately after a error has been detected.
446 ///
447 /// This event may contain a path for which the error was detected.
448 Error(Error, Option<PathBuf>),
449}
450
451impl PartialEq for DebouncedEvent {
452 fn eq(&self, other: &DebouncedEvent) -> bool {
453 match (self, other) {
454 (&DebouncedEvent::NoticeWrite(ref a), &DebouncedEvent::NoticeWrite(ref b))
455 | (&DebouncedEvent::NoticeRemove(ref a), &DebouncedEvent::NoticeRemove(ref b))
456 | (&DebouncedEvent::Create(ref a), &DebouncedEvent::Create(ref b))
457 | (&DebouncedEvent::Write(ref a), &DebouncedEvent::Write(ref b))
458 | (&DebouncedEvent::Chmod(ref a), &DebouncedEvent::Chmod(ref b))
459 | (&DebouncedEvent::Remove(ref a), &DebouncedEvent::Remove(ref b)) => a == b,
460 (&DebouncedEvent::Rename(ref a1, ref a2), &DebouncedEvent::Rename(ref b1, ref b2)) => {
461 (a1 == b1 && a2 == b2)
462 }
463 (&DebouncedEvent::Rescan, &DebouncedEvent::Rescan) => true,
464 _ => false,
465 }
466 }
467}
468
469/// Errors generated from the `notify` crate
470#[derive(Debug)]
471pub enum Error {
472 /// Generic error
473 ///
474 /// May be used in cases where a platform specific error is mapped to this type
475 Generic(String),
476
477 /// I/O errors
478 Io(io::Error),
479
480 /// The provided path does not exist
481 PathNotFound,
482
483 /// Attempted to remove a watch that does not exist
484 WatchNotFound,
485}
486
487impl fmt::Display for Error {
488 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
489 let error = String::from(match *self {
490 Error::PathNotFound => "No path was found.",
491 Error::WatchNotFound => "No watch was found.",
492 Error::Generic(ref err) => err.as_ref(),
493 Error::Io(ref err) => err.description(),
494 });
495
496 write!(f, "{}", error)
497 }
498}
499
500/// Type alias to use this library's `Error` type in a Result
501pub type Result<T> = StdResult<T, Error>;
502
503impl StdError for Error {
504 fn description(&self) -> &str {
505 match *self {
506 Error::PathNotFound => "No path was found",
507 Error::WatchNotFound => "No watch was found",
508 Error::Generic(_) => "Generic error",
509 Error::Io(_) => "I/O Error",
510 }
511 }
512
513 fn cause(&self) -> Option<&StdError> {
514 match *self {
515 Error::Io(ref cause) => Some(cause),
516 _ => None,
517 }
518 }
519}
520
521impl From<io::Error> for Error {
522 fn from(err: io::Error) -> Error {
523 Error::Io(err)
524 }
525}
526
527/// Indicates whether only the provided directory or its sub-directories as well should be watched
528#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
529pub enum RecursiveMode {
530 /// Watch all sub-directories as well, including directories created after installing the watch
531 Recursive,
532
533 /// Watch only the provided directory
534 NonRecursive,
535}
536
537impl RecursiveMode {
538 fn is_recursive(&self) -> bool {
539 match *self {
540 RecursiveMode::Recursive => true,
541 RecursiveMode::NonRecursive => false,
542 }
543 }
544}
545
546/// Type that can deliver file activity notifications
547///
548/// Watcher is implemented per platform using the best implementation available on that platform.
549/// In addition to such event driven implementations, a polling implementation is also provided
550/// that should work on any platform.
551pub trait Watcher: Sized {
552 /// Create a new watcher in _raw_ mode.
553 ///
554 /// Events will be sent using the provided `tx` immediately after they occurred.
555 fn new_raw(tx: Sender<RawEvent>) -> Result<Self>;
556
557 /// Create a new _debounced_ watcher with a `delay`.
558 ///
559 /// Events won't be sent immediately but after the specified delay.
560 ///
561 /// # Advantages
562 ///
563 /// This has the advantage that a lot of logic can be offloaded to `notify`.
564 ///
565 /// For example you won't have to handle `RENAME` events yourself by piecing the two parts of
566 /// rename events together. Instead you will just receive a `Rename{from: PathBuf, to:
567 /// PathBuf}` event.
568 ///
569 /// Also `notify` will detect the beginning and the end of write operations. As soon as
570 /// something is written to a file, a `NoticeWrite` event is emitted. If no new event arrived
571 /// until after the specified `delay`, a `Write` event is emitted.
572 ///
573 /// A practical example would be the safe-saving of a file, where a temporary file is created
574 /// and written to, then only when everything has been written to that file is it renamed to
575 /// overwrite the file that was meant to be saved. Instead of receiving a `CREATE` event for
576 /// the temporary file, `WRITE` events to that file and a `RENAME` event from the temporary
577 /// file to the file being saved, you will just receive a single `Write` event.
578 ///
579 /// If you use a delay of more than 30 seconds, you can avoid receiving repetitions of previous
580 /// events on macOS.
581 ///
582 /// # Disadvantages
583 ///
584 /// Your application might not feel as responsive.
585 ///
586 /// If a file is saved very slowly, you might receive a `Write` event even though the file is
587 /// still being written to.
588 fn new(tx: Sender<DebouncedEvent>, delay: Duration) -> Result<Self>;
589
590 /// Begin watching a new path.
591 ///
592 /// If the `path` is a directory, `recursive_mode` will be evaluated. If `recursive_mode` is
593 /// `RecursiveMode::Recursive` events will be delivered for all files in that tree. Otherwise
594 /// only the directory and its immediate children will be watched.
595 ///
596 /// If the `path` is a file, `recursive_mode` will be ignored and events will be delivered only
597 /// for the file.
598 ///
599 /// On some platforms, if the `path` is renamed or removed while being watched, behaviour may
600 /// be unexpected. See discussions in [#165] and [#166]. If less surprising behaviour is wanted
601 /// one may non-recursively watch the _parent_ directory as well and manage related events.
602 ///
603 /// [#165]: https://github.com/passcod/notify/issues/165
604 /// [#166]: https://github.com/passcod/notify/issues/166
605 fn watch<P: AsRef<Path>>(&mut self, path: P, recursive_mode: RecursiveMode) -> Result<()>;
606
607 /// Stop watching a path.
608 ///
609 /// # Errors
610 ///
611 /// Returns an error in the case that `path` has not been watched or if removing the watch
612 /// fails.
613 fn unwatch<P: AsRef<Path>>(&mut self, path: P) -> Result<()>;
614}
615
616/// The recommended `Watcher` implementation for the current platform
617#[cfg(target_os = "linux")]
618pub type RecommendedWatcher = INotifyWatcher;
619/// The recommended `Watcher` implementation for the current platform
620#[cfg(target_os = "macos")]
621pub type RecommendedWatcher = FsEventWatcher;
622/// The recommended `Watcher` implementation for the current platform
623#[cfg(target_os = "windows")]
624pub type RecommendedWatcher = ReadDirectoryChangesWatcher;
625/// The recommended `Watcher` implementation for the current platform
626#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
627pub type RecommendedWatcher = PollWatcher;
628
629/// Convenience method for creating the `RecommendedWatcher` for the current platform in _raw_ mode.
630///
631/// See [`Watcher::new_raw`](trait.Watcher.html#tymethod.new_raw).
632pub fn raw_watcher(tx: Sender<RawEvent>) -> Result<RecommendedWatcher> {
633 Watcher::new_raw(tx)
634}
635
636/// Convenience method for creating the `RecommendedWatcher` for the current
637/// platform in default (debounced) mode.
638///
639/// See [`Watcher::new`](trait.Watcher.html#tymethod.new).
640pub fn watcher(tx: Sender<DebouncedEvent>, delay: Duration) -> Result<RecommendedWatcher> {
641 Watcher::new(tx, delay)
642}
643
644#[test]
645fn display_formatted_errors() {
646 let expected = "Some error";
647
648 assert_eq!(
649 expected,
650 format!("{}", Error::Generic(String::from(expected)))
651 );
652
653 assert_eq!(
654 expected,
655 format!(
656 "{}",
657 Error::Io(io::Error::new(io::ErrorKind::Other, expected))
658 )
659 );
660}