html_translation_lib/storage/
cache.rs1#![allow(dead_code)] use crate::config::TranslationConfig;
9use crate::error::TranslationResult;
10use std::collections::HashMap;
11use std::time::{Duration, SystemTime};
12use std::path::PathBuf;
13
14pub struct CacheManager {
16 memory_cache: HashMap<String, CacheEntry>,
18
19 config: CacheConfig,
21
22 stats: CacheStats,
24}
25
26impl CacheManager {
27 #[cfg(feature = "async")]
29 pub async fn new(translation_config: &TranslationConfig) -> TranslationResult<Self> {
30 let config = CacheConfig {
31 enable_memory_cache: true,
32 enable_persistent_cache: false,
33 max_entries: translation_config.max_cache_entries,
34 ttl: translation_config.cache_ttl,
35 cache_dir: None,
36 };
37
38 let mut manager = Self {
39 memory_cache: HashMap::new(),
40 config,
41 stats: CacheStats::default(),
42 };
43
44 if manager.config.enable_persistent_cache {
46 manager.load_persistent_cache().await?;
47 }
48
49 Ok(manager)
50 }
51
52 #[cfg(feature = "async")]
54 pub async fn get(&self, text: &str) -> TranslationResult<Option<String>> {
55 let key = self.generate_key(text);
56
57 if let Some(entry) = self.memory_cache.get(&key) {
59 if entry.is_valid() {
60 return Ok(Some(entry.translation.clone()));
61 }
62 }
63
64 Ok(None)
67 }
68
69 #[cfg(feature = "async")]
71 pub async fn set(&mut self, text: &str, translation: String) -> TranslationResult<()> {
72 let key = self.generate_key(text);
73 let entry = CacheEntry::new(text.to_string(), translation, self.config.ttl);
74
75 if self.memory_cache.len() >= self.config.max_entries {
77 self.cleanup_expired_entries();
78
79 if self.memory_cache.len() >= self.config.max_entries {
81 self.evict_oldest_entries(self.config.max_entries / 4);
82 }
83 }
84
85 self.memory_cache.insert(key, entry);
86 self.stats.cache_sets += 1;
87
88 Ok(())
89 }
90
91 pub fn cleanup_expired_entries(&mut self) {
93 let now = SystemTime::now();
94 self.memory_cache.retain(|_, entry| {
95 now.duration_since(entry.created_at)
96 .map(|d| d < entry.ttl)
97 .unwrap_or(false)
98 });
99 }
100
101 fn evict_oldest_entries(&mut self, count: usize) {
103 let mut entries: Vec<(String, SystemTime)> = self.memory_cache
104 .iter()
105 .map(|(k, v)| (k.clone(), v.created_at))
106 .collect();
107
108 entries.sort_by_key(|(_, time)| *time);
109
110 for (key, _) in entries.into_iter().take(count) {
111 self.memory_cache.remove(&key);
112 }
113 }
114
115 fn generate_key(&self, text: &str) -> String {
117 use std::collections::hash_map::DefaultHasher;
118 use std::hash::{Hash, Hasher};
119
120 let mut hasher = DefaultHasher::new();
121 text.hash(&mut hasher);
122 format!("{:x}", hasher.finish())
123 }
124
125 pub fn get_stats(&self) -> &CacheStats {
127 &self.stats
128 }
129
130 pub fn clear(&mut self) {
132 self.memory_cache.clear();
133 self.stats.cache_clears += 1;
134 }
135
136 pub fn size(&self) -> usize {
138 self.memory_cache.len()
139 }
140
141 #[cfg(feature = "async")]
143 async fn load_persistent_cache(&mut self) -> TranslationResult<()> {
144 Ok(())
146 }
147
148 #[cfg(feature = "async")]
150 pub async fn save_persistent_cache(&self) -> TranslationResult<()> {
151 Ok(())
153 }
154}
155
156#[derive(Debug, Clone)]
158pub struct CacheConfig {
159 pub enable_memory_cache: bool,
161
162 pub enable_persistent_cache: bool,
164
165 pub max_entries: usize,
167
168 pub ttl: Duration,
170
171 pub cache_dir: Option<PathBuf>,
173}
174
175impl Default for CacheConfig {
176 fn default() -> Self {
177 Self {
178 enable_memory_cache: true,
179 enable_persistent_cache: false,
180 max_entries: 10000,
181 ttl: Duration::from_secs(3600),
182 cache_dir: None,
183 }
184 }
185}
186
187#[derive(Debug, Clone)]
189pub struct CacheEntry {
190 pub original_text: String,
192
193 pub translation: String,
195
196 pub created_at: SystemTime,
198
199 pub ttl: Duration,
201
202 pub access_count: u32,
204
205 pub last_accessed: SystemTime,
207}
208
209impl CacheEntry {
210 pub fn new(original_text: String, translation: String, ttl: Duration) -> Self {
212 let now = SystemTime::now();
213 Self {
214 original_text,
215 translation,
216 created_at: now,
217 ttl,
218 access_count: 0,
219 last_accessed: now,
220 }
221 }
222
223 pub fn is_valid(&self) -> bool {
225 SystemTime::now()
226 .duration_since(self.created_at)
227 .map(|d| d < self.ttl)
228 .unwrap_or(false)
229 }
230
231 pub fn mark_accessed(&mut self) {
233 self.access_count += 1;
234 self.last_accessed = SystemTime::now();
235 }
236
237 pub fn age(&self) -> Duration {
239 SystemTime::now()
240 .duration_since(self.created_at)
241 .unwrap_or(Duration::ZERO)
242 }
243
244 pub fn remaining_ttl(&self) -> Duration {
246 let age = self.age();
247 if age < self.ttl {
248 self.ttl - age
249 } else {
250 Duration::ZERO
251 }
252 }
253}
254
255#[derive(Debug, Default, Clone)]
257pub struct CacheStats {
258 pub cache_hits: usize,
260
261 pub cache_misses: usize,
263
264 pub cache_sets: usize,
266
267 pub cache_clears: usize,
269
270 pub expired_cleanups: usize,
272}
273
274impl CacheStats {
275 pub fn hit_rate(&self) -> f32 {
277 let total = self.cache_hits + self.cache_misses;
278 if total > 0 {
279 self.cache_hits as f32 / total as f32
280 } else {
281 0.0
282 }
283 }
284
285 pub fn reset(&mut self) {
287 *self = Default::default();
288 }
289}