live: better path match

master
Avril 4 years ago
parent 0ba6b69b35
commit fc6c45baf2
Signed by: flanchan
GPG Key ID: 284488987C31F630

@ -1,4 +1,19 @@
//! Live config reloader. Handles hooks and filesystem wathcers //! 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 super::*;
use std::{ use std::{
path::{ path::{
@ -47,10 +62,13 @@ pub type Event = event::EventKind;
/// Decontruct a notify event into event kind and full paths. /// Decontruct a notify event into event kind and full paths.
#[inline] #[inline]
fn deconstruct_event(event: event::Event) -> (Vec<PathBuf>, Event) fn deconstruct_event<P: AsRef<Path>>(root: P,event: event::Event) -> (Vec<PathBuf>, Event)
{ {
// Dunno if this is needed let root = root.as_ref();
(event.paths.into_iter().map(|x| std::fs::canonicalize(&x).unwrap_or(x)).collect(), event.kind) (event.paths.into_iter().map(|x| {
// Remove root
x.strip_prefix(root).unwrap_or(&x).to_owned()
}).collect(), event.kind)
} }
/// An event filter function /// 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<P,Q>(one: P, two: Q) -> bool
where P: AsRef<Path>,
Q: AsRef<Path>
{
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 { 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. /// 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<time::Duration> = Some(time::Duration::from_secs(5)); const DISPATCH_TIMEOUT: Option<time::Duration> = Some(time::Duration::from_secs(5));
@ -86,7 +123,7 @@ impl Hook {
let (tx, rx) = mpsc::channel(Self::DISPATCH_MAX_BACKLOG); let (tx, rx) = mpsc::channel(Self::DISPATCH_MAX_BACKLOG);
(Self{ (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), sender: Mutex::new(tx),
filter: filter.map(|f| Box::new(f) as EventFilterFn), filter: filter.map(|f| Box::new(f) as EventFilterFn),
}, rx) }, rx)
@ -108,7 +145,7 @@ impl Hook {
/// Try to dispatch on this hook, if needed. /// Try to dispatch on this hook, if needed.
pub async fn try_dispatch(&self, path: impl AsRef<Path>, event: &Event) -> Result<bool, SendTimeoutError<Event>> pub async fn try_dispatch(&self, path: impl AsRef<Path>, event: &Event) -> Result<bool, SendTimeoutError<Event>>
{ {
Ok(if self.path == path.as_ref() { Ok(if is_path_match(&self.path, path) {
self.dispatch(event.to_owned()).await?; self.dispatch(event.to_owned()).await?;
true true
} else { } else {
@ -122,7 +159,7 @@ impl Hook {
P: AsRef<Path> P: AsRef<Path>
{ {
for path in paths.into_iter() { 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?; self.dispatch(event.to_owned()).await?;
return Ok(true); return Ok(true);
} }
@ -205,7 +242,7 @@ pub fn watch(path: impl AsRef<Path>) -> Oneesan
let (shutdown, mut shutdown_rx) = oneshot::channel(); let (shutdown, mut shutdown_rx) = oneshot::channel();
let handle = { let handle = {
let path=path.clone(); let path=std::fs::canonicalize(&path).unwrap_or_else(|_| path.clone());
let hooks = Arc::clone(&hooks); let hooks = Arc::clone(&hooks);
tokio::spawn(async move { tokio::spawn(async move {
let (tx,mut rx) = mpsc::unbounded_channel(); let (tx,mut rx) = mpsc::unbounded_channel();
@ -218,12 +255,12 @@ pub fn watch(path: impl AsRef<Path>) -> Oneesan
}).expect("Failed to initialise watcher"); }).expect("Failed to initialise watcher");
debug!("Watcher initialised, starting for path {:?}", &path); 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 { let work = async {
while let Some(event) = rx.recv().await { while let Some(event) = rx.recv().await {
debug!(" -> {:?}", event); //debug!(" -> {:?}", event);
let (paths, event) = deconstruct_event(event); let (paths, event) = deconstruct_event(&path, event);
let closed: Vec<usize> = { //XXX: Change to generational arena let closed: Vec<usize> = { //XXX: Change to generational arena
let hooks = hooks.read().await; let hooks = hooks.read().await;

@ -33,6 +33,7 @@ mod job;
async fn do_thing_every() -> Result<(mpsc::Sender<()>, task::JoinHandle<()>), Box<dyn std::error::Error>> async fn do_thing_every() -> Result<(mpsc::Sender<()>, task::JoinHandle<()>), Box<dyn std::error::Error>>
{ {
let mut interval = time::interval(Duration::from_secs(10)); let mut interval = time::interval(Duration::from_secs(10));
let (tx, mut rx) = mpsc::channel(16); let (tx, mut rx) = mpsc::channel(16);
let handle = tokio::spawn(async move { let handle = tokio::spawn(async move {
@ -78,6 +79,7 @@ fn print_stats()
status!(""); status!("");
status!("GPl'd with <3"); status!("GPl'd with <3");
status!("Please enjoy"); status!("Please enjoy");
status!("---"); status!("---");
} }
@ -86,14 +88,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
log::init(log::Level::Debug); log::init(log::Level::Debug);
debug!("Logger initialised"); debug!("Logger initialised");
print_stats(); print_stats();
let oneesan = live::watch("."); 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 while let Some(event) = recv.recv().await
{ {
important!("Got ev {:?}", event); important!("Got ev {:?}", event);

Loading…
Cancel
Save