use std::time::Duration;
use update_informer::{registry::GitHub, Check};
const REPO_OWNER: &str = "serkanyersen";
const REPO_NAME: &str = "dotstate";
#[derive(Debug, Clone)]
pub struct UpdateInfo {
pub current_version: String,
pub latest_version: String,
pub release_url: String,
}
impl UpdateInfo {
#[must_use]
pub fn install_script_url() -> &'static str {
"https://dotstate.serkan.dev/install.sh"
}
#[must_use]
pub fn releases_url() -> String {
format!("https://github.com/{REPO_OWNER}/{REPO_NAME}/releases")
}
}
#[must_use]
pub fn check_for_updates(_interval_hours: u64) -> Option<UpdateInfo> {
check_for_updates_now()
}
pub fn check_for_updates_now() -> Option<UpdateInfo> {
let current_version = env!("CARGO_PKG_VERSION");
let repo = format!("{REPO_OWNER}/{REPO_NAME}");
let informer = update_informer::new(GitHub, &repo, current_version).interval(Duration::ZERO);
match informer.check_version() {
Ok(Some(new_version)) => {
let version_str = new_version.to_string();
Some(UpdateInfo {
current_version: current_version.to_string(),
latest_version: version_str.clone(),
release_url: format!(
"https://github.com/{REPO_OWNER}/{REPO_NAME}/releases/tag/{version_str}"
),
})
}
Ok(None) => None,
Err(e) => {
let error_msg = e.to_string();
tracing::debug!(
"Update check failed - error: '{}', error kind: {:?}, source: {:?}",
error_msg,
e,
e.source()
);
None
}
}
}
pub fn check_for_updates_with_result() -> Result<Option<UpdateInfo>, String> {
let current_version = env!("CARGO_PKG_VERSION");
let repo = format!("{REPO_OWNER}/{REPO_NAME}");
let informer = update_informer::new(GitHub, &repo, current_version).interval(Duration::ZERO);
match informer.check_version() {
Ok(Some(new_version)) => {
let version_str = new_version.to_string();
Ok(Some(UpdateInfo {
current_version: current_version.to_string(),
latest_version: version_str.clone(),
release_url: format!(
"https://github.com/{REPO_OWNER}/{REPO_NAME}/releases/tag/{version_str}"
),
}))
}
Ok(None) => Ok(None),
Err(e) => {
let error_msg = format!("{e}");
let mut error_details = error_msg.clone();
if let Some(source) = e.source() {
let source_str = source.to_string();
error_details.push_str(": ");
error_details.push_str(&source_str);
if source_str.contains("timed out") || error_msg.contains("timeout") {
tracing::debug!(
"Update check timed out (GitHub API may be slow or unavailable)"
);
} else {
tracing::debug!("Update check failed: {}", error_details);
}
} else {
tracing::debug!("Update check failed: {}", error_details);
}
Err(error_details)
}
}
}
#[must_use]
pub fn current_version() -> &'static str {
env!("CARGO_PKG_VERSION")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_current_version() {
let version = current_version();
assert!(!version.is_empty());
assert!(version.contains('.'));
}
#[test]
fn test_current_version_matches_cargo() {
let version = current_version();
assert_eq!(version, env!("CARGO_PKG_VERSION"));
}
#[test]
fn test_install_script_url() {
let url = UpdateInfo::install_script_url();
assert!(url.starts_with("https://"));
assert!(url.contains("install.sh"));
assert!(url.contains("dotstate"));
}
#[test]
fn test_releases_url() {
let url = UpdateInfo::releases_url();
assert!(url.contains("github.com"));
assert!(url.contains("releases"));
assert!(url.contains(REPO_OWNER));
assert!(url.contains(REPO_NAME));
}
#[test]
fn test_update_info_creation() {
let info = UpdateInfo {
current_version: "1.0.0".to_string(),
latest_version: "2.0.0".to_string(),
release_url: "https://github.com/test/repo/releases/tag/v2.0.0".to_string(),
};
assert_eq!(info.current_version, "1.0.0");
assert_eq!(info.latest_version, "2.0.0");
assert!(info.release_url.contains("v2.0.0"));
}
#[test]
fn test_update_info_clone() {
let info = UpdateInfo {
current_version: "1.0.0".to_string(),
latest_version: "2.0.0".to_string(),
release_url: "https://example.com".to_string(),
};
let cloned = info.clone();
assert_eq!(info.current_version, cloned.current_version);
assert_eq!(info.latest_version, cloned.latest_version);
assert_eq!(info.release_url, cloned.release_url);
}
#[test]
fn test_repo_constants() {
assert_eq!(REPO_OWNER, "serkanyersen");
assert_eq!(REPO_NAME, "dotstate");
}
#[test]
fn test_releases_url_format() {
let url = UpdateInfo::releases_url();
let expected = format!("https://github.com/{REPO_OWNER}/{REPO_NAME}/releases");
assert_eq!(url, expected);
}
}