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
//!
//! # 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<PathBuf>, Event)
fn deconstruct_event<P: AsRef<Path>>(root: P,event: event::Event) -> (Vec<PathBuf>, 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<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 {
/// 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));
@ -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<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?;
true
} else {
@ -122,7 +159,7 @@ impl Hook {
P: AsRef<Path>
{
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<Path>) -> 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<Path>) -> 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<usize> = { //XXX: Change to generational arena
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>>
{
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<dyn std::error::Error>> {
log::init(log::Level::Debug);
debug!("Logger initialised");
print_stats();
@ -93,7 +94,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
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);

Loading…
Cancel
Save