use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
use semver::Version;
use crate::config::Env;
use crate::util::cache::cache_dir;
pub fn parse_version(tag: &str) -> Option<Version> {
let clean = tag.strip_prefix('v').unwrap_or(tag);
Version::parse(clean).ok()
}
pub fn is_dev_version(v: &Version) -> bool {
!v.pre.is_empty()
}
pub struct BackgroundUpdateChecker {
result: Arc<Mutex<Option<String>>>,
handle: Option<thread::JoinHandle<()>>,
}
impl BackgroundUpdateChecker {
pub fn new() -> Self {
Self {
result: Arc::new(Mutex::new(None)),
handle: None,
}
}
pub fn start(&mut self) {
if !should_check() {
return;
}
let result = Arc::clone(&self.result);
self.handle = Some(thread::spawn(move || {
if let Some(msg) = check_for_update() {
*result.lock().unwrap() = Some(msg);
}
let _ = update_cache_timestamp();
}));
}
pub fn get_result(&self, timeout: Duration) -> Option<String> {
if let Some(ref handle) = self.handle {
let start = std::time::Instant::now();
while start.elapsed() < timeout {
if handle.is_finished() {
return self.result.lock().unwrap().clone();
}
thread::sleep(Duration::from_millis(100));
}
}
self.result.lock().unwrap().clone()
}
}
fn cache_file() -> std::path::PathBuf {
cache_dir("updates").join("last_check")
}
fn should_check() -> bool {
let path = cache_file();
if !path.exists() {
return true;
}
match std::fs::metadata(&path) {
Ok(meta) => {
let age = meta
.modified()
.ok()
.and_then(|t| t.elapsed().ok())
.unwrap_or(Duration::MAX);
age > Duration::from_secs(24 * 3600)
}
Err(_) => true,
}
}
fn update_cache_timestamp() -> std::io::Result<()> {
let path = cache_file();
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(&path, Env::global().version.as_bytes())
}
fn check_for_update() -> Option<String> {
let env = Env::global();
let current = parse_version(&env.version)?;
let url = format!(
"{}/repos/HexRaysSA/ida-hcli/releases/latest",
env.github_api_url
);
let client = reqwest::blocking::Client::builder()
.user_agent(format!("hcli/{}", env.version))
.timeout(Duration::from_secs(5))
.build()
.ok()?;
let resp: serde_json::Value = client.get(&url).send().ok()?.json().ok()?;
let tag = resp.get("tag_name")?.as_str()?;
let latest = parse_version(tag)?;
if latest > current && !is_dev_version(&latest) {
Some(format!(
"\nUpdate available: {} -> {}. Run `hy update` to install.",
current, latest
))
} else {
None
}
}