use std::collections::HashMap;
use std::io;
use std::path::Path;
use atomic_write_file::AtomicWriteFile;
use serde::{Deserialize, Serialize};
use super::cache::{CacheKey, Cached};
use super::features::{BatteryProbe, ProbedFeatures};
const SCHEMA_VERSION: u32 = 2;
#[derive(Serialize, Deserialize)]
struct PersistedCache {
version: u32,
entries: Vec<PersistedEntry>,
}
#[derive(Serialize, Deserialize)]
struct PersistedEntry {
key: PersistedKey,
probe: ProbedFeatures,
battery: Option<BatteryProbe>,
}
#[derive(Clone, Copy, Serialize, Deserialize)]
enum PersistedKey {
Bolt { unit_id: [u8; 4] },
}
fn persistable(key: &CacheKey) -> Option<PersistedKey> {
match key {
CacheKey::Bolt { unit_id } => Some(PersistedKey::Bolt { unit_id: *unit_id }),
CacheKey::UnifyingSlot { .. } | CacheKey::Direct(_) => None,
}
}
pub(super) fn is_persistable(key: &CacheKey) -> bool {
persistable(key).is_some()
}
fn runtime_key(key: PersistedKey) -> CacheKey {
match key {
PersistedKey::Bolt { unit_id } => CacheKey::Bolt { unit_id },
}
}
pub(super) fn save(path: &Path, cache: &HashMap<CacheKey, Cached>) -> io::Result<()> {
let entries: Vec<PersistedEntry> = cache
.iter()
.filter_map(|(key, cached)| {
persistable(key).map(|key| {
let mut probe = cached.probe.clone();
probe.battery = None;
PersistedEntry {
key,
probe,
battery: cached.battery,
}
})
})
.collect();
let file = PersistedCache {
version: SCHEMA_VERSION,
entries,
};
let json = serde_json::to_vec(&file).map_err(io::Error::other)?;
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let mut out = AtomicWriteFile::open(path)?;
io::Write::write_all(&mut out, &json)?;
out.commit()
}
pub(super) fn load(path: &Path) -> HashMap<CacheKey, Cached> {
let Ok(bytes) = std::fs::read(path) else {
return HashMap::new();
};
let Ok(file) = serde_json::from_slice::<PersistedCache>(&bytes) else {
tracing::warn!(?path, "probe cache unreadable — starting cold");
return HashMap::new();
};
if file.version != SCHEMA_VERSION {
tracing::debug!(
version = file.version,
"probe cache from another schema — starting cold"
);
return HashMap::new();
}
file.entries
.into_iter()
.map(|entry| {
(
runtime_key(entry.key),
Cached {
probe: entry.probe,
battery: entry.battery,
probed_tick: 0,
},
)
})
.collect()
}