vivacity_core/
jsonfile.rs1use serde_json::Value;
9use std::collections::HashMap;
10use std::path::{Path, PathBuf};
11use std::sync::{Arc, Mutex, OnceLock};
12
13type Identity = (u128, u64);
14type Entries = HashMap<PathBuf, (Identity, Option<Arc<Value>>)>;
15
16fn identity(path: &Path) -> Option<Identity> {
17 let meta = std::fs::metadata(path).ok()?;
18 let mtime = meta
19 .modified()
20 .ok()?
21 .duration_since(std::time::UNIX_EPOCH)
22 .ok()?
23 .as_nanos();
24 Some((mtime, meta.len()))
25}
26
27fn cache() -> &'static Mutex<Entries> {
28 static CACHE: OnceLock<Mutex<Entries>> = OnceLock::new();
29 CACHE.get_or_init(|| Mutex::new(HashMap::new()))
30}
31
32pub fn read(path: &Path) -> Option<Arc<Value>> {
35 let id = identity(path)?;
36 if let Ok(map) = cache().lock() {
37 if let Some((cached_id, value)) = map.get(path) {
38 if *cached_id == id {
39 return value.clone();
40 }
41 }
42 }
43 let value = std::fs::read_to_string(path)
44 .ok()
45 .and_then(|t| serde_json::from_str::<Value>(&t).ok())
46 .map(Arc::new);
47 if let Ok(mut map) = cache().lock() {
48 map.insert(path.to_path_buf(), (id, value.clone()));
49 }
50 value
51}
52
53#[cfg(test)]
54mod tests {
55 use super::*;
56
57 #[test]
58 fn reparses_when_the_file_changes_and_forgets_when_it_goes() {
59 let dir = tempfile::tempdir().expect("tempdir");
60 let f = dir.path().join("x.json");
61 std::fs::write(&f, r#"{"a":1}"#).expect("write");
62 assert_eq!(read(&f).expect("v")["a"], 1);
63 std::thread::sleep(std::time::Duration::from_millis(20));
65 std::fs::write(&f, r#"{"a":2}"#).expect("write");
66 assert_eq!(read(&f).expect("v")["a"], 2);
67 std::fs::remove_file(&f).expect("rm");
68 assert!(read(&f).is_none());
69 std::fs::write(&f, "not json").expect("write");
70 assert!(read(&f).is_none());
71 }
72}