Files
driftwood/src/core/analysis.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

134 lines
4.6 KiB
Rust

use std::path::PathBuf;
use std::sync::atomic::{AtomicUsize, Ordering};
use crate::core::database::Database;
use crate::core::discovery::AppImageType;
use crate::core::fuse;
use crate::core::inspector;
use crate::core::integrator;
use crate::core::wayland;
/// Maximum number of concurrent background analyses.
const MAX_CONCURRENT_ANALYSES: usize = 2;
/// Counter for currently running analyses.
static RUNNING_ANALYSES: AtomicUsize = AtomicUsize::new(0);
/// Returns the number of currently running background analyses.
pub fn running_count() -> usize {
RUNNING_ANALYSES.load(Ordering::Relaxed)
}
/// RAII guard that decrements the analysis counter on drop.
struct AnalysisGuard;
impl Drop for AnalysisGuard {
fn drop(&mut self) {
RUNNING_ANALYSES.fetch_sub(1, Ordering::Release);
}
}
/// Run the heavy analysis steps for a single AppImage on a background thread.
///
/// This opens its own database connection and updates results as they complete.
/// All errors are logged but non-fatal - fields stay `None`, which the UI
/// already handles gracefully.
///
/// Blocks until a slot is available if the concurrency limit is reached.
pub fn run_background_analysis(id: i64, path: PathBuf, appimage_type: AppImageType, integrate: bool) {
// Wait for a slot to become available
loop {
let current = RUNNING_ANALYSES.load(Ordering::Acquire);
if current < MAX_CONCURRENT_ANALYSES {
if RUNNING_ANALYSES.compare_exchange(current, current + 1, Ordering::AcqRel, Ordering::Relaxed).is_ok() {
break;
}
} else {
std::thread::sleep(std::time::Duration::from_millis(200));
}
}
let _guard = AnalysisGuard;
let db = match Database::open() {
Ok(db) => db,
Err(e) => {
log::error!("Background analysis: failed to open database: {}", e);
return;
}
};
if let Err(e) = db.update_analysis_status(id, "analyzing") {
log::warn!("Failed to set analysis status to 'analyzing' for id {}: {}", id, e);
}
// Inspect metadata (app name, version, icon, desktop entry, etc.)
if let Ok(meta) = inspector::inspect_appimage(&path, &appimage_type) {
let categories = if meta.categories.is_empty() {
None
} else {
Some(meta.categories.join(";"))
};
if let Err(e) = db.update_metadata(
id,
meta.app_name.as_deref(),
meta.app_version.as_deref(),
meta.description.as_deref(),
meta.developer.as_deref(),
categories.as_deref(),
meta.architecture.as_deref(),
meta.cached_icon_path
.as_ref()
.map(|p| p.to_string_lossy())
.as_deref(),
Some(&meta.desktop_entry_content),
) {
log::warn!("Failed to update metadata for id {}: {}", id, e);
}
}
// FUSE status
let fuse_info = fuse::detect_system_fuse();
let app_fuse = fuse::determine_app_fuse_status(&fuse_info, &path);
if let Err(e) = db.update_fuse_status(id, app_fuse.as_str()) {
log::warn!("Failed to update FUSE status for id {}: {}", id, e);
}
// Wayland status
let analysis = wayland::analyze_appimage(&path);
if let Err(e) = db.update_wayland_status(id, analysis.status.as_str()) {
log::warn!("Failed to update Wayland status for id {}: {}", id, e);
}
// SHA256 hash
if let Ok(hash) = crate::core::discovery::compute_sha256(&path) {
if let Err(e) = db.update_sha256(id, &hash) {
log::warn!("Failed to update SHA256 for id {}: {}", id, e);
}
}
// Footprint discovery
if let Ok(Some(rec)) = db.get_appimage_by_id(id) {
crate::core::footprint::discover_and_store(&db, id, &rec);
// Integrate if requested
if integrate {
match integrator::integrate(&rec) {
Ok(result) => {
let desktop_path = result.desktop_file_path.to_string_lossy().to_string();
if let Err(e) = db.set_integrated(id, true, Some(&desktop_path)) {
log::warn!("Failed to set integration status for id {}: {}", id, e);
}
}
Err(e) => {
log::error!("Integration failed for id {}: {}", id, e);
}
}
}
}
if let Err(e) = db.update_analysis_status(id, "complete") {
log::warn!("Failed to set analysis status to 'complete' for id {}: {}", id, e);
}
// _guard dropped here, decrementing RUNNING_ANALYSES
}