From 52de274f6e34a796022cdf93865336231a5f89c9 Mon Sep 17 00:00:00 2001 From: Aron Heinecke Date: Tue, 9 Aug 2022 16:13:16 +0200 Subject: [PATCH] rework docs and example documentation --- README.md | 108 +++------------------------- UPGRADING_V4_TO_V5.md | 13 ++++ examples/Cargo.toml | 2 +- examples/async_monitor.rs | 29 ++++---- examples/debounced.rs | 5 ++ examples/debounced_full_custom.rs | 6 +- examples/hot_reload_tide/Cargo.toml | 2 + examples/monitor_raw.rs | 22 +++--- examples/watcher_kind.rs | 19 +++-- notify-debouncer-mini/src/lib.rs | 8 +-- notify/src/fsevent.rs | 2 +- notify/src/inotify.rs | 2 +- notify/src/kqueue.rs | 2 +- notify/src/lib.rs | 94 ++++++++++++++---------- notify/src/poll.rs | 2 +- notify/src/windows.rs | 2 +- 16 files changed, 141 insertions(+), 177 deletions(-) create mode 100644 UPGRADING_V4_TO_V5.md diff --git a/README.md b/README.md index abbcf393..02989c0b 100644 --- a/README.md +++ b/README.md @@ -9,38 +9,31 @@ _Cross-platform filesystem notification library for Rust._ -**Caution! This is unstable code!** - -You likely want either [the latest 4.0 release] or [5.0.0-pre.15]. - -[the latest 4.0 release]: https://github.com/notify-rs/notify/tree/v4.0.16#notify -[5.0.0-pre.15]: https://github.com/notify-rs/notify/tree/5.0.0-pre.15#notify - (Looking for desktop notifications instead? Have a look at [notify-rust] or [alert-after]!) -- **incomplete [Guides and in-depth docs][wiki]** - [API Documentation][docs] +- [Debouncer Documentation][debouncer] - [Crate page][crate] - [Changelog][changelog] -- Earliest supported Rust version: **1.47.0** +- [Upgrading from v5](UPGRADING_V4_TO_V5.md) +- Earliest supported Rust version: **1.56** +- **incomplete [Guides and in-depth docs][wiki]** As used by: [alacritty], [cargo watch], [cobalt], [docket], [mdBook], [pax], [rdiff], [rust-analyzer], [timetrack], [watchexec], [xi-editor], [watchfiles], and others. -## Installation +## Base Installation ```toml [dependencies] -crossbeam-channel = "0.4.0" notify = "5.0.0-pre.15" ``` ## Usage -The examples below are aspirational only, to preview what the final release may -have looked like. They may not work. Refer to [the API documentation][docs] instead. +A basic example ```rust use notify::{RecommendedWatcher, RecursiveMode, Result, watcher}; @@ -68,92 +61,10 @@ fn main() -> Result<()> { } ``` -### With a channel - -To get a channel for advanced or flexible cases, use: - -```rust -let rx = watcher.channel(); - -loop { - match rx.recv() { - // ... - } -} -``` - -To pass in a channel manually: - -```rust -let (tx, rx) = crossbeam_channel::unbounded(); -let mut watcher: RecommendedWatcher = Watcher::with_channel(tx, Duration::from_secs(2))?; - -for event in rx.iter() { - // ... -} -``` - -### With precise events - -By default, Notify issues generic events that carry little additional -information beyond what path was affected. On some platforms, more is -available; stay aware though that how exactly that manifests varies. To enable -precise events, use: - -```rust -use notify::Config; -watcher.configure(Config::PreciseEvents(true)); -``` - -### With notice events - -Sometimes you want to respond to some events straight away, but not give up the -advantages of debouncing. Notice events appear once immediately when the occur -during a debouncing period, and then a second time as usual at the end of the -debouncing period: - -```rust -use notify::Config; -watcher.configure(Config::NoticeEvents(true)); -``` - -### With ongoing events - -Sometimes frequent writes may be missed or not noticed often enough. Ongoing -write events can be enabled to emit more events even while debouncing: - -```rust -use notify::Config; -watcher.configure(Config::OngoingEvents(Some(Duration::from_millis(500)))); -``` - -### Without debouncing - -To receive events as they are emitted, without debouncing at all: - -```rust -let mut watcher = immediate_watcher()?; -``` - -With a channel: - -```rust -let (tx, rx) = unbounded(); -let mut watcher: RecommendedWatcher = Watcher::immediate_with_channel(tx)?; -``` - -### Serde - -Events can be serialisable via [serde]. To enable the feature: - -```toml -notify = { version = "5.0.0-pre.15", features = ["serde"] } -``` - ## Platforms - Linux / Android: inotify -- macOS: FSEvents +- macOS: FSEvents or kqueue, see features - Windows: ReadDirectoryChangesW - FreeBSD / NetBSD / OpenBSD / DragonflyBSD: kqueue - All platforms: polling @@ -161,7 +72,7 @@ notify = { version = "5.0.0-pre.15", features = ["serde"] } ### FSEvents Due to the inner security model of FSEvents (see [FileSystemEventSecurity]), -some event cannot be observed easily when trying to follow files that do not +some events cannot be observed easily when trying to follow files that do not belong to you. In this case, reverting to the pollwatcher can fix the issue, with a slight performance cost. @@ -183,10 +94,11 @@ Inspired by Go's [fsnotify] and Node.js's [Chokidar], born out of need for [cargo watch], and general frustration at the non-existence of C/Rust cross-platform notify libraries. -Written by [Félix Saparelli] and awesome [contributors]. +Originally created by [Félix Saparelli] and awesome [contributors]. [Chokidar]: https://github.com/paulmillr/chokidar [FileSystemEventSecurity]: https://developer.apple.com/library/mac/documentation/Darwin/Conceptual/FSEvents_ProgGuide/FileSystemEventSecurity/FileSystemEventSecurity.html +[debouncer]: https://github.com/notify-rs/notify/tree/main/notify-debouncer-mini [Félix Saparelli]: https://passcod.name [alacritty]: https://github.com/jwilm/alacritty [alert-after]: https://github.com/frewsxcv/alert-after diff --git a/UPGRADING_V4_TO_V5.md b/UPGRADING_V4_TO_V5.md new file mode 100644 index 00000000..dc3025da --- /dev/null +++ b/UPGRADING_V4_TO_V5.md @@ -0,0 +1,13 @@ +# Upgrading from notify v4 to v5 + +This guide documents changes between v4 and v5 for upgrading existing code. + +Notify v5 only contains precise events. Debouncing is done by a separate crate [notify-debouncer-mini](https://github.com/notify-rs/notify/tree/main/notify-debouncer-mini). + +If you've used the default debounced API, please see [here](https://github.com/notify-rs/notify/blob/main/examples/debounced.rs) for an example. + +For precise events you can see [here](https://github.com/notify-rs/notify/blob/main/examples/monitor_raw.rs). + +Notify v5 by default uses crossbeam-channel internally. You can disable this (required for tokio) as documented in the crate. + +Plattform support in v5 now includes BSD and kqueue on macos in addition to fsevent. \ No newline at end of file diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 6d215db6..f6f54379 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -30,6 +30,6 @@ name = "watcher_kind" path = "watcher_kind.rs" # specifically in its own sub folder -# to prevent audit from complaining +# to prevent cargo audit from complaining #[[example]] #name = "hot_reload_tide" \ No newline at end of file diff --git a/examples/async_monitor.rs b/examples/async_monitor.rs index a940982f..12e56bce 100644 --- a/examples/async_monitor.rs +++ b/examples/async_monitor.rs @@ -5,6 +5,20 @@ use futures::{ use notify::{Event, RecommendedWatcher, RecursiveMode, Watcher}; use std::path::Path; +/// Async, futures channel based event watching +fn main() { + let path = std::env::args() + .nth(1) + .expect("Argument 1 needs to be a path"); + println!("watching {}", path); + + futures::executor::block_on(async { + if let Err(e) = async_watch(path).await { + println!("error: {:?}", e) + } + }); +} + fn async_watcher() -> notify::Result<(RecommendedWatcher, Receiver>)> { let (mut tx, rx) = channel(1); @@ -34,17 +48,4 @@ async fn async_watch>(path: P) -> notify::Result<()> { } Ok(()) -} - -fn main() { - let path = std::env::args() - .nth(1) - .expect("Argument 1 needs to be a path"); - println!("watching {}", path); - - futures::executor::block_on(async { - if let Err(e) = async_watch(path).await { - println!("error: {:?}", e) - } - }); -} +} \ No newline at end of file diff --git a/examples/debounced.rs b/examples/debounced.rs index 8e091e52..5b3f1a4a 100644 --- a/examples/debounced.rs +++ b/examples/debounced.rs @@ -3,7 +3,9 @@ use std::{path::Path, time::Duration}; use notify::{RecursiveMode, Watcher}; use notify_debouncer_mini::new_debouncer; +/// Example for debouncer fn main() { + // emit some events by changing a file std::thread::spawn(|| { let path = Path::new("test.txt"); let _ = std::fs::remove_file(&path); @@ -13,8 +15,10 @@ fn main() { } }); + // setup debouncer let (tx, rx) = std::sync::mpsc::channel(); + // No specific tickrate, max debounce time 2 seconds let mut debouncer = new_debouncer(Duration::from_secs(2), None, tx).unwrap(); debouncer @@ -22,6 +26,7 @@ fn main() { .watch(Path::new("."), RecursiveMode::Recursive) .unwrap(); + // print all events, non returning for events in rx { for e in events { println!("{:?}", e); diff --git a/examples/debounced_full_custom.rs b/examples/debounced_full_custom.rs index 9aa79ef7..555efecf 100644 --- a/examples/debounced_full_custom.rs +++ b/examples/debounced_full_custom.rs @@ -5,6 +5,7 @@ use notify_debouncer_mini::new_debouncer; /// Debouncer with custom backend and waiting for exit fn main() { + // emit some events by changing a file std::thread::spawn(|| { let path = Path::new("test.txt"); let _ = std::fs::remove_file(&path); @@ -14,15 +15,16 @@ fn main() { } }); + // setup debouncer let (tx, rx) = std::sync::mpsc::channel(); - + // select backend via fish operator, here PollWatcher backend let mut debouncer = new_debouncer_opt::<_,notify::PollWatcher>(Duration::from_secs(2), None, tx).unwrap(); debouncer .watcher() .watch(Path::new("."), RecursiveMode::Recursive) .unwrap(); - + // print all events, non returning for events in rx { for e in events { println!("{:?}", e); diff --git a/examples/hot_reload_tide/Cargo.toml b/examples/hot_reload_tide/Cargo.toml index 73ea87bc..54bbe32e 100644 --- a/examples/hot_reload_tide/Cargo.toml +++ b/examples/hot_reload_tide/Cargo.toml @@ -13,4 +13,6 @@ serde_json = "1.0" serde = "1.0.115" notify = { version = "5.0.0-pre.15", features = ["serde"], path = "../../notify" } +# required to prevent mixing with workspace +# hack to prevent cargo audit from catching this [workspace] \ No newline at end of file diff --git a/examples/monitor_raw.rs b/examples/monitor_raw.rs index b5b80395..7effcf0d 100644 --- a/examples/monitor_raw.rs +++ b/examples/monitor_raw.rs @@ -1,6 +1,16 @@ use notify::{RecommendedWatcher, RecursiveMode, Watcher}; use std::path::Path; +fn main() { + let path = std::env::args() + .nth(1) + .expect("Argument 1 needs to be a path"); + println!("watching {}", path); + if let Err(e) = watch(path) { + println!("error: {:?}", e) + } +} + fn watch>(path: P) -> notify::Result<()> { let (tx, rx) = std::sync::mpsc::channel(); @@ -20,14 +30,4 @@ fn watch>(path: P) -> notify::Result<()> { } Ok(()) -} - -fn main() { - let path = std::env::args() - .nth(1) - .expect("Argument 1 needs to be a path"); - println!("watching {}", path); - if let Err(e) = watch(path) { - println!("error: {:?}", e) - } -} +} \ No newline at end of file diff --git a/examples/watcher_kind.rs b/examples/watcher_kind.rs index 2e7b96cf..a74bcfc3 100644 --- a/examples/watcher_kind.rs +++ b/examples/watcher_kind.rs @@ -1,16 +1,27 @@ -use std::time::Duration; +use std::{path::Path, time::Duration}; use notify::{poll::PollWatcherConfig, *}; fn main() { - let (tx, _rx) = std::sync::mpsc::channel(); - let _watcher: Box = if RecommendedWatcher::kind() == WatcherKind::PollWatcher { + let (tx, rx) = std::sync::mpsc::channel(); + let mut watcher: Box = if RecommendedWatcher::kind() == WatcherKind::PollWatcher { + // custom config for PollWatcher kind let config = PollWatcherConfig { poll_interval: Duration::from_secs(1), ..Default::default() }; Box::new(PollWatcher::with_config(tx, config).unwrap()) } else { + // use default config for everything else Box::new(RecommendedWatcher::new(tx).unwrap()) }; - // use _watcher here + + // watch some stuff + watcher + .watch(Path::new("."), RecursiveMode::Recursive) + .unwrap(); + + // just print all events, this blocks forever + for e in rx { + println!("{:?}", e); + } } diff --git a/notify-debouncer-mini/src/lib.rs b/notify-debouncer-mini/src/lib.rs index bad3a54a..efa5fd4b 100644 --- a/notify-debouncer-mini/src/lib.rs +++ b/notify-debouncer-mini/src/lib.rs @@ -1,5 +1,5 @@ //! Debouncer for notify -//! +//! //! # Installation //! //! ```toml @@ -31,11 +31,11 @@ //! debouncer.watcher().watch(Path::new("."), RecursiveMode::Recursive).unwrap(); //! # } //! ``` -//! +//! //! # Features -//! +//! //! The following feature can be turned on or off. -//! +//! //! - `crossbeam-channel` enabled by default, adds DebounceEventHandler support for crossbeam channels. //! - `serde` enabled serde support for events. #[cfg(feature = "serde")] diff --git a/notify/src/fsevent.rs b/notify/src/fsevent.rs index ff245b7e..b6b23e96 100644 --- a/notify/src/fsevent.rs +++ b/notify/src/fsevent.rs @@ -15,7 +15,7 @@ #![allow(non_upper_case_globals, dead_code)] use crate::event::*; -use crate::{Config, Error, EventHandler, RecursiveMode, Result, Watcher, unbounded, Sender}; +use crate::{unbounded, Config, Error, EventHandler, RecursiveMode, Result, Sender, Watcher}; use fsevent_sys as fs; use fsevent_sys::core_foundation as cf; use std::collections::HashMap; diff --git a/notify/src/inotify.rs b/notify/src/inotify.rs index aba1ad06..a85ac626 100644 --- a/notify/src/inotify.rs +++ b/notify/src/inotify.rs @@ -6,7 +6,7 @@ use super::event::*; use super::{Config, Error, ErrorKind, EventHandler, RecursiveMode, Result, Watcher}; -use crate::{unbounded, bounded, Sender, BoundSender, Receiver}; +use crate::{bounded, unbounded, BoundSender, Receiver, Sender}; use inotify as inotify_sys; use inotify_sys::{EventMask, Inotify, WatchDescriptor, WatchMask}; use std::collections::HashMap; diff --git a/notify/src/kqueue.rs b/notify/src/kqueue.rs index 1399fa04..aac7c34e 100644 --- a/notify/src/kqueue.rs +++ b/notify/src/kqueue.rs @@ -6,7 +6,7 @@ use super::event::*; use super::{Error, EventHandler, RecursiveMode, Result, Watcher}; -use crate::{unbounded, Sender, Receiver}; +use crate::{unbounded, Receiver, Sender}; use kqueue::{EventData, EventFilter, FilterFlag, Ident}; use std::collections::HashMap; use std::env; diff --git a/notify/src/lib.rs b/notify/src/lib.rs index e1a51701..94331b62 100644 --- a/notify/src/lib.rs +++ b/notify/src/lib.rs @@ -6,20 +6,69 @@ //! [dependencies] //! notify = "5.0.0-pre.15" //! ``` +//! +//! If you want debounced events, see [notify-debouncer-mini](https://github.com/notify-rs/notify/tree/main/notify-debouncer-mini) +//! +//! ## Features +//! +//! List of compilation features, see below for details +//! +//! - `serde` for serialization of events +//! - `macos_fsevent` enabled by default, for fsevent backend on macos +//! - `macos_kqueue` for kqueue backend on macos +//! - `crossbeam-channel` enabled by default, see below //! -//! ## Serde +//! ### Serde //! -//! Events are serialisable via [serde] if the `serde` feature is enabled: +//! Events are serialisable via [serde](https://serde.rs) if the `serde` feature is enabled: //! //! ```toml //! notify = { version = "5.0.0-pre.15", features = ["serde"] } //! ``` +//! +//! ### Crossbeam-Channel & Tokio +//! +//! By default crossbeam-channel is used internally by notify. +//! This can [cause issues](https://github.com/notify-rs/notify/issues/380) when used inside tokio. +//! +//! You can disable crossbeam-channel, letting notify fallback to std channels via +//! +//! ```toml +//! notify = { version = "5.0.0-pre.15", default-features = false, feature=["macos_kqueue"] } +//! // Alternatively macos_fsevent instead of macos_kqueue +//! ``` +//! Note the `macos_kqueue` requirement here, otherwise no backend is available on macos. +//! +//! # Known Problems +//! +//! ### Docker with Linux on MacOS M1 +//! +//! Docker on macos M1 [throws](https://github.com/notify-rs/notify/issues/423) `Function not implemented (os error 38)`. +//! You have to manually use the [PollWatcher], as the native backend isn't available inside the emulation. +//! +//! ### FSEvents +//! +//! Due to the inner security model of FSEvents (see [FileSystemEventSecurity](https://developer.apple.com/library/mac/documentation/Darwin/Conceptual/FSEvents_ProgGuide/FileSystemEventSecurity/FileSystemEventSecurity.html)), +//! some events cannot be observed easily when trying to follow files that do not +//! belong to you. In this case, reverting to the pollwatcher can fix the issue, +//! with a slight performance cost. +//! +//! ### Editor Behaviour +//! +//! If you rely on precise events (Write/Delete/Create..), you will notice that the actual events +//! can differ a lot between file editors. Some truncate the file on save, some create a new one and replace the old one. +//! 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. //! -//! [serde]: https://serde.rs -//! +//! ### Parent folder deletion +//! +//! 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`. +//! See [here](https://github.com/notify-rs/notify/issues/403) for more details. +//! //! # Examples +//! +//! For more examples visit the [examples folder](https://github.com/notify-rs/notify/tree/main/examples) in the repository. //! -//! ``` +//! ```rust,no_exec //! # use std::path::Path; //! use notify::{Watcher, RecommendedWatcher, RecursiveMode, Result}; //! @@ -40,38 +89,6 @@ //! } //! ``` //! -//! ## With precise events -//! -//! By default, Notify emits non-descript events containing only the affected path and some -//! metadata. To get richer details about _what_ the events are about, you need to enable -//! [`Config::PreciseEvents`](config/enum.Config.html#variant.PreciseEvents). The full event -//! classification is described in the [`event`](event/index.html) module documentation. -//! -//! ``` -//! # use notify::{Watcher, RecommendedWatcher, RecursiveMode, Result}; -//! # use std::path::Path; -//! # use std::time::Duration; -//! # fn main() -> Result<()> { -//! # // Automatically select the best implementation for your platform. -//! # let mut watcher = RecommendedWatcher::new(|res| { -//! # match res { -//! # Ok(event) => println!("event: {:?}", event), -//! # Err(e) => println!("watch error: {:?}", e), -//! # } -//! # })?; -//! -//! # // Add a path to be watched. All files and directories at that path and -//! # // below will be monitored for changes. -//! # watcher.watch(Path::new("."), RecursiveMode::Recursive)?; -//! -//! use notify::Config; -//! watcher.configure(Config::PreciseEvents(true))?; -//! -//! # Ok(()) -//! # } -//! -//! ``` -//! //! ## With different configurations //! //! It is possible to create several watchers with different configurations or implementations that @@ -90,6 +107,7 @@ //! } //! //! let mut watcher1 = notify::recommended_watcher(event_fn)?; +//! // we will just use the same watcher kind again here //! let mut watcher2 = notify::recommended_watcher(event_fn)?; //! # watcher1.watch(Path::new("."), RecursiveMode::Recursive)?; //! # watcher2.watch(Path::new("."), RecursiveMode::Recursive)?; @@ -237,7 +255,7 @@ pub enum WatcherKind { Inotify, /// FS-Event backend (mac) Fsevent, - /// KQueue backend (bsd,mac) + /// KQueue backend (bsd,optionally mac) Kqueue, /// Polling based backend (fallback) PollWatcher, diff --git a/notify/src/poll.rs b/notify/src/poll.rs index 400d33d5..370a97df 100644 --- a/notify/src/poll.rs +++ b/notify/src/poll.rs @@ -532,7 +532,7 @@ impl Watcher for PollWatcher { /// Create a new [PollWatcher]. /// /// The default poll frequency is 30 seconds. - /// Use [with_config] to manually set the poll frequency. + /// Use [PollWatcher::with_config] to manually set the poll frequency. fn new(event_handler: F) -> crate::Result { Self::with_config(event_handler, PollWatcherConfig::default()) } diff --git a/notify/src/windows.rs b/notify/src/windows.rs index b4f23b8a..d5a90f76 100644 --- a/notify/src/windows.rs +++ b/notify/src/windows.rs @@ -15,9 +15,9 @@ use winapi::um::synchapi; use winapi::um::winbase::{self, INFINITE, WAIT_OBJECT_0}; use winapi::um::winnt::{self, FILE_NOTIFY_INFORMATION, HANDLE}; +use crate::{bounded, unbounded, BoundSender, Receiver, Sender}; use crate::{event::*, WatcherKind}; use crate::{Config, Error, EventHandler, RecursiveMode, Result, Watcher}; -use crate::{unbounded, bounded, Sender, Receiver, BoundSender}; use std::collections::HashMap; use std::env; use std::ffi::OsString;