Add update-all, autostart, purge, and verify CLI commands
This commit is contained in:
204
src/cli.rs
204
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<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 {
|
||||
|
||||
Reference in New Issue
Block a user