Implement Driftwood AppImage manager - Phases 1 and 2
Phase 1 - Application scaffolding: - GTK4/libadwaita application window with AdwNavigationView - GSettings-backed window state persistence - GResource-compiled CSS and schema - Library view with grid/list toggle, search, sorting, filtering - Detail view with file info, desktop integration controls - Preferences window with scan directories, theme, behavior settings - CLI with list, scan, integrate, remove, clean, inspect commands - AppImage discovery, metadata extraction, desktop integration - Orphaned desktop entry detection and cleanup - AppImage packaging script Phase 2 - Intelligence layer: - Database schema v2 with migration for status tracking columns - FUSE detection engine (libfuse2/3, fusermount, /dev/fuse, AppImageLauncher) - Wayland awareness engine (session type, toolkit detection, XWayland) - Update info parsing from AppImage ELF sections (.upd_info) - GitHub/GitLab Releases API integration for update checking - Update download with progress tracking and atomic apply - Launch wrapper with FUSE auto-detection and usage tracking - Duplicate and multi-version detection with recommendations - Dashboard with system health, library stats, disk usage - Update check dialog (single and batch) - Duplicate resolution dialog - Status badges on library cards and detail view - Extended CLI: status, check-updates, duplicates, launch commands 49 tests passing across all modules.
This commit is contained in:
663
src/cli.rs
Normal file
663
src/cli.rs
Normal file
@@ -0,0 +1,663 @@
|
||||
use clap::{Parser, Subcommand};
|
||||
use glib::ExitCode;
|
||||
use gtk::prelude::*;
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::core::database::Database;
|
||||
use crate::core::discovery;
|
||||
use crate::core::duplicates;
|
||||
use crate::core::fuse;
|
||||
use crate::core::inspector;
|
||||
use crate::core::integrator;
|
||||
use crate::core::launcher;
|
||||
use crate::core::orphan;
|
||||
use crate::core::updater;
|
||||
use crate::core::wayland;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "driftwood", version, about = "Modern AppImage manager for GNOME desktops")]
|
||||
pub struct Cli {
|
||||
#[command(subcommand)]
|
||||
pub command: Option<Commands>,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum Commands {
|
||||
/// List all known AppImages
|
||||
List {
|
||||
/// Output format: table or json
|
||||
#[arg(long, default_value = "table")]
|
||||
format: String,
|
||||
},
|
||||
/// Scan for AppImages in configured directories
|
||||
Scan,
|
||||
/// Integrate an AppImage (create .desktop and install icon)
|
||||
Integrate {
|
||||
/// Path to the AppImage
|
||||
path: String,
|
||||
},
|
||||
/// Remove integration for an AppImage
|
||||
Remove {
|
||||
/// Path to the AppImage
|
||||
path: String,
|
||||
},
|
||||
/// Clean orphaned desktop entries
|
||||
Clean,
|
||||
/// Inspect an AppImage and show its metadata
|
||||
Inspect {
|
||||
/// Path to the AppImage
|
||||
path: String,
|
||||
},
|
||||
/// Show system status (FUSE, Wayland, desktop environment)
|
||||
Status,
|
||||
/// Check all AppImages for updates
|
||||
CheckUpdates,
|
||||
/// Find duplicate and multi-version AppImages
|
||||
Duplicates,
|
||||
/// Launch an AppImage (with tracking and FUSE detection)
|
||||
Launch {
|
||||
/// Path to the AppImage
|
||||
path: String,
|
||||
},
|
||||
}
|
||||
|
||||
pub fn run_command(command: Commands) -> ExitCode {
|
||||
let db = match Database::open() {
|
||||
Ok(db) => db,
|
||||
Err(e) => {
|
||||
eprintln!("Error: Failed to open database: {}", e);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
|
||||
match command {
|
||||
Commands::List { format } => cmd_list(&db, &format),
|
||||
Commands::Scan => cmd_scan(&db),
|
||||
Commands::Integrate { path } => cmd_integrate(&db, &path),
|
||||
Commands::Remove { path } => cmd_remove(&db, &path),
|
||||
Commands::Clean => cmd_clean(),
|
||||
Commands::Inspect { path } => cmd_inspect(&path),
|
||||
Commands::Status => cmd_status(),
|
||||
Commands::CheckUpdates => cmd_check_updates(&db),
|
||||
Commands::Duplicates => cmd_duplicates(&db),
|
||||
Commands::Launch { path } => cmd_launch(&db, &path),
|
||||
}
|
||||
}
|
||||
|
||||
fn cmd_list(db: &Database, format: &str) -> ExitCode {
|
||||
let records = match db.get_all_appimages() {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
eprintln!("Error: {}", e);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
|
||||
if records.is_empty() {
|
||||
println!("No AppImages found. Run 'driftwood scan' first.");
|
||||
return ExitCode::SUCCESS;
|
||||
}
|
||||
|
||||
if format == "json" {
|
||||
// Simple JSON output
|
||||
println!("[");
|
||||
for (i, r) in records.iter().enumerate() {
|
||||
let comma = if i + 1 < records.len() { "," } else { "" };
|
||||
println!(
|
||||
" {{\"name\": \"{}\", \"version\": \"{}\", \"path\": \"{}\", \"size\": {}, \"integrated\": {}}}{}",
|
||||
r.app_name.as_deref().unwrap_or(&r.filename),
|
||||
r.app_version.as_deref().unwrap_or(""),
|
||||
r.path,
|
||||
r.size_bytes,
|
||||
r.integrated,
|
||||
comma,
|
||||
);
|
||||
}
|
||||
println!("]");
|
||||
return ExitCode::SUCCESS;
|
||||
}
|
||||
|
||||
// Table output
|
||||
let name_width = records
|
||||
.iter()
|
||||
.map(|r| r.app_name.as_deref().unwrap_or(&r.filename).len())
|
||||
.max()
|
||||
.unwrap_or(4)
|
||||
.max(4)
|
||||
.min(30);
|
||||
|
||||
let ver_width = records
|
||||
.iter()
|
||||
.map(|r| r.app_version.as_deref().unwrap_or("").len())
|
||||
.max()
|
||||
.unwrap_or(7)
|
||||
.max(7)
|
||||
.min(15);
|
||||
|
||||
println!(
|
||||
" {:<name_w$} {:<ver_w$} {:>10} {}",
|
||||
"Name", "Version", "Size", "Integrated",
|
||||
name_w = name_width,
|
||||
ver_w = ver_width,
|
||||
);
|
||||
println!(
|
||||
" {:-<name_w$} {:-<ver_w$} {:->10} ----------",
|
||||
"", "", "",
|
||||
name_w = name_width,
|
||||
ver_w = ver_width,
|
||||
);
|
||||
|
||||
let mut integrated_count = 0;
|
||||
for r in &records {
|
||||
let name = r.app_name.as_deref().unwrap_or(&r.filename);
|
||||
let display_name = if name.len() > name_width {
|
||||
&name[..name_width]
|
||||
} else {
|
||||
name
|
||||
};
|
||||
let version = r.app_version.as_deref().unwrap_or("");
|
||||
let size = humansize::format_size(r.size_bytes as u64, humansize::BINARY);
|
||||
let status = if r.integrated { "Yes" } else { "No" };
|
||||
if r.integrated {
|
||||
integrated_count += 1;
|
||||
}
|
||||
println!(
|
||||
" {:<name_w$} {:<ver_w$} {:>10} {}",
|
||||
display_name, version, size, status,
|
||||
name_w = name_width,
|
||||
ver_w = ver_width,
|
||||
);
|
||||
}
|
||||
|
||||
println!();
|
||||
println!(
|
||||
" {} AppImages found, {} integrated",
|
||||
records.len(),
|
||||
integrated_count,
|
||||
);
|
||||
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
|
||||
fn cmd_scan(db: &Database) -> ExitCode {
|
||||
let settings = gtk::gio::Settings::new(crate::config::APP_ID);
|
||||
let dirs: Vec<String> = settings
|
||||
.strv("scan-directories")
|
||||
.iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
|
||||
println!("Scanning directories:");
|
||||
for d in &dirs {
|
||||
let expanded = discovery::expand_tilde(d);
|
||||
println!(" {}", expanded.display());
|
||||
}
|
||||
|
||||
let start = Instant::now();
|
||||
let discovered = discovery::scan_directories(&dirs);
|
||||
let total = discovered.len();
|
||||
let mut new_count = 0;
|
||||
|
||||
for d in &discovered {
|
||||
let existing = db
|
||||
.get_appimage_by_path(&d.path.to_string_lossy())
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
let modified = d.modified_time
|
||||
.and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
|
||||
.and_then(|dur| {
|
||||
chrono::DateTime::from_timestamp(dur.as_secs() as i64, 0)
|
||||
.map(|dt| dt.format("%Y-%m-%d %H:%M:%S").to_string())
|
||||
});
|
||||
|
||||
let id = db.upsert_appimage(
|
||||
&d.path.to_string_lossy(),
|
||||
&d.filename,
|
||||
Some(d.appimage_type.as_i32()),
|
||||
d.size_bytes as i64,
|
||||
d.is_executable,
|
||||
modified.as_deref(),
|
||||
).unwrap_or(0);
|
||||
|
||||
if existing.is_none() {
|
||||
new_count += 1;
|
||||
println!(" [NEW] {}", d.filename);
|
||||
}
|
||||
|
||||
let needs_metadata = existing
|
||||
.as_ref()
|
||||
.map(|r| r.app_name.is_none())
|
||||
.unwrap_or(true);
|
||||
|
||||
if needs_metadata {
|
||||
print!(" Inspecting {}... ", d.filename);
|
||||
match inspector::inspect_appimage(&d.path, &d.appimage_type) {
|
||||
Ok(metadata) => {
|
||||
let categories = if metadata.categories.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(metadata.categories.join(";"))
|
||||
};
|
||||
db.update_metadata(
|
||||
id,
|
||||
metadata.app_name.as_deref(),
|
||||
metadata.app_version.as_deref(),
|
||||
metadata.description.as_deref(),
|
||||
metadata.developer.as_deref(),
|
||||
categories.as_deref(),
|
||||
metadata.architecture.as_deref(),
|
||||
metadata.cached_icon_path.as_ref().map(|p| p.to_string_lossy()).as_deref(),
|
||||
Some(&metadata.desktop_entry_content),
|
||||
).ok();
|
||||
println!(
|
||||
"{}",
|
||||
metadata.app_name.as_deref().unwrap_or("(no name)")
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("failed: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let duration = start.elapsed();
|
||||
db.log_scan(
|
||||
"cli",
|
||||
&dirs,
|
||||
total as i32,
|
||||
new_count,
|
||||
0,
|
||||
duration.as_millis() as i64,
|
||||
).ok();
|
||||
|
||||
println!();
|
||||
println!(
|
||||
"Scan complete: {} found, {} new ({:.1}s)",
|
||||
total,
|
||||
new_count,
|
||||
duration.as_secs_f64(),
|
||||
);
|
||||
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
|
||||
fn cmd_integrate(db: &Database, path: &str) -> 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;
|
||||
}
|
||||
};
|
||||
|
||||
if record.integrated {
|
||||
println!("{} is already integrated.", record.app_name.as_deref().unwrap_or(&record.filename));
|
||||
return ExitCode::SUCCESS;
|
||||
}
|
||||
|
||||
match integrator::integrate(&record) {
|
||||
Ok(result) => {
|
||||
db.set_integrated(
|
||||
record.id,
|
||||
true,
|
||||
Some(&result.desktop_file_path.to_string_lossy()),
|
||||
).ok();
|
||||
println!(
|
||||
"Integrated {} -> {}",
|
||||
record.app_name.as_deref().unwrap_or(&record.filename),
|
||||
result.desktop_file_path.display(),
|
||||
);
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error: {}", e);
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn cmd_remove(db: &Database, path: &str) -> ExitCode {
|
||||
let record = match db.get_appimage_by_path(path) {
|
||||
Ok(Some(r)) => r,
|
||||
Ok(None) => {
|
||||
eprintln!("Error: '{}' is not in the database.", path);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error: {}", e);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
|
||||
if !record.integrated {
|
||||
println!("{} is not integrated.", record.app_name.as_deref().unwrap_or(&record.filename));
|
||||
return ExitCode::SUCCESS;
|
||||
}
|
||||
|
||||
match integrator::remove_integration(&record) {
|
||||
Ok(()) => {
|
||||
db.set_integrated(record.id, false, None).ok();
|
||||
println!(
|
||||
"Removed integration for {}",
|
||||
record.app_name.as_deref().unwrap_or(&record.filename),
|
||||
);
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error: {}", e);
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn cmd_clean() -> ExitCode {
|
||||
let orphans = orphan::detect_orphans();
|
||||
|
||||
if orphans.is_empty() {
|
||||
println!("No orphaned desktop entries found.");
|
||||
return ExitCode::SUCCESS;
|
||||
}
|
||||
|
||||
println!("Found {} orphaned entries:", orphans.len());
|
||||
for entry in &orphans {
|
||||
println!(
|
||||
" {} (was: {})",
|
||||
entry.app_name.as_deref().unwrap_or("Unknown"),
|
||||
entry.original_appimage_path,
|
||||
);
|
||||
}
|
||||
|
||||
match orphan::clean_all_orphans() {
|
||||
Ok(summary) => {
|
||||
println!(
|
||||
"Cleaned {} desktop entries, {} icons",
|
||||
summary.entries_removed,
|
||||
summary.icons_removed,
|
||||
);
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error during cleanup: {}", e);
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn cmd_inspect(path: &str) -> ExitCode {
|
||||
let file_path = std::path::Path::new(path);
|
||||
if !file_path.exists() {
|
||||
eprintln!("Error: file not found: {}", path);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
|
||||
// Detect AppImage type
|
||||
let discovered = discovery::scan_directories(&[path.to_string()]);
|
||||
if discovered.is_empty() {
|
||||
// Try scanning the parent directory and finding by path
|
||||
let parent = file_path.parent().unwrap_or(std::path::Path::new("."));
|
||||
let all = discovery::scan_directories(&[parent.to_string_lossy().to_string()]);
|
||||
let found = all.iter().find(|d| d.path == file_path);
|
||||
if let Some(d) = found {
|
||||
return do_inspect(file_path, &d.appimage_type);
|
||||
}
|
||||
eprintln!("Error: '{}' does not appear to be an AppImage", path);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
|
||||
let d = &discovered[0];
|
||||
do_inspect(file_path, &d.appimage_type)
|
||||
}
|
||||
|
||||
fn cmd_status() -> ExitCode {
|
||||
println!("System Status");
|
||||
println!("=============");
|
||||
println!();
|
||||
|
||||
// Display server
|
||||
let session = wayland::detect_session_type();
|
||||
println!(" Display server: {}", session.label());
|
||||
|
||||
// Desktop environment
|
||||
let de = wayland::detect_desktop_environment();
|
||||
println!(" Desktop: {}", de);
|
||||
|
||||
// XWayland
|
||||
println!(
|
||||
" XWayland: {}",
|
||||
if wayland::has_xwayland() {
|
||||
"running"
|
||||
} else {
|
||||
"not detected"
|
||||
}
|
||||
);
|
||||
println!();
|
||||
|
||||
// FUSE
|
||||
let fuse_info = fuse::detect_system_fuse();
|
||||
println!(" FUSE status: {}", fuse_info.status.label());
|
||||
println!(" libfuse2: {}", if fuse_info.has_libfuse2 { "yes" } else { "no" });
|
||||
println!(" libfuse3: {}", if fuse_info.has_libfuse3 { "yes" } else { "no" });
|
||||
println!(" fusermount: {}", fuse_info.fusermount_path.as_deref().unwrap_or("not found"));
|
||||
println!(" /dev/fuse: {}", if fuse_info.has_dev_fuse { "present" } else { "missing" });
|
||||
|
||||
if let Some(ref hint) = fuse_info.install_hint {
|
||||
println!();
|
||||
println!(" Fix: {}", hint);
|
||||
}
|
||||
|
||||
// AppImageLauncher
|
||||
if let Some(version) = fuse::detect_appimagelauncher() {
|
||||
println!();
|
||||
println!(" WARNING: AppImageLauncher v{} detected (may conflict)", version);
|
||||
}
|
||||
|
||||
println!();
|
||||
|
||||
// AppImageUpdate tool
|
||||
println!(
|
||||
" AppImageUpdate: {}",
|
||||
if updater::has_appimage_update_tool() {
|
||||
"available (delta updates enabled)"
|
||||
} else {
|
||||
"not found (full downloads only)"
|
||||
}
|
||||
);
|
||||
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
|
||||
fn cmd_check_updates(db: &Database) -> ExitCode {
|
||||
let records = match db.get_all_appimages() {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
eprintln!("Error: {}", e);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
|
||||
if records.is_empty() {
|
||||
println!("No AppImages found. Run 'driftwood scan' first.");
|
||||
return ExitCode::SUCCESS;
|
||||
}
|
||||
|
||||
println!("Checking {} AppImages for updates...", records.len());
|
||||
println!();
|
||||
|
||||
let mut updates_found = 0;
|
||||
|
||||
for record in &records {
|
||||
let name = record.app_name.as_deref().unwrap_or(&record.filename);
|
||||
let appimage_path = std::path::Path::new(&record.path);
|
||||
|
||||
if !appimage_path.exists() {
|
||||
continue;
|
||||
}
|
||||
|
||||
print!(" {} ... ", name);
|
||||
|
||||
let (type_label, raw_info, check_result) = updater::check_appimage_for_update(
|
||||
appimage_path,
|
||||
record.app_version.as_deref(),
|
||||
);
|
||||
|
||||
// Store update info
|
||||
if raw_info.is_some() || type_label.is_some() {
|
||||
db.update_update_info(record.id, raw_info.as_deref(), type_label.as_deref()).ok();
|
||||
}
|
||||
|
||||
match check_result {
|
||||
Some(result) if result.update_available => {
|
||||
let latest = result.latest_version.as_deref().unwrap_or("unknown");
|
||||
println!(
|
||||
"UPDATE AVAILABLE ({} -> {})",
|
||||
record.app_version.as_deref().unwrap_or("?"),
|
||||
latest,
|
||||
);
|
||||
db.set_update_available(record.id, Some(latest), result.download_url.as_deref()).ok();
|
||||
updates_found += 1;
|
||||
}
|
||||
Some(_) => {
|
||||
println!("up to date");
|
||||
db.clear_update_available(record.id).ok();
|
||||
}
|
||||
None => {
|
||||
if raw_info.is_none() {
|
||||
println!("no update info");
|
||||
} else {
|
||||
println!("check failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!();
|
||||
if updates_found == 0 {
|
||||
println!("All AppImages are up to date.");
|
||||
} else {
|
||||
println!("{} update{} available.", updates_found, if updates_found == 1 { "" } else { "s" });
|
||||
}
|
||||
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
|
||||
fn cmd_duplicates(db: &Database) -> ExitCode {
|
||||
let groups = duplicates::detect_duplicates(db);
|
||||
|
||||
if groups.is_empty() {
|
||||
println!("No duplicate or multi-version AppImages found.");
|
||||
return ExitCode::SUCCESS;
|
||||
}
|
||||
|
||||
let summary = duplicates::summarize_duplicates(&groups);
|
||||
println!(
|
||||
"Found {} duplicate groups ({} exact, {} multi-version)",
|
||||
summary.total_groups,
|
||||
summary.exact_duplicates,
|
||||
summary.multi_version,
|
||||
);
|
||||
println!(
|
||||
"Potential savings: {}",
|
||||
humansize::format_size(summary.total_potential_savings, humansize::BINARY),
|
||||
);
|
||||
println!();
|
||||
|
||||
for group in &groups {
|
||||
println!(" {} ({})", group.app_name, group.match_reason.label());
|
||||
for member in &group.members {
|
||||
let r = &member.record;
|
||||
let version = r.app_version.as_deref().unwrap_or("?");
|
||||
let size = humansize::format_size(r.size_bytes as u64, humansize::BINARY);
|
||||
let rec = member.recommendation.label();
|
||||
println!(" {} v{} ({}) - {}", r.path, version, size, rec);
|
||||
}
|
||||
if group.potential_savings > 0 {
|
||||
println!(
|
||||
" Savings: {}",
|
||||
humansize::format_size(group.potential_savings, humansize::BINARY),
|
||||
);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
|
||||
fn cmd_launch(db: &Database, path: &str) -> ExitCode {
|
||||
let file_path = std::path::Path::new(path);
|
||||
if !file_path.exists() {
|
||||
eprintln!("Error: file not found: {}", path);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
|
||||
// Try to find in database for tracking
|
||||
let record = db.get_appimage_by_path(path).ok().flatten();
|
||||
|
||||
if let Some(ref record) = record {
|
||||
match launcher::launch_appimage(db, record.id, file_path, "cli", &[], &[]) {
|
||||
launcher::LaunchResult::Started { method, .. } => {
|
||||
println!(
|
||||
"Launched {} ({})",
|
||||
record.app_name.as_deref().unwrap_or(&record.filename),
|
||||
method.as_str(),
|
||||
);
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
launcher::LaunchResult::Failed(msg) => {
|
||||
eprintln!("Error: {}", msg);
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Not in database - launch without tracking
|
||||
match launcher::launch_appimage_simple(file_path, &[]) {
|
||||
launcher::LaunchResult::Started { method, .. } => {
|
||||
println!("Launched {} ({})", path, method.as_str());
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
launcher::LaunchResult::Failed(msg) => {
|
||||
eprintln!("Error: {}", msg);
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn do_inspect(path: &std::path::Path, appimage_type: &discovery::AppImageType) -> ExitCode {
|
||||
println!("Inspecting: {}", path.display());
|
||||
println!("Type: {:?}", appimage_type);
|
||||
|
||||
match inspector::inspect_appimage(path, appimage_type) {
|
||||
Ok(metadata) => {
|
||||
println!("Name: {}", metadata.app_name.as_deref().unwrap_or("(unknown)"));
|
||||
println!("Version: {}", metadata.app_version.as_deref().unwrap_or("(unknown)"));
|
||||
if let Some(ref desc) = metadata.description {
|
||||
println!("Description: {}", desc);
|
||||
}
|
||||
if let Some(ref arch) = metadata.architecture {
|
||||
println!("Architecture: {}", arch);
|
||||
}
|
||||
if !metadata.categories.is_empty() {
|
||||
println!("Categories: {}", metadata.categories.join(", "));
|
||||
}
|
||||
if let Some(ref icon) = metadata.cached_icon_path {
|
||||
println!("Icon: {}", icon.display());
|
||||
}
|
||||
if !metadata.desktop_entry_content.is_empty() {
|
||||
println!();
|
||||
println!("--- Desktop Entry ---");
|
||||
println!("{}", metadata.desktop_entry_content);
|
||||
}
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Inspection failed: {}", e);
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user