//! Data Cache //! //! Store items for some time use alloc::rc::Rc; use alloc::vec::Vec; use core::ops::Sub; pub trait Cachable where K: core::cmp::PartialEq, { fn key(&self) -> K; } pub trait Initable where T: Cachable, K: core::cmp::PartialEq, { fn init(key: K) -> T; } struct CacheItem where T: Cachable, K: core::cmp::PartialEq, { timestamp: chrono::NaiveDateTime, data: Rc>, phantom: core::marker::PhantomData, // rust wants this } impl core::fmt::Display for CacheItem where T: Cachable, K: core::cmp::PartialEq + core::fmt::Display, { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!( f, "CacheItem({}, {:?})", self.data.borrow().key(), self.timestamp ) } } impl CacheItem where T: Cachable, K: core::cmp::PartialEq, { fn new(time_now: chrono::NaiveDateTime, data: T) -> Self { Self { timestamp: time_now, data: Rc::new(core::cell::RefCell::new(data)), phantom: core::marker::PhantomData, } } } #[derive(Debug, Clone)] pub enum CacheError where K: core::fmt::Display, { KeyNotFound(K), InsertKeyAlreadyPresent(K), } impl core::fmt::Display for CacheError where K: core::fmt::Display, { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { CacheError::KeyNotFound(key) => write!(f, "Key '{key}' doesn't exist in cache"), CacheError::InsertKeyAlreadyPresent(key) => { write!(f, "Insert failed b/c key '{key}' already exists in cache") } } } } /// Item cache /// /// Stores items for a certain time. /// Items need to implement `Cachable` so that an itentifier (key) can be retreived from them. pub struct Cache where T: Cachable, K: core::cmp::PartialEq, { lifetime: chrono::Duration, items: Vec>, } impl core::fmt::Display for Cache where T: Cachable, K: core::cmp::PartialEq + core::fmt::Display, { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "Cache({}, {} items)", self.lifetime, self.items.len()) } } #[allow(unused)] impl Cache where T: Cachable + Clone, K: core::cmp::PartialEq + core::fmt::Display, { /// Creates a new cache with a certain item lifetime pub const fn new(lifetime: chrono::Duration) -> Self { Self { lifetime, items: alloc::vec![], } } /// Determines if a key is in the cache pub fn contains(&mut self, key: &K) -> bool { self.items .iter() .find(|i| i.data.borrow().key() == *key) .is_some() } /// Determines the index of a certain key fn find(&mut self, key: &K) -> Option { self.items .iter() .position(|i| i.data.borrow().key() == *key) } /// Retrieves all known keys pub fn keys(&self) -> Vec { self.items.iter().map(|i| i.data.borrow().key()).collect() } /// Adds a new item to the cache /// /// Call [`Self::update`] to update the contents of the cached item /// or just to bump the timestamp. /// /// # Errors /// Fails if the specified key already exist pub fn insert( &mut self, time_now: chrono::NaiveDateTime, data: T, ) -> Result<(), CacheError> { let key = data.key(); if self.contains(&key) { Err(CacheError::InsertKeyAlreadyPresent(key)) } else { self.items.push(CacheItem::new(time_now, data)); Ok(()) } } /// Retrieves an item from the cache /// /// Returns `None` if no item exists for this key pub fn get(&mut self, key: &K) -> Option>> { self.items.iter().find_map(|i| { if i.data.borrow().key() == *key { Some(i.data.clone()) } else { None } }) } /// Retrieves items from the cache which match the predicate pub fn get_filtered

(&self, predicate: P) -> Vec>> where P: Fn(&T) -> bool, { self.items .iter() .filter_map(|i| { let data = i.data.borrow(); if predicate(&data) { Some(i.data.clone()) } else { None } }) .collect() } /// Updates an item in the cache using the provided function /// /// This can also be used to just bump the timestamp of the cached item by providing an empty closure. /// /// # Errors /// Fails if the specified key does not exist in the cache pub fn update( &mut self, time_now: chrono::NaiveDateTime, key: K, func: F, ) -> Result<(), CacheError> where F: Fn(&mut T), K: core::fmt::Display, { // find item let Some(pos) = self.find(&key) else { return Err(CacheError::KeyNotFound(key)); }; // unwrap is fine since we fetched the position before let item = self.items.get_mut(pos).unwrap(); let mut data = item.data.borrow_mut(); func(&mut data); item.timestamp = time_now; Ok(()) } /// Updates an item in the cache using the provided function or create a new item with default values pub fn update_or_init(&mut self, time_now: chrono::NaiveDateTime, key: K, mut func: F) where F: FnMut(&mut T), K: core::fmt::Display, T: Initable, { if let Some(pos) = self.find(&key) { // update existsting item // unwrap is fine since we fetched the position before let item = self.items.get_mut(pos).unwrap(); let mut data = item.data.borrow_mut(); func(&mut data); item.timestamp = time_now; } else { // insert new item self.items.push(CacheItem::new(time_now, T::init(key))); } } /// Cleans items which timed out from the cache /// /// Call this regularly to prune old items. pub fn prune(&mut self, time_now: chrono::NaiveDateTime) { self.items.retain(|i| { let age = time_now.sub(i.timestamp); age < self.lifetime }); } }