Add update-all, autostart, purge, and verify CLI commands

This commit is contained in:
lashman
2026-02-28 00:13:31 +02:00
parent 27eb9f259d
commit f2abfba753

View File

@@ -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<String>,
},
/// 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 {