use crate::constants::env::{ai, system};
use std::collections::HashMap;
use std::path::PathBuf;
const IMAGE_STORE_DIR: &str = "image-cache";
const MAX_STORED_IMAGE_PATHS: usize = 200;
static STORED_IMAGE_PATHS: once_cell::sync::Lazy<std::sync::Mutex<HashMap<u64, String>>> =
once_cell::sync::Lazy::new(|| std::sync::Mutex::new(HashMap::new()));
fn get_image_store_dir() -> PathBuf {
let session_id = std::env::var(ai::CODE_SESSION_ID).unwrap_or_else(|_| "default".to_string());
let config_home = std::env::var(ai::CONFIG_HOME)
.or_else(|_| std::env::var(ai::CLAUDE_CONFIG_HOME))
.or_else(|_| std::env::var(system::HOME).map(|h| format!("{}/.ai", h)))
.unwrap_or_else(|_| "~/.ai".to_string());
PathBuf::from(config_home)
.join(IMAGE_STORE_DIR)
.join(session_id)
}
async fn ensure_image_store_dir() -> std::io::Result<()> {
let dir = get_image_store_dir();
tokio::fs::create_dir_all(dir).await
}
fn get_image_path(image_id: u64, media_type: &str) -> PathBuf {
let extension = media_type.split('/').nth(1).unwrap_or("png");
get_image_store_dir().join(format!("{}.{}", image_id, extension))
}
fn evict_oldest_if_at_cap() {
let mut map = STORED_IMAGE_PATHS.lock().unwrap();
while map.len() >= MAX_STORED_IMAGE_PATHS {
if let Some(oldest) = map.keys().next().copied() {
map.remove(&oldest);
} else {
break;
}
}
}
pub fn get_stored_image_path(image_id: u64) -> Option<String> {
STORED_IMAGE_PATHS.lock().unwrap().get(&image_id).cloned()
}
pub fn clear_stored_image_paths() {
STORED_IMAGE_PATHS.lock().unwrap().clear();
}
pub async fn store_image(image_id: u64, media_type: &str, content: &str) -> Option<String> {
ensure_image_store_dir().await.ok()?;
let image_path = get_image_path(image_id, media_type);
let data = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, content).ok()?;
tokio::fs::write(&image_path, data).await.ok()?;
evict_oldest_if_at_cap();
let path_str = image_path.to_string_lossy().to_string();
STORED_IMAGE_PATHS
.lock()
.unwrap()
.insert(image_id, path_str.clone());
Some(path_str)
}
pub async fn store_images(images: &HashMap<u64, (&str, &str)>) -> HashMap<u64, String> {
let mut path_map = HashMap::new();
for (&id, (media_type, content)) in images {
if let Some(path) = store_image(id, media_type, content).await {
path_map.insert(id, path);
}
}
path_map
}
pub async fn cleanup_old_image_caches() {
let base_dir = {
let config_home = std::env::var(ai::CONFIG_HOME)
.or_else(|_| std::env::var(ai::CLAUDE_CONFIG_HOME))
.or_else(|_| std::env::var(system::HOME).map(|h| format!("{}/.ai", h)))
.unwrap_or_else(|_| "~/.ai".to_string());
PathBuf::from(config_home).join(IMAGE_STORE_DIR)
};
let current_session_id =
std::env::var(ai::CODE_SESSION_ID).unwrap_or_else(|_| "default".to_string());
let mut entries = match tokio::fs::read_dir(&base_dir).await {
Ok(e) => e,
Err(_) => return,
};
let mut entries_vec: Vec<tokio::fs::DirEntry> = Vec::new();
loop {
match entries.next_entry().await {
Ok(Some(entry)) => entries_vec.push(entry),
Ok(None) => break,
Err(_) => break,
}
}
for entry in entries_vec {
let name = entry.file_name().to_string_lossy().to_string();
if name == current_session_id {
continue;
}
let path = base_dir.join(&name);
let _ = tokio::fs::remove_dir_all(path).await;
}
}