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 clear(&mut self) {
92 self.entries.clear();
93 self.total_bytes = 0;
94 }
95
96 pub fn len(&self) -> usize {
98 self.entries.len()
99 }
100
101 pub fn bytes(&self) -> usize {
103 self.total_bytes
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn test_cache_read_and_invalidate() {
113 let dir = tempfile::tempdir().unwrap();
114 let file = dir.path().join("test.txt");
115 std::fs::write(&file, "hello").unwrap();
116
117 let mut cache = FileCache::new();
118
119 let content = cache.read(&file).unwrap();
121 assert_eq!(content, "hello");
122 assert_eq!(cache.len(), 1);
123
124 let content2 = cache.read(&file).unwrap();
126 assert_eq!(content2, "hello");
127
128 cache.invalidate(&file);
130 assert_eq!(cache.len(), 0);
131 }
132}