use std::fs;
use std::io;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Mutex;
const INDEX_FILENAME: &str = ".cache_size";
static ADJUST_LOCK: Mutex<()> = Mutex::new(());
fn index_path(cache_root: &Path) -> PathBuf { cache_root.join(INDEX_FILENAME) }
pub(super) fn read(cache_root: &Path) -> Option<u64> {
let raw = fs::read_to_string(index_path(cache_root)).ok()?;
raw.trim().parse::<u64>().ok()
}
pub(super) fn write(cache_root: &Path, bytes: u64) -> io::Result<()> {
fs::create_dir_all(cache_root)?;
let final_path = index_path(cache_root);
let tmp_path = final_path.with_extension("tmp");
{
let mut file = fs::File::create(&tmp_path)?;
write!(file, "{bytes}")?;
file.sync_all()?;
}
fs::rename(tmp_path, final_path)
}
pub(super) fn file_size_or_zero(target_path: &Path) -> u64 {
fs::metadata(target_path).map_or(0, |meta| meta.len())
}
pub(super) fn apply_write_delta(cache_root: &Path, old_size: u64, new_size: u64) {
if new_size == old_size {
return;
}
let delta = i128::from(new_size) - i128::from(old_size);
let clamped = i64::try_from(delta).unwrap_or(if delta < 0 { i64::MIN } else { i64::MAX });
adjust(cache_root, clamped);
}
pub(super) fn adjust(cache_root: &Path, delta: i64) {
let _guard = ADJUST_LOCK.lock();
let Some(current) = read(cache_root) else {
return;
};
let next = if delta >= 0 {
current.saturating_add(u64::try_from(delta).unwrap_or(u64::MAX))
} else {
current.saturating_sub(delta.unsigned_abs())
};
let _ = write(cache_root, next);
}
#[cfg(test)]
#[allow(
clippy::expect_used,
reason = "tests should panic on unexpected values"
)]
mod tests {
use tempfile::tempdir;
use super::*;
#[test]
fn read_missing_returns_none() {
let dir = tempdir().expect("tempdir");
assert_eq!(read(dir.path()), None);
}
#[test]
fn write_then_read_roundtrips() {
let dir = tempdir().expect("tempdir");
write(dir.path(), 12_345).expect("write");
assert_eq!(read(dir.path()), Some(12_345));
}
#[test]
fn adjust_increments_and_decrements() {
let dir = tempdir().expect("tempdir");
write(dir.path(), 1_000).expect("write");
adjust(dir.path(), 250);
assert_eq!(read(dir.path()), Some(1_250));
adjust(dir.path(), -500);
assert_eq!(read(dir.path()), Some(750));
}
#[test]
fn adjust_saturates_at_zero() {
let dir = tempdir().expect("tempdir");
write(dir.path(), 100).expect("write");
adjust(dir.path(), -500);
assert_eq!(read(dir.path()), Some(0));
}
#[test]
fn adjust_no_op_when_missing() {
let dir = tempdir().expect("tempdir");
adjust(dir.path(), 250);
assert_eq!(read(dir.path()), None);
}
}