anycache/
lib.rs

1use std::error::Error;
2use std::io::Read;
3use std::sync::{Arc, RwLock};
4use std::hash::Hash;
5use std::{fs, thread};
6use std::time::Duration;
7use tokio::sync::RwLock as TokioRwLock;
8
9
10/// Provides a reliable cache for HashMap<Key, Value> where value can be derived from key, but could be expensive to generate.
11/// The cache is thread safe and can be used in multi-threaded environment.
12/// The cache is not async, so it is not suitable for async environment.
13/// For async cache, use CasheAsync instead.
14/// 
15/// ```
16/// use anycache::Cache;
17/// use anycache::CacheAsync;
18/// 
19/// fn my_gen(x:&String) -> String {
20///   println!("Generating {}", x);
21///   let mut y = x.clone();
22///   y.push_str("@");
23///   y
24/// }
25/// 
26/// // For async cache, use CacheAsync
27/// fn test_sync_cache() {
28///   let c = Cache::new(my_gen);
29/// 
30///   for j in 0..2 {
31///     for i in 0..10 {
32///       let key = format!("key{}", i);
33///       let v = c.get(&key);
34///       println!("{}:{}: {}", j, i, *v);
35///     }
36///   }
37/// }
38/// 
39/// // For sync cache, use Cache
40/// async fn test_cache_async() {
41///   // Cache is only generated once above. Similarly, for Async.
42/// 
43///   let c = CacheAsync::new(my_gen);
44/// 
45///   for j in 0..2 {
46///     for i in 0..10 {
47///       let key = format!("key{}", i);
48///       let v = c.get(&key).await;
49///       println!("{}:{}: {}", j, i, *v);
50///     }
51///   }
52/// }
53/// 
54/// ```
55
56
57/// Create a Cache with K, V type. Similar to Map. 
58/// However, the value is generated from key on-demand using generate function
59pub struct Cache<K,V> where 
60    K: Hash + std::cmp::Eq + Clone,
61{
62    map: Arc<RwLock<std::collections::HashMap<K, Arc<V>>>>,
63    generator: Generator<K, V>,
64}
65
66// Same as Cache, but using Async RwLock
67pub struct CacheAsync<K, V> where 
68    K: Hash + std::cmp::Eq + Clone,
69{
70    map: Arc<TokioRwLock<std::collections::HashMap<K, Arc<V>>>>,
71    generator: GeneratorAsync<K, V>,
72}
73
74/// A generator function place holder
75struct Generator<K, V> where 
76K:Hash + Eq + Clone 
77{
78    generator: Box<dyn Fn(&K) -> V + 'static + Sync>
79}
80
81struct GeneratorAsync<K, V> where 
82    K:Hash + Eq + Clone 
83{
84    generator: Box<dyn Fn(&K) -> V + 'static + Sync>
85}
86
87impl <K, V> Cache<K,V> where 
88    K:Hash + Eq + Clone 
89{
90    /// Create a new cache using the given generator function
91    pub fn new(generator:impl Fn(&K) -> V + 'static + Sync) -> Self 
92        where K: Hash + Eq {
93        Cache {
94            map: Arc::new(RwLock::new(std::collections::HashMap::new())),
95            generator: Generator{generator: Box::new(generator)},
96        }
97    }
98
99    /// Get from the cache. If the key is missing, do not generate it and return None
100    pub fn get_if(&self, key: &K) -> Option<Arc<V>> {
101        let r = self.map.read().unwrap();
102        let value = r.get(key);
103        match value {
104            Some(v) => Some(Arc::clone(v)),
105            None => None
106        }
107    }
108
109    /// Drop the key from the cache if it exists
110    /// Does nothing if the key is not there
111    pub fn drop(&self, key: &K) {
112        let mut w = self.map.write().unwrap();
113        w.remove(key);
114    }
115
116    /// Get the key from cache. If not found, generate one.
117    pub fn get(&self, key: &K) -> Arc<V> {
118        let r = self.map.read().unwrap();
119        let value = r.get(key);
120        match value {
121            Some(v) => Arc::clone(v),
122            None => {
123                drop(r);
124                let mut w = self.map.write().unwrap();
125                let value = (self.generator.generator)(key);
126                let arc = Arc::new(value);
127                w.insert(key.clone(), Arc::clone(&arc));
128                drop(w);
129                arc
130            }
131        }
132    }
133}
134
135
136/// Similar to Cache, but using async RwLock
137impl <K, V> CacheAsync<K,V> where 
138    K:Hash + Eq + Clone 
139{
140    /// Create a new cache using the given generator function
141    pub fn new(generator:impl Fn(&K) -> V + 'static + Sync) -> Self 
142        where K: Hash + Eq {
143        CacheAsync {
144            map: Arc::new(TokioRwLock::new(std::collections::HashMap::new())),
145            generator: GeneratorAsync{generator: Box::new(generator)},
146        }
147    }
148
149    /// Get from the cache. If the key is missing, do not generate it and return None
150    pub async fn get_if(&self, key: &K) -> Option<Arc<V>> {
151        let r = self.map.read().await;
152        let value = r.get(key);
153        match value {
154            Some(v) => Some(Arc::clone(v)),
155            None => None
156        }
157    }
158
159    /// Drop the key from the cache if it it exists
160    pub async fn drop(&self, key: &K) {
161        let mut w = self.map.write().await;
162        w.remove(key);
163    }
164    /// Get the key from cache. If not found, generate one.
165    pub async fn get(&self, key: &K) -> Arc<V> {
166        let r = self.map.read().await;
167        let value = r.get(key);
168        match value {
169            Some(v) => Arc::clone(v),
170            None => {
171                drop(r);
172                let mut w = self.map.write().await;
173                let value = (self.generator.generator)(key);
174                let arc = Arc::new(value);
175                w.insert(key.clone(), Arc::clone(&arc));
176                drop(w);
177                arc
178            }
179        }
180    }
181}
182
183/// FromWatchedFile is a struct that reads a file and watches for changes to the file.
184/// When the file changes, the struct will reload the file and update the value in the background.
185/// This struct is useful for reloading configuration files or other files that are read frequently.
186/// It is thread safe. Note: Each FromWatchedFile spawns a new thread to watch the file do not use too many of them!
187/// 
188/// ```
189/// 
190/// // Your load function can return Option<T> where T is the desired type
191/// // If your function returns None, the file will not be reloaded, and the current modified version
192/// // of the file is not retried. Until it is modified again.
193/// fn load_file_from_bytes(bytes: &[u8]) -> Result<String, FileParseError> {
194///     Ok(String::from_utf8_lossy(bytes).to_string())
195/// }
196/// 
197/// // Initialize the FromWatchedFile struct
198/// let cfg: FromWatchedFile<String> = FromWatchedFile::new("config.json", load_file_from_bytes, Duration::from_secs(5));
199/// let config = cfg.get();
200/// match config {
201///     Ok(c) => {
202///         println!("Config: {}", c); // c is Arc of your type. Cloned on each get - pointer only
203///         // Do something with the config
204///         // whenever you call .get(), it is the current version of the config.
205///     },
206///     Err(cause) => println!("Config not loaded yet"), 
207///     // err is the first cause if it ever happens. Because subsequent load is either successful (no error), 
208///     // or error (no replace). So the cache always keep a valid copy of the reference if it ever happened.
209/// }
210/// ```
211pub struct FromWatchedFile<T> {
212    value: Arc<RwLock<Result<Arc<T>, FileParseError>>>,
213}
214
215#[derive(Debug, Clone)]
216pub struct FileParseError {
217    cause: String
218}
219
220impl From<String> for FileParseError {
221    fn from(value: String) -> Self {
222        FileParseError {
223            cause: value
224        }
225    }
226}
227
228impl From<&str> for FileParseError {
229    fn from(value:&str) -> Self {
230        value.to_string().into()
231    }
232}
233
234impl From<Box<dyn Error>> for FileParseError {
235    fn from(value: Box<dyn Error>) -> Self {
236        format!("{}", value).into()
237    }
238}
239
240impl std::fmt::Display for FileParseError {
241    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
242        write!(f, "{}", self.cause)
243    }
244}
245
246impl Error for FileParseError {
247}
248impl<T> FromWatchedFile<T>
249where
250    T: Send + Sync + 'static,
251{
252    /// Read bytes from file
253    fn read_file(file_path: &str) -> Result<Vec<u8>, std::io::Error> {
254        let mut file = fs::File::open(file_path)?;
255        let mut contents = Vec::new();
256        file.read_to_end(&mut contents)?;
257        Ok(contents)
258    }
259
260    /// Create a new FromWatchedFile struct and spawn a new thread with given interval and converter function.
261    /// Converter function converts a slice of bytes to the desired type.
262    /// 
263    /// The file will be check based on interval. On change detected, the parser will be used
264    /// to convert the file content to desired type.
265    /// 
266    /// You can get the latest copy using the `get` method.
267    /// 
268    /// Upon initialization, the first copy will be constructed.
269    /// 
270    /// The code never fails. If the file gone missing, or the file is not readable, the value will be None.
271    pub fn new<F>(file_path: &str, parser: F, interval: Duration) -> Self
272        where
273        F: Fn(&[u8]) -> Result<T, FileParseError> + Send + Sync + 'static
274    {
275        let current_content = Self::read_file(file_path);
276
277        // initial loading
278        let value = match current_content {
279            Ok(content) => {
280                let parsed = parser(&content);
281                match parsed {
282                    Ok(v) => {
283                        Arc::new(RwLock::new(Ok(Arc::new(v))))
284                    },
285                    Err(cause) => {
286                        Arc::new(RwLock::new(Err(cause)))
287                    }
288                }
289            },
290            Err(cause) => {
291                let err:Box<dyn Error> = Box::new(cause);
292                Arc::new(RwLock::new(Err(FileParseError::from(err))))
293            }
294        };
295        let value_clone = value.clone();
296        let file_path = file_path.to_string();
297
298
299        let mut last_modified = fs::metadata(&file_path).ok().and_then(|m| m.modified().ok());
300        thread::spawn(move || {
301            loop {
302                thread::sleep(interval);
303                let metadata = fs::metadata(&file_path).ok();
304                let modified = metadata.and_then(|m| m.modified().ok());
305
306                if modified != last_modified {
307                    let content = Self::read_file(file_path.as_str());
308                    // we won't load the file again no matter what, until it is changed again...
309                    last_modified = modified;
310                    match content {
311                        Ok(bytes) => {
312                            let parsed_value = parser(&bytes);
313                        
314                            match parsed_value {
315                                Ok(v) => {
316                                    let mut w = value_clone.write().unwrap();
317                                    *w =Ok(Arc::new(v));
318                                },
319                                Err(_) => {
320                                    // parser error - silently ignore
321                                }
322                            }
323                        },
324                        Err(_) => {
325                            // file read error - silently ignore
326                        }
327                    }
328                }
329            }
330        });
331        return Self{
332            value
333        }
334    }
335
336
337    /// Get the desired converted type from the file
338    /// If the file become not readable, it will return the last good copy.
339    pub fn get<'a>(&'a self) -> Result<Arc<T>, FileParseError>
340    {
341        let result = self.value.read().unwrap();
342        match result.as_ref() {
343            Ok(what) => {
344                Ok(Arc::clone(what))
345            },
346            Err(cause) => {
347                Err(cause.clone())
348            }
349        }
350    }
351}
352
353#[cfg(test)]
354mod tests {
355    use thread::sleep;
356
357    use super::*;
358
359    #[test]
360    fn it_works() {
361        println!("Running test...");
362        let c = Cache::new(|x:&String| -> String {
363            println!("Generating {}", x);
364            let mut y = x.clone();
365            y.push_str("@");
366            y
367        });
368
369        for j in 0..2 {
370            for i in 0..10 {
371                let key = format!("key{}", i);
372                let v = c.get(&key);
373                println!("{}:{}: {}", j, i, *v);
374            }
375        }
376    }
377
378    #[tokio::test]
379    async fn test_cache_async() {
380        println!("Running test...");
381        let c = CacheAsync::new(|x:&String| -> String {
382            println!("Generating {}", x);
383            let mut y = x.clone();
384            y.push_str("@");
385            y
386        });
387
388        for j in 0..2 {
389            for i in 0..10 {
390                let key = format!("key{}", i);
391                let v = c.get(&key).await;
392                println!("{}:{}: {}", j, i, *v);
393            }
394        }
395    }
396
397    #[test]
398    fn test_load_file() {
399        fn file_to_string(bytes: &[u8]) -> Result<String, FileParseError> {
400            Ok(String::from_utf8_lossy(bytes).to_string())
401        }
402    
403        // Initialize the FromWatchedFile struct
404        let cfg: FromWatchedFile<String> = FromWatchedFile::new("config.json", file_to_string, Duration::from_secs(5));
405    
406        for _i in 0..100 {
407            // Access the current value using get_ref()
408            let config = cfg.get();
409            match config {
410                Ok(c) => println!("Config: {}", c),
411                Err(_)=> println!("Config not loaded yet"),
412            }   
413            // Sleep for 5 seconds before checking the config again
414            sleep(Duration::from_secs(1));
415        }
416    }
417}