libsubconverter/utils/
memory_cache.rs

1use crate::utils::system::safe_system_time;
2use once_cell::sync::Lazy;
3use std::collections::HashMap;
4use std::sync::{Arc, Mutex};
5use std::time::{Duration, SystemTime};
6
7/// Global in-memory cache for storing ruleset content and other frequently accessed data
8static MEMORY_CACHE: Lazy<Arc<Mutex<MemoryCache>>> =
9    Lazy::new(|| Arc::new(Mutex::new(MemoryCache::new())));
10
11/// Structure to hold cached content along with metadata
12#[derive(Clone)]
13struct CachedItem {
14    /// The content stored in the cache
15    content: String,
16    /// When this item was stored
17    timestamp: SystemTime,
18}
19
20/// Memory cache manager
21struct MemoryCache {
22    /// Map of cache keys to cached content
23    cache: HashMap<String, CachedItem>,
24}
25
26impl MemoryCache {
27    /// Create a new empty memory cache
28    fn new() -> Self {
29        Self {
30            cache: HashMap::new(),
31        }
32    }
33}
34
35/// Store content in the in-memory cache
36///
37/// # Arguments
38/// * `key` - Cache key (usually a path or URL)
39/// * `content` - Content to store
40///
41/// # Returns
42/// * `Ok(())` - Content was successfully stored
43/// * `Err(String)` - Error storing content
44pub fn store(key: &str, content: &str) -> Result<(), String> {
45    let mut cache = match MEMORY_CACHE.lock() {
46        Ok(cache) => cache,
47        Err(e) => return Err(format!("Failed to lock memory cache: {}", e)),
48    };
49
50    // Store the content with current timestamp
51    cache.cache.insert(
52        key.to_string(),
53        CachedItem {
54            content: content.to_string(),
55            timestamp: safe_system_time(),
56        },
57    );
58
59    Ok(())
60}
61
62/// Retrieve content from the in-memory cache
63///
64/// # Arguments
65/// * `key` - Cache key (usually a path or URL)
66///
67/// # Returns
68/// * `Some(String)` - Content was found
69/// * `None` - Content was not found
70pub fn get(key: &str) -> Option<String> {
71    let cache = match MEMORY_CACHE.lock() {
72        Ok(cache) => cache,
73        Err(_) => return None,
74    };
75
76    cache.cache.get(key).map(|item| item.content.clone())
77}
78
79/// Check if a key exists in the in-memory cache
80///
81/// # Arguments
82/// * `key` - Cache key to check
83///
84/// # Returns
85/// * `true` - Key exists in cache
86/// * `false` - Key does not exist in cache
87pub fn exists(key: &str) -> bool {
88    let cache = match MEMORY_CACHE.lock() {
89        Ok(cache) => cache,
90        Err(_) => return false,
91    };
92
93    cache.cache.contains_key(key)
94}
95
96/// Check if a key exists in the in-memory cache and is not expired
97///
98/// # Arguments
99/// * `key` - Cache key to check
100/// * `max_age` - Maximum age in seconds
101///
102/// # Returns
103/// * `true` - Key exists and is not expired
104/// * `false` - Key does not exist or is expired
105pub fn is_valid(key: &str, max_age: u32) -> bool {
106    let cache = match MEMORY_CACHE.lock() {
107        Ok(cache) => cache,
108        Err(_) => return false,
109    };
110
111    if let Some(item) = cache.cache.get(key) {
112        let now = safe_system_time();
113        if let Ok(elapsed) = now.duration_since(item.timestamp) {
114            return elapsed.as_secs() < u64::from(max_age);
115        }
116    }
117
118    false
119}
120
121/// Retrieve content from the in-memory cache if it exists and is not expired
122///
123/// # Arguments
124/// * `key` - Cache key (usually a path or URL)
125/// * `max_age` - Maximum age in seconds
126///
127/// # Returns
128/// * `Some(String)` - Content was found and is not expired
129/// * `None` - Content was not found or is expired
130pub fn get_if_valid(key: &str, max_age: u32) -> Option<String> {
131    let cache = match MEMORY_CACHE.lock() {
132        Ok(cache) => cache,
133        Err(_) => return None,
134    };
135
136    if let Some(item) = cache.cache.get(key) {
137        let now = safe_system_time();
138        if let Ok(elapsed) = now.duration_since(item.timestamp) {
139            if elapsed.as_secs() < u64::from(max_age) {
140                return Some(item.content.clone());
141            }
142        }
143    }
144
145    None
146}
147
148/// Remove an item from the cache
149///
150/// # Arguments
151/// * `key` - Cache key to remove
152pub fn remove(key: &str) {
153    if let Ok(mut cache) = MEMORY_CACHE.lock() {
154        cache.cache.remove(key);
155    }
156}
157
158/// Clear all items from the cache
159pub fn clear() {
160    if let Ok(mut cache) = MEMORY_CACHE.lock() {
161        cache.cache.clear();
162    }
163}
164
165/// Get the number of items in the cache
166pub fn size() -> usize {
167    if let Ok(cache) = MEMORY_CACHE.lock() {
168        return cache.cache.len();
169    }
170    0
171}
172
173/// Clear expired items from the cache
174///
175/// # Arguments
176/// * `max_age` - Maximum age in seconds
177pub fn clean_expired(max_age: u32) {
178    if let Ok(mut cache) = MEMORY_CACHE.lock() {
179        let now = safe_system_time();
180        let max_duration = Duration::from_secs(u64::from(max_age));
181
182        // Use retain to keep only non-expired items
183        cache.cache.retain(|_, item| {
184            if let Ok(elapsed) = now.duration_since(item.timestamp) {
185                elapsed <= max_duration
186            } else {
187                // If we can't calculate duration (clock went backwards), keep the item
188                true
189            }
190        });
191    }
192}