Skip to content

Commit aaaac45

Browse files
committed
feat: rework PathsMut to be more consistent
The feature to add / remove paths to watch in batch was introduced in #692 There was a problem with the implementation: if you drop the instance without `commit` it may leave the watcher in an inconsistent state (especially fsevents). This commit changes behavior and api and introduces `update_watches` method of the `Watcher` trait, which do the same stuff but in more consistent way. The `paths_mut` method was kept, but returning struct is just a builder for the method `update_watches` that will be called by `PathsMut::commit` related #653 closes #704
1 parent c685ea7 commit aaaac45

File tree

8 files changed

+199
-74
lines changed

8 files changed

+199
-74
lines changed

notify/src/error.rs

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
//! Error types
22
3-
use crate::Config;
3+
use crate::{Config, WatchOp};
44
use std::error::Error as StdError;
5+
use std::fmt::Debug;
56
use std::path::PathBuf;
67
use std::result::Result as StdResult;
78
use std::{self, fmt, io};
@@ -158,14 +159,55 @@ impl<T> From<std::sync::PoisonError<T>> for Error {
158159
}
159160
}
160161

161-
#[test]
162-
fn display_formatted_errors() {
163-
let expected = "Some error";
162+
/// The error provided by [`crate::Watcher::update_watches`] method
163+
#[derive(Debug)]
164+
pub struct UpdateWatchesError {
165+
/// The original error
166+
pub source: Error,
167+
168+
/// The remaining operations that haven't been applied
169+
pub remaining: Vec<WatchOp>,
170+
}
171+
172+
impl fmt::Display for UpdateWatchesError {
173+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174+
write!(f, "unable to apply the batch operation: {}", self.source)
175+
}
176+
}
177+
178+
impl StdError for UpdateWatchesError {
179+
fn source(&self) -> Option<&(dyn StdError + 'static)> {
180+
Some(&self.source)
181+
}
182+
}
183+
184+
#[cfg(test)]
185+
mod tests {
186+
use super::*;
187+
188+
#[test]
189+
fn display_formatted_errors() {
190+
let expected = "Some error";
191+
192+
assert_eq!(expected, format!("{}", Error::generic(expected)));
164193

165-
assert_eq!(expected, format!("{}", Error::generic(expected)));
194+
assert_eq!(
195+
expected,
196+
format!("{}", Error::io(io::Error::other(expected)))
197+
);
198+
}
199+
200+
#[test]
201+
fn display_update_watches() {
202+
let actual = UpdateWatchesError {
203+
source: Error::generic("Some error"),
204+
remaining: Default::default(),
205+
}
206+
.to_string();
166207

167-
assert_eq!(
168-
expected,
169-
format!("{}", Error::io(io::Error::other(expected)))
170-
);
208+
assert_eq!(
209+
format!("unable to apply the batch operation: Some error"),
210+
actual
211+
);
212+
}
171213
}

notify/src/fsevent.rs

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,8 @@
1414
1515
#![allow(non_upper_case_globals, dead_code)]
1616

17-
use crate::event::*;
18-
use crate::{
19-
unbounded, Config, Error, EventHandler, PathsMut, RecursiveMode, Result, Sender, Watcher,
20-
};
17+
use crate::{event::*, WatchOp};
18+
use crate::{unbounded, Config, Error, EventHandler, RecursiveMode, Result, Sender, Watcher};
2119
use fsevent_sys as fs;
2220
use fsevent_sys::core_foundation as cf;
2321
use std::collections::HashMap;
@@ -267,29 +265,6 @@ extern "C" {
267265
fn CFRunLoopIsWaiting(runloop: cf::CFRunLoopRef) -> cf::Boolean;
268266
}
269267

270-
struct FsEventPathsMut<'a>(&'a mut FsEventWatcher);
271-
impl<'a> FsEventPathsMut<'a> {
272-
fn new(watcher: &'a mut FsEventWatcher) -> Self {
273-
watcher.stop();
274-
Self(watcher)
275-
}
276-
}
277-
impl PathsMut for FsEventPathsMut<'_> {
278-
fn add(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> {
279-
self.0.append_path(path, recursive_mode)
280-
}
281-
282-
fn remove(&mut self, path: &Path) -> Result<()> {
283-
self.0.remove_path(path)
284-
}
285-
286-
fn commit(self: Box<Self>) -> Result<()> {
287-
// ignore return error: may be empty path list
288-
let _ = self.0.run();
289-
Ok(())
290-
}
291-
}
292-
293268
impl FsEventWatcher {
294269
fn from_event_handler(event_handler: Arc<Mutex<dyn EventHandler>>) -> Result<Self> {
295270
Ok(FsEventWatcher {
@@ -321,6 +296,23 @@ impl FsEventWatcher {
321296
result
322297
}
323298

299+
fn update_watches_inner(
300+
&mut self,
301+
ops: Vec<crate::WatchOp>,
302+
) -> crate::StdResult<(), crate::UpdateWatchesError> {
303+
self.stop();
304+
305+
let result = crate::update_watches(ops, |op| match op {
306+
crate::WatchOp::Watch(path, recursive_mode) => self.append_path(&path, recursive_mode),
307+
crate::WatchOp::Unwatch(path) => self.remove_path(&path),
308+
});
309+
310+
// ignore return error: may be empty path list
311+
let _ = self.run();
312+
313+
result
314+
}
315+
324316
#[inline]
325317
fn is_running(&self) -> bool {
326318
self.runloop.is_some()
@@ -586,14 +578,17 @@ impl Watcher for FsEventWatcher {
586578
self.watch_inner(path, recursive_mode)
587579
}
588580

589-
fn paths_mut<'me>(&'me mut self) -> Box<dyn PathsMut + 'me> {
590-
Box::new(FsEventPathsMut::new(self))
591-
}
592-
593581
fn unwatch(&mut self, path: &Path) -> Result<()> {
594582
self.unwatch_inner(path)
595583
}
596584

585+
fn update_watches(
586+
&mut self,
587+
ops: Vec<WatchOp>,
588+
) -> crate::StdResult<(), crate::UpdateWatchesError> {
589+
self.update_watches_inner(ops)
590+
}
591+
597592
fn configure(&mut self, config: Config) -> Result<bool> {
598593
let (tx, rx) = unbounded();
599594
self.configure_raw_mode(config, tx);
@@ -603,6 +598,10 @@ impl Watcher for FsEventWatcher {
603598
fn kind() -> crate::WatcherKind {
604599
crate::WatcherKind::Fsevent
605600
}
601+
602+
fn paths_mut(&mut self) -> crate::PathsMut<'_> {
603+
crate::PathsMut::new(self)
604+
}
606605
}
607606

608607
impl Drop for FsEventWatcher {

notify/src/inotify.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,10 @@ impl Watcher for INotifyWatcher {
601601
fn kind() -> crate::WatcherKind {
602602
crate::WatcherKind::Inotify
603603
}
604+
605+
fn paths_mut(&mut self) -> crate::PathsMut<'_> {
606+
crate::PathsMut::new(self)
607+
}
604608
}
605609

606610
impl Drop for INotifyWatcher {

notify/src/kqueue.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,10 @@ impl Watcher for KqueueWatcher {
447447
fn kind() -> crate::WatcherKind {
448448
crate::WatcherKind::Kqueue
449449
}
450+
451+
fn paths_mut(&mut self) -> crate::PathsMut<'_> {
452+
crate::PathsMut::new(self)
453+
}
450454
}
451455

452456
impl Drop for KqueueWatcher {

notify/src/lib.rs

Lines changed: 98 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -163,10 +163,11 @@
163163
#![deny(missing_docs)]
164164

165165
pub use config::{Config, RecursiveMode};
166-
pub use error::{Error, ErrorKind, Result};
166+
pub use error::{Error, ErrorKind, Result, UpdateWatchesError};
167167
pub use notify_types::event::{self, Event, EventKind};
168-
use std::path::Path;
168+
use std::path::{Path, PathBuf};
169169

170+
pub(crate) type StdResult<T, E> = std::result::Result<T, E>;
170171
pub(crate) type Receiver<T> = std::sync::mpsc::Receiver<T>;
171172
pub(crate) type Sender<T> = std::sync::mpsc::Sender<T>;
172173
#[cfg(any(target_os = "linux", target_os = "android", target_os = "windows"))]
@@ -295,19 +296,56 @@ pub enum WatcherKind {
295296

296297
/// Providing methods for adding and removing paths to watch.
297298
///
298-
/// `Box<dyn PathsMut>` is created by [`Watcher::paths_mut`]. See its documentation for more.
299-
pub trait PathsMut {
300-
/// Add a new path to watch. See [`Watcher::watch`] for more.
301-
fn add(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()>;
299+
/// It is created by [`Watcher::paths_mut`].
300+
pub struct PathsMut<'a> {
301+
ops: Vec<WatchOp>,
302+
watcher: &'a mut dyn Watcher,
303+
}
304+
305+
impl<'a> Extend<WatchOp> for PathsMut<'a> {
306+
fn extend<T: IntoIterator<Item = WatchOp>>(&mut self, iter: T) {
307+
self.ops.extend(iter);
308+
}
309+
}
310+
311+
impl<'a> PathsMut<'a> {
312+
/// Creates new [`PathsMut`] to operate with watched paths in a bulk manner
313+
pub fn new<W: Watcher + 'a>(watcher: &'a mut W) -> Self {
314+
Self {
315+
ops: Default::default(),
316+
watcher,
317+
}
318+
}
319+
320+
/// Plan a path to be added to watching. See [`Watcher::unwatch`] for more information.
321+
pub fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) {
322+
self.ops
323+
.push(WatchOp::Watch(path.to_owned(), recursive_mode));
324+
}
302325

303-
/// Remove a path from watching. See [`Watcher::unwatch`] for more.
304-
fn remove(&mut self, path: &Path) -> Result<()>;
326+
/// Plan a path to be removed from watching. See [`Watcher::unwatch`] for more information.
327+
pub fn unwatch(&mut self, path: &Path) {
328+
self.ops.push(WatchOp::Unwatch(path.to_owned()));
329+
}
305330

306-
/// Ensure added/removed paths are applied.
331+
/// Commit all the operations.
307332
///
308-
/// The behaviour of dropping a [`PathsMut`] without calling [`commit`] is unspecified.
309-
/// The implementation is free to ignore the changes or not, and may leave the watcher in a started or stopped state.
310-
fn commit(self: Box<Self>) -> Result<()>;
333+
/// Dropping a [`PathsMut`] without calling [`Self::commit`] is noop.
334+
pub fn commit(self) -> StdResult<(), UpdateWatchesError> {
335+
self.watcher.update_watches(self.ops)
336+
}
337+
}
338+
339+
/// An operation to apply to a watcher
340+
///
341+
/// See [`Watcher::paths_mut`] for more information
342+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
343+
pub enum WatchOp {
344+
/// Path should be watcher
345+
Watch(PathBuf, RecursiveMode),
346+
347+
/// Path should be unwatched
348+
Unwatch(PathBuf),
311349
}
312350

313351
/// Type that can deliver file activity notifications
@@ -345,40 +383,47 @@ pub trait Watcher {
345383
/// fails.
346384
fn unwatch(&mut self, path: &Path) -> Result<()>;
347385

348-
/// Add/remove paths to watch.
386+
/// Add/remove paths to watch in batch.
349387
///
350-
/// For some watcher implementations this method provides better performance than multiple calls to [`Watcher::watch`] and [`Watcher::unwatch`] if you want to add/remove many paths at once.
388+
/// It is ergonomic way to call [`Watcher::update_watches`]
351389
///
352390
/// # Examples
353391
///
354392
/// ```
355-
/// # use notify::{Watcher, RecursiveMode, Result};
393+
/// # use notify::{Watcher, RecursiveMode};
356394
/// # use std::path::Path;
357-
/// # fn main() -> Result<()> {
395+
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
358396
/// # let many_paths_to_add = vec![];
397+
/// # let many_paths_to_remove = vec![];
359398
/// let mut watcher = notify::recommended_watcher(|_event| { /* event handler */ })?;
360399
/// let mut watcher_paths = watcher.paths_mut();
400+
///
361401
/// for path in many_paths_to_add {
362-
/// watcher_paths.add(path, RecursiveMode::Recursive)?;
402+
/// // just some memory stuff
403+
/// watcher_paths.watch(path, RecursiveMode::Recursive);
363404
/// }
405+
///
406+
/// for path in many_paths_to_remove {
407+
/// // just some memory stuff
408+
/// watcher_paths.unwatch(path);
409+
/// }
410+
///
411+
/// // real work is done there
364412
/// watcher_paths.commit()?;
365413
/// # Ok(())
366414
/// # }
367415
/// ```
368-
fn paths_mut<'me>(&'me mut self) -> Box<dyn PathsMut + 'me> {
369-
struct DefaultPathsMut<'a, T: ?Sized>(&'a mut T);
370-
impl<'a, T: Watcher + ?Sized> PathsMut for DefaultPathsMut<'a, T> {
371-
fn add(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> {
372-
self.0.watch(path, recursive_mode)
373-
}
374-
fn remove(&mut self, path: &Path) -> Result<()> {
375-
self.0.unwatch(path)
376-
}
377-
fn commit(self: Box<Self>) -> Result<()> {
378-
Ok(())
379-
}
380-
}
381-
Box::new(DefaultPathsMut(self))
416+
fn paths_mut(&mut self) -> crate::PathsMut<'_>;
417+
418+
/// Add/remove paths to watch in batch.
419+
///
420+
/// For some [`Watcher`] implementations this method provides better performance than multiple
421+
/// calls to [`Watcher::watch`] and [`Watcher::unwatch`] if you want to add/remove many paths at once.
422+
fn update_watches(&mut self, ops: Vec<WatchOp>) -> StdResult<(), UpdateWatchesError> {
423+
update_watches(ops, |op| match op {
424+
WatchOp::Watch(path, recursive_mode) => self.watch(&path, recursive_mode),
425+
WatchOp::Unwatch(path) => self.unwatch(&path),
426+
})
382427
}
383428

384429
/// Configure the watcher at runtime.
@@ -442,6 +487,25 @@ where
442487
RecommendedWatcher::new(event_handler, Config::default())
443488
}
444489

490+
pub(crate) fn update_watches<F>(
491+
ops: Vec<WatchOp>,
492+
mut apply: F,
493+
) -> StdResult<(), UpdateWatchesError>
494+
where
495+
F: FnMut(WatchOp) -> Result<()>,
496+
{
497+
let mut iter = ops.into_iter();
498+
while let Some(op) = iter.next() {
499+
if let Err(source) = apply(op) {
500+
return Err(UpdateWatchesError {
501+
source,
502+
remaining: iter.collect(),
503+
});
504+
}
505+
}
506+
Ok(())
507+
}
508+
445509
#[cfg(test)]
446510
mod tests {
447511
use std::{
@@ -557,8 +621,8 @@ mod tests {
557621
// start watching a and b
558622
{
559623
let mut watcher_paths = watcher.paths_mut();
560-
watcher_paths.add(&dir_a, RecursiveMode::Recursive)?;
561-
watcher_paths.add(&dir_b, RecursiveMode::Recursive)?;
624+
watcher_paths.watch(&dir_a, RecursiveMode::Recursive);
625+
watcher_paths.watch(&dir_b, RecursiveMode::Recursive);
562626
watcher_paths.commit()?;
563627
}
564628

@@ -588,7 +652,7 @@ mod tests {
588652
// stop watching a
589653
{
590654
let mut watcher_paths = watcher.paths_mut();
591-
watcher_paths.remove(&dir_a)?;
655+
watcher_paths.unwatch(&dir_a);
592656
watcher_paths.commit()?;
593657
}
594658

notify/src/null.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,8 @@ impl Watcher for NullWatcher {
3636
fn kind() -> crate::WatcherKind {
3737
crate::WatcherKind::NullWatcher
3838
}
39+
40+
fn paths_mut(&mut self) -> crate::PathsMut<'_> {
41+
crate::PathsMut::new(self)
42+
}
3943
}

0 commit comments

Comments
 (0)