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 }