Add security, i18n, analysis, backup, notifications

This commit is contained in:
2026-02-27 17:16:41 +02:00
parent d6a6fb9eca
commit a1c7f72d1d
46 changed files with 7908 additions and 481 deletions

View File

@@ -5,6 +5,7 @@ use std::rc::Rc;
use crate::config::APP_ID;
use crate::core::database::{AppImageRecord, Database};
use crate::core::updater;
use crate::i18n::{i18n, i18n_f};
/// Show an update check + apply dialog for a single AppImage.
pub fn show_update_dialog(
@@ -13,13 +14,13 @@ pub fn show_update_dialog(
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)
.heading(&i18n("Check for Updates"))
.body(&i18n_f(
"Checking for updates for {name}...",
&[("{name}", record.app_name.as_deref().unwrap_or(&record.filename))],
))
.build();
dialog.add_response("close", "Close");
dialog.add_response("close", &i18n("Close"));
dialog.set_default_response(Some("close"));
dialog.set_close_response("close");
@@ -58,15 +59,17 @@ pub fn show_update_dialog(
db_ref.set_update_available(record_id, Some(version), check_result.download_url.as_deref()).ok();
}
let mut body = format!(
"{} -> {}",
record_clone.app_version.as_deref().unwrap_or("unknown"),
check_result.latest_version.as_deref().unwrap_or("unknown"),
let mut body = i18n_f(
"{current} -> {latest}",
&[
("{current}", record_clone.app_version.as_deref().unwrap_or("unknown")),
("{latest}", check_result.latest_version.as_deref().unwrap_or("unknown")),
],
);
if let Some(size) = check_result.file_size {
body.push_str(&format!(" ({})", humansize::format_size(size, humansize::BINARY)));
}
body.push_str("\n\nA new version is available.");
body.push_str(&format!("\n\n{}", i18n("A new version is available.")));
if let Some(ref notes) = check_result.release_notes {
if !notes.is_empty() {
// Truncate long release notes for the dialog
@@ -75,12 +78,12 @@ pub fn show_update_dialog(
body.push_str(&format!("\n\n{}{}", truncated, suffix));
}
}
dialog_ref.set_heading(Some("Update Available"));
dialog_ref.set_heading(Some(&i18n("Update Available")));
dialog_ref.set_body(&body);
// Add "Update Now" button if we have a download URL
if let Some(download_url) = check_result.download_url {
dialog_ref.add_response("update", "Update Now");
dialog_ref.add_response("update", &i18n("Update Now"));
dialog_ref.set_response_appearance("update", adw::ResponseAppearance::Suggested);
dialog_ref.set_default_response(Some("update"));
@@ -101,11 +104,13 @@ pub fn show_update_dialog(
});
}
} 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"),
dialog_ref.set_heading(Some(&i18n("Up to Date")));
dialog_ref.set_body(&i18n_f(
"{name} is already at the latest version ({version}).",
&[
("{name}", record_clone.app_name.as_deref().unwrap_or(&record_clone.filename)),
("{version}", record_clone.app_version.as_deref().unwrap_or("unknown")),
],
));
db_ref.clear_update_available(record_id).ok();
}
@@ -113,19 +118,18 @@ pub fn show_update_dialog(
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.");
dialog_ref.set_heading(Some(&i18n("Check Failed")));
dialog_ref.set_body(&i18n("Could not reach the update server. Try again later."));
} else {
dialog_ref.set_heading(Some("No Update Info"));
dialog_ref.set_heading(Some(&i18n("No Update Info")));
dialog_ref.set_body(
"This app does not support automatic updates. \
Check the developer's website for newer versions.",
&i18n("This app does not support automatic updates. Check the developer's website for newer versions."),
);
}
}
Err(_) => {
dialog_ref.set_heading(Some("Error"));
dialog_ref.set_body("An error occurred while checking for updates.");
dialog_ref.set_heading(Some(&i18n("Error")));
dialog_ref.set_body(&i18n("An error occurred while checking for updates."));
}
}
});
@@ -142,8 +146,8 @@ fn start_update(
new_version: Option<&str>,
db: &Rc<Database>,
) {
dialog.set_heading(Some("Updating..."));
dialog.set_body("Downloading update. This may take a moment.");
dialog.set_heading(Some(&i18n("Updating...")));
dialog.set_body(&i18n("Downloading update. This may take a moment."));
dialog.set_response_enabled("update", false);
let path = appimage_path.to_string();
@@ -171,12 +175,14 @@ fn start_update(
}
db_ref.clear_update_available(record_id).ok();
let success_body = format!(
"Updated to {}\nPath: {}",
applied.new_version.as_deref().unwrap_or("latest"),
applied.new_path.display(),
let success_body = i18n_f(
"Updated to {version}\nPath: {path}",
&[
("{version}", applied.new_version.as_deref().unwrap_or("latest")),
("{path}", &applied.new_path.display().to_string()),
],
);
dialog.set_heading(Some("Update Complete"));
dialog.set_heading(Some(&i18n("Update Complete")));
dialog.set_body(&success_body);
dialog.set_response_enabled("update", false);
@@ -186,12 +192,15 @@ fn start_update(
}
}
Ok(Err(e)) => {
dialog.set_heading(Some("Update Failed"));
dialog.set_body(&format!("The update could not be applied: {}", e));
dialog.set_heading(Some(&i18n("Update Failed")));
dialog.set_body(&i18n_f(
"The update could not be applied: {error}",
&[("{error}", &e.to_string())],
));
}
Err(_) => {
dialog.set_heading(Some("Update Failed"));
dialog.set_body("An unexpected error occurred during the update.");
dialog.set_heading(Some(&i18n("Update Failed")));
dialog.set_body(&i18n("An unexpected error occurred during the update."));
}
}
});
@@ -215,18 +224,18 @@ fn handle_old_version_cleanup(dialog: &adw::AlertDialog, old_path: PathBuf) {
}
"never" => {
// Keep the backup, just inform
dialog.set_body(&format!(
"Update complete. The old version is saved at:\n{}",
old_path.display()
dialog.set_body(&i18n_f(
"Update complete. The old version is saved at:\n{path}",
&[("{path}", &old_path.display().to_string())],
));
}
_ => {
// "ask" - prompt the user
dialog.set_body(&format!(
"Update complete.\n\nRemove the old version?\n{}",
old_path.display()
dialog.set_body(&i18n_f(
"Update complete.\n\nRemove the old version?\n{path}",
&[("{path}", &old_path.display().to_string())],
));
dialog.add_response("remove-old", "Remove Old Version");
dialog.add_response("remove-old", &i18n("Remove Old Version"));
dialog.set_response_appearance("remove-old", adw::ResponseAppearance::Destructive);
let path = old_path.clone();