use crate::paths::Paths;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq)]
pub struct Entry {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub five_h: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub five_h_reset: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub seven_d: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub seven_d_reset: Option<i64>,
pub at: i64,
}
pub type Cache = BTreeMap<String, Entry>;
fn file(paths: &Paths) -> std::path::PathBuf {
paths.store_dir().join("quota-cache.json")
}
pub fn load(paths: &Paths) -> Cache {
std::fs::read(file(paths))
.ok()
.and_then(|b| serde_json::from_slice(&b).ok())
.unwrap_or_default()
}
pub fn update(paths: &Paths, fresh: &[(String, Entry)]) {
if fresh.is_empty() {
return;
}
let mut c = load(paths);
for (name, e) in fresh {
c.insert(name.clone(), *e);
}
if let Ok(bytes) = serde_json::to_vec_pretty(&c) {
let _ = std::fs::create_dir_all(paths.store_dir());
let _ = crate::atomic::write_secret(&file(paths), &bytes);
}
}
pub fn age_secs(e: &Entry, now: i64) -> i64 {
(now - e.at).max(0)
}
#[cfg(test)]
mod tests {
use super::*;
fn entry(pct: f64, at: i64) -> Entry {
Entry {
five_h: Some(pct),
at,
..Default::default()
}
}
#[test]
fn readings_persist_and_merge_without_erasing_others() {
let root = tempfile::tempdir().unwrap();
let paths = Paths::rooted(root.path());
assert!(load(&paths).is_empty());
update(&paths, &[("a".into(), entry(10.0, 100))]);
update(&paths, &[("b".into(), entry(20.0, 200))]);
let c = load(&paths);
assert_eq!(c.len(), 2, "a later write keeps the earlier account");
assert_eq!(c["a"].five_h, Some(10.0));
update(&paths, &[("a".into(), entry(55.0, 300))]);
let c = load(&paths);
assert_eq!(c["a"].five_h, Some(55.0));
assert_eq!(c["b"].five_h, Some(20.0), "b is untouched");
assert_eq!(age_secs(&c["b"], 260), 60);
}
#[test]
fn an_unreadable_cache_is_empty_not_an_error() {
let root = tempfile::tempdir().unwrap();
let paths = Paths::rooted(root.path());
std::fs::create_dir_all(paths.store_dir()).unwrap();
std::fs::write(super::file(&paths), b"{ not json").unwrap();
assert!(load(&paths).is_empty());
update(&paths, &[("a".into(), entry(1.0, 1))]);
assert_eq!(load(&paths).len(), 1);
}
}