diff --git a/notify-debouncer-full/src/lib.rs b/notify-debouncer-full/src/lib.rs index b049755f..14366b21 100644 --- a/notify-debouncer-full/src/lib.rs +++ b/notify-debouncer-full/src/lib.rs @@ -88,7 +88,8 @@ pub use notify_types::debouncer_full::DebouncedEvent; use file_id::FileId; use notify::{ event::{ModifyKind, RemoveKind, RenameMode}, - Error, ErrorKind, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher, WatcherKind, + Error, ErrorKind, Event, EventKind, RecommendedWatcher, RecursiveMode, WatchFilter, Watcher, + WatcherKind, }; /// The set of requirements for watcher debounce event handling functions. @@ -580,7 +581,17 @@ impl Debouncer { path: impl AsRef, recursive_mode: RecursiveMode, ) -> notify::Result<()> { - self.watcher.watch(path.as_ref(), recursive_mode)?; + self.watch_filtered(path, recursive_mode, WatchFilter::accept_all()) + } + + pub fn watch_filtered( + &mut self, + path: impl AsRef, + recursive_mode: RecursiveMode, + watch_filter: WatchFilter, + ) -> notify::Result<()> { + self.watcher + .watch_filtered(path.as_ref(), recursive_mode, watch_filter)?; self.add_root(path.as_ref(), recursive_mode); Ok(()) } diff --git a/notify/src/fsevent.rs b/notify/src/fsevent.rs index e11e9624..dff66104 100644 --- a/notify/src/fsevent.rs +++ b/notify/src/fsevent.rs @@ -15,7 +15,9 @@ #![allow(non_upper_case_globals, dead_code)] use crate::event::*; -use crate::{unbounded, Config, Error, EventHandler, RecursiveMode, Result, Sender, Watcher}; +use crate::{ + unbounded, Config, Error, EventHandler, RecursiveMode, Result, Sender, WatchFilter, Watcher, +}; use fsevent_sys as fs; use fsevent_sys::core_foundation as cf; use std::collections::HashMap; @@ -66,7 +68,7 @@ pub struct FsEventWatcher { flags: fs::FSEventStreamCreateFlags, event_handler: Arc>, runloop: Option<(cf::CFRunLoopRef, thread::JoinHandle<()>)>, - recursive_info: HashMap, + recursive_info: HashMap, } impl fmt::Debug for FsEventWatcher { @@ -242,7 +244,7 @@ fn translate_flags(flags: StreamFlags, precise: bool) -> Vec { struct StreamContextInfo { event_handler: Arc>, - recursive_info: HashMap, + recursive_info: HashMap, } // Free the context when the stream created by `FSEventStreamCreate` is released. @@ -280,9 +282,14 @@ impl FsEventWatcher { }) } - fn watch_inner(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> { + fn watch_inner( + &mut self, + path: &Path, + recursive_mode: RecursiveMode, + watch_filter: WatchFilter, + ) -> Result<()> { self.stop(); - let result = self.append_path(path, recursive_mode); + let result = self.append_path(path, recursive_mode, watch_filter); // ignore return error: may be empty path list let _ = self.run(); result @@ -360,7 +367,12 @@ impl FsEventWatcher { } // https://github.com/thibaudgg/rb-fsevent/blob/master/ext/fsevent_watch/main.c - fn append_path(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> { + fn append_path( + &mut self, + path: &Path, + recursive_mode: RecursiveMode, + watch_filter: WatchFilter, + ) -> Result<()> { if !path.exists() { return Err(Error::path_not_found().add_path(path.into())); } @@ -378,8 +390,10 @@ impl FsEventWatcher { cf::CFArrayAppendValue(self.paths, cf_path); cf::CFRelease(cf_path); } - self.recursive_info - .insert(canonical_path, recursive_mode.is_recursive()); + self.recursive_info.insert( + canonical_path, + (recursive_mode.is_recursive(), watch_filter), + ); Ok(()) } @@ -522,8 +536,8 @@ unsafe fn callback_impl( }); let mut handle_event = false; - for (p, r) in &(*info).recursive_info { - if path.starts_with(p) { + for (p, (r, filt)) in &(*info).recursive_info { + if path.starts_with(p) && filt.should_watch(&path) { if *r || &path == p { handle_event = true; break; @@ -557,8 +571,13 @@ impl Watcher for FsEventWatcher { Self::from_event_handler(Arc::new(Mutex::new(event_handler))) } - fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> { - self.watch_inner(path, recursive_mode) + fn watch_filtered( + &mut self, + path: &Path, + recursive_mode: RecursiveMode, + watch_filter: WatchFilter, + ) -> Result<()> { + self.watch_inner(path, recursive_mode, watch_filter) } fn unwatch(&mut self, path: &Path) -> Result<()> { diff --git a/notify/src/inotify.rs b/notify/src/inotify.rs index 472da24c..1e43dcd8 100644 --- a/notify/src/inotify.rs +++ b/notify/src/inotify.rs @@ -5,7 +5,7 @@ //! will return events for the directory itself, and for files inside the directory. use super::event::*; -use super::{Config, Error, ErrorKind, EventHandler, RecursiveMode, Result, Watcher}; +use super::{Config, Error, ErrorKind, EventHandler, RecursiveMode, Result, WatchFilter, Watcher}; use crate::{bounded, unbounded, BoundSender, Receiver, Sender}; use inotify as inotify_sys; use inotify_sys::{EventMask, Inotify, WatchDescriptor, WatchMask}; @@ -37,7 +37,7 @@ struct EventLoop { inotify: Option, event_handler: Box, /// PathBuf -> (WatchDescriptor, WatchMask, is_recursive, is_dir) - watches: HashMap, + watches: HashMap, paths: HashMap, rename_event: Option, follow_links: bool, @@ -51,7 +51,7 @@ pub struct INotifyWatcher { } enum EventLoopMsg { - AddWatch(PathBuf, RecursiveMode, Sender>), + AddWatch(PathBuf, RecursiveMode, WatchFilter, Sender>), RemoveWatch(PathBuf, Sender>), Shutdown, Configure(Config, BoundSender>), @@ -61,15 +61,15 @@ enum EventLoopMsg { fn add_watch_by_event( path: &Option, event: &inotify_sys::Event<&OsStr>, - watches: &HashMap, - add_watches: &mut Vec, + watches: &HashMap, + add_watches: &mut Vec<(PathBuf, WatchFilter)>, ) { if let Some(ref path) = *path { if event.mask.contains(EventMask::ISDIR) { if let Some(parent_path) = path.parent() { - if let Some(&(_, _, is_recursive, _)) = watches.get(parent_path) { + if let Some(&(_, _, is_recursive, _, ref filter)) = watches.get(parent_path) { if is_recursive { - add_watches.push(path.to_owned()); + add_watches.push((path.to_owned(), filter.clone())); } } } @@ -80,7 +80,7 @@ fn add_watch_by_event( #[inline] fn remove_watch_by_event( path: &Option, - watches: &HashMap, + watches: &HashMap, remove_watches: &mut Vec, ) { if let Some(ref path) = *path { @@ -172,8 +172,13 @@ impl EventLoop { fn handle_messages(&mut self) { while let Ok(msg) = self.event_loop_rx.try_recv() { match msg { - EventLoopMsg::AddWatch(path, recursive_mode, tx) => { - let _ = tx.send(self.add_watch(path, recursive_mode.is_recursive(), true)); + EventLoopMsg::AddWatch(path, recursive_mode, watch_filter, tx) => { + let _ = tx.send(self.add_watch( + path, + recursive_mode.is_recursive(), + true, + watch_filter, + )); } EventLoopMsg::RemoveWatch(path, tx) => { let _ = tx.send(self.remove_watch(path, false)); @@ -307,8 +312,8 @@ impl EventLoop { Some(watched_path) => { let current_watch = self.watches.get(watched_path); match current_watch { - Some(&(_, _, _, true)) => RemoveKind::Folder, - Some(&(_, _, _, false)) => RemoveKind::File, + Some(&(_, _, _, true, _)) => RemoveKind::Folder, + Some(&(_, _, _, false, _)) => RemoveKind::File, None => RemoveKind::Other, } } @@ -391,24 +396,40 @@ impl EventLoop { self.remove_watch(path, true).ok(); } - for path in add_watches { - self.add_watch(path, true, false).ok(); + for (path, filter) in add_watches { + self.add_watch(path, true, false, filter).ok(); } } - fn add_watch(&mut self, path: PathBuf, is_recursive: bool, mut watch_self: bool) -> Result<()> { + fn add_watch( + &mut self, + path: PathBuf, + is_recursive: bool, + mut watch_self: bool, + watch_filter: WatchFilter, + ) -> Result<()> { + if !watch_filter.should_watch(&path) { + return Ok(()); + } + // If the watch is not recursive, or if we determine (by stat'ing the path to get its // metadata) that the watched path is not a directory, add a single path watch. if !is_recursive || !metadata(&path).map_err(Error::io_watch)?.is_dir() { - return self.add_single_watch(path, false, true); + return self.add_single_watch(path, false, true, WatchFilter::accept_all()); } for entry in WalkDir::new(path) .follow_links(self.follow_links) .into_iter() .filter_map(filter_dir) + .filter(|e| watch_filter.should_watch(e.path())) { - self.add_single_watch(entry.path().to_path_buf(), is_recursive, watch_self)?; + self.add_single_watch( + entry.path().to_path_buf(), + is_recursive, + watch_self, + watch_filter.clone(), + )?; watch_self = false; } @@ -420,6 +441,7 @@ impl EventLoop { path: PathBuf, is_recursive: bool, watch_self: bool, + watch_filter: WatchFilter, ) -> Result<()> { let mut watchmask = WatchMask::ATTRIB | WatchMask::CREATE @@ -435,7 +457,7 @@ impl EventLoop { watchmask.insert(WatchMask::MOVE_SELF); } - if let Some(&(_, old_watchmask, _, _)) = self.watches.get(&path) { + if let Some(&(_, old_watchmask, _, _, _)) = self.watches.get(&path) { watchmask.insert(old_watchmask); watchmask.insert(WatchMask::MASK_ADD); } @@ -456,8 +478,16 @@ impl EventLoop { Ok(w) => { watchmask.remove(WatchMask::MASK_ADD); let is_dir = metadata(&path).map_err(Error::io)?.is_dir(); - self.watches - .insert(path.clone(), (w.clone(), watchmask, is_recursive, is_dir)); + self.watches.insert( + path.clone(), + ( + w.clone(), + watchmask, + is_recursive, + is_dir, + watch_filter.clone(), + ), + ); self.paths.insert(w, path); Ok(()) } @@ -470,7 +500,7 @@ impl EventLoop { fn remove_watch(&mut self, path: PathBuf, remove_recursive: bool) -> Result<()> { match self.watches.remove(&path) { None => return Err(Error::watch_not_found().add_path(path)), - Some((w, _, is_recursive, _)) => { + Some((w, _, is_recursive, _, _)) => { if let Some(ref mut inotify) = self.inotify { let mut inotify_watches = inotify.watches(); log::trace!("removing inotify watch: {}", path.display()); @@ -541,7 +571,12 @@ impl INotifyWatcher { Ok(INotifyWatcher { channel, waker }) } - fn watch_inner(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> { + fn watch_inner( + &mut self, + path: &Path, + recursive_mode: RecursiveMode, + watch_filter: WatchFilter, + ) -> Result<()> { let pb = if path.is_absolute() { path.to_owned() } else { @@ -549,7 +584,7 @@ impl INotifyWatcher { p.join(path) }; let (tx, rx) = unbounded(); - let msg = EventLoopMsg::AddWatch(pb, recursive_mode, tx); + let msg = EventLoopMsg::AddWatch(pb, recursive_mode, watch_filter, tx); // we expect the event loop to live and reply => unwraps must not panic self.channel.send(msg).unwrap(); @@ -580,8 +615,13 @@ impl Watcher for INotifyWatcher { Self::from_event_handler(Box::new(event_handler), config.follow_symlinks()) } - fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> { - self.watch_inner(path, recursive_mode) + fn watch_filtered( + &mut self, + path: &Path, + recursive_mode: RecursiveMode, + watch_filter: WatchFilter, + ) -> Result<()> { + self.watch_inner(path, recursive_mode, watch_filter) } fn unwatch(&mut self, path: &Path) -> Result<()> { diff --git a/notify/src/kqueue.rs b/notify/src/kqueue.rs index c4a57241..84e5dbb6 100644 --- a/notify/src/kqueue.rs +++ b/notify/src/kqueue.rs @@ -6,7 +6,7 @@ use super::event::*; use super::{Config, Error, EventHandler, RecursiveMode, Result, Watcher}; -use crate::{unbounded, Receiver, Sender}; +use crate::{unbounded, Receiver, Sender, WatchFilter}; use kqueue::{EventData, EventFilter, FilterFlag, Ident}; use std::collections::HashMap; use std::env; @@ -435,7 +435,12 @@ impl Watcher for KqueueWatcher { Self::from_event_handler(Box::new(event_handler), config.follow_symlinks()) } - fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> { + fn watch_filtered( + &mut self, + path: &Path, + recursive_mode: RecursiveMode, + _watch_filter: WatchFilter, + ) -> Result<()> { self.watch_inner(path, recursive_mode) } diff --git a/notify/src/lib.rs b/notify/src/lib.rs index 23da0fd2..205a9f66 100644 --- a/notify/src/lib.rs +++ b/notify/src/lib.rs @@ -166,7 +166,7 @@ pub use config::{Config, RecursiveMode}; pub use error::{Error, ErrorKind, Result}; pub use notify_types::event::{self, Event, EventKind}; -use std::path::Path; +use std::{path::Path, sync::Arc}; pub(crate) type Receiver = std::sync::mpsc::Receiver; pub(crate) type Sender = std::sync::mpsc::Sender; @@ -287,6 +287,37 @@ pub enum WatcherKind { NullWatcher, } +type FilterFn = dyn Fn(&Path) -> bool + Send + Sync; +/// Path filter to limit what gets watched. +#[derive(Clone)] +pub struct WatchFilter(Option>); + +impl std::fmt::Debug for WatchFilter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("WatchFilterFn") + .field(&self.0.as_ref().map_or("no filter", |_| "filter fn")) + .finish() + } +} + +impl WatchFilter { + /// A filter that accepts any path, use to watch all paths. + pub fn accept_all() -> WatchFilter { + WatchFilter(None) + } + + /// A fitler to limit the paths that get watched. + /// + /// Only paths for which `filter` returns `true` will be watched. + pub fn with_filter(filter: Arc) -> WatchFilter { + WatchFilter(Some(filter)) + } + + fn should_watch(&self, path: &Path) -> bool { + self.0.as_ref().map_or(true, |f| f(path)) + } +} + /// Type that can deliver file activity notifications /// /// `Watcher` is implemented per platform using the best implementation available on that platform. @@ -312,7 +343,31 @@ pub trait Watcher { /// /// [#165]: https://github.com/notify-rs/notify/issues/165 /// [#166]: https://github.com/notify-rs/notify/issues/166 - fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()>; + fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> { + self.watch_filtered(path, recursive_mode, WatchFilter::accept_all()) + } + + /// Begin watching a new path, filtering out sub-paths by name. + /// + /// If the `path` is a directory, `recursive_mode` will be evaluated. If `recursive_mode` is + /// `RecursiveMode::Recursive` events will be delivered for all files in that tree. Otherwise + /// only the directory and its immediate children will be watched. + /// + /// If the `path` is a file, `recursive_mode` will be ignored and events will be delivered only + /// for the file. + /// + /// On some platforms, if the `path` is renamed or removed while being watched, behaviour may + /// be unexpected. See discussions in [#165] and [#166]. If less surprising behaviour is wanted + /// one may non-recursively watch the _parent_ directory as well and manage related events. + /// + /// [#165]: https://github.com/notify-rs/notify/issues/165 + /// [#166]: https://github.com/notify-rs/notify/issues/166 + fn watch_filtered( + &mut self, + path: &Path, + recursive_mode: RecursiveMode, + watch_filter: WatchFilter, + ) -> Result<()>; /// Stop watching a path. /// diff --git a/notify/src/null.rs b/notify/src/null.rs index bbcd80d9..be227005 100644 --- a/notify/src/null.rs +++ b/notify/src/null.rs @@ -4,7 +4,7 @@ use crate::Config; -use super::{RecursiveMode, Result, Watcher}; +use super::{RecursiveMode, Result, WatchFilter, Watcher}; use std::path::Path; /// Stub `Watcher` implementation @@ -14,7 +14,12 @@ use std::path::Path; pub struct NullWatcher; impl Watcher for NullWatcher { - fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> { + fn watch_filtered( + &mut self, + path: &Path, + recursive_mode: RecursiveMode, + watch_filter: WatchFilter, + ) -> Result<()> { Ok(()) } diff --git a/notify/src/poll.rs b/notify/src/poll.rs index 65d7defd..c5522860 100644 --- a/notify/src/poll.rs +++ b/notify/src/poll.rs @@ -3,7 +3,9 @@ //! Checks the `watch`ed paths periodically to detect changes. This implementation only uses //! Rust stdlib APIs and should work on all of the platforms it supports. -use crate::{unbounded, Config, Error, EventHandler, Receiver, RecursiveMode, Sender, Watcher}; +use crate::{ + unbounded, Config, Error, EventHandler, Receiver, RecursiveMode, Sender, WatchFilter, Watcher, +}; use std::{ collections::HashMap, path::{Path, PathBuf}, @@ -635,7 +637,12 @@ impl Watcher for PollWatcher { Self::new(event_handler, config) } - fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> crate::Result<()> { + fn watch_filtered( + &mut self, + path: &Path, + recursive_mode: RecursiveMode, + _watch_filter: WatchFilter, + ) -> crate::Result<()> { self.watch_inner(path, recursive_mode); Ok(()) diff --git a/notify/src/windows.rs b/notify/src/windows.rs index f6425cdc..a559b3db 100644 --- a/notify/src/windows.rs +++ b/notify/src/windows.rs @@ -5,7 +5,7 @@ //! //! [ref]: https://msdn.microsoft.com/en-us/library/windows/desktop/aa363950(v=vs.85).aspx -use crate::{bounded, unbounded, BoundSender, Config, Receiver, Sender}; +use crate::{bounded, unbounded, BoundSender, Config, Receiver, Sender, WatchFilter}; use crate::{event::*, WatcherKind}; use crate::{Error, EventHandler, RecursiveMode, Result, Watcher}; use std::alloc; @@ -51,10 +51,11 @@ struct ReadDirectoryRequest { buffer: [u8; BUF_SIZE as usize], handle: HANDLE, data: ReadData, + watch_filter: WatchFilter, } enum Action { - Watch(PathBuf, RecursiveMode), + Watch(PathBuf, RecursiveMode, WatchFilter), Unwatch(PathBuf), Stop, Configure(Config, BoundSender>), @@ -114,8 +115,8 @@ impl ReadDirectoryChangesServer { while let Ok(action) = self.rx.try_recv() { match action { - Action::Watch(path, recursive_mode) => { - let res = self.add_watch(path, recursive_mode.is_recursive()); + Action::Watch(path, recursive_mode, watch_filter) => { + let res = self.add_watch(path, recursive_mode.is_recursive(), watch_filter); let _ = self.cmd_tx.send(res); } Action::Unwatch(path) => self.remove_watch(path), @@ -151,7 +152,12 @@ impl ReadDirectoryChangesServer { } } - fn add_watch(&mut self, path: PathBuf, is_recursive: bool) -> Result { + fn add_watch( + &mut self, + path: PathBuf, + is_recursive: bool, + watch_filter: WatchFilter, + ) -> Result { // path must exist and be either a file or directory if !path.is_dir() && !path.is_file() { return Err( @@ -223,7 +229,7 @@ impl ReadDirectoryChangesServer { complete_sem: semaphore, }; self.watches.insert(path.clone(), ws); - start_read(&rd, self.event_handler.clone(), handle); + start_read(&rd, self.event_handler.clone(), handle, watch_filter); Ok(path) } @@ -254,12 +260,18 @@ fn stop_watch(ws: &WatchState, meta_tx: &Sender) { let _ = meta_tx.send(MetaEvent::SingleWatchComplete); } -fn start_read(rd: &ReadData, event_handler: Arc>, handle: HANDLE) { +fn start_read( + rd: &ReadData, + event_handler: Arc>, + handle: HANDLE, + watch_filter: WatchFilter, +) { let request = Box::new(ReadDirectoryRequest { event_handler, handle, buffer: [0u8; BUF_SIZE as usize], data: rd.clone(), + watch_filter, }); let flags = FILE_NOTIFY_CHANGE_FILE_NAME @@ -325,7 +337,12 @@ unsafe extern "system" fn handle_event( } // Get the next request queued up as soon as possible - start_read(&request.data, request.event_handler.clone(), request.handle); + start_read( + &request.data, + request.event_handler.clone(), + request.handle, + request.watch_filter.clone(), + ); // The FILE_NOTIFY_INFORMATION struct has a variable length due to the variable length // string as its last member. Each struct contains an offset for getting the next entry in @@ -351,11 +368,13 @@ unsafe extern "system" fn handle_event( // if we are watching a single file, ignore the event unless the path is exactly // the watched file - let skip = match request.data.file { + let mut skip = match request.data.file { None => false, Some(ref watch_path) => *watch_path != path, }; + skip = skip || !request.watch_filter.should_watch(&path); + if !skip { log::trace!( "Event: path = `{}`, action = {:?}", @@ -479,7 +498,12 @@ impl ReadDirectoryChangesWatcher { } } - fn watch_inner(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> { + fn watch_inner( + &mut self, + path: &Path, + recursive_mode: RecursiveMode, + watch_filter: WatchFilter, + ) -> Result<()> { let pb = if path.is_absolute() { path.to_owned() } else { @@ -492,7 +516,7 @@ impl ReadDirectoryChangesWatcher { "Input watch path is neither a file nor a directory.", )); } - self.send_action_require_ack(Action::Watch(pb.clone(), recursive_mode), &pb) + self.send_action_require_ack(Action::Watch(pb.clone(), recursive_mode, watch_filter), &pb) } fn unwatch_inner(&mut self, path: &Path) -> Result<()> { @@ -520,8 +544,13 @@ impl Watcher for ReadDirectoryChangesWatcher { Self::create(event_handler, meta_tx) } - fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> { - self.watch_inner(path, recursive_mode) + fn watch_filtered( + &mut self, + path: &Path, + recursive_mode: RecursiveMode, + watch_filter: WatchFilter, + ) -> Result<()> { + self.watch_inner(path, recursive_mode, watch_filter) } fn unwatch(&mut self, path: &Path) -> Result<()> {