diff --git a/src/cli.rs b/src/cli.rs index 1eb55dd..f76313b 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -12,6 +12,7 @@ use crate::core::integrator; use crate::core::launcher; use crate::core::orphan; use crate::core::updater; +use crate::core::verification; use crate::core::wayland; #[derive(Parser)] @@ -59,6 +60,29 @@ pub enum Commands { /// Path to the AppImage path: String, }, + /// Update all AppImages that have available updates + UpdateAll, + /// Enable or disable autostart for an AppImage + Autostart { + /// Path to the AppImage + path: String, + /// Enable autostart + #[arg(long, conflicts_with = "disable")] + enable: bool, + /// Disable autostart + #[arg(long)] + disable: bool, + }, + /// Remove all system modifications made by Driftwood + Purge, + /// Verify an AppImage's integrity (signature or SHA256) + Verify { + /// Path to the AppImage + path: String, + /// Expected SHA256 hash (if not provided, checks embedded signature) + #[arg(long)] + sha256: Option, + }, /// Export app library to a JSON file Export { /// Output file path (default: stdout) @@ -92,6 +116,10 @@ pub fn run_command(command: Commands) -> ExitCode { Commands::CheckUpdates => cmd_check_updates(&db), Commands::Duplicates => cmd_duplicates(&db), Commands::Launch { path } => cmd_launch(&db, &path), + Commands::UpdateAll => cmd_update_all(&db), + Commands::Autostart { path, enable, disable } => cmd_autostart(&db, &path, enable, disable), + Commands::Purge => cmd_purge(&db), + Commands::Verify { path, sha256 } => cmd_verify(&path, sha256.as_deref()), Commands::Export { output } => cmd_export(&db, output.as_deref()), Commands::Import { file } => cmd_import(&db, &file), } @@ -678,6 +706,182 @@ fn do_inspect(path: &std::path::Path, appimage_type: &discovery::AppImageType) - } } +fn cmd_update_all(db: &Database) -> ExitCode { + let records = match db.get_all_appimages() { + Ok(r) => r, + Err(e) => { + eprintln!("Error: {}", e); + return ExitCode::FAILURE; + } + }; + + let updatable: Vec<_> = records + .iter() + .filter(|r| r.latest_version.is_some()) + .collect(); + + if updatable.is_empty() { + println!("No updates available. Run 'driftwood check-updates' first."); + return ExitCode::SUCCESS; + } + + println!("Updating {} AppImages...", updatable.len()); + let mut success_count = 0u32; + let mut fail_count = 0u32; + + for record in &updatable { + let name = record.app_name.as_deref().unwrap_or(&record.filename); + let latest = record.latest_version.as_deref().unwrap_or("?"); + print!(" {} -> {} ... ", name, latest); + + let appimage_path = std::path::Path::new(&record.path); + match updater::perform_update(appimage_path, record.update_url.as_deref(), true, None) { + Ok(result) => { + println!("done ({})", result.new_path.display()); + // Store rollback path + if let Some(ref backup) = result.old_path_backup { + db.set_previous_version(record.id, Some(&backup.to_string_lossy())).ok(); + } + db.clear_update_available(record.id).ok(); + // Record in update history + db.record_update( + record.id, + record.app_version.as_deref(), + Some(latest), + Some("cli"), + None, + true, + ).ok(); + success_count += 1; + } + Err(e) => { + println!("FAILED: {}", e); + fail_count += 1; + } + } + } + + println!(); + println!( + "Updated {} of {} AppImages ({} failed)", + success_count, + updatable.len(), + fail_count, + ); + + ExitCode::SUCCESS +} + +fn cmd_autostart(db: &Database, path: &str, enable: bool, disable: bool) -> ExitCode { + let record = match db.get_appimage_by_path(path) { + Ok(Some(r)) => r, + Ok(None) => { + eprintln!("Error: '{}' is not in the database. Run 'driftwood scan' first.", path); + return ExitCode::FAILURE; + } + Err(e) => { + eprintln!("Error: {}", e); + return ExitCode::FAILURE; + } + }; + + let name = record.app_name.as_deref().unwrap_or(&record.filename); + + if enable { + match integrator::enable_autostart(db, &record) { + Ok(desktop_path) => { + println!("Autostart enabled for {} ({})", name, desktop_path.display()); + ExitCode::SUCCESS + } + Err(e) => { + eprintln!("Error: {}", e); + ExitCode::FAILURE + } + } + } else if disable { + match integrator::disable_autostart(db, record.id) { + Ok(()) => { + println!("Autostart disabled for {}", name); + ExitCode::SUCCESS + } + Err(e) => { + eprintln!("Error: {}", e); + ExitCode::FAILURE + } + } + } else { + // Show current status + println!("{}: autostart {}", name, if record.autostart { "enabled" } else { "disabled" }); + ExitCode::SUCCESS + } +} + +fn cmd_purge(db: &Database) -> ExitCode { + let records = match db.get_all_appimages() { + Ok(r) => r, + Err(e) => { + eprintln!("Error: {}", e); + return ExitCode::FAILURE; + } + }; + + let mut total_mods = 0u32; + for record in &records { + let mods = db.get_modifications(record.id).unwrap_or_default(); + if !mods.is_empty() { + let name = record.app_name.as_deref().unwrap_or(&record.filename); + println!(" Undoing {} modifications for {}", mods.len(), name); + total_mods += mods.len() as u32; + integrator::undo_all_modifications(db, record.id).ok(); + } + } + + if total_mods == 0 { + println!("No system modifications to undo."); + } else { + println!("Removed {} system modifications.", total_mods); + } + + ExitCode::SUCCESS +} + +fn cmd_verify(path: &str, expected_sha256: Option<&str>) -> ExitCode { + let file_path = std::path::Path::new(path); + if !file_path.exists() { + eprintln!("Error: file not found: {}", path); + return ExitCode::FAILURE; + } + + if let Some(expected) = expected_sha256 { + // SHA256 verification + println!("Verifying SHA256 for {}...", path); + let status = verification::verify_sha256(file_path, expected); + println!(" {}", status.label()); + match status { + verification::VerificationStatus::ChecksumMatch => ExitCode::SUCCESS, + _ => ExitCode::FAILURE, + } + } else { + // Embedded signature check + println!("Checking embedded signature for {}...", path); + let status = verification::check_embedded_signature(file_path); + println!(" {}", status.label()); + + // Also compute and display SHA256 + println!(); + match verification::compute_sha256(file_path) { + Ok(hash) => { + println!(" SHA256: {}", hash); + ExitCode::SUCCESS + } + Err(e) => { + eprintln!(" SHA256 computation failed: {}", e); + ExitCode::FAILURE + } + } + } +} + // --- Export/Import library --- fn cmd_export(db: &Database, output: Option<&str>) -> ExitCode {