diff --git a/espanso/src/cli/daemon/watcher.rs b/espanso/src/cli/daemon/watcher.rs index 9bf817a..ce3f29c 100644 --- a/espanso/src/cli/daemon/watcher.rs +++ b/espanso/src/cli/daemon/watcher.rs @@ -17,36 +17,42 @@ * along with espanso. If not, see . */ -use std::{ - path::Path, - time::{Duration, Instant}, -}; +use std::{path::Path, time::Duration}; use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher}; use anyhow::Result; -use crossbeam::channel::Sender; +use crossbeam::{channel::Sender, select}; use log::{error, info, warn}; -const WATCHER_DEBOUNCE_DURATION: u64 = 1; +const WATCHER_NOTIFY_DELAY_MS: u64 = 500; +const WATCHER_DEBOUNCE_DURATION_MS: u64 = 1000; pub fn initialize_and_spawn(config_dir: &Path, watcher_notify: Sender<()>) -> Result<()> { let config_dir = config_dir.to_path_buf(); + let (debounce_tx, debounce_rx) = crossbeam::channel::unbounded(); + std::thread::Builder::new() .name("watcher".to_string()) .spawn(move || { - watcher_main(&config_dir, &watcher_notify); + watcher_main(&config_dir, debounce_tx); + })?; + + std::thread::Builder::new() + .name("watcher-debouncer".to_string()) + .spawn(move || { + debouncer_main(debounce_rx, &watcher_notify); })?; Ok(()) } -fn watcher_main(config_dir: &Path, watcher_notify: &Sender<()>) { +fn watcher_main(config_dir: &Path, debounce_tx: crossbeam::channel::Sender<()>) { let (tx, rx) = std::sync::mpsc::channel(); let mut watcher: RecommendedWatcher = - Watcher::new(tx, Duration::from_secs(WATCHER_DEBOUNCE_DURATION)) + Watcher::new(tx, Duration::from_millis(WATCHER_NOTIFY_DELAY_MS)) .expect("unable to create file watcher"); watcher @@ -55,8 +61,6 @@ fn watcher_main(config_dir: &Path, watcher_notify: &Sender<()>) { info!("watching for changes in path: {:?}", config_dir); - let mut last_event_arrival = Instant::now(); - loop { let should_reload = match rx.recv() { Ok(event) => { @@ -92,16 +96,35 @@ fn watcher_main(config_dir: &Path, watcher_notify: &Sender<()>) { } }; - // Send only one event, otherwise we could run the risk of useless reloads or even race conditions. - if should_reload - && last_event_arrival.elapsed() > std::time::Duration::from_secs(WATCHER_DEBOUNCE_DURATION) - { - if let Err(error) = watcher_notify.send(()) { - error!("unable to send watcher file changed event: {}", error); + if should_reload { + if let Err(error) = debounce_tx.send(()) { + error!( + "unable to send watcher file changed event to debouncer: {}", + error + ); } } + } +} - last_event_arrival = Instant::now(); +fn debouncer_main(debounce_rx: crossbeam::channel::Receiver<()>, watcher_notify: &Sender<()>) { + let mut has_received_event = false; + + loop { + select! { + recv(debounce_rx) -> _ => { + has_received_event = true; + }, + default(Duration::from_millis(WATCHER_DEBOUNCE_DURATION_MS)) => { + if has_received_event { + if let Err(error) = watcher_notify.send(()) { + error!("unable to send watcher file changed event: {}", error); + } + } + + has_received_event = false; + }, + } } }