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}