use serde_json::Value;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex, OnceLock};
type Identity = (u128, u64);
type Entries = HashMap<PathBuf, (Identity, Option<Arc<Value>>)>;
fn identity(path: &Path) -> Option<Identity> {
let meta = std::fs::metadata(path).ok()?;
let mtime = meta
.modified()
.ok()?
.duration_since(std::time::UNIX_EPOCH)
.ok()?
.as_nanos();
Some((mtime, meta.len()))
}
fn cache() -> &'static Mutex<Entries> {
static CACHE: OnceLock<Mutex<Entries>> = OnceLock::new();
CACHE.get_or_init(|| Mutex::new(HashMap::new()))
}
pub fn read(path: &Path) -> Option<Arc<Value>> {
let id = identity(path)?;
if let Ok(map) = cache().lock() {
if let Some((cached_id, value)) = map.get(path) {
if *cached_id == id {
return value.clone();
}
}
}
let value = std::fs::read_to_string(path)
.ok()
.and_then(|t| serde_json::from_str::<Value>(&t).ok())
.map(Arc::new);
if let Ok(mut map) = cache().lock() {
map.insert(path.to_path_buf(), (id, value.clone()));
}
value
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reparses_when_the_file_changes_and_forgets_when_it_goes() {
let dir = tempfile::tempdir().expect("tempdir");
let f = dir.path().join("x.json");
std::fs::write(&f, r#"{"a":1}"#).expect("write");
assert_eq!(read(&f).expect("v")["a"], 1);
std::thread::sleep(std::time::Duration::from_millis(20));
std::fs::write(&f, r#"{"a":2}"#).expect("write");
assert_eq!(read(&f).expect("v")["a"], 2);
std::fs::remove_file(&f).expect("rm");
assert!(read(&f).is_none());
std::fs::write(&f, "not json").expect("write");
assert!(read(&f).is_none());
}
}