fallow_extract/cache/
store.rs1use std::path::Path;
4
5use rustc_hash::FxHashMap;
6
7use bincode::{Decode, Encode};
8
9use super::types::{CACHE_VERSION, CachedModule, MAX_CACHE_SIZE};
10
11#[derive(Debug, Encode, Decode)]
13pub struct CacheStore {
14 version: u32,
15 entries: FxHashMap<String, CachedModule>,
17}
18
19impl CacheStore {
20 pub fn new() -> Self {
22 Self {
23 version: CACHE_VERSION,
24 entries: FxHashMap::default(),
25 }
26 }
27
28 pub fn load(cache_dir: &Path) -> Option<Self> {
30 let cache_file = cache_dir.join("cache.bin");
31 let data = std::fs::read(&cache_file).ok()?;
32 if data.len() > MAX_CACHE_SIZE {
33 tracing::warn!(
34 size_mb = data.len() / (1024 * 1024),
35 "Cache file exceeds size limit, ignoring"
36 );
37 return None;
38 }
39 let (store, _): (Self, usize) =
40 bincode::decode_from_slice(&data, bincode::config::standard()).ok()?;
41 if store.version != CACHE_VERSION {
42 return None;
43 }
44 Some(store)
45 }
46
47 pub fn save(&self, cache_dir: &Path) -> Result<(), String> {
49 std::fs::create_dir_all(cache_dir)
50 .map_err(|e| format!("Failed to create cache dir: {e}"))?;
51 let cache_file = cache_dir.join("cache.bin");
52 let data = bincode::encode_to_vec(self, bincode::config::standard())
53 .map_err(|e| format!("Failed to serialize cache: {e}"))?;
54 std::fs::write(&cache_file, data).map_err(|e| format!("Failed to write cache: {e}"))?;
55 Ok(())
56 }
57
58 pub fn get(&self, path: &Path, content_hash: u64) -> Option<&CachedModule> {
61 let key = path.to_string_lossy().to_string();
62 let entry = self.entries.get(&key)?;
63 if entry.content_hash == content_hash {
64 Some(entry)
65 } else {
66 None
67 }
68 }
69
70 pub fn insert(&mut self, path: &Path, module: CachedModule) {
72 let key = path.to_string_lossy().to_string();
73 self.entries.insert(key, module);
74 }
75
76 pub fn get_by_metadata(
83 &self,
84 path: &Path,
85 mtime_secs: u64,
86 file_size: u64,
87 ) -> Option<&CachedModule> {
88 let key = path.to_string_lossy().to_string();
89 let entry = self.entries.get(&key)?;
90 if entry.mtime_secs == mtime_secs && entry.file_size == file_size && mtime_secs > 0 {
91 Some(entry)
92 } else {
93 None
94 }
95 }
96
97 pub fn get_by_path_only(&self, path: &Path) -> Option<&CachedModule> {
101 let key = path.to_string_lossy().to_string();
102 self.entries.get(&key)
103 }
104
105 pub fn retain_paths(&mut self, files: &[fallow_types::discover::DiscoveredFile]) {
108 use rustc_hash::FxHashSet;
109 let current_paths: FxHashSet<String> = files
110 .iter()
111 .map(|f| f.path.to_string_lossy().to_string())
112 .collect();
113 self.entries.retain(|key, _| current_paths.contains(key));
114 }
115
116 pub fn len(&self) -> usize {
118 self.entries.len()
119 }
120
121 pub fn is_empty(&self) -> bool {
123 self.entries.is_empty()
124 }
125}
126
127impl Default for CacheStore {
128 fn default() -> Self {
129 Self::new()
130 }
131}