use std::path::PathBuf;
const CRATE: &str = "cmd-usage";
const CURRENT: &str = env!("CARGO_PKG_VERSION");
const MAX_AGE_SECS: u64 = 86_400;
fn cache_path() -> PathBuf {
crate::paths::home().join(".cache/cmd-usage/last-check")
}
pub fn check_sync() -> Option<String> {
let now = cmduse_core::dates::now_secs();
if let Ok(text) = std::fs::read_to_string(cache_path()) {
if let Ok(v) = serde_json::from_str::<serde_json::Value>(&text) {
if let (Some(checked), Some(latest)) = (v["checkedAt"].as_u64(), v["latest"].as_str()) {
if now.saturating_sub(checked) < MAX_AGE_SECS {
return update_msg(latest);
}
}
}
}
if let Some(dir) = cache_path().parent() {
let _ = std::fs::create_dir_all(dir);
}
let url = format!("https://crates.io/api/v1/crates/{CRATE}");
let resp = ureq::get(&url)
.set("User-Agent", &format!("cmduse/{CURRENT}"))
.timeout(std::time::Duration::from_secs(5))
.call()
.ok()?;
let json = resp.into_json::<serde_json::Value>().ok()?;
let latest = json["crate"]["max_stable_version"]
.as_str()
.or_else(|| json["crate"]["max_version"].as_str())?;
let entry = serde_json::json!({ "checkedAt": now, "latest": latest });
let _ = std::fs::write(cache_path(), entry.to_string());
update_msg(latest)
}
fn update_msg(latest: &str) -> Option<String> {
if !newer(latest, CURRENT) {
return None;
}
Some(format!(
"cmduse: update available {CURRENT} → {latest} (cargo install cmd-usage / brew upgrade jeffreyjyz/tap/cmduse)"
))
}
fn newer(latest: &str, current: &str) -> bool {
let parse = |v: &str| -> Vec<u64> {
v.split(['-', '+'])
.next()
.unwrap_or(v)
.split('.')
.map(|p| p.trim().parse().unwrap_or(0))
.collect()
};
parse(latest) > parse(current)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn version_compare() {
assert!(newer("0.1.10", "0.1.9"));
assert!(newer("1.0.0", "0.99.99"));
assert!(newer("0.6.2", "0.6.1"));
assert!(!newer("0.1.9", "0.1.10"));
assert!(!newer("0.1.9", "0.1.9"));
assert!(!newer("0.6.6-beta.1", "0.6.6"));
assert!(!newer("0.6.5+build.2", "0.6.5"));
assert!(newer("0.6.6-beta.1", "0.6.5"));
}
#[test]
fn update_message_only_when_newer() {
assert!(update_msg(CURRENT).is_none());
assert!(update_msg("0.0.1").is_none());
let msg = update_msg("99.0.0").expect("newer version yields a message");
assert!(msg.contains("update available"), "{msg}");
assert!(msg.contains("99.0.0"), "{msg}");
}
}