agent_code_lib/services/
file_cache.rs1use std::collections::HashMap;
8use std::path::{Path, PathBuf};
9use std::time::SystemTime;
10
11const MAX_CACHE_BYTES: usize = 50 * 1024 * 1024;
13
14#[derive(Debug, Clone)]
16struct CachedFile {
17 content: String,
18 modified: SystemTime,
19 size: usize,
20}
21
22pub struct FileCache {
24 entries: HashMap<PathBuf, CachedFile>,
25 total_bytes: usize,
26}
27
28impl FileCache {
29 pub fn new() -> Self {
30 Self {
31 entries: HashMap::new(),
32 total_bytes: 0,
33 }
34 }
35
36 pub fn read(&mut self, path: &Path) -> Result<String, String> {
38 let canonical = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
39
40 if let Some(cached) = self.entries.get(&canonical)
42 && let Ok(meta) = std::fs::metadata(&canonical)
43 && let Ok(modified) = meta.modified()
44 && modified == cached.modified
45 {
46 return Ok(cached.content.clone());
47 }
48
49 let content =
51 std::fs::read_to_string(&canonical).map_err(|e| format!("read error: {e}"))?;
52
53 let meta = std::fs::metadata(&canonical).map_err(|e| format!("metadata error: {e}"))?;
54
55 let modified = meta.modified().unwrap_or(SystemTime::UNIX_EPOCH);
56 let size = content.len();
57
58 while self.total_bytes + size > MAX_CACHE_BYTES && !self.entries.is_empty() {
60 if let Some(key) = self.entries.keys().next().cloned()
62 && let Some(evicted) = self.entries.remove(&key)
63 {
64 self.total_bytes -= evicted.size;
65 }
66 }
67
68 self.total_bytes += size;
69 self.entries.insert(
70 canonical,
71 CachedFile {
72 content: content.clone(),
73 modified,
74 size,
75 },
76 );
77
78 Ok(content)
79 }
80
81 pub fn invalidate(&mut self, path: &Path) {
83 let canonical = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
84
85 if let Some(evicted) = self.entries.remove(&canonical) {
86 self.total_bytes -= evicted.size;
87 }
88 }
89
90 pub fn last_read_mtime(&self, path: &Path) -> Option<SystemTime> {
94 let canonical = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
95 self.entries.get(&canonical).map(|e| e.modified)
96 }
97
98 pub fn clear(&mut self) {
100 self.entries.clear();
101 self.total_bytes = 0;
102 }
103
104 pub fn len(&self) -> usize {
106 self.entries.len()
107 }
108
109 pub fn bytes(&self) -> usize {
111 self.total_bytes
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn test_cache_read_and_invalidate() {
121 let dir = tempfile::tempdir().unwrap();
122 let file = dir.path().join("test.txt");
123 std::fs::write(&file, "hello").unwrap();
124
125 let mut cache = FileCache::new();
126
127 let content = cache.read(&file).unwrap();
129 assert_eq!(content, "hello");
130 assert_eq!(cache.len(), 1);
131
132 let content2 = cache.read(&file).unwrap();
134 assert_eq!(content2, "hello");
135
136 cache.invalidate(&file);
138 assert_eq!(cache.len(), 0);
139 }
140}