Files
driftwood/src/core/watcher.rs

82 lines
2.5 KiB
Rust

#![allow(dead_code)]
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<PathBuf>),
}
/// 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<F: Fn(WatchEvent) + Send + 'static>(
dirs: Vec<PathBuf>,
on_event: F,
) -> Option<RecommendedWatcher> {
let (notify_tx, notify_rx) = mpsc::channel::<Result<Event, notify::Error>>();
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<PathBuf> = 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,
}
}