Skip to main content

cache_lite/
cache.rs

1/*
2 * @filename: cache.rs
3 * @description: Main cache manager for cache-lite library
4 * @author: TaimWay <taimway@gmail.com>
5 * 
6 * Copyright (C) 2026 TaimWay
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files (the "Software"), to deal
10 * in the Software without restriction, including without limitation the rights
11 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 * copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in all
16 * copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 * SOFTWARE.
25 */
26
27use std::collections::HashMap;
28use std::time::SystemTime;
29use chrono::{DateTime, Local};
30use crate::config::CacheConfig;
31use crate::object::CacheObject;
32use crate::{CacheError, CacheResult};
33
34fn time_format(time: SystemTime, format: &str) -> String {
35    let datetime: DateTime<Local> = time.into();
36    datetime.format(format).to_string()
37}
38
39/// Main cache manager handling multiple cache objects
40pub struct Cache {
41    config: CacheConfig,
42    objects: HashMap<String, CacheObject>,
43    next_id: u32,
44}
45
46impl Cache {
47    /// Creates a new Cache with given configuration
48    /// 
49    /// # Parameters
50    /// - `config: CacheConfig` - Cache configuration
51    /// 
52    /// # Returns
53    /// New Cache instance
54    pub fn new(config: CacheConfig) -> Self {
55        Cache {
56            config,
57            objects: HashMap::new(),
58            next_id: 1,
59        }
60    }
61
62    /// Creates a new cache object with optional custom configuration
63    /// 
64    /// # Parameters
65    /// - `name: &str` - Cache object identifier
66    /// - `custom_config: Option<&str>` - Optional JSON configuration override
67    /// 
68    /// # Returns
69    /// New CacheObject instance
70    pub fn create(&mut self, name: &str, custom_config: Option<&str>) -> CacheResult<CacheObject> {
71        validate_name(name)?;
72        
73        let id = self.next_id;
74        self.next_id += 1;
75        
76        let mut merged_config = self.config.clone();
77        
78        if let Some(config_str) = custom_config {
79            match serde_json::from_str::<CacheConfig>(config_str) {
80                Ok(custom) => {
81                    if !custom.path.windows.is_empty() {
82                        merged_config.path.windows = custom.path.windows.clone();
83                    }
84                    if !custom.path.linux.is_empty() {
85                        merged_config.path.linux = custom.path.linux.clone();
86                    }
87                    
88                    if !custom.format.filename.is_empty() {
89                        merged_config.format.filename = custom.format.filename.clone();
90                    }
91                    if !custom.format.time.is_empty() {
92                        merged_config.format.time = custom.format.time.clone();
93                    }
94                }
95                Err(e) => return Err(CacheError::ConfigParse(e.to_string())),
96            }
97        }
98        
99        let cache_path = if cfg!(windows) {
100            self.expand_path(&merged_config.path.windows)
101        } else {
102            self.expand_path(&merged_config.path.linux)
103        };
104        
105        let filename = merged_config.format.filename
106            .replace("{name}", name)
107            .replace("{id}", &id.to_string())
108            .replace("{time}", &time_format(SystemTime::now(), &merged_config.format.time));
109            
110        let full_path = std::path::PathBuf::from(&cache_path).join(&filename);
111        
112        #[cfg(windows)]
113        let full_path = std::path::PathBuf::from(
114            full_path.to_string_lossy().replace('/', "\\")
115        );
116        
117        // Create directory if it doesn't exist
118        if let Some(parent) = full_path.parent() {
119            std::fs::create_dir_all(parent).map_err(|e| {
120                CacheError::InvalidPath(format!("Failed to create cache directory: {}", e))
121            })?;
122        }
123        
124        let cache_object = CacheObject::new(
125            name.to_string(),
126            full_path,
127            id
128        );
129        
130        self.objects.insert(name.to_string(), cache_object.clone());
131        
132        Ok(cache_object)
133    }
134
135    /// Expands environment variables in path
136    pub fn expand_path(&self, path: &str) -> String {
137        let mut expanded = path.to_string();
138        
139        // Expand Windows environment variables
140        if cfg!(windows) && path.contains("%") {
141            expanded = self.expand_windows_env_vars(path);
142        }
143        
144        // Expand tilde for home directory (Unix-like systems)
145        if expanded.starts_with('~') {
146            if let Some(home) = dirs::home_dir() {
147                expanded = home.to_string_lossy().to_string() + &expanded[1..];
148            }
149        }
150        
151        #[cfg(windows)]
152        {
153            expanded = expanded.replace('/', "\\");
154        }
155        
156        expanded
157    }
158
159    /// Expands Windows environment variables
160    fn expand_windows_env_vars(&self, path: &str) -> String {
161        use std::env;
162        let mut result = path.to_string();
163        
164        // Simple environment variable expansion
165        if let Ok(temp) = env::var("TEMP") {
166            result = result.replace("%temp%", &temp);
167        }
168        if let Ok(tmp) = env::var("TMP") {
169            result = result.replace("%tmp%", &tmp);
170        }
171        if let Ok(appdata) = env::var("APPDATA") {
172            result = result.replace("%appdata%", &appdata);
173        }
174        if let Ok(localappdata) = env::var("LOCALAPPDATA") {
175            result = result.replace("%localappdata%", &localappdata);
176        }
177        if let Ok(userprofile) = env::var("USERPROFILE") {
178            result = result.replace("%userprofile%", &userprofile);
179        }
180        
181        result
182    }
183
184    /// Retrieves an existing cache object by name
185    /// 
186    /// # Parameters
187    /// - `name: &str` - Cache object identifier
188    /// 
189    /// # Returns
190    /// `CacheResult<CacheObject>` - Retrieved cache object or error
191    pub fn get(&self, name: &str) -> CacheResult<CacheObject> {
192        self.objects.get(name)
193            .cloned()
194            .ok_or_else(|| CacheError::NotFound(format!("Cache object '{}' not found", name)))
195    }
196
197    /// Returns the number of cache objects
198    /// 
199    /// # Returns
200    /// `usize` - Count of cache objects
201    pub fn len(&self) -> usize {
202        self.objects.len()
203    }
204
205    /// Check if the cache list is empty
206    /// 
207    /// # Returns
208    /// `bool` - True if the cache list is empty, false otherwise
209    pub fn is_empty(&self) -> bool {
210        self.objects.is_empty()
211    }
212
213    /// Removes a cache object by name
214    /// 
215    /// # Parameters
216    /// - `name: &str` - Cache object identifier
217    /// 
218    /// # Returns
219    /// `CacheResult<()>` - Success or error
220    pub fn remove(&mut self, name: &str) -> CacheResult<()> {
221        if let Some(cache_obj) = self.objects.remove(name) {
222            cache_obj.delete()?;
223        }
224        Ok(())
225    }
226
227    /// Cleans up expired cache objects
228    /// 
229    /// # Returns
230    /// `CacheResult<u32>` - Number of cleaned objects
231    #[deprecated(note = "Due to being deprecated in its lifecycle, this function only returns 0; please use the Cache::clear()")]
232    pub fn cleanup(&mut self) -> CacheResult<u32> {
233        let count = self.objects.len() as u32;
234        self.clear()?;
235        Ok(count)
236    }
237
238    /// Clears all cache objects
239    /// 
240    /// # Returns
241    /// `CacheResult<()>` - Success or error
242    pub fn clear(&mut self) -> CacheResult<()> {
243        let mut errors = Vec::new();
244        
245        for (name, cache_obj) in &self.objects {
246            if let Err(e) = cache_obj.delete() {
247                errors.push(format!("Failed to delete cache object '{}': {}", name, e));
248            }
249        }
250        
251        self.objects.clear();
252        
253        if !errors.is_empty() {
254            return Err(CacheError::Generic(format!(
255                "Errors occurred while clearing cache: {}", 
256                errors.join("; ")
257            )));
258        }
259        
260        Ok(())
261    }
262
263    /// Updates the cache configuration
264    /// 
265    /// # Parameters
266    /// - `config: CacheConfig` - New configuration
267    pub fn set_config(&mut self, config: CacheConfig) {
268        self.config = config;
269    }
270
271    /// Returns current cache configuration
272    /// 
273    /// # Returns
274    /// `CacheConfig` - Current configuration
275    pub fn get_config(&self) -> CacheConfig {
276        self.config.clone()
277    }
278
279    /// Returns iterator over all cache objects
280    /// 
281    /// # Returns
282    /// `impl Iterator<Item = &CacheObject>` - Iterator over cache objects
283    pub fn iter(&self) -> impl Iterator<Item = &CacheObject> {
284        self.objects.values()
285    }
286}
287
288/// Used to verify if the name is valid
289/// 
290/// # Parameters
291/// - `name: &str` - Cache object identifier
292/// 
293/// # Returns
294/// `CacheResult<()>` - Success or error
295fn validate_name(name: &str) -> CacheResult<()> {
296    if name.is_empty() {
297        return Err(CacheError::InvalidName(
298            "Cache name cannot be empty".to_string()
299        ));
300    }
301    
302    if name.contains("..") || name.contains(std::path::MAIN_SEPARATOR) || 
303       name.contains('/') || name.contains('\\') {
304        return Err(CacheError::InvalidName(
305            "Invalid cache name: contains path components".to_string()
306        ));
307    }
308    
309    #[cfg(windows)]
310    {
311        let reserved_names = ["CON", "PRN", "AUX", "NUL", 
312                             "COM1", "COM2", "COM3", "COM4", "COM5", 
313                             "COM6", "COM7", "COM8", "COM9",
314                             "LPT1", "LPT2", "LPT3", "LPT4", "LPT5",
315                             "LPT6", "LPT7", "LPT8", "LPT9"]; 
316        
317        let uppercase_name = name.to_uppercase();
318        for reserved in &reserved_names {
319            if uppercase_name == *reserved || uppercase_name.starts_with(&format!("{}.", reserved)) {
320                return Err(CacheError::InvalidName(
321                    format!("Cache name '{}' is a reserved system name", name)
322                ));
323            }
324        }
325    }
326    
327    if name.len() > 255 {
328        return Err(CacheError::InvalidName(
329            "Cache name too long (max 255 characters)".to_string()
330        ));
331    }
332    
333    Ok(())
334}