258 lines
6.5 KiB
Rust
258 lines
6.5 KiB
Rust
//! Data Cache
|
|
//!
|
|
//! Store items for some time
|
|
|
|
use alloc::rc::Rc;
|
|
use alloc::vec::Vec;
|
|
use core::ops::Sub;
|
|
|
|
pub trait Cachable<K>
|
|
where
|
|
K: core::cmp::PartialEq,
|
|
{
|
|
fn key(&self) -> K;
|
|
}
|
|
|
|
pub trait Initable<T, K>
|
|
where
|
|
T: Cachable<K>,
|
|
K: core::cmp::PartialEq,
|
|
{
|
|
fn init(key: K) -> T;
|
|
}
|
|
|
|
struct CacheItem<K, T>
|
|
where
|
|
T: Cachable<K>,
|
|
K: core::cmp::PartialEq,
|
|
{
|
|
timestamp: chrono::NaiveDateTime,
|
|
data: Rc<core::cell::RefCell<T>>,
|
|
phantom: core::marker::PhantomData<K>, // rust wants this
|
|
}
|
|
|
|
impl<K, T> core::fmt::Display for CacheItem<K, T>
|
|
where
|
|
T: Cachable<K>,
|
|
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<K, T> CacheItem<K, T>
|
|
where
|
|
T: Cachable<K>,
|
|
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<K>
|
|
where
|
|
K: core::fmt::Display,
|
|
{
|
|
KeyNotFound(K),
|
|
InsertKeyAlreadyPresent(K),
|
|
}
|
|
|
|
impl<K> core::fmt::Display for CacheError<K>
|
|
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<K, T>
|
|
where
|
|
T: Cachable<K>,
|
|
K: core::cmp::PartialEq,
|
|
{
|
|
lifetime: chrono::Duration,
|
|
items: Vec<CacheItem<K, T>>,
|
|
}
|
|
|
|
impl<K, T> core::fmt::Display for Cache<K, T>
|
|
where
|
|
T: Cachable<K>,
|
|
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<K, T> Cache<K, T>
|
|
where
|
|
T: Cachable<K> + 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<usize> {
|
|
self.items
|
|
.iter()
|
|
.position(|i| i.data.borrow().key() == *key)
|
|
}
|
|
|
|
/// Retrieves all known keys
|
|
pub fn keys(&self) -> Vec<K> {
|
|
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<K>> {
|
|
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<Rc<core::cell::RefCell<T>>> {
|
|
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<P>(&self, predicate: P) -> Vec<Rc<core::cell::RefCell<T>>>
|
|
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<F>(
|
|
&mut self,
|
|
time_now: chrono::NaiveDateTime,
|
|
key: K,
|
|
func: F,
|
|
) -> Result<(), CacheError<K>>
|
|
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<F>(&mut self, time_now: chrono::NaiveDateTime, key: K, mut func: F)
|
|
where
|
|
F: FnMut(&mut T),
|
|
K: core::fmt::Display,
|
|
T: Initable<T, K>,
|
|
{
|
|
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
|
|
});
|
|
}
|
|
}
|