trae_agent_rs_core/config/
cache.rs1use serde::{Deserialize, Serialize};
4use std::path::{Path, PathBuf};
5use tokio::fs;
6use crate::error::Result;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct ConfigCache {
11 pub selected_provider: Option<String>,
13
14 pub last_updated: Option<u64>,
16
17 pub version: u32,
19}
20
21impl ConfigCache {
22 pub fn new() -> Self {
24 Self {
25 selected_provider: None,
26 last_updated: None,
27 version: 1,
28 }
29 }
30
31 pub fn default_cache_path() -> PathBuf {
33 let mut path = dirs::config_dir()
34 .unwrap_or_else(|| PathBuf::from("."));
35 path.push("trae-agent");
36 path.push("cache.json");
37 path
38 }
39
40 pub async fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
42 let path = path.as_ref();
43
44 if !path.exists() {
45 return Ok(Self::new());
46 }
47
48 let content = fs::read_to_string(path).await?;
49 let cache: ConfigCache = serde_json::from_str(&content)
50 .unwrap_or_else(|_| Self::new());
51
52 Ok(cache)
53 }
54
55 pub async fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
57 let path = path.as_ref();
58
59 if let Some(parent) = path.parent() {
61 fs::create_dir_all(parent).await?;
62 }
63
64 let content = serde_json::to_string_pretty(self)?;
65 fs::write(path, content).await?;
66
67 Ok(())
68 }
69
70 pub fn set_selected_provider(&mut self, provider: String) {
72 self.selected_provider = Some(provider);
73 self.last_updated = Some(
74 std::time::SystemTime::now()
75 .duration_since(std::time::UNIX_EPOCH)
76 .unwrap_or_default()
77 .as_secs()
78 );
79 }
80
81 pub fn get_selected_provider(&self) -> Option<&String> {
83 self.selected_provider.as_ref()
84 }
85
86 pub fn is_expired(&self) -> bool {
88 if let Some(last_updated) = self.last_updated {
89 let now = std::time::SystemTime::now()
90 .duration_since(std::time::UNIX_EPOCH)
91 .unwrap_or_default()
92 .as_secs();
93
94 let thirty_days = 30 * 24 * 60 * 60; now - last_updated > thirty_days
96 } else {
97 true
98 }
99 }
100
101 pub fn clear(&mut self) {
103 self.selected_provider = None;
104 self.last_updated = None;
105 }
106}
107
108impl Default for ConfigCache {
109 fn default() -> Self {
110 Self::new()
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117 use tempfile::tempdir;
118
119 #[tokio::test]
120 async fn test_cache_save_load() {
121 let temp_dir = tempdir().unwrap();
122 let cache_path = temp_dir.path().join("test_cache.json");
123
124 let mut cache = ConfigCache::new();
125 cache.set_selected_provider("openai".to_string());
126
127 cache.save(&cache_path).await.unwrap();
129
130 let loaded_cache = ConfigCache::load(&cache_path).await.unwrap();
132
133 assert_eq!(loaded_cache.selected_provider, Some("openai".to_string()));
134 assert!(loaded_cache.last_updated.is_some());
135 }
136
137 #[tokio::test]
138 async fn test_cache_load_nonexistent() {
139 let temp_dir = tempdir().unwrap();
140 let cache_path = temp_dir.path().join("nonexistent.json");
141
142 let cache = ConfigCache::load(&cache_path).await.unwrap();
143
144 assert_eq!(cache.selected_provider, None);
145 assert_eq!(cache.last_updated, None);
146 }
147
148 #[test]
149 fn test_cache_expiry() {
150 let mut cache = ConfigCache::new();
151
152 assert!(cache.is_expired());
154
155 cache.set_selected_provider("anthropic".to_string());
157
158 assert!(!cache.is_expired());
160
161 cache.last_updated = Some(0); assert!(cache.is_expired());
166 }
167}