use std::path::PathBuf;
use std::sync::Mutex;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::time::{Duration, Instant};
use runner_manager_domain::coordinator_protocol::{HostVersions, ManagedWsl};
use runner_manager_platform::paths::AppPaths;
use runner_manager_platform::wsl::fence::GuestRecoveryConfig;
use runner_manager_platform::wsl::record::WslProviderRecord;
use super::daemon::BinaryStamp;
use super::update::{Installation, service_source};
const REFRESH: Duration = Duration::from_secs(60);
struct Source {
path: PathBuf,
stamp: Option<BinaryStamp>,
version: Option<String>,
describe: &'static str,
updatable: bool,
}
#[derive(Default)]
struct Reading {
versions: Option<HostVersions>,
at: Option<Instant>,
source: Option<Source>,
}
static READING: Mutex<Reading> = Mutex::new(Reading {
versions: None,
at: None,
source: None,
});
static REFRESHING: AtomicBool = AtomicBool::new(false);
static GENERATION: AtomicU64 = AtomicU64::new(0);
fn reading() -> std::sync::MutexGuard<'static, Reading> {
READING
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
#[must_use]
pub fn report(paths: &AppPaths) -> Option<HostVersions> {
let (versions, stale) = {
let reading = reading();
(
reading.versions.clone(),
reading.at.is_none_or(|at| at.elapsed() >= REFRESH),
)
};
if stale {
refresh_in_background(paths.clone());
}
versions.map(|mut versions| {
versions.update = super::remote::update_progress();
versions.bounded()
})
}
pub fn invalidate() {
GENERATION.fetch_add(1, Ordering::AcqRel);
reading().at = None;
}
pub fn prime(paths: &AppPaths) {
if reading().versions.is_none() {
refresh(paths);
}
}
fn refresh_in_background(paths: AppPaths) {
if REFRESHING.swap(true, Ordering::AcqRel) {
return;
}
std::thread::spawn(move || {
refresh(&paths);
REFRESHING.store(false, Ordering::Release);
});
}
fn refresh(paths: &AppPaths) {
let generation = GENERATION.load(Ordering::Acquire);
let mut versions = HostVersions {
service: env!("RUNNER_MANAGER_BUILD_VERSION").to_owned(),
wsl: WslProviderRecord::all(paths)
.unwrap_or_default()
.into_iter()
.map(|record| ManagedWsl {
distribution: record.distribution,
installed_version: record.installed_version,
})
.collect(),
wsl_guest: GuestRecoveryConfig::path(paths).exists(),
..HostVersions::default()
};
let previous = reading().source.take();
let source = service_source(paths).ok().flatten().map(|path| {
let stamp = BinaryStamp::of(&path);
match previous {
Some(source) if source.path == path && source.stamp == stamp => source,
_ => {
let version = super::update::executable_version(&path);
let installation = Installation::of_source(paths, path.clone(), version.as_deref());
Source {
describe: installation.channel.describe(),
updatable: installation.refusal().is_none(),
path,
stamp,
version,
}
}
}
});
if let Some(source) = &source {
versions.app.clone_from(&source.version);
versions.app_source = Some(source.describe.to_owned());
versions.updatable = source.updatable;
}
let mut reading = reading();
reading.versions = Some(versions);
reading.at = (GENERATION.load(Ordering::Acquire) == generation).then(Instant::now);
reading.source = source;
}