use super::types::{EnhancedCacheEntry, EnhancedCacheStats};
use once_cell::sync::Lazy;
use quick_cache::sync::Cache;
use serde_json::Value;
use std::future::Future;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::time::Duration;
use parking_lot::RwLock;
use crate::cache::estimate_json_size;
use vtcode_config::FileReadCacheConfig;
pub static FILE_CACHE: Lazy<FileCache> = Lazy::new(|| FileCache::new(1000));
static FILE_READ_CACHE_CONFIG: Lazy<RwLock<FileReadCacheConfig>> =
Lazy::new(|| RwLock::new(FileReadCacheConfig::default()));
pub struct FileCache {
file_cache: Arc<Cache<String, EnhancedCacheEntry<Arc<Value>>>>,
directory_cache: Arc<Cache<String, EnhancedCacheEntry<Arc<Value>>>>,
stats: Arc<parking_lot::Mutex<EnhancedCacheStats>>,
max_size_bytes: AtomicUsize,
ttl_millis: AtomicU64,
}
impl FileCache {
pub fn new(capacity: usize) -> Self {
Self {
file_cache: Arc::new(Cache::new(capacity)),
directory_cache: Arc::new(Cache::new(capacity / 2)),
stats: Arc::new(parking_lot::Mutex::new(EnhancedCacheStats::default())),
max_size_bytes: AtomicUsize::new(50 * 1024 * 1024), ttl_millis: AtomicU64::new(300_000), }
}
#[inline]
fn ttl(&self) -> Duration {
Duration::from_millis(self.ttl_millis.load(Ordering::Relaxed))
}
#[inline]
fn max_size_bytes(&self) -> usize {
self.max_size_bytes.load(Ordering::Relaxed)
}
pub async fn get_file(&self, key: &str) -> Option<Value> {
self.get_file_arc(key).await.map(|arc| (*arc).clone())
}
pub async fn get_file_arc(&self, key: &str) -> Option<Arc<Value>> {
let mut stats = self.stats.lock();
if let Some(entry) = self.file_cache.get(key) {
if entry.timestamp.elapsed() < self.ttl() {
stats.hits += 1;
return Some(Arc::clone(&entry.data));
} else {
let size = entry.size_bytes;
self.file_cache.remove(key);
stats.expired_evictions += 1;
stats.total_size_bytes = stats.total_size_bytes.saturating_sub(size);
stats.file_size_bytes = stats.file_size_bytes.saturating_sub(size);
}
}
stats.misses += 1;
None
}
#[inline]
fn estimate_value_size(value: &Value) -> usize {
estimate_json_size(value) as usize
}
pub fn put_file(&self, key: String, value: Value) -> impl Future<Output = ()> + '_ {
self.put_file_arc(key, Arc::new(value))
}
pub async fn put_file_arc(&self, key: String, value: Arc<Value>) {
let size_bytes = Self::estimate_value_size(&value);
let entry = EnhancedCacheEntry::new(value, size_bytes);
self.file_cache.insert(key, entry);
let mut stats = self.stats.lock();
stats.file_entries = self.file_cache.len();
stats.entries = stats.file_entries + stats.directory_entries;
stats.file_size_bytes += size_bytes;
stats.total_size_bytes = stats.file_size_bytes + stats.directory_size_bytes;
}
pub async fn get_directory(&self, key: &str) -> Option<Value> {
self.get_directory_arc(key).await.map(|arc| (*arc).clone())
}
pub async fn get_directory_arc(&self, key: &str) -> Option<Arc<Value>> {
let mut stats = self.stats.lock();
if let Some(entry) = self.directory_cache.get(key) {
if entry.timestamp.elapsed() < self.ttl() {
stats.hits += 1;
return Some(Arc::clone(&entry.data));
} else {
let size = entry.size_bytes;
self.directory_cache.remove(key);
stats.expired_evictions += 1;
stats.total_size_bytes = stats.total_size_bytes.saturating_sub(size);
stats.directory_size_bytes = stats.directory_size_bytes.saturating_sub(size);
}
}
stats.misses += 1;
None
}
pub fn put_directory(&self, key: String, value: Value) -> impl Future<Output = ()> + '_ {
self.put_directory_arc(key, Arc::new(value))
}
pub async fn put_directory_arc(&self, key: String, value: Arc<Value>) {
let size_bytes = Self::estimate_value_size(&value);
let entry = EnhancedCacheEntry::new(value, size_bytes);
self.directory_cache.insert(key, entry);
let mut stats = self.stats.lock();
stats.directory_entries = self.directory_cache.len();
stats.entries = stats.file_entries + stats.directory_entries;
stats.directory_size_bytes += size_bytes;
stats.total_size_bytes = stats.file_size_bytes + stats.directory_size_bytes;
}
pub async fn stats(&self) -> EnhancedCacheStats {
self.stats.lock().clone()
}
pub async fn clear(&self) {
self.file_cache.clear();
self.directory_cache.clear();
*self.stats.lock() = EnhancedCacheStats::default();
}
pub fn capacity(&self) -> (usize, usize) {
(
self.file_cache.capacity().try_into().unwrap_or(0),
self.directory_cache.capacity().try_into().unwrap_or(0),
)
}
pub fn len(&self) -> (usize, usize) {
(self.file_cache.len(), self.directory_cache.len())
}
pub async fn check_pressure_and_evict(&self) {
let mut stats = self.stats.lock();
let current_size = stats.total_size_bytes;
let max_size = self.max_size_bytes();
if current_size > max_size {
self.directory_cache.clear();
stats.directory_entries = 0;
stats.directory_size_bytes = 0;
stats.total_size_bytes = stats.file_size_bytes;
stats.entries = stats.file_entries;
if stats.total_size_bytes as f64 > max_size as f64 * 1.5 {
self.file_cache.clear();
stats.file_entries = 0;
stats.file_size_bytes = 0;
stats.total_size_bytes = 0;
stats.entries = 0;
stats.memory_evictions += 1;
} else if stats.total_size_bytes > max_size {
self.file_cache.clear();
stats.file_entries = 0;
stats.file_size_bytes = 0;
stats.total_size_bytes = 0;
stats.entries = 0;
stats.memory_evictions += 1;
}
} else if current_size as f64 > max_size as f64 * 0.9 {
self.directory_cache.clear();
stats.directory_entries = 0;
stats.directory_size_bytes = 0;
stats.total_size_bytes = stats.file_size_bytes;
stats.entries = stats.file_entries;
}
}
pub fn set_capacity_limit(&self, max_bytes: usize) {
self.max_size_bytes.store(max_bytes, Ordering::Relaxed);
}
pub fn apply_read_cache_config(&self, config: &FileReadCacheConfig) {
self.max_size_bytes.store(config.max_size_bytes, Ordering::Relaxed);
self.ttl_millis.store(config.ttl_secs.saturating_mul(1000), Ordering::Relaxed);
}
}
pub fn configure_file_cache(config: &FileReadCacheConfig) {
*FILE_READ_CACHE_CONFIG.write() = config.clone();
FILE_CACHE.apply_read_cache_config(config);
}
pub fn file_read_cache_config() -> FileReadCacheConfig {
FILE_READ_CACHE_CONFIG.read().clone()
}