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