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 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 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#[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#[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
108pub 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), cleanup_interval: Duration::minutes(10), 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 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 let _ = self.perform_periodic_cleanup(&mut cache);
145
146 if let Some(item) = cache.get(&path_str) {
147 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 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 if let Ok(mut miss_count) = self.miss_count.lock() {
178 *miss_count += 1;
179 }
180
181 None
182 }
183
184 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 if cache.len() > self.max_cache_size {
201 let _ = self
202 .cleanup_expired_entries(&mut cache);
203
204 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 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}