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}