- 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
80 lines
2.5 KiB
Rust
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,
|
|
}
|
|
}
|