2025-12-30 02:08:26 +05:30
|
|
|
use crate::models::{CacheEntry, FileMetadata, OrganizationPlan};
|
2026-01-08 20:33:44 +05:30
|
|
|
use blake3::Hasher;
|
2025-12-28 19:15:53 +05:30
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
use std::fs;
|
|
|
|
|
use std::path::Path;
|
|
|
|
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
|
pub struct Cache {
|
|
|
|
|
entries: HashMap<String, CacheEntry>,
|
2025-12-28 23:58:40 +05:30
|
|
|
max_entries: usize,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for Cache {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self::new()
|
|
|
|
|
}
|
2025-12-28 19:15:53 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Cache {
|
|
|
|
|
pub fn new() -> Self {
|
2025-12-28 23:58:40 +05:30
|
|
|
Self::with_max_entries(1000)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn with_max_entries(max_entries: usize) -> Self {
|
2025-12-28 19:15:53 +05:30
|
|
|
Self {
|
|
|
|
|
entries: HashMap::new(),
|
2025-12-28 23:58:40 +05:30
|
|
|
max_entries,
|
2025-12-28 19:15:53 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn load_or_create(cache_path: &Path) -> Self {
|
2026-01-10 22:24:05 +05:30
|
|
|
if !cache_path.exists() {
|
|
|
|
|
return Self::new();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match fs::read_to_string(cache_path) {
|
|
|
|
|
Ok(content) => match serde_json::from_str::<Cache>(&content) {
|
|
|
|
|
Ok(cache) => {
|
|
|
|
|
println!("Loaded cache with {} entries", cache.entries.len());
|
|
|
|
|
cache
|
|
|
|
|
}
|
2025-12-28 19:15:53 +05:30
|
|
|
Err(_) => {
|
2026-01-10 22:24:05 +05:30
|
|
|
println!("Cache corrupted, creating new cache");
|
2025-12-28 19:15:53 +05:30
|
|
|
Self::new()
|
|
|
|
|
}
|
2026-01-10 22:24:05 +05:30
|
|
|
},
|
|
|
|
|
Err(_) => {
|
|
|
|
|
println!("Failed to read cache, creating new cache");
|
|
|
|
|
Self::new()
|
2025-12-28 19:15:53 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn save(&self, cache_path: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
|
|
|
|
if let Some(parent) = cache_path.parent() {
|
|
|
|
|
fs::create_dir_all(parent)?;
|
|
|
|
|
}
|
2025-12-29 00:54:29 +05:30
|
|
|
|
2025-12-28 19:15:53 +05:30
|
|
|
let content = serde_json::to_string_pretty(self)?;
|
|
|
|
|
fs::write(cache_path, content)?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-10 22:24:05 +05:30
|
|
|
pub fn check_cache(&self, filenames: &[String], base_path: &Path) -> Option<OrganizationPlan> {
|
|
|
|
|
let cache_key = Self::generate_cache_key(filenames);
|
|
|
|
|
let entry = self.entries.get(&cache_key)?;
|
2025-12-28 23:58:40 +05:30
|
|
|
|
2026-01-10 22:24:05 +05:30
|
|
|
let all_unchanged = filenames.iter().all(|filename| {
|
|
|
|
|
let file_path = base_path.join(filename);
|
|
|
|
|
FileMetadata::from_path(&file_path).ok().as_ref() == entry.file_metadata.get(filename)
|
2026-01-08 23:18:39 +05:30
|
|
|
});
|
|
|
|
|
|
2026-01-10 22:24:05 +05:30
|
|
|
if all_unchanged {
|
|
|
|
|
println!("Using cached response (timestamp: {})", entry.timestamp);
|
|
|
|
|
Some(entry.response.clone())
|
|
|
|
|
} else {
|
|
|
|
|
None
|
2025-12-28 19:15:53 +05:30
|
|
|
}
|
2026-01-08 23:18:39 +05:30
|
|
|
}
|
|
|
|
|
|
2025-12-28 23:58:40 +05:30
|
|
|
pub fn cache_response(
|
|
|
|
|
&mut self,
|
|
|
|
|
filenames: &[String],
|
|
|
|
|
response: OrganizationPlan,
|
|
|
|
|
base_path: &Path,
|
|
|
|
|
) {
|
2026-01-10 22:24:05 +05:30
|
|
|
let cache_key = Self::generate_cache_key(filenames);
|
2025-12-28 23:58:40 +05:30
|
|
|
|
2026-01-10 22:24:05 +05:30
|
|
|
let file_metadata: HashMap<String, FileMetadata> = filenames
|
|
|
|
|
.iter()
|
|
|
|
|
.filter_map(|filename| {
|
|
|
|
|
let file_path = base_path.join(filename);
|
|
|
|
|
FileMetadata::from_path(&file_path)
|
|
|
|
|
.ok()
|
|
|
|
|
.map(|m| (filename.clone(), m))
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
2025-12-28 23:58:40 +05:30
|
|
|
|
2025-12-28 19:15:53 +05:30
|
|
|
let timestamp = SystemTime::now()
|
|
|
|
|
.duration_since(UNIX_EPOCH)
|
|
|
|
|
.unwrap_or_default()
|
|
|
|
|
.as_secs();
|
2025-12-28 23:58:40 +05:30
|
|
|
|
2025-12-28 19:15:53 +05:30
|
|
|
let entry = CacheEntry {
|
|
|
|
|
response,
|
|
|
|
|
timestamp,
|
2025-12-28 23:58:40 +05:30
|
|
|
file_metadata,
|
2025-12-28 19:15:53 +05:30
|
|
|
};
|
2025-12-28 23:58:40 +05:30
|
|
|
|
2025-12-28 19:15:53 +05:30
|
|
|
self.entries.insert(cache_key, entry);
|
2025-12-28 23:58:40 +05:30
|
|
|
|
|
|
|
|
if self.entries.len() > self.max_entries {
|
|
|
|
|
self.evict_oldest();
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-28 19:15:53 +05:30
|
|
|
println!("Cached response for {} files", filenames.len());
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-10 22:24:05 +05:30
|
|
|
fn generate_cache_key(filenames: &[String]) -> String {
|
2026-01-08 20:33:44 +05:30
|
|
|
let mut hasher = Hasher::new();
|
2026-01-10 22:24:05 +05:30
|
|
|
let mut sorted: Vec<_> = filenames.iter().collect();
|
|
|
|
|
sorted.sort();
|
|
|
|
|
|
|
|
|
|
for filename in sorted {
|
2025-12-28 19:15:53 +05:30
|
|
|
hasher.update(filename.as_bytes());
|
|
|
|
|
hasher.update(b"|");
|
|
|
|
|
}
|
2025-12-28 23:58:40 +05:30
|
|
|
|
2026-01-08 20:33:44 +05:30
|
|
|
hasher.finalize().to_hex().to_string()
|
2025-12-28 19:15:53 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn cleanup_old_entries(&mut self, max_age_seconds: u64) {
|
|
|
|
|
let current_time = SystemTime::now()
|
|
|
|
|
.duration_since(UNIX_EPOCH)
|
|
|
|
|
.unwrap_or_default()
|
|
|
|
|
.as_secs();
|
2025-12-28 23:58:40 +05:30
|
|
|
|
2025-12-28 19:15:53 +05:30
|
|
|
let initial_count = self.entries.len();
|
2025-12-28 23:58:40 +05:30
|
|
|
|
2025-12-29 00:54:29 +05:30
|
|
|
self.entries
|
|
|
|
|
.retain(|_, entry| current_time - entry.timestamp < max_age_seconds);
|
2025-12-28 23:58:40 +05:30
|
|
|
|
2025-12-28 19:15:53 +05:30
|
|
|
let removed_count = initial_count - self.entries.len();
|
|
|
|
|
if removed_count > 0 {
|
|
|
|
|
println!("Cleaned up {} old cache entries", removed_count);
|
|
|
|
|
}
|
2025-12-28 23:58:40 +05:30
|
|
|
|
2026-01-10 22:24:05 +05:30
|
|
|
while self.entries.len() > self.max_entries {
|
|
|
|
|
self.evict_oldest();
|
2025-12-28 23:58:40 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn evict_oldest(&mut self) {
|
|
|
|
|
if let Some(oldest_key) = self
|
|
|
|
|
.entries
|
|
|
|
|
.iter()
|
|
|
|
|
.min_by_key(|(_, entry)| entry.timestamp)
|
|
|
|
|
.map(|(k, _)| k.clone())
|
|
|
|
|
{
|
|
|
|
|
self.entries.remove(&oldest_key);
|
|
|
|
|
println!("Evicted oldest cache entry to maintain limit");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-10 22:24:05 +05:30
|
|
|
pub fn len(&self) -> usize {
|
|
|
|
|
self.entries.len()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn is_empty(&self) -> bool {
|
|
|
|
|
self.entries.is_empty()
|
2025-12-28 23:58:40 +05:30
|
|
|
}
|
|
|
|
|
}
|