pub(super) const CURRENT_VERSION: &str = env!("CARGO_PKG_VERSION");
pub(super) const CRATE_NAME: &str = env!("CARGO_PKG_NAME");
pub(super) const CRATES_IO_API: &str = "https://crates.io/api/v1/crates";
#[derive(serde::Deserialize)]
pub(super) struct CratesIoResponse {
#[serde(rename = "crate")]
pub(super) info: CrateInfo,
}
#[derive(serde::Deserialize)]
pub(super) struct CrateInfo {
pub(super) newest_version: String,
}
pub(super) fn api_url() -> String {
format!("{}/{}", CRATES_IO_API, CRATE_NAME)
}
pub(super) fn fetch_from_crates_io(url: &str) -> Option<CratesIoResponse> {
ureq::get(url)
.header("User-Agent", &format!("{}/{}", CRATE_NAME, CURRENT_VERSION))
.call()
.ok()?
.body_mut()
.read_json()
.ok()
}
pub(super) fn is_newer_version(version: &str) -> Option<bool> {
let current = semver::Version::parse(CURRENT_VERSION).ok()?;
let latest = semver::Version::parse(version).ok()?;
Some(latest > current)
}
pub(super) const CHECK_INTERVAL_SECS: u64 = 60 * 60 * 24;
pub(super) fn should_check_for_update() -> bool {
let Some(path) = last_checked_path() else {
return true;
};
let Ok(contents) = std::fs::read_to_string(&path) else {
return true;
};
let Ok(timestamp) = contents.trim().parse::<u64>() else {
return true;
};
let Ok(now) = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) else {
return true;
};
now.as_secs().saturating_sub(timestamp) >= CHECK_INTERVAL_SECS
}
pub(super) fn record_last_checked() {
let Some(path) = last_checked_path() else {
return;
};
let Ok(now) = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) else {
return;
};
if let Some(parent) = path.parent() {
let _ = std::fs::create_dir_all(parent);
}
let _ = std::fs::write(&path, now.as_secs().to_string());
}
fn last_checked_path() -> Option<std::path::PathBuf> {
directories::ProjectDirs::from("", "", "hostcraft")
.map(|dirs| dirs.cache_dir().join("last_update_check"))
}