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 crate::config::CacheConfig;
28use crate::object::CacheObject;
29use crate::utils::{expand_path, validate_name};
30use crate::{CacheError, CacheResult};
31use chrono::{DateTime, Local};
32use std::collections::HashMap;
33use std::time::SystemTime;
34
35fn time_format(time: SystemTime, format: &str) -> String {
36    let datetime: DateTime<Local> = time.into();
37    datetime.format(format).to_string()
38}
39
40/// Main cache manager handling multiple cache objects
41pub struct Cache {
42    config: CacheConfig,
43    objects: HashMap<String, CacheObject>,
44    next_id: u32
45}
46
47impl Cache {
48    /// Creates a new Cache with given configuration
49    ///
50    /// # Parameters
51    /// - `config: CacheConfig` - Cache configuration
52    ///
53    /// # Returns
54    /// New Cache instance
55    pub fn new(config: CacheConfig) -> CacheResult<Self> {
56        Ok(Cache {
57            config,
58            objects: HashMap::new(),
59            next_id: 1
60        })
61    }
62
63    /// Creates a new cache object with optional custom configuration
64    ///
65    /// # Parameters
66    /// - `name: &str` - Cache object identifier
67    /// - `custom_config: Option<&str>` - Optional JSON configuration override
68    ///
69    /// # Returns
70    /// New CacheObject instance
71    pub fn create(&mut self, name: &str, custom_config: Option<&str>) -> CacheResult<CacheObject> {
72        validate_name(name)?;
73
74        if self.objects.contains_key(name) {
75            return Err(CacheError::AlreadyExists(format!(
76                "Cache object '{}' already exists",
77                name
78            )));
79        }
80
81        let id = self.next_id;
82        self.next_id += 1;
83
84        let mut merged_config = self.config.clone();
85
86        if let Some(config_str) = custom_config {
87            match serde_json::from_str::<CacheConfig>(config_str) {
88                Ok(custom) => {
89                    if !custom.path.windows.is_empty() {
90                        merged_config.path.windows = custom.path.windows.clone();
91                    }
92                    if !custom.path.linux.is_empty() {
93                        merged_config.path.linux = custom.path.linux.clone();
94                    }
95
96                    if !custom.format.filename.is_empty() {
97                        merged_config.format.filename = custom.format.filename.clone();
98                    }
99                    if !custom.format.time.is_empty() {
100                        merged_config.format.time = custom.format.time.clone();
101                    }
102                }
103                Err(e) => return Err(CacheError::ConfigParse(e.to_string())),
104            }
105        }
106
107        let cache_path = if cfg!(windows) {
108            expand_path(&merged_config.path.windows)
109        } else {
110            expand_path(&merged_config.path.linux)
111        };
112
113        let filename = merged_config
114            .format
115            .filename
116            .replace("{name}", name)
117            .replace("{id}", &id.to_string())
118            .replace(
119                "{time}",
120                &time_format(SystemTime::now(), &merged_config.format.time),
121            );
122
123        let full_path = std::path::PathBuf::from(&cache_path).join(&filename);
124
125        #[cfg(windows)]
126        let full_path = std::path::PathBuf::from(full_path.to_string_lossy().replace('/', "\\"));
127
128        // Create directory if it doesn't exist
129        if let Some(parent) = full_path.parent() {
130            std::fs::create_dir_all(parent).map_err(|e| {
131                CacheError::InvalidPath(format!("Failed to create cache directory: {}", e))
132            })?;
133        }
134
135        let cache_object = CacheObject::new(name.to_string(), full_path.clone(), id);
136
137        #[cfg(unix)]
138        {
139            use std::os::unix::fs::PermissionsExt;
140            let perms = std::fs::Permissions::from_mode(0o600); // rw-------
141            if let Ok(file) = std::fs::File::create(&full_path) {
142                file.set_permissions(perms)
143                    .map_err(|e| CacheError::PermissionDenied(e.to_string()))?;
144            }
145        }
146
147        self.objects.insert(name.to_string(), cache_object.clone());
148
149        Ok(cache_object)
150    }
151
152    /// Retrieves an existing cache object by name
153    ///
154    /// # Parameters
155    /// - `name: &str` - Cache object identifier
156    ///
157    /// # Returns
158    /// `CacheResult<CacheObject>` - Retrieved cache object or error
159    pub fn get(&self, name: &str) -> CacheResult<CacheObject> {
160        self.objects
161            .get(name)
162            .cloned()
163            .ok_or_else(|| CacheError::NotFound(format!("Cache object '{}' not found", name)))
164    }
165
166    /// Returns the number of cache objects
167    ///
168    /// # Returns
169    /// `usize` - Count of cache objects
170    pub fn len(&self) -> usize {
171        self.objects.len()
172    }
173
174    /// Check if the cache list is empty
175    ///
176    /// # Returns
177    /// `bool` - True if the cache list is empty, false otherwise
178    pub fn is_empty(&self) -> bool {
179        self.objects.is_empty()
180    }
181
182    /// Removes a cache object by name
183    ///
184    /// # Parameters
185    /// - `name: &str` - Cache object identifier
186    ///
187    /// # Returns
188    /// `CacheResult<()>` - Success or error
189    pub fn remove(&mut self, name: &str) -> CacheResult<()> {
190        if let Some(cache_obj) = self.objects.remove(name) {
191            cache_obj.delete()?;
192        }
193        Ok(())
194    }
195
196    /// Clears all cache objects
197    ///
198    /// # Returns
199    /// `CacheResult<()>` - Success or error
200    pub fn clear(&mut self) -> CacheResult<()> {
201        let mut errors = Vec::new();
202
203        for (name, cache_obj) in &self.objects {
204            if let Err(e) = cache_obj.delete() {
205                errors.push(format!("Failed to delete cache object '{}': {}", name, e));
206            }
207        }
208
209        self.objects.clear();
210
211        if !errors.is_empty() {
212            return Err(CacheError::Generic(format!(
213                "Errors occurred while clearing cache: {}",
214                errors.join("; ")
215            )));
216        }
217
218        Ok(())
219    }
220
221    /// Updates the cache configuration
222    ///
223    /// # Parameters
224    /// - `config: CacheConfig` - New configuration
225    pub fn set_config(&mut self, config: CacheConfig) {
226        self.config = config;
227    }
228
229    /// Returns current cache configuration
230    ///
231    /// # Returns
232    /// `CacheConfig` - Current configuration
233    pub fn get_config(&self) -> CacheConfig {
234        self.config.clone()
235    }
236
237    /// Returns iterator over all cache objects
238    ///
239    /// # Returns
240    /// `impl Iterator<Item = &CacheObject>` - Iterator over cache objects
241    pub fn iter(&self) -> impl Iterator<Item = &CacheObject> {
242        self.objects.values()
243    }
244}