1use std::cell::{Cell, RefCell};
6use std::collections::HashMap;
7use std::fs;
8use std::path::{Path, PathBuf};
9use std::time::SystemTime;
10
11use anyhow::Result;
12use serde::{Deserialize, Serialize};
13
14use crate::item_counts::ItemCounts;
15use crate::traits::cache_store::CacheStore;
16
17#[derive(Debug)]
18pub struct Cache {
19 cache_dir: PathBuf,
20 store: RefCell<HashMap<PathBuf, CachedEntry>>,
21 dirty: Cell<bool>,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25struct CachedEntry {
26 mtime_secs: u128,
27 len: u64,
28 counts: ItemCounts,
29}
30
31impl Cache {
32 #[must_use]
33 pub fn new(root: &Path) -> Self {
34 let cache_dir = root.join(".grip_cache");
35 let store = if cache_dir.exists() {
36 Self::load(&cache_dir).unwrap_or_default()
37 } else {
38 HashMap::new()
39 };
40 Self {
41 cache_dir,
42 store: RefCell::new(store),
43 dirty: Cell::new(false),
44 }
45 }
46
47 fn mtime_secs(path: &Path) -> Option<u128> {
48 let metadata = fs::metadata(path).ok()?;
49 let mtime = metadata.modified().ok()?;
50 Some(mtime.duration_since(SystemTime::UNIX_EPOCH).ok()?.as_secs() as u128)
51 }
52
53 fn load(cache_dir: &Path) -> Result<HashMap<PathBuf, CachedEntry>> {
54 let path = cache_dir.join("cache.json");
55 let json = fs::read_to_string(&path)?;
56 Ok(serde_json::from_str(&json)?)
57 }
58}
59
60impl CacheStore for Cache {
61 fn get(&self, path: &Path) -> Option<ItemCounts> {
62 let mtime_secs = Self::mtime_secs(path)?;
63 let len = fs::metadata(path).ok()?.len();
64 let store = self.store.borrow();
65 let entry = store.get(path)?;
66 if entry.mtime_secs == mtime_secs && entry.len == len {
67 Some(entry.counts.clone())
68 } else {
69 None
70 }
71 }
72
73 fn set(&self, path: &Path, source: &str, counts: &ItemCounts) {
74 let Some(mtime_secs) = Self::mtime_secs(path) else {
75 return;
76 };
77 self.store.borrow_mut().insert(
78 path.to_path_buf(),
79 CachedEntry {
80 mtime_secs,
81 len: source.len() as u64,
82 counts: counts.clone(),
83 },
84 );
85 self.dirty.set(true);
86 }
87
88 fn flush(&self) {
89 if !self.dirty.get() {
90 return;
91 }
92 let _ = fs::create_dir_all(&self.cache_dir);
93 if let Ok(json) = serde_json::to_string(&*self.store.borrow()) {
94 let _ = fs::write(self.cache_dir.join("cache.json"), json);
95 }
96 }
97}