use std::sync::Mutex;
use std::sync::atomic::{AtomicU32, Ordering};
use std::time::{Duration, Instant};
use kanade_shared::manifest::Require;
static LATEST_SYSTEM_CPU: AtomicU32 = AtomicU32::new(f32::NAN.to_bits());
pub fn set_system_cpu(pct: Option<f32>) {
let bits = pct.unwrap_or(f32::NAN).to_bits();
LATEST_SYSTEM_CPU.store(bits, Ordering::Relaxed);
}
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
fn system_cpu() -> Option<f64> {
let v = f32::from_bits(LATEST_SYSTEM_CPU.load(Ordering::Relaxed));
if v.is_nan() { None } else { Some(v as f64) }
}
static LATEST_CONSOLE_IDLE: Mutex<Option<(Instant, Duration)>> = Mutex::new(None);
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
pub(crate) const SESSION_IDLE_SAMPLE_INTERVAL: Duration = Duration::from_secs(10);
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
const SESSION_IDLE_STALE_AFTER: Duration = Duration::from_secs(35);
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
pub(crate) fn set_console_idle(idle: Option<Duration>) {
let mut g = LATEST_CONSOLE_IDLE
.lock()
.unwrap_or_else(|e| e.into_inner());
*g = idle.map(|d| (Instant::now(), d));
}
pub fn require_satisfied(req: &Require) -> bool {
if req.is_empty() {
return true; }
#[cfg(target_os = "windows")]
{
use kanade_shared::manifest::{EnvState, require_met};
let (ac_online, idle, network_up) = sense_windows();
require_met(
req,
&EnvState {
ac_online,
idle,
cpu_pct: system_cpu(),
network_up,
},
)
}
#[cfg(not(target_os = "windows"))]
{
let _ = req;
true
}
}
#[cfg(target_os = "windows")]
fn sense_windows() -> (bool, Option<std::time::Duration>, bool) {
use windows::Win32::NetworkManagement::IpHelper::GetNetworkConnectivityHint;
use windows::Win32::Networking::WinSock::{
NL_NETWORK_CONNECTIVITY_HINT, NetworkConnectivityLevelHintInternetAccess,
};
use windows::Win32::System::Power::{GetSystemPowerStatus, SYSTEM_POWER_STATUS};
let ac_online = {
let mut st = SYSTEM_POWER_STATUS::default();
match unsafe { GetSystemPowerStatus(&mut st) } {
Ok(()) => st.ACLineStatus == 1,
Err(_) => false,
}
};
let idle = console_idle();
let network_up = {
let mut hint = NL_NETWORK_CONNECTIVITY_HINT::default();
match unsafe { GetNetworkConnectivityHint(&mut hint) } {
ret if ret.0 == 0 => {
hint.ConnectivityLevel == NetworkConnectivityLevelHintInternetAccess
}
_ => false,
}
};
(ac_online, idle, network_up)
}
#[cfg(target_os = "windows")]
pub(crate) fn console_idle() -> Option<std::time::Duration> {
let g = LATEST_CONSOLE_IDLE
.lock()
.unwrap_or_else(|e| e.into_inner());
match *g {
Some((set_at, idle)) if set_at.elapsed() <= SESSION_IDLE_STALE_AFTER => Some(idle),
_ => Some(Duration::MAX),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_require_is_satisfied_without_syscalls() {
assert!(require_satisfied(&Require::default()));
assert!(require_satisfied(&Require {
ac_power: false,
idle: None,
cpu_below: None,
network: false,
}));
#[cfg(not(target_os = "windows"))]
assert!(require_satisfied(&Require {
ac_power: true,
idle: Some("10m".into()),
cpu_below: Some(20.0),
network: true,
}));
}
#[test]
fn system_cpu_roundtrips_and_unknown_is_none() {
set_system_cpu(Some(42.5));
assert_eq!(system_cpu(), Some(42.5));
set_system_cpu(None);
assert!(system_cpu().is_none());
}
#[cfg(target_os = "windows")]
#[test]
fn console_idle_returns_fresh_cache_then_max_when_cleared() {
use std::time::Duration;
set_console_idle(Some(Duration::from_secs(3)));
assert_eq!(console_idle(), Some(Duration::from_secs(3)));
set_console_idle(None);
assert_eq!(console_idle(), Some(Duration::MAX));
}
}