diff --git a/src/core/database.rs b/src/core/database.rs index b17b235..d7836cc 100644 --- a/src/core/database.rs +++ b/src/core/database.rs @@ -1744,6 +1744,24 @@ impl Database { Ok(self.conn.last_insert_rowid()) } + // --- Version rollback --- + + pub fn set_previous_version(&self, id: i64, path: Option<&str>) -> SqlResult<()> { + self.conn.execute( + "UPDATE appimages SET previous_version_path = ?2 WHERE id = ?1", + params![id, path], + )?; + Ok(()) + } + + pub fn get_previous_version(&self, id: i64) -> SqlResult> { + self.conn.query_row( + "SELECT previous_version_path FROM appimages WHERE id = ?1", + params![id], + |row| row.get(0), + ) + } + // --- System modification tracking --- pub fn register_modification( diff --git a/src/ui/detail_view.rs b/src/ui/detail_view.rs index ac06f7e..454d1c5 100644 --- a/src/ui/detail_view.rs +++ b/src/ui/detail_view.rs @@ -989,6 +989,56 @@ fn build_system_tab(record: &AppImageRecord, db: &Rc, toast_overlay: & } inner.append(&integration_group); + // Version Rollback group + if let Some(ref prev_path) = record.previous_version_path { + let prev = std::path::Path::new(prev_path); + if prev.exists() { + let rollback_group = adw::PreferencesGroup::builder() + .title("Version Rollback") + .description("A previous version is available from the last update.") + .build(); + let rollback_row = adw::ActionRow::builder() + .title("Previous version available") + .subtitle(prev_path.as_str()) + .build(); + let rollback_btn = gtk::Button::builder() + .label("Rollback") + .valign(gtk::Align::Center) + .css_classes(["destructive-action"]) + .build(); + rollback_row.add_suffix(&rollback_btn); + + let current_path = record.path.clone(); + let prev_path_owned = prev_path.clone(); + let record_id_rb = record.id; + let db_rb = db.clone(); + let toast_rb = toast_overlay.clone(); + rollback_btn.connect_clicked(move |btn| { + let current = std::path::Path::new(¤t_path); + let prev = std::path::Path::new(&prev_path_owned); + let temp_path = current.with_extension("AppImage.rollback-tmp"); + // Swap: current -> tmp, prev -> current, tmp -> prev + let result = std::fs::rename(current, &temp_path) + .and_then(|_| std::fs::rename(prev, current)) + .and_then(|_| std::fs::rename(&temp_path, prev)); + match result { + Ok(()) => { + db_rb.set_previous_version(record_id_rb, Some(&prev_path_owned)).ok(); + toast_rb.add_toast(adw::Toast::new("Rolled back to previous version")); + btn.set_sensitive(false); + } + Err(e) => { + log::error!("Rollback failed: {}", e); + toast_rb.add_toast(adw::Toast::new("Rollback failed")); + } + } + }); + + rollback_group.add(&rollback_row); + inner.append(&rollback_group); + } + } + // Runtime Compatibility group let compat_group = adw::PreferencesGroup::builder() .title("Compatibility") diff --git a/src/ui/update_dialog.rs b/src/ui/update_dialog.rs index 0506de0..a597e0c 100644 --- a/src/ui/update_dialog.rs +++ b/src/ui/update_dialog.rs @@ -184,6 +184,14 @@ fn start_update( dialog.set_body(&success_body); dialog.set_response_enabled("update", false); + // Save previous version path for rollback + if let Some(ref old_path) = applied.old_path_backup { + db_ref.set_previous_version( + record_id, + Some(&old_path.to_string_lossy()), + ).ok(); + } + // Handle old version cleanup if let Some(old_path) = applied.old_path_backup { handle_old_version_cleanup(&dialog, old_path);