Skip to content

Commit

Permalink
fix: filter events, not paths before watching
Browse files Browse the repository at this point in the history
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
  • Loading branch information
konradmalik committed Dec 4, 2023
1 parent de45ced commit b78cf58
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 13 deletions.
28 changes: 26 additions & 2 deletions src/filters.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::path::PathBuf;
use std::{fs, path::PathBuf};

pub trait PathFilter {
fn paths(&self) -> Vec<PathBuf>;
Expand All @@ -8,16 +8,32 @@ pub struct IgnorePathFilter {
root: PathBuf,
}

pub struct MultiFilter<F: PathFilter> {
filters: Vec<F>,
}

impl PathFilter for IgnorePathFilter {
fn paths(&self) -> Vec<PathBuf> {
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<F: PathFilter> PathFilter for MultiFilter<F> {
fn paths(&self) -> Vec<PathBuf> {
self.filters.iter().flat_map(|x| x.paths()).collect()
}
}

impl<F: PathFilter> MultiFilter<F> {
pub fn new(filters: Vec<F>) -> Self {
MultiFilter { filters }
}
}

impl IgnorePathFilter {
pub fn new(root: PathBuf) -> Self {
IgnorePathFilter { root }
Expand All @@ -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();
Expand Down
11 changes: 6 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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")
});

Expand Down
22 changes: 16 additions & 6 deletions src/watching.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
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;

pub trait PathWatcher {
fn watch(&self, changes: mpsc::SyncSender<ChangeEvent>) -> Result<()>;
}

pub struct NotifyWatcher {
pub struct NotifyWatcher<F: PathFilter> {
paths: Vec<PathBuf>,
filter: F,
}

impl PathWatcher for NotifyWatcher {
impl<F: PathFilter> PathWatcher for NotifyWatcher<F> {
fn watch(&self, changes: mpsc::SyncSender<ChangeEvent>) -> Result<()> {
let (tx, rx) = mpsc::channel();

Expand All @@ -25,14 +29,20 @@ impl PathWatcher for NotifyWatcher {
}

let change = ChangeEvent;
let filtered_paths: HashSet<PathBuf> = 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");
};
}
}
Expand All @@ -44,8 +54,8 @@ impl PathWatcher for NotifyWatcher {
}
}

impl NotifyWatcher {
pub fn new(paths: Vec<PathBuf>) -> Self {
NotifyWatcher { paths }
impl<F: PathFilter> NotifyWatcher<F> {
pub fn new(paths: Vec<PathBuf>, filter: F) -> Self {
NotifyWatcher { paths, filter }
}
}

0 comments on commit b78cf58

Please sign in to comment.