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}