//! Version check against Gitea releases API. use serde::Serialize; #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct UpdateInfo { pub current_version: String, pub latest_version: String, pub update_available: bool, pub download_url: String, pub release_notes: String, } /// Check Gitea for the latest release and compare with current version. #[tauri::command] pub async fn check_for_updates(app_handle: tauri::AppHandle) -> Result { // Read version from tauri.conf.json (patched by CI from git tag) // rather than CARGO_PKG_VERSION which is always 0.1.0 let current = app_handle.config().version.clone().unwrap_or_else(|| "0.0.0".to_string()); let client = reqwest::Client::builder() .timeout(std::time::Duration::from_secs(10)) .build() .map_err(|e| format!("HTTP client error: {}", e))?; let resp = client .get("https://git.command.vigilcyber.com/api/v1/repos/vstockwell/wraith/releases?limit=1") .header("Accept", "application/json") .send() .await .map_err(|e| format!("Failed to check for updates: {}", e))?; let releases: Vec = resp.json().await .map_err(|e| format!("Failed to parse releases: {}", e))?; let latest = releases.first() .ok_or_else(|| "No releases found".to_string())?; let tag = latest.get("tag_name") .and_then(|v| v.as_str()) .unwrap_or("v0.0.0") .trim_start_matches('v') .to_string(); let notes = latest.get("body") .and_then(|v| v.as_str()) .unwrap_or("") .to_string(); let html_url = latest.get("html_url") .and_then(|v| v.as_str()) .unwrap_or("https://git.command.vigilcyber.com/vstockwell/wraith/releases") .to_string(); let update_available = version_is_newer(&tag, ¤t); Ok(UpdateInfo { current_version: current, latest_version: tag, update_available, download_url: html_url, release_notes: notes, }) } /// Compare semver strings. Returns true if `latest` is newer than `current`. fn version_is_newer(latest: &str, current: &str) -> bool { let parse = |v: &str| -> Vec { v.split('.').filter_map(|s| s.parse().ok()).collect() }; let l = parse(latest); let c = parse(current); for i in 0..3 { let lv = l.get(i).copied().unwrap_or(0); let cv = c.get(i).copied().unwrap_or(0); if lv > cv { return true; } if lv < cv { return false; } } false } #[cfg(test)] mod tests { use super::*; #[test] fn version_comparison() { assert!(version_is_newer("1.5.7", "1.5.6")); assert!(version_is_newer("1.6.0", "1.5.9")); assert!(version_is_newer("2.0.0", "1.9.9")); assert!(!version_is_newer("1.5.6", "1.5.6")); assert!(!version_is_newer("1.5.5", "1.5.6")); assert!(!version_is_newer("1.4.0", "1.5.0")); } }