Files
driftwood/src/core/watcher.rs
lashman 423323d5a9 Add Phase 5 enhancements: security, i18n, analysis, backup, notifications
- Database v8 migration: tags, pinned, avg_startup_ms columns
- Security scanning with CVE matching and batch scan
- Bundled library extraction and vulnerability reports
- Desktop notification system for security alerts
- Backup/restore system for AppImage configurations
- i18n framework with gettext support
- Runtime analysis and Wayland compatibility detection
- AppStream metadata and Flatpak-style build support
- File watcher module for live directory monitoring
- Preferences panel with GSettings integration
- CLI interface for headless operation
- Detail view: tabbed layout with ViewSwitcher in title bar,
  health score, sandbox controls, changelog links
- Library view: sort dropdown, context menu enhancements
- Dashboard: system status, disk usage, launch history
- Security report page with scan and export
- Packaging: meson build, PKGBUILD, metainfo
2026-02-27 17:16:41 +02:00

80 lines
2.5 KiB
Rust

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,
}
}