use crate::error::{LoglyError, Result};
const CRATE_VERSION: &str = env!("CARGO_PKG_VERSION");
pub struct VersionChecker {
enabled: bool,
}
impl VersionChecker {
pub fn new(enabled: bool) -> Self {
Self { enabled }
}
pub fn check_for_updates(&self) -> Result<Option<String>> {
if !self.enabled {
return Ok(None);
}
#[cfg(feature = "auto-update-check")]
{
match self.fetch_latest_version() {
Ok(latest) => {
if self.is_newer(&latest, CRATE_VERSION) {
Ok(Some(format!(
"\n\u{2139}\u{fe0f} A new version of {} is available: {} (current: {})\n Update with: cargo update -p {}\n",
env!("CARGO_PKG_NAME"),
latest,
CRATE_VERSION,
env!("CARGO_PKG_NAME")
)))
} else {
Ok(None)
}
}
Err(_) => Ok(None),
}
}
#[cfg(not(feature = "auto-update-check"))]
Ok(None)
}
#[cfg(feature = "auto-update-check")]
fn fetch_latest_version(&self) -> Result<String> {
let url = format!("https://crates.io/api/v1/crates/{}", env!("CARGO_PKG_NAME"));
match ureq::get(&url).call() {
Ok(mut response) => {
let body = response.body_mut().read_to_string().map_err(|e| {
LoglyError::VersionCheckError(format!("Failed to read response: {}", e))
})?;
let json: serde_json::Value = serde_json::from_str(&body).map_err(|e| {
LoglyError::VersionCheckError(format!("Failed to parse JSON: {}", e))
})?;
json["crate"]["max_version"]
.as_str()
.map(|s| s.to_string())
.ok_or_else(|| {
LoglyError::VersionCheckError("Version not found in response".to_string())
})
}
Err(e) => Err(LoglyError::VersionCheckError(format!(
"HTTP request failed: {}",
e
))),
}
}
#[allow(dead_code)]
fn is_newer(&self, latest: &str, current: &str) -> bool {
let parse_version =
|v: &str| -> Vec<u32> { v.split('.').filter_map(|s| s.parse().ok()).collect() };
let latest_parts = parse_version(latest);
let current_parts = parse_version(current);
for (l, c) in latest_parts.iter().zip(current_parts.iter()) {
if l > c {
return true;
} else if l < c {
return false;
}
}
latest_parts.len() > current_parts.len()
}
pub fn current_version() -> &'static str {
CRATE_VERSION
}
pub fn enable(&mut self) {
self.enabled = true;
}
pub fn disable(&mut self) {
self.enabled = false;
}
}
impl Default for VersionChecker {
fn default() -> Self {
Self::new(false)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_version_comparison() {
let checker = VersionChecker::new(false);
assert!(checker.is_newer("0.2.0", "0.1.9"));
assert!(checker.is_newer("1.0.0", "0.9.9"));
assert!(!checker.is_newer("0.1.5", "0.1.9"));
assert!(!checker.is_newer("0.1.7", "0.1.7"));
}
}