use std::process::Command;
use std::sync::mpsc::Sender;
use std::thread;
use std::time::Duration;
use crate::event::AppEvent;
const MANIFEST_URL: &str = "https://bohay.dev/latest.json";
const CURRENT: &str = env!("CARGO_PKG_VERSION");
fn manifest_url() -> String {
std::env::var("BOHAY_UPDATE_MANIFEST").unwrap_or_else(|_| MANIFEST_URL.to_string())
}
pub fn spawn_check(tx: Sender<AppEvent>) {
thread::spawn(move || {
thread::sleep(Duration::from_secs(5));
loop {
if let Some(latest) = fetch_latest() {
if is_newer(&latest, CURRENT) {
let _ = tx.send(AppEvent::UpdateAvailable(latest));
}
}
thread::sleep(Duration::from_secs(24 * 60 * 60));
}
});
}
fn fetch_latest() -> Option<String> {
parse_version(&http_get(&manifest_url())?)
}
fn parse_version(body: &str) -> Option<String> {
let v: serde_json::Value = serde_json::from_str(body).ok()?;
let s = v.get("version")?.as_str()?.trim();
Some(s.trim_start_matches('v').to_string())
}
pub fn is_newer(latest: &str, current: &str) -> bool {
semver(latest) > semver(current)
}
fn semver(s: &str) -> (u32, u32, u32) {
let s = s.trim().trim_start_matches('v');
let mut it = s.split('.').map(|part| {
part.split(|c: char| !c.is_ascii_digit())
.next()
.unwrap_or("")
.parse::<u32>()
.unwrap_or(0)
});
(
it.next().unwrap_or(0),
it.next().unwrap_or(0),
it.next().unwrap_or(0),
)
}
fn http_get(url: &str) -> Option<String> {
let curl = ["-fsSL", "--max-time", "15", "-H", "User-Agent: bohay", url];
if let Some(out) = try_cmd("curl", &curl) {
return Some(out);
}
let wget = [
"-q",
"-O",
"-",
"--timeout=15",
"--header=User-Agent: bohay",
url,
];
try_cmd("wget", &wget)
}
fn try_cmd(prog: &str, args: &[&str]) -> Option<String> {
let out = Command::new(prog).args(args).output().ok()?;
out.status
.success()
.then(|| String::from_utf8_lossy(&out.stdout).into_owned())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn newer_compares_semver_with_optional_v() {
assert!(is_newer("0.9.3", "0.9.2"));
assert!(is_newer("v0.10.0", "0.9.9"));
assert!(is_newer("1.0.0", "0.9.9"));
assert!(!is_newer("0.9.2", "0.9.2"), "same version is not newer");
assert!(!is_newer("0.9.1", "0.9.2"), "older is not newer");
assert!(is_newer("0.9.3-rc1", "0.9.2"));
}
#[test]
fn parses_the_manifest_version() {
assert_eq!(
parse_version(r#"{"version":"0.9.3","notes":"x"}"#).as_deref(),
Some("0.9.3")
);
assert_eq!(
parse_version(r#"{"version":"v1.2.0"}"#).as_deref(),
Some("1.2.0")
);
assert_eq!(parse_version("not json"), None);
assert_eq!(parse_version(r#"{"other":1}"#), None);
}
}