//! Image cache with LRU eviction. //! //! Stores downloaded images in `~/.cache/tele-tui/images/` with size-based eviction. use std::fs; use std::path::PathBuf; /// Кэш изображений с LRU eviction по mtime pub struct ImageCache { cache_dir: PathBuf, max_size_bytes: u64, } impl ImageCache { /// Создаёт новый кэш с указанным лимитом в МБ pub fn new(cache_size_mb: u64) -> Self { let cache_dir = dirs::cache_dir() .unwrap_or_else(|| PathBuf::from("/tmp")) .join("tele-tui") .join("images"); // Создаём директорию кэша если не существует let _ = fs::create_dir_all(&cache_dir); Self { cache_dir, max_size_bytes: cache_size_mb * 1024 * 1024, } } /// Проверяет, есть ли файл в кэше pub fn get_cached(&self, file_id: i32) -> Option { let path = self.cache_dir.join(format!("{}.jpg", file_id)); if path.exists() { // Обновляем mtime для LRU let _ = filetime::set_file_mtime( &path, filetime::FileTime::now(), ); Some(path) } else { None } } /// Кэширует файл, копируя из source_path pub fn cache_file(&self, file_id: i32, source_path: &str) -> Result { let dest = self.cache_dir.join(format!("{}.jpg", file_id)); fs::copy(source_path, &dest) .map_err(|e| format!("Ошибка кэширования: {}", e))?; // Evict если превышен лимит self.evict_if_needed(); Ok(dest) } /// Удаляет старые файлы если кэш превышает лимит fn evict_if_needed(&self) { let entries = match fs::read_dir(&self.cache_dir) { Ok(entries) => entries, Err(_) => return, }; let mut files: Vec<(PathBuf, u64, std::time::SystemTime)> = entries .filter_map(|e| e.ok()) .filter_map(|e| { let meta = e.metadata().ok()?; let mtime = meta.modified().ok()?; Some((e.path(), meta.len(), mtime)) }) .collect(); let total_size: u64 = files.iter().map(|(_, size, _)| size).sum(); if total_size <= self.max_size_bytes { return; } // Сортируем по mtime (старые первые) files.sort_by_key(|(_, _, mtime)| *mtime); let mut current_size = total_size; for (path, size, _) in &files { if current_size <= self.max_size_bytes { break; } let _ = fs::remove_file(path); current_size -= size; } } } /// Обёртка для установки mtime без внешней зависимости mod filetime { use std::path::Path; pub struct FileTime; impl FileTime { pub fn now() -> Self { FileTime } } pub fn set_file_mtime(_path: &Path, _time: FileTime) -> Result<(), std::io::Error> { // На macOS/Linux можно использовать utime, но для простоты // достаточно прочитать файл (обновит atime) — LRU по mtime не критичен // для нашего use case. Файл будет перезаписан при повторном скачивании. Ok(()) } }