From b78cf58e1138f9fbbee905d64cd50d44b0858b17 Mon Sep 17 00:00:00 2001 From: Konrad Malik Date: Mon, 4 Dec 2023 23:22:20 +0100 Subject: [PATCH] fix: filter events, not paths before watching this is to fix not detecting changes after file replace file replace can happen for ex. when saving in text editor may be flow on very big file trees, we'll see --- src/filters.rs | 28 ++++++++++++++++++++++++++-- src/main.rs | 11 ++++++----- src/watching.rs | 22 ++++++++++++++++------ 3 files changed, 48 insertions(+), 13 deletions(-) diff --git a/src/filters.rs b/src/filters.rs index 99b8678..c408ef4 100644 --- a/src/filters.rs +++ b/src/filters.rs @@ -1,4 +1,4 @@ -use std::path::PathBuf; +use std::{fs, path::PathBuf}; pub trait PathFilter { fn paths(&self) -> Vec; @@ -8,16 +8,32 @@ pub struct IgnorePathFilter { root: PathBuf, } +pub struct MultiFilter { + filters: Vec, +} + impl PathFilter for IgnorePathFilter { fn paths(&self) -> Vec { ignore::Walk::new(&self.root) .flatten() .filter(|d| d.file_type().map(|ft| ft.is_file()).unwrap_or(false)) - .map(|d| d.into_path()) + .flat_map(|d| fs::canonicalize(d.into_path())) .collect() } } +impl PathFilter for MultiFilter { + fn paths(&self) -> Vec { + self.filters.iter().flat_map(|x| x.paths()).collect() + } +} + +impl MultiFilter { + pub fn new(filters: Vec) -> Self { + MultiFilter { filters } + } +} + impl IgnorePathFilter { pub fn new(root: PathBuf) -> Self { IgnorePathFilter { root } @@ -28,6 +44,14 @@ impl IgnorePathFilter { mod tests { use super::*; + #[test] + fn test_includes_only_absolute_paths() { + let root_path = get_root_path(); + let filter = IgnorePathFilter::new(root_path.to_owned()); + let paths = filter.paths(); + assert!(paths.iter().all(|p| p.is_absolute())) + } + #[test] fn test_does_include_this_file() { let root_path = get_root_path(); diff --git a/src/main.rs b/src/main.rs index 2e5d6f1..02a9321 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use anyhow::Result; use clap::Parser; -use filters::PathFilter; +use filters::MultiFilter; use std::{sync::mpsc, thread}; use watching::PathWatcher; mod cli; @@ -23,14 +23,15 @@ fn main() -> Result<()> { let (tx, rx) = mpsc::sync_channel(0); thread::spawn(|| { - let mut paths_to_watch = Vec::new(); - for path in paths { + let mut filters = Vec::new(); + for path in paths.clone() { log::debug!("Watching {}", &path.display()); let filter = filters::IgnorePathFilter::new(path); - paths_to_watch.extend_from_slice(&filter.paths()); + filters.push(filter); } - let watcher = watching::NotifyWatcher::new(paths_to_watch); + let filter = MultiFilter::new(filters); + let watcher = watching::NotifyWatcher::new(paths, filter); watcher.watch(tx).expect("cannot start watcher") }); diff --git a/src/watching.rs b/src/watching.rs index 6b8d4a6..e8760b0 100644 --- a/src/watching.rs +++ b/src/watching.rs @@ -1,8 +1,11 @@ use anyhow::Result; use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher}; +use std::collections::HashSet; use std::path::PathBuf; use std::sync::mpsc; +use crate::filters::PathFilter; + #[derive(Debug, Clone, Copy)] pub struct ChangeEvent; @@ -10,11 +13,12 @@ pub trait PathWatcher { fn watch(&self, changes: mpsc::SyncSender) -> Result<()>; } -pub struct NotifyWatcher { +pub struct NotifyWatcher { paths: Vec, + filter: F, } -impl PathWatcher for NotifyWatcher { +impl PathWatcher for NotifyWatcher { fn watch(&self, changes: mpsc::SyncSender) -> Result<()> { let (tx, rx) = mpsc::channel(); @@ -25,14 +29,20 @@ impl PathWatcher for NotifyWatcher { } let change = ChangeEvent; + let filtered_paths: HashSet = HashSet::from_iter(self.filter.paths()); for res in rx { match res { Ok(event) => { log::debug!("Change in: {:?}", event.paths); for path in event.paths { + if !filtered_paths.contains(&path) { + log::debug!("ignored"); + continue; + }; + if changes.try_send(change).is_err() { - log::debug!("buffer full, ignoring event in: {}", path.display()); + log::debug!("buffer full, skipping event"); }; } } @@ -44,8 +54,8 @@ impl PathWatcher for NotifyWatcher { } } -impl NotifyWatcher { - pub fn new(paths: Vec) -> Self { - NotifyWatcher { paths } +impl NotifyWatcher { + pub fn new(paths: Vec, filter: F) -> Self { + NotifyWatcher { paths, filter } } }