use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::time::SystemTime;
const MAX_CACHE_BYTES: usize = 50 * 1024 * 1024;
#[derive(Debug, Clone)]
struct CachedFile {
content: String,
modified: SystemTime,
size: usize,
}
pub struct FileCache {
entries: HashMap<PathBuf, CachedFile>,
total_bytes: usize,
}
impl FileCache {
pub fn new() -> Self {
Self {
entries: HashMap::new(),
total_bytes: 0,
}
}
pub fn read(&mut self, path: &Path) -> Result<String, String> {
let canonical = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
if let Some(cached) = self.entries.get(&canonical)
&& let Ok(meta) = std::fs::metadata(&canonical)
&& let Ok(modified) = meta.modified()
&& modified == cached.modified
{
return Ok(cached.content.clone());
}
let content =
std::fs::read_to_string(&canonical).map_err(|e| format!("read error: {e}"))?;
let meta = std::fs::metadata(&canonical).map_err(|e| format!("metadata error: {e}"))?;
let modified = meta.modified().unwrap_or(SystemTime::UNIX_EPOCH);
let size = content.len();
while self.total_bytes + size > MAX_CACHE_BYTES && !self.entries.is_empty() {
if let Some(key) = self.entries.keys().next().cloned()
&& let Some(evicted) = self.entries.remove(&key)
{
self.total_bytes -= evicted.size;
}
}
self.total_bytes += size;
self.entries.insert(
canonical,
CachedFile {
content: content.clone(),
modified,
size,
},
);
Ok(content)
}
pub fn invalidate(&mut self, path: &Path) {
let canonical = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
if let Some(evicted) = self.entries.remove(&canonical) {
self.total_bytes -= evicted.size;
}
}
pub fn clear(&mut self) {
self.entries.clear();
self.total_bytes = 0;
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn bytes(&self) -> usize {
self.total_bytes
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cache_read_and_invalidate() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("test.txt");
std::fs::write(&file, "hello").unwrap();
let mut cache = FileCache::new();
let content = cache.read(&file).unwrap();
assert_eq!(content, "hello");
assert_eq!(cache.len(), 1);
let content2 = cache.read(&file).unwrap();
assert_eq!(content2, "hello");
cache.invalidate(&file);
assert_eq!(cache.len(), 0);
}
}