notify-debouncer-mini-0.2.1/.cargo_vcs_info.json0000644000000001630000000000100152420ustar { "git": { "sha1": "0775e62c1d4d209bd027bc804f38efc96b354371" }, "path_in_vcs": "notify-debouncer-mini" }notify-debouncer-mini-0.2.1/Cargo.toml0000644000000024360000000000100132450ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.56" name = "notify-debouncer-mini" version = "0.2.1" authors = ["Aron Heinecke "] description = "notify mini debouncer for events" homepage = "https://github.com/notify-rs/notify" documentation = "https://docs.rs/notify-debouncer-mini" readme = "README.md" keywords = [ "events", "filesystem", "notify", "watch", ] license = "CC0-1.0 OR Artistic-2.0" repository = "https://github.com/notify-rs/notify.git" [lib] name = "notify_debouncer_mini" path = "src/lib.rs" [dependencies.crossbeam-channel] version = "0.5" optional = true [dependencies.notify] version = "5.0.0" [dependencies.serde] version = "1.0.89" features = ["derive"] optional = true [features] crossbeam = [ "crossbeam-channel", "notify/crossbeam-channel", ] default = ["crossbeam"] notify-debouncer-mini-0.2.1/Cargo.toml.orig000064400000000000000000000017450072674642500167600ustar 00000000000000[package] name = "notify-debouncer-mini" version = "0.2.1" edition = "2021" rust-version = "1.56" description = "notify mini debouncer for events" documentation = "https://docs.rs/notify-debouncer-mini" homepage = "https://github.com/notify-rs/notify" repository = "https://github.com/notify-rs/notify.git" authors = ["Aron Heinecke "] keywords = ["events", "filesystem", "notify", "watch"] license = "CC0-1.0 OR Artistic-2.0" readme = "README.md" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] name = "notify_debouncer_mini" path = "src/lib.rs" [features] default = ["crossbeam"] # can't use dep:crossbeam-channel and feature name crossbeam-channel below rust 1.60 crossbeam = ["crossbeam-channel","notify/crossbeam-channel"] [dependencies] notify = "5.0.0" crossbeam-channel = { version = "0.5", optional = true } serde = { version = "1.0.89", features = ["derive"], optional = true }notify-debouncer-mini-0.2.1/README.md000064400000000000000000000013530072674642500153430ustar 00000000000000# Notify debouncer [![ยป Docs](https://flat.badgen.net/badge/api/docs.rs/df3600)][docs] Tiny debouncer for [notify]. Filters incoming events and emits only one event per timeframe per file. ## Features - `crossbeam` enabled by default, for crossbeam channel support. This may create problems used in tokio environments. See [#380](https://github.com/notify-rs/notify/issues/380). Use someting like the following to disable it. ```toml notify-debouncer-mini = { version = "*", default-features = false } ``` This also passes through to notify as `crossbeam-channel` feature. - `serde` for serde support of event types, off by default [docs]: https://docs.rs/notify-debouncer-mini [notify]: https://crates.io/crates/notifynotify-debouncer-mini-0.2.1/src/lib.rs000064400000000000000000000262540072674642500157760ustar 00000000000000//! Debouncer for notify //! //! # Installation //! //! ```toml //! [dependencies] //! notify-debouncer-mini = "0.2.0" //! ``` //! In case you want to select specific features of notify, //! specify notify as dependency explicitely in your dependencies. //! Otherwise you can just use the re-export of notify from debouncer-mini. //! ```toml //! notify-debouncer-mini = "0.2.0" //! notify = { version = "..", features = [".."] } //! ``` //! //! # Examples //! //! ```rust,no_run //! # use std::path::Path; //! # use std::time::Duration; //! use notify_debouncer_mini::{notify::*,new_debouncer,DebounceEventResult}; //! //! # fn main() { //! // setup initial watcher backend config //! let config = Config::default(); //! //! // Select recommended watcher for debouncer. //! // Using a callback here, could also be a channel. //! let mut debouncer = new_debouncer(Duration::from_secs(2), None, |res: DebounceEventResult| { //! match res { //! Ok(events) => events.iter().for_each(|e|println!("Event {:?} for {:?}",e.kind,e.path)), //! Err(errors) => errors.iter().for_each(|e|println!("Error {:?}",e)), //! } //! }).unwrap(); //! //! // Add a path to be watched. All files and directories at that path and //! // below will be monitored for changes. //! debouncer.watcher().watch(Path::new("."), RecursiveMode::Recursive).unwrap(); //! # } //! ``` //! //! # Features //! //! The following crate features can be turned on or off in your cargo dependency config: //! //! - `crossbeam` enabled by default, adds [`DebounceEventHandler`](DebounceEventHandler) support for crossbeam channels. //! Also enables crossbeam-channel in the re-exported notify. You may want to disable this when using the tokio async runtime. //! - `serde` enables serde support for events. #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, path::PathBuf, sync::{ atomic::{AtomicBool, Ordering}, Arc, Mutex, }, time::{Duration, Instant}, }; pub use notify; use notify::{Error, ErrorKind, Event, RecommendedWatcher, Watcher}; /// The set of requirements for watcher debounce event handling functions. /// /// # Example implementation /// /// ```rust,no_run /// # use notify::{Event, Result, EventHandler}; /// # use notify_debouncer_mini::{DebounceEventHandler,DebounceEventResult}; /// /// /// Prints received events /// struct EventPrinter; /// /// impl DebounceEventHandler for EventPrinter { /// fn handle_event(&mut self, event: DebounceEventResult) { /// match event { /// Ok(events) => { /// for event in events { /// println!("Event {:?} for path {:?}",event.kind,event.path); /// } /// }, /// // errors are batched, so you get either events or errors, probably both per debounce tick (two calls) /// Err(errors) => errors.iter().for_each(|e|println!("Got error {:?}",e)), /// } /// } /// } /// ``` pub trait DebounceEventHandler: Send + 'static { /// Handles an event. fn handle_event(&mut self, event: DebounceEventResult); } impl DebounceEventHandler for F where F: FnMut(DebounceEventResult) + Send + 'static, { fn handle_event(&mut self, event: DebounceEventResult) { (self)(event); } } #[cfg(feature = "crossbeam")] impl DebounceEventHandler for crossbeam_channel::Sender { fn handle_event(&mut self, event: DebounceEventResult) { let _ = self.send(event); } } impl DebounceEventHandler for std::sync::mpsc::Sender { fn handle_event(&mut self, event: DebounceEventResult) { let _ = self.send(event); } } /// Deduplicate event data entry struct EventData { /// Insertion Time insert: Instant, /// Last Update update: Instant, } impl EventData { fn new_any() -> Self { let time = Instant::now(); Self { insert: time.clone(), update: time, } } } /// A result of debounced events. /// Comes with either a vec of events or vec of errors. pub type DebounceEventResult = Result, Vec>; /// A debounced event kind. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[non_exhaustive] pub enum DebouncedEventKind { /// No precise events Any, /// Event but debounce timed out (for example continuous writes) AnyContinuous, } /// A debounced event. /// /// Does not emit any specific event type on purpose, only distinguishes between an any event and a continuous any event. #[derive(Clone, Debug, Eq, Hash, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct DebouncedEvent { /// Event path pub path: PathBuf, /// Event kind pub kind: DebouncedEventKind, } impl DebouncedEvent { fn new(path: PathBuf, kind: DebouncedEventKind) -> Self { Self { path, kind } } } type DebounceData = Arc>; #[derive(Default)] struct DebounceDataInner { d: HashMap, timeout: Duration, e: Vec, } impl DebounceDataInner { /// Retrieve a vec of debounced events, removing them if not continuous pub fn debounced_events(&mut self) -> Vec { let mut events_expired = Vec::with_capacity(self.d.len()); let mut data_back = HashMap::with_capacity(self.d.len()); // TODO: perfect fit for drain_filter https://github.com/rust-lang/rust/issues/59618 for (k, v) in self.d.drain() { if v.update.elapsed() >= self.timeout { events_expired.push(DebouncedEvent::new(k, DebouncedEventKind::Any)); } else if v.insert.elapsed() >= self.timeout { data_back.insert(k.clone(), v); events_expired.push(DebouncedEvent::new(k, DebouncedEventKind::AnyContinuous)); } else { data_back.insert(k, v); } } self.d = data_back; events_expired } /// Returns all currently stored errors pub fn errors(&mut self) -> Vec { let mut v = Vec::new(); std::mem::swap(&mut v, &mut self.e); v } /// Add an error entry to re-send later on pub fn add_error(&mut self, e: crate::Error) { self.e.push(e); } /// Add new event to debouncer cache pub fn add_event(&mut self, e: Event) { for path in e.paths.into_iter() { if let Some(v) = self.d.get_mut(&path) { v.update = Instant::now(); } else { self.d.insert(path, EventData::new_any()); } } } } /// Debouncer guard, stops the debouncer on drop pub struct Debouncer { stop: Arc, watcher: T, debouncer_thread: Option>, } impl Debouncer { /// Stop the debouncer, waits for the event thread to finish. /// May block for the duration of one tick_rate. pub fn stop(mut self) { self.set_stop(); if let Some(t) = self.debouncer_thread.take() { let _ = t.join(); } } /// Stop the debouncer, does not wait for the event thread to finish. pub fn stop_nonblocking(self) { self.set_stop(); } fn set_stop(&self) { self.stop.store(true, Ordering::Relaxed); } /// Access to the internally used notify Watcher backend pub fn watcher(&mut self) -> &mut dyn Watcher { &mut self.watcher } } impl Drop for Debouncer { fn drop(&mut self) { // don't imitate c++ async futures and block on drop self.set_stop(); } } /// Creates a new debounced watcher with custom configuration. /// /// Timeout is the amount of time after which a debounced event is emitted or a continuous event is send, if there still are events incoming for the specific path. /// /// If tick_rate is None, notify will select a tick rate that is less than the provided timeout. pub fn new_debouncer_opt( timeout: Duration, tick_rate: Option, mut event_handler: F, config: notify::Config ) -> Result, Error> { let data = DebounceData::default(); let stop = Arc::new(AtomicBool::new(false)); let tick_div = 4; let tick = match tick_rate { Some(v) => { if v > timeout { return Err(Error::new(ErrorKind::Generic(format!( "Invalid tick_rate, tick rate {:?} > {:?} timeout!", v, timeout )))); } v } None => timeout.checked_div(tick_div).ok_or_else(|| { Error::new(ErrorKind::Generic(format!( "Failed to calculate tick as {:?}/{}!", timeout, tick_div ))) })?, }; { let mut data_w = data.lock().unwrap(); data_w.timeout = timeout; } let data_c = data.clone(); let stop_c = stop.clone(); let thread = std::thread::Builder::new() .name("notify-rs debouncer loop".to_string()) .spawn(move || loop { if stop_c.load(Ordering::Acquire) { break; } std::thread::sleep(tick); let send_data; let errors: Vec; { let mut lock = data_c.lock().expect("Can't lock debouncer data!"); send_data = lock.debounced_events(); errors = lock.errors(); } if send_data.len() > 0 { event_handler.handle_event(Ok(send_data)); } if errors.len() > 0 { event_handler.handle_event(Err(errors)); } })?; let watcher = T::new(move |e: Result| { let mut lock = data.lock().expect("Can't lock debouncer data!"); match e { Ok(e) => lock.add_event(e), // can't have multiple TX, so we need to pipe that through our debouncer Err(e) => lock.add_error(e), } }, config)?; let guard = Debouncer { watcher, debouncer_thread: Some(thread), stop, }; Ok(guard) } /// Short function to create a new debounced watcher with the recommended debouncer. /// /// Timeout is the amount of time after which a debounced event is emitted or a continuous event is send, if there still are events incoming for the specific path. /// /// If tick_rate is None, notify will select a tick rate that is less than the provided timeout. pub fn new_debouncer( timeout: Duration, tick_rate: Option, event_handler: F ) -> Result, Error> { new_debouncer_opt::(timeout, tick_rate, event_handler, notify::Config::default()) }