feat: add rate-limited update check with desktop notification
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -86,6 +86,10 @@ impl WindowHandler {
|
|||||||
data.ready_players.push(new_player);
|
data.ready_players.push(new_player);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
std::thread::spawn(|| {
|
||||||
|
ahfail_ui::update::check_for_update(ahfail_ui::VERSION);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|||||||
@@ -1 +1,87 @@
|
|||||||
pub fn check_for_update(_current_version: &str) {}
|
use std::path::PathBuf;
|
||||||
|
use std::process::Command;
|
||||||
|
use std::time::SystemTime;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
const GITEA_API: &str =
|
||||||
|
"https://gitea.weircon.dk/api/v1/repos/agw/gtk-ahfail/releases/latest";
|
||||||
|
const UPDATE_NOTIFY_MSG: &str =
|
||||||
|
"Update available — visit https://gitea.weircon.dk/agw/gtk-ahfail/releases";
|
||||||
|
const CHECK_INTERVAL_SECS: u64 = 86_400;
|
||||||
|
|
||||||
|
pub fn is_newer(latest: &str, current: &str) -> bool {
|
||||||
|
let parse = |s: &str| -> Option<(u32, u32, u32)> {
|
||||||
|
let s = s.trim_start_matches('v');
|
||||||
|
let p: Vec<&str> = s.splitn(3, '.').collect();
|
||||||
|
if p.len() != 3 { return None; }
|
||||||
|
Some((p[0].parse().ok()?, p[1].parse().ok()?, p[2].parse().ok()?))
|
||||||
|
};
|
||||||
|
match (parse(latest), parse(current)) {
|
||||||
|
(Some(l), Some(c)) => l > c,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cache_file() -> PathBuf {
|
||||||
|
let dir = std::env::var("HOME")
|
||||||
|
.map(|h| PathBuf::from(h).join(".cache/ahfail"))
|
||||||
|
.unwrap_or_else(|_| PathBuf::from("/tmp/ahfail-cache"));
|
||||||
|
let _ = fs::create_dir_all(&dir);
|
||||||
|
dir.join("last_update_check")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rate_limited() -> bool {
|
||||||
|
let path = cache_file();
|
||||||
|
if let Ok(meta) = fs::metadata(&path) {
|
||||||
|
if let Ok(modified) = meta.modified() {
|
||||||
|
let age = SystemTime::now()
|
||||||
|
.duration_since(modified)
|
||||||
|
.unwrap_or_default();
|
||||||
|
return age.as_secs() < CHECK_INTERVAL_SECS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn touch_cache() {
|
||||||
|
let _ = fs::write(cache_file(), b"");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_notification() {
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
let _ = Command::new("notify-send")
|
||||||
|
.args(["ahfail", UPDATE_NOTIFY_MSG])
|
||||||
|
.spawn();
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
let _ = Command::new("osascript")
|
||||||
|
.args(["-e", &format!(
|
||||||
|
"display notification \"{}\" with title \"ahfail\"",
|
||||||
|
UPDATE_NOTIFY_MSG
|
||||||
|
)])
|
||||||
|
.spawn();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run in a background thread. Checks Gitea for a newer release and sends a
|
||||||
|
/// desktop notification if one exists. Rate-limited to once per 24 hours.
|
||||||
|
/// Fails silently on any error.
|
||||||
|
pub fn check_for_update(current_version: &str) {
|
||||||
|
if rate_limited() { return; }
|
||||||
|
touch_cache();
|
||||||
|
|
||||||
|
let Ok(resp) = ureq::get(GITEA_API).call() else { return };
|
||||||
|
let Ok(body) = resp.into_string() else { return };
|
||||||
|
|
||||||
|
if let Some(tag) = extract_tag_name(&body) {
|
||||||
|
if is_newer(&tag, current_version) {
|
||||||
|
send_notification();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_tag_name(json: &str) -> Option<String> {
|
||||||
|
let key = "\"tag_name\":\"";
|
||||||
|
let start = json.find(key)? + key.len();
|
||||||
|
let end = json[start..].find('"')? + start;
|
||||||
|
Some(json[start..end].to_string())
|
||||||
|
}
|
||||||
|
|||||||
14
crates/ahfail-ui/tests/update_tests.rs
Normal file
14
crates/ahfail-ui/tests/update_tests.rs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
use ahfail_ui::update::is_newer;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn newer_version_detected() {
|
||||||
|
assert!(is_newer("v0.2.0", "v0.1.0"));
|
||||||
|
assert!(!is_newer("v0.1.0", "v0.1.0"));
|
||||||
|
assert!(!is_newer("v0.1.0", "v0.2.0"));
|
||||||
|
assert!(!is_newer("garbage", "v0.1.0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn strips_v_prefix() {
|
||||||
|
assert!(is_newer("0.2.0", "0.1.0"));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user