use std::time::Duration;
use crate::config::{AutoUpdateMode, GlobalConfig};
use crate::error::{Error, Result};
const OWNER: &str = "yukimemi";
const BIN: &str = env!("CARGO_PKG_NAME");
const VERSION: &str = env!("CARGO_PKG_VERSION");
const FINALIZE_TIMEOUT: Duration = Duration::from_secs(5);
fn kaishin_opts() -> kaishin::KaishinOptions {
kaishin::KaishinOptions::new(OWNER, BIN, BIN, VERSION)
}
fn state_path() -> Option<std::path::PathBuf> {
crate::paths::template_cache_dir()
.ok()
.and_then(|c| c.parent().map(|p| p.to_path_buf()))
.map(|cache| cache.join("last_update_check.json").into_std_path_buf())
}
fn env_value_disables(value: Option<&str>) -> bool {
match value {
Some(v) => {
let v = v.trim();
!v.is_empty() && !v.eq_ignore_ascii_case("0") && !v.eq_ignore_ascii_case("false")
}
None => false,
}
}
pub fn auto_update_disabled_by_env() -> bool {
env_value_disables(std::env::var("KATA_NO_AUTOUPDATE").ok().as_deref())
}
fn resolve_mode(config: Option<&GlobalConfig>) -> AutoUpdateMode {
if auto_update_disabled_by_env() {
return AutoUpdateMode::Off;
}
config.map(|c| c.defaults.update_mode()).unwrap_or_default()
}
fn build_checker(config: Option<&GlobalConfig>) -> Option<kaishin::Checker> {
let interval = config
.and_then(|c| c.defaults.update_check_interval.as_deref())
.and_then(|s| kaishin::parse_interval(s).ok())
.unwrap_or_else(kaishin::default_interval);
let mut checker = kaishin::Checker::new(BIN, kaishin_opts()).interval(interval);
if let Some(p) = state_path() {
checker = checker.state_path(p);
}
Some(checker)
}
pub enum AutoUpdateHandle {
Notify {
checker: kaishin::Checker,
handle: tokio::task::JoinHandle<Result<Option<kaishin::LatestRelease>, anyhow::Error>>,
},
Install {
handle: tokio::task::JoinHandle<Result<Option<kaishin::LatestRelease>, anyhow::Error>>,
},
}
pub async fn maybe_spawn_auto_update_check() -> Option<AutoUpdateHandle> {
let config = GlobalConfig::load().ok();
match resolve_mode(config.as_ref()) {
AutoUpdateMode::Off => None,
AutoUpdateMode::Notify => {
let checker = build_checker(config.as_ref())?;
let worker = checker.clone();
let handle = tokio::spawn(async move { worker.check_and_save().await });
Some(AutoUpdateHandle::Notify { checker, handle })
}
AutoUpdateMode::Install => {
let checker = build_checker(config.as_ref())?;
let handle = tokio::spawn(async move { checker.auto_update().await });
Some(AutoUpdateHandle::Install { handle })
}
}
}
pub async fn finalize_auto_update_check(handle: AutoUpdateHandle) {
match handle {
AutoUpdateHandle::Notify { checker, handle } => {
let res = tokio::time::timeout(FINALIZE_TIMEOUT, handle).await;
if let Ok(Ok(Ok(Some(latest)))) = res {
eprintln!("\n{}", checker.format_banner(&latest));
}
}
AutoUpdateHandle::Install { handle } => {
let res = tokio::time::timeout(FINALIZE_TIMEOUT, handle).await;
if let Ok(Ok(Ok(Some(rel)))) = res {
let version = rel.tag_name.trim_start_matches('v');
eprintln!(
"\u{2713} {BIN} {version} installed in the background — restart to apply."
);
}
}
}
}
pub async fn run_self_update(yes: bool, check_only: bool, non_interactive: bool) -> Result<()> {
let upd = kaishin::UpdateOptions::new()
.yes(yes)
.check_only(check_only)
.non_interactive(non_interactive);
kaishin::run_self_update(&kaishin_opts(), upd)
.await
.map_err(Error::Other)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn env_kill_switch_unset_is_enabled() {
assert!(!env_value_disables(None), "absent → not disabled");
}
#[test]
fn env_kill_switch_truthy_disables() {
for v in ["1", "true", "TRUE", "yes", "on", " 1 "] {
assert!(env_value_disables(Some(v)), "{v:?} → disabled");
}
}
#[test]
fn env_kill_switch_falsey_stays_enabled() {
for v in ["", " ", "0", "false", "False", "FALSE", " 0 ", " false "] {
assert!(!env_value_disables(Some(v)), "{v:?} → not disabled");
}
}
static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
#[test]
fn auto_update_disabled_by_env_reads_the_var() {
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let key = "KATA_NO_AUTOUPDATE";
let prev = std::env::var_os(key);
unsafe {
std::env::remove_var(key);
assert!(!auto_update_disabled_by_env(), "absent → not disabled");
std::env::set_var(key, "1");
assert!(auto_update_disabled_by_env(), "set → disabled");
std::env::set_var(key, "0");
assert!(!auto_update_disabled_by_env(), "\"0\" → not disabled");
match prev {
Some(val) => std::env::set_var(key, val),
None => std::env::remove_var(key),
}
}
}
}