Files
driftwood/src/ui/update_dialog.rs
lashman fa28955919 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.
2026-02-26 23:04:27 +02:00

148 lines
5.2 KiB
Rust

use adw::prelude::*;
use gtk::gio;
use std::rc::Rc;
use crate::core::database::{AppImageRecord, Database};
use crate::core::updater;
/// Show an update check + apply dialog for a single AppImage.
pub fn show_update_dialog(
parent: &impl IsA<gtk::Widget>,
record: &AppImageRecord,
db: &Rc<Database>,
) {
let dialog = adw::AlertDialog::builder()
.heading("Check for Updates")
.body(&format!(
"Checking for updates for {}...",
record.app_name.as_deref().unwrap_or(&record.filename)
))
.build();
dialog.add_response("close", "Close");
dialog.set_default_response(Some("close"));
dialog.set_close_response("close");
let record_clone = record.clone();
let db_ref = db.clone();
let dialog_ref = dialog.clone();
// Start the update check in the background
let record_id = record.id;
let path = record.path.clone();
let current_version = record.app_version.clone();
glib::spawn_future_local(async move {
let result = gio::spawn_blocking(move || {
let appimage_path = std::path::Path::new(&path);
updater::check_appimage_for_update(
appimage_path,
current_version.as_deref(),
)
})
.await;
match result {
Ok((type_label, raw_info, Some(check_result))) => {
// Store update info in DB
let now = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string();
db_ref
.update_update_info(
record_id,
raw_info.as_deref(),
type_label.as_deref(),
)
.ok();
if check_result.update_available {
if let Some(ref version) = check_result.latest_version {
db_ref.set_update_available(record_id, Some(version), check_result.download_url.as_deref()).ok();
}
let body = format!(
"{} -> {}\n\nA new version is available.",
record_clone.app_version.as_deref().unwrap_or("unknown"),
check_result.latest_version.as_deref().unwrap_or("unknown"),
);
dialog_ref.set_heading(Some("Update Available"));
dialog_ref.set_body(&body);
// Future: add "Update" response to trigger download
} else {
dialog_ref.set_heading(Some("Up to Date"));
dialog_ref.set_body(&format!(
"{} is already at the latest version ({}).",
record_clone.app_name.as_deref().unwrap_or(&record_clone.filename),
record_clone.app_version.as_deref().unwrap_or("unknown"),
));
db_ref.clear_update_available(record_id).ok();
}
}
Ok((type_label, raw_info, None)) => {
if raw_info.is_some() {
db_ref.update_update_info(record_id, raw_info.as_deref(), type_label.as_deref()).ok();
dialog_ref.set_heading(Some("Check Failed"));
dialog_ref.set_body("Could not reach the update server. Try again later.");
} else {
dialog_ref.set_heading(Some("No Update Info"));
dialog_ref.set_body(
"This AppImage does not contain update information. \
Updates must be downloaded manually.",
);
}
}
Err(_) => {
dialog_ref.set_heading(Some("Error"));
dialog_ref.set_body("An error occurred while checking for updates.");
}
}
});
dialog.present(Some(parent));
}
/// Batch check all AppImages for updates. Returns count of updates found.
pub fn batch_check_updates(db: &Database) -> u32 {
let records = match db.get_all_appimages() {
Ok(r) => r,
Err(e) => {
log::error!("Failed to get appimages for update check: {}", e);
return 0;
}
};
let mut updates_found = 0u32;
for record in &records {
let appimage_path = std::path::Path::new(&record.path);
if !appimage_path.exists() {
continue;
}
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();
}
if let Some(result) = check_result {
if result.update_available {
if let Some(ref version) = result.latest_version {
db.set_update_available(record.id, Some(version), result.download_url.as_deref()).ok();
updates_found += 1;
}
} else {
db.clear_update_available(record.id).ok();
}
}
}
updates_found
}