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::launcher;
|
||||||
use crate::core::orphan;
|
use crate::core::orphan;
|
||||||
use crate::core::updater;
|
use crate::core::updater;
|
||||||
|
use crate::core::verification;
|
||||||
use crate::core::wayland;
|
use crate::core::wayland;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
@@ -59,6 +60,29 @@ pub enum Commands {
|
|||||||
/// Path to the AppImage
|
/// Path to the AppImage
|
||||||
path: String,
|
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 app library to a JSON file
|
||||||
Export {
|
Export {
|
||||||
/// Output file path (default: stdout)
|
/// Output file path (default: stdout)
|
||||||
@@ -92,6 +116,10 @@ pub fn run_command(command: Commands) -> ExitCode {
|
|||||||
Commands::CheckUpdates => cmd_check_updates(&db),
|
Commands::CheckUpdates => cmd_check_updates(&db),
|
||||||
Commands::Duplicates => cmd_duplicates(&db),
|
Commands::Duplicates => cmd_duplicates(&db),
|
||||||
Commands::Launch { path } => cmd_launch(&db, &path),
|
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::Export { output } => cmd_export(&db, output.as_deref()),
|
||||||
Commands::Import { file } => cmd_import(&db, &file),
|
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 ---
|
// --- Export/Import library ---
|
||||||
|
|
||||||
fn cmd_export(db: &Database, output: Option<&str>) -> ExitCode {
|
fn cmd_export(db: &Database, output: Option<&str>) -> ExitCode {
|
||||||
|
|||||||
Reference in New Issue
Block a user