Refactor cache API and expand installation docs
- Remove CacheCheckResult and simplify Cache::check_cache to return Option<OrganizationPlan> - Replace cache_response_with_metadata with cache_response that takes a base path; update Gemini client and tests to use new API - Improve load_or_create error handling and early-return when cache file missing - Expand README/docs/INSTALLATION.md with detailed per-OS install and PATH instructions
This commit is contained in:
@@ -6,12 +6,6 @@ use std::fs;
|
||||
use std::path::Path;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
/// Result of checking the cache - includes pre-fetched metadata to avoid double lookups
|
||||
pub struct CacheCheckResult {
|
||||
pub cached_response: Option<OrganizationPlan>,
|
||||
pub file_metadata: HashMap<String, FileMetadata>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Cache {
|
||||
entries: HashMap<String, CacheEntry>,
|
||||
@@ -37,26 +31,25 @@ impl Cache {
|
||||
}
|
||||
|
||||
pub fn load_or_create(cache_path: &Path) -> Self {
|
||||
if cache_path.exists() {
|
||||
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
|
||||
}
|
||||
Err(_) => {
|
||||
println!("Cache corrupted, creating new cache");
|
||||
Self::new()
|
||||
}
|
||||
},
|
||||
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
|
||||
}
|
||||
Err(_) => {
|
||||
println!("Failed to read cache, creating new cache");
|
||||
println!("Cache corrupted, creating new cache");
|
||||
Self::new()
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
println!("Failed to read cache, creating new cache");
|
||||
Self::new()
|
||||
}
|
||||
} else {
|
||||
println!("Creating new cache file");
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,107 +63,40 @@ impl Cache {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Checks cache and returns pre-fetched metadata to avoid double lookups.
|
||||
/// The returned metadata can be passed to `cache_response_with_metadata` on cache miss.
|
||||
pub fn check_cache(&self, filenames: &[String], base_path: &Path) -> CacheCheckResult {
|
||||
// Fetch metadata once for all files
|
||||
let file_metadata: HashMap<String, FileMetadata> = filenames
|
||||
.iter()
|
||||
.filter_map(|filename| {
|
||||
let file_path = base_path.join(filename);
|
||||
Self::get_file_metadata(&file_path)
|
||||
.ok()
|
||||
.map(|m| (filename.clone(), m))
|
||||
})
|
||||
.collect();
|
||||
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)?;
|
||||
|
||||
let cache_key = self.generate_cache_key(filenames);
|
||||
|
||||
let cached_response = self.entries.get(&cache_key).and_then(|entry| {
|
||||
// Validate all files are unchanged using pre-fetched metadata
|
||||
let all_unchanged = filenames.iter().all(|filename| {
|
||||
match (
|
||||
file_metadata.get(filename),
|
||||
entry.file_metadata.get(filename),
|
||||
) {
|
||||
(Some(current), Some(cached)) => current == cached,
|
||||
_ => false,
|
||||
}
|
||||
});
|
||||
|
||||
if all_unchanged {
|
||||
println!("Using cached response (timestamp: {})", entry.timestamp);
|
||||
Some(entry.response.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
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)
|
||||
});
|
||||
|
||||
CacheCheckResult {
|
||||
cached_response,
|
||||
file_metadata,
|
||||
if all_unchanged {
|
||||
println!("Using cached response (timestamp: {})", entry.timestamp);
|
||||
Some(entry.response.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Cache response using pre-fetched metadata (avoids double metadata lookup)
|
||||
pub fn cache_response_with_metadata(
|
||||
&mut self,
|
||||
filenames: &[String],
|
||||
response: OrganizationPlan,
|
||||
file_metadata: HashMap<String, FileMetadata>,
|
||||
) {
|
||||
let cache_key = self.generate_cache_key(filenames);
|
||||
|
||||
let timestamp = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs();
|
||||
|
||||
let entry = CacheEntry {
|
||||
response,
|
||||
timestamp,
|
||||
file_metadata,
|
||||
};
|
||||
|
||||
self.entries.insert(cache_key, entry);
|
||||
|
||||
if self.entries.len() > self.max_entries {
|
||||
self.evict_oldest();
|
||||
}
|
||||
|
||||
println!("Cached response for {} files", filenames.len());
|
||||
}
|
||||
|
||||
/// Legacy method - checks cache for a response (fetches metadata internally)
|
||||
#[deprecated(
|
||||
note = "Use check_cache() + cache_response_with_metadata() to avoid double metadata lookups"
|
||||
)]
|
||||
pub fn get_cached_response(
|
||||
&self,
|
||||
filenames: &[String],
|
||||
base_path: &Path,
|
||||
) -> Option<OrganizationPlan> {
|
||||
let result = self.check_cache(filenames, base_path);
|
||||
result.cached_response
|
||||
}
|
||||
|
||||
/// Legacy method - caches a response (fetches metadata internally)
|
||||
#[deprecated(note = "Use cache_response_with_metadata() with pre-fetched metadata")]
|
||||
pub fn cache_response(
|
||||
&mut self,
|
||||
filenames: &[String],
|
||||
response: OrganizationPlan,
|
||||
base_path: &Path,
|
||||
) {
|
||||
let cache_key = self.generate_cache_key(filenames);
|
||||
let mut file_metadata = HashMap::new();
|
||||
let cache_key = Self::generate_cache_key(filenames);
|
||||
|
||||
for filename in filenames {
|
||||
let file_path = base_path.join(filename);
|
||||
if let Ok(metadata) = Self::get_file_metadata(&file_path) {
|
||||
file_metadata.insert(filename.clone(), metadata);
|
||||
}
|
||||
}
|
||||
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();
|
||||
|
||||
let timestamp = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
@@ -192,12 +118,12 @@ impl Cache {
|
||||
println!("Cached response for {} files", filenames.len());
|
||||
}
|
||||
|
||||
fn generate_cache_key(&self, filenames: &[String]) -> String {
|
||||
let mut sorted_filenames = filenames.to_vec();
|
||||
sorted_filenames.sort();
|
||||
|
||||
fn generate_cache_key(filenames: &[String]) -> String {
|
||||
let mut hasher = Hasher::new();
|
||||
for filename in &sorted_filenames {
|
||||
let mut sorted: Vec<_> = filenames.iter().collect();
|
||||
sorted.sort();
|
||||
|
||||
for filename in sorted {
|
||||
hasher.update(filename.as_bytes());
|
||||
hasher.update(b"|");
|
||||
}
|
||||
@@ -205,16 +131,6 @@ impl Cache {
|
||||
hasher.finalize().to_hex().to_string()
|
||||
}
|
||||
|
||||
fn get_file_metadata(file_path: &Path) -> Result<FileMetadata, Box<dyn std::error::Error>> {
|
||||
let metadata = fs::metadata(file_path)?;
|
||||
let modified = metadata.modified()?.duration_since(UNIX_EPOCH)?.as_secs();
|
||||
|
||||
Ok(FileMetadata {
|
||||
size: metadata.len(),
|
||||
modified,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn cleanup_old_entries(&mut self, max_age_seconds: u64) {
|
||||
let current_time = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
@@ -231,8 +147,8 @@ impl Cache {
|
||||
println!("Cleaned up {} old cache entries", removed_count);
|
||||
}
|
||||
|
||||
if self.entries.len() > self.max_entries {
|
||||
self.compact_cache();
|
||||
while self.entries.len() > self.max_entries {
|
||||
self.evict_oldest();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,9 +164,11 @@ impl Cache {
|
||||
}
|
||||
}
|
||||
|
||||
fn compact_cache(&mut self) {
|
||||
while self.entries.len() > self.max_entries {
|
||||
self.evict_oldest();
|
||||
}
|
||||
pub fn len(&self) -> usize {
|
||||
self.entries.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.entries.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user