diff --git a/src/imfs/fetcher.rs b/src/imfs/fetcher.rs index f86f702a..ac6ca7ac 100644 --- a/src/imfs/fetcher.rs +++ b/src/imfs/fetcher.rs @@ -29,4 +29,9 @@ pub trait ImfsFetcher { fn watch(&mut self, path: &Path); fn unwatch(&mut self, path: &Path); fn receiver(&self) -> Receiver; + + /// A method intended for debugging what paths the fetcher is watching. + fn watched_paths(&self) -> Vec<&Path> { + Vec::new() + } } diff --git a/src/imfs/imfs.rs b/src/imfs/imfs.rs index 915f9f84..3798472c 100644 --- a/src/imfs/imfs.rs +++ b/src/imfs/imfs.rs @@ -24,7 +24,13 @@ use super::{ /// Most operations return `ImfsEntry` objects to work around this, which is /// effectively a index into the `Imfs`. pub struct Imfs { + /// A hierarchical map from paths to items that have been read or partially + /// read into memory by the Imfs. inner: PathMap, + + /// This Imfs's fetcher, which is used for all actual interactions with the + /// filesystem. It's referred to by the type parameter `F` all over, and is + /// generic in order to make it feasible to mock. fetcher: F, } @@ -270,9 +276,10 @@ pub trait ImfsDebug { fn debug_contents<'a>(&'a self, path: &Path) -> Option<&'a [u8]>; fn debug_children<'a>(&'a self, path: &Path) -> Option<(bool, Vec<&'a Path>)>; fn debug_orphans(&self) -> Vec<&Path>; + fn debug_watched_paths(&self) -> Vec<&Path>; } -impl ImfsDebug for Imfs { +impl ImfsDebug for Imfs { fn debug_load_snapshot>(&mut self, path: P, snapshot: ImfsSnapshot) { let path = path.as_ref(); @@ -328,6 +335,10 @@ impl ImfsDebug for Imfs { fn debug_orphans(&self) -> Vec<&Path> { self.inner.orphans().collect() } + + fn debug_watched_paths(&self) -> Vec<&Path> { + self.fetcher.watched_paths() + } } /// A reference to file or folder in an `Imfs`. Can only be produced by the diff --git a/src/imfs/real_fetcher.rs b/src/imfs/real_fetcher.rs index b3159950..e9dc221a 100644 --- a/src/imfs/real_fetcher.rs +++ b/src/imfs/real_fetcher.rs @@ -2,6 +2,7 @@ //! std::fs interface and notify as the file watcher. use std::{ + collections::HashSet, fs, io, path::{Path, PathBuf}, sync::mpsc, @@ -35,7 +36,13 @@ pub struct RealFetcher { /// Thread handle to convert notify's mpsc channel messages into /// crossbeam_channel messages. _converter_thread: JoinHandle<()>, + + /// The crossbeam receiver filled with events from the converter thread. receiver: Receiver, + + /// All of the paths that the fetcher is watching, tracked here because + /// notify does not expose this information. + watched_paths: HashSet, } impl RealFetcher { @@ -69,6 +76,7 @@ impl RealFetcher { watcher, _converter_thread: handle, receiver, + watched_paths: HashSet::new(), } } } @@ -132,8 +140,13 @@ impl ImfsFetcher for RealFetcher { log::trace!("Watching path {}", path.display()); if let Some(watcher) = self.watcher.as_mut() { - if let Err(err) = watcher.watch(path, RecursiveMode::NonRecursive) { - log::warn!("Couldn't watch path {}: {:?}", path.display(), err); + match watcher.watch(path, RecursiveMode::NonRecursive) { + Ok(_) => { + self.watched_paths.insert(path.to_path_buf()); + } + Err(err) => { + log::warn!("Couldn't watch path {}: {:?}", path.display(), err); + } } } } @@ -142,6 +155,11 @@ impl ImfsFetcher for RealFetcher { log::trace!("Stopped watching path {}", path.display()); if let Some(watcher) = self.watcher.as_mut() { + // Remove the path from our watched paths regardless of the outcome + // of notify's unwatch to ensure we drop old paths in the event of a + // rename. + self.watched_paths.remove(path); + if let Err(err) = watcher.unwatch(path) { log::warn!("Couldn't unwatch path {}: {:?}", path.display(), err); } @@ -151,4 +169,8 @@ impl ImfsFetcher for RealFetcher { fn receiver(&self) -> Receiver { self.receiver.clone() } + + fn watched_paths(&self) -> Vec<&Path> { + self.watched_paths.iter().map(|v| v.as_path()).collect() + } }