pcapfile_io/business/
cache.rs1use chrono::{DateTime, Duration, Utc};
6use std::collections::HashMap;
7use std::sync::{Arc, Mutex};
8
9use crate::data::models::FileInfo;
10
11#[derive(Debug, Clone)]
13pub struct CacheStats {
14 pub cache_entries: usize,
16 pub hit_count: u64,
18 pub miss_count: u64,
20 pub hit_rate: f64,
22}
23
24impl CacheStats {
25 #[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 #[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#[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#[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
110pub 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), cleanup_interval: Duration::minutes(10), 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 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 let _ = self.perform_periodic_cleanup(&mut cache);
147
148 if let Some(item) = cache.get(&path_str) {
149 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 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 if let Ok(mut miss_count) = self.miss_count.lock() {
180 *miss_count += 1;
181 }
182
183 None
184 }
185
186 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 if cache.len() > self.max_cache_size {
203 let _ = self
204 .cleanup_expired_entries(&mut cache);
205
206 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 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}