use std::path::PathBuf; use std::sync::mpsc; use std::time::Duration; use notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher}; /// Events sent from the file watcher to the UI thread. #[derive(Debug, Clone)] pub enum WatchEvent { /// One or more AppImage files were created, modified, or deleted. Changed(Vec), } /// Start watching the given directories for AppImage file changes. /// Returns the watcher handle (must be kept alive). /// The callback `on_event` is invoked on the background debounce thread. pub fn start_watcher( dirs: Vec, on_event: F, ) -> Option { let (notify_tx, notify_rx) = mpsc::channel::>(); let mut watcher = RecommendedWatcher::new( move |res| { notify_tx.send(res).ok(); }, Config::default().with_poll_interval(Duration::from_secs(2)), ).ok()?; for dir in &dirs { if dir.is_dir() { watcher.watch(dir, RecursiveMode::NonRecursive).ok(); } } // Spawn a thread to debounce and forward events std::thread::spawn(move || { let mut pending: Vec = Vec::new(); let debounce = Duration::from_millis(500); loop { match notify_rx.recv_timeout(debounce) { Ok(Ok(event)) => { if is_appimage_event(&event) { for path in event.paths { if !pending.contains(&path) { pending.push(path); } } } } Ok(Err(_)) => {} Err(mpsc::RecvTimeoutError::Timeout) => { if !pending.is_empty() { let paths = std::mem::take(&mut pending); on_event(WatchEvent::Changed(paths)); } } Err(mpsc::RecvTimeoutError::Disconnected) => break, } } }); Some(watcher) } fn is_appimage_event(event: &Event) -> bool { match event.kind { EventKind::Create(_) | EventKind::Remove(_) | EventKind::Modify(_) => { event.paths.iter().any(|p| { p.extension() .and_then(|e| e.to_str()) .map(|e| e.eq_ignore_ascii_case("appimage")) .unwrap_or(false) }) } _ => false, } }