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