use std::path::Path;
pub const STAMP_FILE: &str = "last_used";
pub const MIN_WRITE_INTERVAL_SECS: u64 = 60;
pub type StampCache = std::sync::Arc<dashmap::DashMap<String, u64>>;
pub fn read(data_dir: &Path) -> Option<u64> {
let raw = std::fs::read_to_string(data_dir.join(STAMP_FILE)).ok()?;
raw.trim().parse::<u64>().ok()
}
pub fn write(data_dir: &Path, now_unix: u64) -> std::io::Result<()> {
let target = data_dir.join(STAMP_FILE);
let tmp = data_dir.join(format!("{STAMP_FILE}.tmp"));
std::fs::write(&tmp, now_unix.to_string())?;
std::fs::rename(&tmp, &target)
}
pub fn stamp(cache: &StampCache, data_dir: &Path, palace_id: &str, now_unix: u64) -> bool {
let fresh = cache
.get(palace_id)
.map(|prev| now_unix.saturating_sub(*prev) < MIN_WRITE_INTERVAL_SECS)
.unwrap_or(false);
if fresh {
return false;
}
cache.insert(palace_id.to_string(), now_unix);
if let Err(e) = write(data_dir, now_unix) {
tracing::debug!(palace = %palace_id, "last_used stamp write failed: {e}");
}
true
}
pub fn now_unix() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0)
}
#[cfg(test)]
mod tests {
use super::*;
fn cache() -> StampCache {
std::sync::Arc::new(dashmap::DashMap::new())
}
#[test]
fn stamp_round_trips() {
let dir = tempfile::tempdir().unwrap();
write(dir.path(), 1_800_000_000).unwrap();
assert_eq!(read(dir.path()), Some(1_800_000_000));
}
#[test]
fn read_missing_is_none() {
let dir = tempfile::tempdir().unwrap();
assert_eq!(
read(dir.path()),
None,
"a palace with no stamp is 'never used', not 'used at the epoch'"
);
}
#[test]
fn read_garbage_is_none() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join(STAMP_FILE), "not-a-timestamp").unwrap();
assert_eq!(read(dir.path()), None);
}
#[test]
fn stamp_survives_a_fresh_cache() {
let dir = tempfile::tempdir().unwrap();
assert!(stamp(&cache(), dir.path(), "p", 1_850_000_000));
assert_eq!(read(dir.path()), Some(1_850_000_000));
}
#[test]
fn stamp_throttles_within_the_window() {
let dir = tempfile::tempdir().unwrap();
let c = cache();
assert!(
stamp(&c, dir.path(), "p", 1_000_000),
"first use always writes"
);
assert!(
!stamp(&c, dir.path(), "p", 1_000_000 + MIN_WRITE_INTERVAL_SECS - 1),
"a second use inside the window must not write"
);
assert_eq!(
read(dir.path()),
Some(1_000_000),
"the throttled call must leave the earlier stamp untouched"
);
}
#[test]
fn stamp_writes_again_after_the_window() {
let dir = tempfile::tempdir().unwrap();
let c = cache();
let later = 1_000_000 + MIN_WRITE_INTERVAL_SECS;
assert!(stamp(&c, dir.path(), "p", 1_000_000));
assert!(stamp(&c, dir.path(), "p", later));
assert_eq!(read(dir.path()), Some(later));
}
#[test]
fn stamp_throttles_per_palace_not_globally() {
let a = tempfile::tempdir().unwrap();
let b = tempfile::tempdir().unwrap();
let c = cache();
assert!(stamp(&c, a.path(), "a", 1_000_000));
assert!(
stamp(&c, b.path(), "b", 1_000_000),
"one palace's write must not throttle another's"
);
assert_eq!(read(b.path()), Some(1_000_000));
}
}