Skip to main content

cool_plugin/
cache.rs

1//! 插件缓存存储
2//!
3//! 对应 TypeScript 版本的 `cache/store.ts`
4//!
5//! 提供基于文件系统的缓存存储功能
6
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::fs;
10use std::path::PathBuf;
11use std::time::{SystemTime, UNIX_EPOCH};
12use thiserror::Error;
13
14/// 缓存错误
15#[derive(Error, Debug)]
16pub enum CacheError {
17    #[error("缓存操作失败: {0}")]
18    OperationFailed(String),
19    #[error("IO 错误: {0}")]
20    Io(#[from] std::io::Error),
21    #[error("序列化错误: {0}")]
22    Serialization(#[from] serde_json::Error),
23}
24
25pub type CacheResult<T> = Result<T, CacheError>;
26
27/// 缓存项
28#[derive(Debug, Clone, Serialize, Deserialize)]
29struct CacheItem<T> {
30    value: T,
31    expires_at: Option<u64>, // Unix 时间戳(秒)
32}
33
34/// 文件系统缓存存储
35///
36/// 对应 TypeScript 版本的 `FsCacheStore`
37pub struct FsCacheStore {
38    /// 缓存目录路径
39    cache_dir: PathBuf,
40    /// 默认 TTL(秒),-1 表示永不过期
41    default_ttl: i64,
42    /// 内存缓存(用于快速访问)
43    memory_cache: parking_lot::RwLock<HashMap<String, (serde_json::Value, Option<u64>)>>,
44}
45
46impl FsCacheStore {
47    /// 创建新的缓存存储
48    ///
49    /// # 参数
50    ///
51    /// * `options` - 配置选项
52    ///   - `path`: 缓存目录路径,默认为 "cache"
53    ///   - `ttl`: 默认 TTL(秒),-1 表示永不过期,默认为 -1
54    pub fn new(options: CacheOptions) -> CacheResult<Self> {
55        let cache_dir = PathBuf::from(options.path.unwrap_or_else(|| "cache".to_string()));
56        let default_ttl = options.ttl.unwrap_or(-1);
57
58        // 确保缓存目录存在
59        if !cache_dir.exists() {
60            fs::create_dir_all(&cache_dir)?;
61        }
62
63        Ok(Self {
64            cache_dir,
65            default_ttl,
66            memory_cache: parking_lot::RwLock::new(HashMap::new()),
67        })
68    }
69
70    /// 获取缓存值
71    ///
72    /// 对应 TypeScript 版本的 `get`
73    pub fn get<T>(&self, key: &str) -> CacheResult<Option<T>>
74    where
75        T: for<'de> Deserialize<'de>,
76    {
77        // 先检查内存缓存
78        {
79            let memory = self.memory_cache.read();
80            if let Some((value, expires_at)) = memory.get(key) {
81                // 检查是否过期
82                if let Some(expires) = *expires_at {
83                    let now = SystemTime::now()
84                        .duration_since(UNIX_EPOCH)
85                        .unwrap()
86                        .as_secs();
87                    if now >= expires {
88                        // 已过期,从内存缓存中移除
89                        drop(memory);
90                        let mut memory = self.memory_cache.write();
91                        memory.remove(key);
92                    } else {
93                        // 未过期,返回缓存值
94                        let value: T = serde_json::from_value(value.clone())?;
95                        return Ok(Some(value));
96                    }
97                } else {
98                    // 永不过期
99                    let value: T = serde_json::from_value(value.clone())?;
100                    return Ok(Some(value));
101                }
102            }
103        }
104
105        // 从文件系统读取
106        let file_path = self.get_file_path(key);
107        if !file_path.exists() {
108            return Ok(None);
109        }
110
111        let content = fs::read_to_string(&file_path)?;
112        let item: CacheItem<T> = serde_json::from_str(&content)?;
113
114        // 检查是否过期
115        if let Some(expires_at) = item.expires_at {
116            let now = SystemTime::now()
117                .duration_since(UNIX_EPOCH)
118                .unwrap()
119                .as_secs();
120            if now >= expires_at {
121                // 已过期,删除文件
122                let _ = fs::remove_file(&file_path);
123                return Ok(None);
124            }
125        }
126
127        // 更新内存缓存
128        // 注意:由于 T 可能没有实现 Serialize,我们无法将值存储在内存缓存中
129        // 为了性能,这里暂时不更新内存缓存,下次访问时从文件系统读取
130        // 如果需要内存缓存,需要确保 T 同时实现 Serialize 和 Deserialize
131
132        Ok(Some(item.value))
133    }
134
135    /// 设置缓存值
136    ///
137    /// 对应 TypeScript 版本的 `set`
138    pub fn set<T>(&self, key: &str, value: T, ttl: Option<i64>) -> CacheResult<()>
139    where
140        T: Serialize,
141    {
142        let ttl_seconds = ttl.unwrap_or(self.default_ttl);
143        let expires_at = if ttl_seconds > 0 {
144            let now = SystemTime::now()
145                .duration_since(UNIX_EPOCH)
146                .unwrap()
147                .as_secs();
148            Some(now + ttl_seconds as u64)
149        } else {
150            None // 永不过期
151        };
152
153        let item = CacheItem { value, expires_at };
154
155        // 保存到文件系统
156        let file_path = self.get_file_path(key);
157        if let Some(parent) = file_path.parent() {
158            fs::create_dir_all(parent)?;
159        }
160        let content = serde_json::to_string(&item)?;
161        fs::write(&file_path, content)?;
162
163        // 更新内存缓存
164        // 注意:由于 T 可能没有实现 Serialize,我们无法将值存储在内存缓存中
165        // 为了性能,这里暂时不更新内存缓存,下次访问时从文件系统读取
166        // 如果需要内存缓存,需要确保 T 同时实现 Serialize 和 Deserialize
167
168        Ok(())
169    }
170
171    /// 删除缓存
172    ///
173    /// 对应 TypeScript 版本的 `del`
174    pub fn del(&self, key: &str) -> CacheResult<()> {
175        // 从内存缓存删除
176        {
177            let mut memory = self.memory_cache.write();
178            memory.remove(key);
179        }
180
181        // 从文件系统删除
182        let file_path = self.get_file_path(key);
183        if file_path.exists() {
184            fs::remove_file(&file_path)?;
185        }
186
187        Ok(())
188    }
189
190    /// 重置所有缓存
191    ///
192    /// 对应 TypeScript 版本的 `reset`
193    pub fn reset(&self) -> CacheResult<()> {
194        // 清空内存缓存
195        {
196            let mut memory = self.memory_cache.write();
197            memory.clear();
198        }
199
200        // 删除缓存目录下的所有文件
201        if self.cache_dir.exists() {
202            for entry in fs::read_dir(&self.cache_dir)? {
203                let entry = entry?;
204                let path = entry.path();
205                if path.is_file() {
206                    fs::remove_file(path)?;
207                }
208            }
209        }
210
211        Ok(())
212    }
213
214    /// 获取缓存文件路径
215    fn get_file_path(&self, key: &str) -> PathBuf {
216        // 使用 key 的哈希值作为文件名,避免特殊字符问题
217        use std::collections::hash_map::DefaultHasher;
218        use std::hash::{Hash, Hasher};
219
220        let mut hasher = DefaultHasher::new();
221        key.hash(&mut hasher);
222        let hash = hasher.finish();
223        let filename = format!("{:x}.json", hash);
224
225        self.cache_dir.join(filename)
226    }
227}
228
229/// 缓存配置选项
230#[derive(Debug, Clone, Default)]
231pub struct CacheOptions {
232    /// 缓存目录路径
233    pub path: Option<String>,
234    /// 默认 TTL(秒),-1 表示永不过期
235    pub ttl: Option<i64>,
236}
237
238/// 创建缓存存储的便捷函数
239///
240/// 对应 TypeScript 版本的 `CoolCacheStore`
241pub fn create_cache_store(options: CacheOptions) -> CacheResult<FsCacheStore> {
242    FsCacheStore::new(options)
243}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248
249    #[tokio::test]
250    async fn test_cache_store() {
251        let temp_dir = std::env::temp_dir().join("cool_plugin_cache_test");
252        let _ = fs::remove_dir_all(&temp_dir);
253
254        let options = CacheOptions {
255            path: Some(temp_dir.to_string_lossy().to_string()),
256            ttl: Some(60),
257        };
258
259        let store = FsCacheStore::new(options).unwrap();
260
261        // 测试设置和获取
262        store.set("test_key", "test_value", None).unwrap();
263        let value: Option<String> = store.get("test_key").unwrap();
264        assert_eq!(value, Some("test_value".to_string()));
265
266        // 测试删除
267        store.del("test_key").unwrap();
268        let value: Option<String> = store.get("test_key").unwrap();
269        assert_eq!(value, None);
270
271        // 清理
272        let _ = fs::remove_dir_all(&temp_dir);
273    }
274}