diff --git a/data/app.driftwood.Driftwood.gschema.xml b/data/app.driftwood.Driftwood.gschema.xml index 4e35b55..9872109 100644 --- a/data/app.driftwood.Driftwood.gschema.xml +++ b/data/app.driftwood.Driftwood.gschema.xml @@ -61,6 +61,17 @@ Auto check updates Automatically check for AppImage updates periodically. + + + 24 + Update check interval + Hours between automatic update checks. + + + '' + Last update check timestamp + ISO timestamp of the last automatic update check. + false Auto integrate diff --git a/src/ui/dashboard.rs b/src/ui/dashboard.rs index 4fd0fbc..4545139 100644 --- a/src/ui/dashboard.rs +++ b/src/ui/dashboard.rs @@ -1,6 +1,8 @@ use adw::prelude::*; +use gtk::gio; use std::rc::Rc; +use crate::config::APP_ID; use crate::core::database::Database; use crate::core::duplicates; use crate::core::fuse; @@ -267,6 +269,32 @@ fn build_updates_summary_group(db: &Rc) -> adw::PreferencesGroup { updates_row.add_suffix(&updates_arrow); group.add(&updates_row); + // Last checked timestamp + let settings = gio::Settings::new(APP_ID); + let last_check = settings.string("last-update-check"); + let last_label = if last_check.is_empty() { + "Never".to_string() + } else if let Ok(ts) = chrono::NaiveDateTime::parse_from_str(last_check.as_str(), "%Y-%m-%d %H:%M:%S") { + let now = chrono::Utc::now().naive_utc(); + let elapsed = now.signed_duration_since(ts); + if elapsed.num_minutes() < 1 { + "Just now".to_string() + } else if elapsed.num_hours() < 1 { + format!("{}m ago", elapsed.num_minutes()) + } else if elapsed.num_hours() < 24 { + format!("{}h ago", elapsed.num_hours()) + } else { + format!("{}d ago", elapsed.num_days()) + } + } else { + "Unknown".to_string() + }; + let last_row = adw::ActionRow::builder() + .title("Last checked") + .subtitle(&last_label) + .build(); + group.add(&last_row); + group } diff --git a/src/ui/preferences.rs b/src/ui/preferences.rs index 300536d..64babc5 100644 --- a/src/ui/preferences.rs +++ b/src/ui/preferences.rs @@ -235,6 +235,25 @@ fn build_behavior_page(settings: &gio::Settings) -> adw::PreferencesPage { }); automation_group.add(&auto_update_row); + let interval_row = adw::SpinRow::builder() + .title(&i18n("Update check interval")) + .subtitle(&i18n("Hours between automatic update checks")) + .build(); + let interval_adj = gtk::Adjustment::new( + settings.int("update-check-interval-hours") as f64, + 1.0, + 168.0, + 1.0, + 6.0, + 0.0, + ); + interval_row.set_adjustment(Some(&interval_adj)); + let settings_interval = settings.clone(); + interval_row.connect_value_notify(move |row| { + settings_interval.set_int("update-check-interval-hours", row.value() as i32).ok(); + }); + automation_group.add(&interval_row); + let auto_integrate_row = adw::SwitchRow::builder() .title(&i18n("Auto-integrate new AppImages")) .subtitle(&i18n("Automatically add newly discovered AppImages to the desktop menu")) diff --git a/src/window.rs b/src/window.rs index 7aa5cb2..f39271e 100644 --- a/src/window.rs +++ b/src/window.rs @@ -961,6 +961,37 @@ impl DriftwoodWindow { }); } + // Scheduled background update check + let settings_upd = self.settings().clone(); + if settings_upd.boolean("auto-check-updates") { + let last_check = settings_upd.string("last-update-check").to_string(); + let interval_hours = settings_upd.int("update-check-interval-hours") as i64; + let should_check = if last_check.is_empty() { + true + } else if let Ok(last) = chrono::NaiveDateTime::parse_from_str(&last_check, "%Y-%m-%d %H:%M:%S") { + let now = chrono::Utc::now().naive_utc(); + let elapsed = now.signed_duration_since(last); + elapsed.num_hours() >= interval_hours + } else { + true + }; + if should_check { + let settings_save = settings_upd.clone(); + glib::spawn_future_local(async move { + let result = gio::spawn_blocking(move || { + let bg_db = Database::open().expect("DB open failed"); + update_dialog::batch_check_updates(&bg_db) + }) + .await; + if let Ok(count) = result { + log::info!("Background update check: {} updates available", count); + } + let now = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(); + settings_save.set_string("last-update-check", &now).ok(); + }); + } + } + // Check for orphaned desktop entries in the background let toast_overlay = self.imp().toast_overlay.get().unwrap().clone(); glib::spawn_future_local(async move {