From fc6c45baf2aba3fb75e9dabc04defac467d20a4c Mon Sep 17 00:00:00 2001 From: Avril Date: Mon, 3 Aug 2020 03:21:43 +0100 Subject: [PATCH] live: better path match --- src/live.rs | 59 +++++++++++++++++++++++++++++++++++++++++++---------- src/main.rs | 5 +++-- 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/src/live.rs b/src/live.rs index 92f6fd7..570aeea 100644 --- a/src/live.rs +++ b/src/live.rs @@ -1,4 +1,19 @@ //! Live config reloader. Handles hooks and filesystem wathcers +//! +//! # Notes +//! - The path passed to `watch()` MUST exist, or child will panic (TODO). But the path can be removed afterwards. +//! - When `Oneesan` is dropped, it signals child worker to gracefully shutdown +//! - When child worker panics, waiting on `Oneesan` can cause deadlock. +//! - The filename passed to `watch()` is canonicalised. +//! ## Hook format +//! - File paths are all relative to their root. +//! - Appending a directory path with `/` will cause events in file within the directory path being dispatched to also be dispatched to this hook. Otherwise, only events on the directory itself will dispatch. +//! - The empty string matches only the root directory +//! - A single `/` string will match everything. +//! # TODO +//! - Make child not panic if `watch()` is called on non-existant path +//! - Change hook from just string path prefix to pattern match with optional regex capability. (For finding and dispatching on global/local config files differently) + use super::*; use std::{ path::{ @@ -47,10 +62,13 @@ pub type Event = event::EventKind; /// Decontruct a notify event into event kind and full paths. #[inline] -fn deconstruct_event(event: event::Event) -> (Vec, Event) +fn deconstruct_event>(root: P,event: event::Event) -> (Vec, Event) { - // Dunno if this is needed - (event.paths.into_iter().map(|x| std::fs::canonicalize(&x).unwrap_or(x)).collect(), event.kind) + let root = root.as_ref(); + (event.paths.into_iter().map(|x| { + // Remove root + x.strip_prefix(root).unwrap_or(&x).to_owned() + }).collect(), event.kind) } /// An event filter function @@ -72,6 +90,25 @@ impl std::fmt::Debug for Hook } +/// Check if `two` is a partial match of `one`. +#[inline] +fn is_path_match(one: P, two: Q) -> bool +where P: AsRef, + Q: AsRef +{ + let (one, two) = (one.as_ref(), two.as_ref()); + + if one.as_os_str() == "/" { + return true; //match-all case + } + + //debug!("Checking {:?} ?== {:?}", one,two); + //println!(" {:?} {:?} {} {}", one, two, one.to_str().map(|x| x.ends_with("/")).unwrap_or(false), two.starts_with(one)); + one == two || { + one.to_str().map(|x| x.ends_with("/")).unwrap_or(false) && two.starts_with(one) + } +} + impl Hook { /// Optional timeout for dispatches. None for infinite wait. A high or `None` value can cause event backlogs to overflow when receivers are not taking events properly. const DISPATCH_TIMEOUT: Option = Some(time::Duration::from_secs(5)); @@ -86,7 +123,7 @@ impl Hook { let (tx, rx) = mpsc::channel(Self::DISPATCH_MAX_BACKLOG); (Self{ - path: std::fs::canonicalize(&path).unwrap_or(path.as_ref().to_owned()), + path: path.as_ref().to_owned(),//std::fs::canonicalize(&path).unwrap_or(path.as_ref().to_owned()), sender: Mutex::new(tx), filter: filter.map(|f| Box::new(f) as EventFilterFn), }, rx) @@ -108,7 +145,7 @@ impl Hook { /// Try to dispatch on this hook, if needed. pub async fn try_dispatch(&self, path: impl AsRef, event: &Event) -> Result> { - Ok(if self.path == path.as_ref() { + Ok(if is_path_match(&self.path, path) { self.dispatch(event.to_owned()).await?; true } else { @@ -122,7 +159,7 @@ impl Hook { P: AsRef { for path in paths.into_iter() { - if self.path == path.as_ref() { + if is_path_match(&self.path, path) { self.dispatch(event.to_owned()).await?; return Ok(true); } @@ -205,7 +242,7 @@ pub fn watch(path: impl AsRef) -> Oneesan let (shutdown, mut shutdown_rx) = oneshot::channel(); let handle = { - let path=path.clone(); + let path=std::fs::canonicalize(&path).unwrap_or_else(|_| path.clone()); let hooks = Arc::clone(&hooks); tokio::spawn(async move { let (tx,mut rx) = mpsc::unbounded_channel(); @@ -216,14 +253,14 @@ pub fn watch(path: impl AsRef) -> Oneesan Err(e) => error!("Watcher returned error: {}", e), } }).expect("Failed to initialise watcher"); - + debug!("Watcher initialised, starting for path {:?}", &path); - watcher.watch(path, RecursiveMode::Recursive).expect("Failed to start watcher"); + watcher.watch(&path, RecursiveMode::Recursive).expect("Failed to start watcher"); let work = async { while let Some(event) = rx.recv().await { - debug!(" -> {:?}", event); - let (paths, event) = deconstruct_event(event); + //debug!(" -> {:?}", event); + let (paths, event) = deconstruct_event(&path, event); let closed: Vec = { //XXX: Change to generational arena let hooks = hooks.read().await; diff --git a/src/main.rs b/src/main.rs index 29d42b0..1fec57a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,6 +33,7 @@ mod job; async fn do_thing_every() -> Result<(mpsc::Sender<()>, task::JoinHandle<()>), Box> { let mut interval = time::interval(Duration::from_secs(10)); + let (tx, mut rx) = mpsc::channel(16); let handle = tokio::spawn(async move { @@ -78,6 +79,7 @@ fn print_stats() status!(""); status!("GPl'd with <3"); status!("Please enjoy"); + status!("---"); } @@ -85,7 +87,6 @@ fn print_stats() async fn main() -> Result<(), Box> { log::init(log::Level::Debug); - debug!("Logger initialised"); print_stats(); @@ -93,7 +94,7 @@ async fn main() -> Result<(), Box> { let oneesan = live::watch("."); { - let mut recv = oneesan.hook("src/main.rs", live::filter::ALL).await; + let mut recv = oneesan.hook("main.rs", live::filter::ALL).await; while let Some(event) = recv.recv().await { important!("Got ev {:?}", event);