Fix second audit findings and restore crash detection dialog

Address 29 issues found in comprehensive API/spec audit:
- Fix .desktop Exec key path escaping per Desktop Entry spec
- Fix update dialog double-dispatch with connect_response
- Fix version comparison total ordering with lexicographic fallback
- Use RETURNING id for reliable upsert in database
- Replace tilde-based path fallbacks with proper XDG helpers
- Fix backup create/restore path asymmetry for non-home paths
- HTML-escape severity class in security reports
- Use AppStream <custom> element instead of <metadata>
- Fix has_appimage_update_tool to check .is_ok() not .success()
- Use ListBoxRow instead of ActionRow::set_child in ExpanderRow
- Add ELF magic validation to architecture detection
- Add timeout to extract_update_info_runtime
- Skip symlinks in dir_size calculation
- Use Condvar instead of busy-wait in analysis thread pool
- Restore crash detection to single blocking call architecture
This commit is contained in:
lashman
2026-02-27 22:48:43 +02:00
parent e9343da249
commit 830c3cad9d
21 changed files with 228 additions and 181 deletions

View File

@@ -24,7 +24,7 @@ pub fn build_detail_page(record: &AppImageRecord, db: &Rc<Database>) -> adw::Nav
// Toast overlay for copy actions
let toast_overlay = adw::ToastOverlay::new();
// ViewStack for tabbed content with crossfade transitions.
// ViewStack for tabbed content (transitions disabled for instant switching).
// vhomogeneous=false so the stack sizes to the visible child only,
// preventing shorter tabs from having excess scrollable empty space.
let view_stack = adw::ViewStack::new();
@@ -124,10 +124,10 @@ pub fn build_detail_page(record: &AppImageRecord, db: &Rc<Database>) -> adw::Nav
btn_ref.set_sensitive(true);
match result {
Ok(launcher::LaunchResult::Started { child, method }) => {
let pid = child.id();
Ok(launcher::LaunchResult::Started { pid, method }) => {
log::info!("Launched: {} (PID: {}, method: {})", path, pid, method.as_str());
// App survived startup - do Wayland analysis after a delay
let db_wayland = db_launch.clone();
let path_clone = path.clone();
glib::spawn_future_local(async move {
@@ -488,12 +488,7 @@ fn build_overview_tab(record: &AppImageRecord, db: &Rc<Database>) -> gtk::Box {
spinner_ref.set_visible(false);
if let Ok(Some(data)) = result {
let gbytes = glib::Bytes::from(&data);
let stream = gio::MemoryInputStream::from_bytes(&gbytes);
if let Ok(pixbuf) = gtk::gdk_pixbuf::Pixbuf::from_stream(
&stream,
None::<&gio::Cancellable>,
) {
let texture = gtk::gdk::Texture::for_pixbuf(&pixbuf);
if let Ok(texture) = gtk::gdk::Texture::from_bytes(&gbytes) {
picture_ref.set_paintable(Some(&texture));
if let Some(slot) = textures_load.borrow_mut().get_mut(idx) {
*slot = Some(texture);
@@ -708,8 +703,11 @@ fn build_overview_tab(record: &AppImageRecord, db: &Rc<Database>) -> gtk::Box {
.margin_start(12)
.margin_end(12)
.build();
let label_row = adw::ActionRow::new();
label_row.set_child(Some(&label));
let label_row = gtk::ListBoxRow::builder()
.activatable(false)
.selectable(false)
.child(&label)
.build();
row.add_row(&label_row);
release_group.add(&row);
@@ -2080,12 +2078,7 @@ fn fetch_favicon_async(url: &str, image: &gtk::Image) {
if let Ok(Some(data)) = result {
let gbytes = glib::Bytes::from(&data);
let stream = gio::MemoryInputStream::from_bytes(&gbytes);
if let Ok(pixbuf) = gtk::gdk_pixbuf::Pixbuf::from_stream(
&stream,
None::<&gio::Cancellable>,
) {
let texture = gtk::gdk::Texture::for_pixbuf(&pixbuf);
if let Ok(texture) = gtk::gdk::Texture::from_bytes(&gbytes) {
image_ref.set_paintable(Some(&texture));
}
}

View File

@@ -90,17 +90,15 @@ pub fn show_update_dialog(
let db_update = db_ref.clone();
let record_path = record_clone.path.clone();
let new_version = check_result.latest_version.clone();
dialog_ref.connect_response(None, move |dlg, response| {
if response == "update" {
start_update(
dlg,
&record_path,
&download_url,
record_id,
new_version.as_deref(),
&db_update,
);
}
dialog_ref.connect_response(Some("update"), move |dlg, _response| {
start_update(
dlg,
&record_path,
&download_url,
record_id,
new_version.as_deref(),
&db_update,
);
});
}
} else {
@@ -239,11 +237,9 @@ fn handle_old_version_cleanup(dialog: &adw::AlertDialog, old_path: PathBuf) {
dialog.set_response_appearance("remove-old", adw::ResponseAppearance::Destructive);
let path = old_path.clone();
dialog.connect_response(None, move |_dlg, response| {
if response == "remove-old" {
if path.exists() {
std::fs::remove_file(&path).ok();
}
dialog.connect_response(Some("remove-old"), move |_dlg, _response| {
if path.exists() {
std::fs::remove_file(&path).ok();
}
});
}

View File

@@ -182,12 +182,10 @@ pub fn copy_button(text_to_copy: &str, toast_overlay: Option<&adw::ToastOverlay>
let text = text_to_copy.to_string();
let toast = toast_overlay.cloned();
btn.connect_clicked(move |button| {
if let Some(display) = button.display().into() {
let clipboard = gtk::gdk::Display::clipboard(&display);
clipboard.set_text(&text);
if let Some(ref overlay) = toast {
overlay.add_toast(adw::Toast::new("Copied to clipboard"));
}
let clipboard = button.display().clipboard();
clipboard.set_text(&text);
if let Some(ref overlay) = toast {
overlay.add_toast(adw::Toast::new("Copied to clipboard"));
}
});
btn