- 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
134 lines
4.6 KiB
Rust
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
|
|
}
|