pcapfile_io/business/
cache.rs

1//! 文件信息缓存管理模块
2//!
3//! 提供高效的文件信息缓存策略,减少重复的文件系统访问,提升性能。
4
5use chrono::{DateTime, Duration, Utc};
6use std::collections::HashMap;
7use std::sync::{Arc, Mutex};
8
9use crate::data::models::FileInfo;
10
11/// 缓存统计信息
12#[derive(Debug, Clone)]
13pub struct CacheStats {
14    /// 缓存条目总数
15    pub cache_entries: usize,
16    /// 缓存命中次数
17    pub hit_count: u64,
18    /// 缓存未命中次数
19    pub miss_count: u64,
20    /// 缓存命中率
21    pub hit_rate: f64,
22}
23
24impl CacheStats {
25    /// 创建新的缓存统计信息
26    pub fn new() -> Self {
27        Self {
28            cache_entries: 0,
29            hit_count: 0,
30            miss_count: 0,
31            hit_rate: 0.0,
32        }
33    }
34
35    /// 更新命中率
36    pub fn update_hit_rate(&mut self) {
37        let total_requests =
38            self.hit_count + self.miss_count;
39        self.hit_rate = if total_requests > 0 {
40            self.hit_count as f64 / total_requests as f64
41        } else {
42            0.0
43        };
44    }
45}
46
47impl Default for CacheStats {
48    fn default() -> Self {
49        Self::new()
50    }
51}
52
53/// 文件信息缓存项
54#[derive(Debug, Clone)]
55pub struct FileInfoCacheItem {
56    pub file_info: FileInfo,
57    pub cache_time: DateTime<Utc>,
58}
59
60impl FileInfoCacheItem {
61    pub fn new(file_info: FileInfo) -> Self {
62        Self {
63            file_info,
64            cache_time: Utc::now(),
65        }
66    }
67
68    pub fn is_valid(
69        &self,
70        current_file_size: u64,
71        current_write_time: DateTime<Utc>,
72    ) -> bool {
73        self.file_info.file_size == current_file_size
74            && self.file_info.modified_time
75                == current_write_time.to_rfc3339()
76    }
77
78    pub fn is_expired(
79        &self,
80        expiration_duration: Duration,
81    ) -> bool {
82        Utc::now().signed_duration_since(self.cache_time)
83            >= expiration_duration
84    }
85}
86
87/// 缓存统计信息
88#[derive(Debug, Clone)]
89pub struct CacheStatistics {
90    pub cache_entries: usize,
91    pub max_cache_size: usize,
92    pub expired_entries: usize,
93    pub last_cleanup_time: DateTime<Utc>,
94}
95
96impl CacheStatistics {
97    pub fn usage_percentage(&self) -> f64 {
98        if self.max_cache_size == 0 {
99            0.0
100        } else {
101            (self.cache_entries as f64
102                / self.max_cache_size as f64)
103                * 100.0
104        }
105    }
106}
107
108/// 文件信息缓存
109pub struct FileInfoCache {
110    cache: Arc<Mutex<HashMap<String, FileInfoCacheItem>>>,
111    max_cache_size: usize,
112    cache_expiration: Duration,
113    cleanup_interval: Duration,
114    last_cleanup: Arc<Mutex<DateTime<Utc>>>,
115    hit_count: Arc<Mutex<u64>>,
116    miss_count: Arc<Mutex<u64>>,
117}
118
119impl FileInfoCache {
120    pub fn new(max_cache_size: usize) -> Self {
121        Self {
122            cache: Arc::new(Mutex::new(HashMap::new())),
123            max_cache_size,
124            cache_expiration: Duration::minutes(30), // 30分钟
125            cleanup_interval: Duration::minutes(10), // 10分钟
126            last_cleanup: Arc::new(Mutex::new(Utc::now())),
127            hit_count: Arc::new(Mutex::new(0)),
128            miss_count: Arc::new(Mutex::new(0)),
129        }
130    }
131
132    /// 从缓存中获取文件信息
133    pub fn get<P: AsRef<std::path::Path>>(
134        &self,
135        file_path: P,
136    ) -> Option<FileInfo> {
137        let path_str = file_path
138            .as_ref()
139            .to_string_lossy()
140            .to_string();
141        let mut cache = self.cache.lock().ok()?;
142
143        // 执行定期清理
144        let _ = self.perform_periodic_cleanup(&mut cache);
145
146        if let Some(item) = cache.get(&path_str) {
147            // 检查文件是否已修改
148            if let Ok(metadata) =
149                std::fs::metadata(&file_path)
150            {
151                if let Ok(modified_time) =
152                    metadata.modified()
153                {
154                    let modified_datetime =
155                        DateTime::<Utc>::from(
156                            modified_time,
157                        );
158                    if item.is_valid(
159                        metadata.len(),
160                        modified_datetime,
161                    ) {
162                        // 缓存命中
163                        if let Ok(mut hit_count) =
164                            self.hit_count.lock()
165                        {
166                            *hit_count += 1;
167                        }
168                        return Some(
169                            item.file_info.clone(),
170                        );
171                    }
172                }
173            }
174        }
175
176        // 缓存未命中
177        if let Ok(mut miss_count) = self.miss_count.lock() {
178            *miss_count += 1;
179        }
180
181        None
182    }
183
184    /// 向缓存中插入文件信息
185    pub fn insert<P: AsRef<std::path::Path>>(
186        &self,
187        file_path: P,
188        file_info: FileInfo,
189    ) {
190        let path_str = file_path
191            .as_ref()
192            .to_string_lossy()
193            .to_string();
194
195        if let Ok(mut cache) = self.cache.lock() {
196            let item = FileInfoCacheItem::new(file_info);
197            cache.insert(path_str, item);
198
199            // 检查缓存大小限制
200            if cache.len() > self.max_cache_size {
201                let _ = self
202                    .cleanup_expired_entries(&mut cache);
203
204                // 如果清理后仍然超过限制,移除最旧的条目
205                if cache.len() > self.max_cache_size {
206                    let oldest_key = cache
207                        .iter()
208                        .min_by_key(|(_, item)| {
209                            item.cache_time
210                        })
211                        .map(|(key, _)| key.clone());
212
213                    if let Some(key) = oldest_key {
214                        cache.remove(&key);
215                    }
216                }
217            }
218        }
219    }
220
221    /// 获取缓存统计信息
222    pub fn get_cache_stats(&self) -> CacheStats {
223        let cache_entries = self
224            .cache
225            .lock()
226            .map(|cache| cache.len())
227            .unwrap_or(0);
228
229        let hit_count = self
230            .hit_count
231            .lock()
232            .map(|guard| *guard)
233            .unwrap_or(0);
234        let miss_count = self
235            .miss_count
236            .lock()
237            .map(|guard| *guard)
238            .unwrap_or(0);
239
240        let mut stats = CacheStats {
241            cache_entries,
242            hit_count,
243            miss_count,
244            hit_rate: 0.0,
245        };
246
247        stats.update_hit_rate();
248        stats
249    }
250
251    fn perform_periodic_cleanup(
252        &self,
253        cache: &mut HashMap<String, FileInfoCacheItem>,
254    ) -> Result<(), String> {
255        let mut last_cleanup = self
256            .last_cleanup
257            .lock()
258            .map_err(|_| "清理时间锁定失败")?;
259        let now = Utc::now();
260
261        if now.signed_duration_since(*last_cleanup)
262            >= self.cleanup_interval
263        {
264            self.cleanup_expired_entries(cache)?;
265            *last_cleanup = now;
266        }
267
268        Ok(())
269    }
270
271    fn cleanup_expired_entries(
272        &self,
273        cache: &mut HashMap<String, FileInfoCacheItem>,
274    ) -> Result<(), String> {
275        let expired_keys: Vec<String> = cache
276            .iter()
277            .filter(|(_, item)| {
278                item.is_expired(self.cache_expiration)
279            })
280            .map(|(key, _)| key.clone())
281            .collect();
282
283        for key in expired_keys {
284            cache.remove(&key);
285        }
286
287        Ok(())
288    }
289
290    pub fn invalidate_file(
291        &self,
292        file_path: &str,
293    ) -> Result<(), String> {
294        let mut cache = self
295            .cache
296            .lock()
297            .map_err(|_| "缓存锁定失败")?;
298        cache.remove(file_path);
299        Ok(())
300    }
301
302    pub fn clear(&self) -> Result<(), String> {
303        let mut cache = self
304            .cache
305            .lock()
306            .map_err(|_| "缓存锁定失败")?;
307        cache.clear();
308        Ok(())
309    }
310
311    pub fn get_statistics(
312        &self,
313    ) -> Result<CacheStatistics, String> {
314        let cache = self
315            .cache
316            .lock()
317            .map_err(|_| "缓存锁定失败")?;
318
319        let expired_entries = cache
320            .values()
321            .filter(|item| {
322                item.is_expired(self.cache_expiration)
323            })
324            .count();
325
326        let last_cleanup = *self
327            .last_cleanup
328            .lock()
329            .map_err(|_| "清理时间锁定失败")?;
330
331        Ok(CacheStatistics {
332            cache_entries: cache.len(),
333            max_cache_size: self.max_cache_size,
334            expired_entries,
335            last_cleanup_time: last_cleanup,
336        })
337    }
338}
339
340impl Default for FileInfoCache {
341    fn default() -> Self {
342        Self::new(1000)
343    }
344}