use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use tokio::fs;
use crate::error::Result;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfigCache {
pub selected_provider: Option<String>,
pub last_updated: Option<u64>,
pub version: u32,
}
impl ConfigCache {
pub fn new() -> Self {
Self {
selected_provider: None,
last_updated: None,
version: 1,
}
}
pub fn default_cache_path() -> PathBuf {
let mut path = dirs::config_dir()
.unwrap_or_else(|| PathBuf::from("."));
path.push("trae-agent");
path.push("cache.json");
path
}
pub async fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
let path = path.as_ref();
if !path.exists() {
return Ok(Self::new());
}
let content = fs::read_to_string(path).await?;
let cache: ConfigCache = serde_json::from_str(&content)
.unwrap_or_else(|_| Self::new());
Ok(cache)
}
pub async fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let path = path.as_ref();
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).await?;
}
let content = serde_json::to_string_pretty(self)?;
fs::write(path, content).await?;
Ok(())
}
pub fn set_selected_provider(&mut self, provider: String) {
self.selected_provider = Some(provider);
self.last_updated = Some(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
);
}
pub fn get_selected_provider(&self) -> Option<&String> {
self.selected_provider.as_ref()
}
pub fn is_expired(&self) -> bool {
if let Some(last_updated) = self.last_updated {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let thirty_days = 30 * 24 * 60 * 60; now - last_updated > thirty_days
} else {
true
}
}
pub fn clear(&mut self) {
self.selected_provider = None;
self.last_updated = None;
}
}
impl Default for ConfigCache {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[tokio::test]
async fn test_cache_save_load() {
let temp_dir = tempdir().unwrap();
let cache_path = temp_dir.path().join("test_cache.json");
let mut cache = ConfigCache::new();
cache.set_selected_provider("openai".to_string());
cache.save(&cache_path).await.unwrap();
let loaded_cache = ConfigCache::load(&cache_path).await.unwrap();
assert_eq!(loaded_cache.selected_provider, Some("openai".to_string()));
assert!(loaded_cache.last_updated.is_some());
}
#[tokio::test]
async fn test_cache_load_nonexistent() {
let temp_dir = tempdir().unwrap();
let cache_path = temp_dir.path().join("nonexistent.json");
let cache = ConfigCache::load(&cache_path).await.unwrap();
assert_eq!(cache.selected_provider, None);
assert_eq!(cache.last_updated, None);
}
#[test]
fn test_cache_expiry() {
let mut cache = ConfigCache::new();
assert!(cache.is_expired());
cache.set_selected_provider("anthropic".to_string());
assert!(!cache.is_expired());
cache.last_updated = Some(0);
assert!(cache.is_expired());
}
}