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
This commit is contained in:
79
src/core/watcher.rs
Normal file
79
src/core/watcher.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user