use std::time::Duration;
use anyhow::Result;
use camino::{Utf8Path, Utf8PathBuf};
use crate::config;
use crate::paths;
use crate::vars::YuiVars;
const OWNER: &str = "yukimemi";
const REPO: &str = "yui";
const BIN: &str = "yui";
fn kaishin_opts() -> kaishin::KaishinOptions {
kaishin::KaishinOptions::new(OWNER, REPO, BIN, env!("CARGO_PKG_VERSION"))
}
fn make_runtime() -> Result<tokio::runtime::Runtime> {
Ok(tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()?)
}
pub fn run_self_update(yes: bool, check_only: bool, non_interactive: bool) -> Result<()> {
let opts = kaishin_opts();
let upd_opts = kaishin::UpdateOptions::new()
.yes(yes)
.check_only(check_only)
.non_interactive(non_interactive);
let rt = make_runtime()?;
rt.block_on(async { kaishin::run_self_update(&opts, upd_opts).await })
}
pub fn default_interval() -> Duration {
kaishin::default_interval()
}
pub struct Checker {
inner: kaishin::Checker,
}
impl Checker {
pub fn new() -> Result<Self> {
let inner = kaishin::Checker::new(BIN, kaishin_opts());
Ok(Self { inner })
}
pub fn interval(mut self, interval: Duration) -> Self {
self.inner = self.inner.interval(interval);
self
}
pub fn should_check(&self) -> bool {
self.inner.should_check()
}
pub fn check_and_save(&self) -> Result<kaishin::LatestRelease> {
let rt = make_runtime()?;
rt.block_on(async { self.inner.check_and_save().await })
}
pub fn cached_update(&self) -> Option<kaishin::LatestRelease> {
self.inner.cached_update()
}
pub fn format_banner(&self, latest: &kaishin::LatestRelease) -> String {
self.inner.format_banner(latest)
}
}
pub enum AutoUpdateHandle {
CachedAvailable {
checker: Checker,
latest: kaishin::LatestRelease,
},
Pending {
checker: Checker,
rx: std::sync::mpsc::Receiver<Result<kaishin::LatestRelease>>,
cached_latest: Option<kaishin::LatestRelease>,
},
}
pub fn maybe_spawn_auto_update_check(cli_source: Option<&Utf8Path>) -> Option<AutoUpdateHandle> {
let source = detect_source(cli_source)?;
let yui = YuiVars::detect(&source);
let loaded = config::load(&source, &yui).ok()?;
if !loaded.ui.auto_update_check {
return None;
}
let interval = match loaded.ui.update_check_interval.as_deref() {
None => default_interval(),
Some(s) => match kaishin::parse_interval(s) {
Ok(d) => d,
Err(e) => {
tracing::warn!(
"invalid [ui] update_check_interval = {s:?} ({e}); \
falling back to default {:?}",
default_interval()
);
default_interval()
}
},
};
let checker = Checker::new().ok()?.interval(interval);
if !checker.should_check() {
if let Some(latest) = checker.cached_update() {
return Some(AutoUpdateHandle::CachedAvailable { checker, latest });
}
return None;
}
let cached_latest = checker.cached_update();
let (tx, rx) = std::sync::mpsc::channel();
let checker_clone = Checker::new().ok()?.interval(interval);
std::thread::spawn(move || {
let _ = tx.send(checker_clone.check_and_save());
});
Some(AutoUpdateHandle::Pending {
checker,
rx,
cached_latest,
})
}
pub fn finalize_auto_update_check(handle: AutoUpdateHandle) {
let (checker, latest) = match handle {
AutoUpdateHandle::CachedAvailable { checker, latest } => (checker, Some(latest)),
AutoUpdateHandle::Pending {
checker,
rx,
cached_latest,
} => {
let latest = rx
.recv_timeout(Duration::from_secs(1))
.ok()
.and_then(|r| r.ok())
.or(cached_latest);
(checker, latest)
}
};
if let Some(latest) = latest {
let banner = checker.format_banner(&latest);
if !banner.is_empty() {
eprintln!("\n{banner}");
}
}
}
fn detect_source(cli_source: Option<&Utf8Path>) -> Option<Utf8PathBuf> {
if let Some(s) = cli_source {
return Some(absolutize_best_effort(s));
}
if let Ok(s) = std::env::var("YUI_SOURCE") {
return Some(absolutize_best_effort(Utf8Path::new(&s)));
}
let cwd = current_dir()?;
for ancestor in cwd.ancestors() {
if ancestor.join("config.toml").is_file() {
return Some(ancestor.to_path_buf());
}
}
None
}
fn absolutize_best_effort(p: &Utf8Path) -> Utf8PathBuf {
let expanded = paths::expand_tilde(p.as_str());
if expanded.is_absolute() {
return expanded;
}
current_dir()
.map(|cwd| cwd.join(&expanded))
.unwrap_or(expanded)
}
fn current_dir() -> Option<Utf8PathBuf> {
let cwd = std::env::current_dir().ok()?;
Utf8PathBuf::from_path_buf(cwd).ok()
}