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 {