use std::{borrow::Borrow, collections::HashMap, hash::Hash, time::Duration};
use ruma::time::Instant;
const DEFAULT_LIFETIME: Duration = Duration::from_secs(24 * 60 * 60);
#[derive(Debug)]
struct TtlItem<V: Clone> {
value: V,
insertion_time: Instant,
lifetime: Duration,
}
impl<V: Clone> TtlItem<V> {
fn expired(&self) -> bool {
self.insertion_time.elapsed() >= self.lifetime
}
}
#[derive(Debug)]
pub struct TtlCache<K: Eq + Hash, V: Clone> {
lifetime: Duration,
items: HashMap<K, TtlItem<V>>,
}
impl<K, V> TtlCache<K, V>
where
K: Eq + Hash,
V: Clone,
{
pub fn new() -> Self {
Self { items: Default::default(), lifetime: DEFAULT_LIFETIME }
}
pub fn contains<Q>(&self, key: &Q) -> bool
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let cache = &self.items;
if let Some(item) = cache.get(key) { !item.expired() } else { false }
}
pub fn insert(&mut self, key: K, value: V) {
self.extend([(key, value)]);
}
pub fn extend(&mut self, iterator: impl IntoIterator<Item = (K, V)>) {
let cache = &mut self.items;
let now = Instant::now();
for (key, value) in iterator {
let item = TtlItem { value, insertion_time: now, lifetime: self.lifetime };
cache.insert(key, item);
}
}
pub fn remove<Q>(&mut self, key: &Q) -> Option<V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
self.items.remove(key.borrow()).map(|item| item.value)
}
pub fn get<Q>(&mut self, key: &Q) -> Option<V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
self.items.retain(|_, value| !value.expired());
self.items.get(key.borrow()).map(|item| item.value.clone())
}
#[doc(hidden)]
pub fn expire<Q>(&mut self, key: &Q)
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
if let Some(item) = self.items.get_mut(key) {
item.lifetime = Duration::from_secs(0);
}
}
}
impl<K: Eq + Hash, V: Clone> Default for TtlCache<K, V> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::TtlCache;
#[test]
fn test_ttl_cache_insertion() {
let mut cache = TtlCache::new();
assert!(!cache.contains("A"));
cache.insert("A", 1);
assert!(cache.contains("A"));
let value = cache.get("A").expect("The value should be in the cache");
assert_eq!(value, 1);
cache.expire("A");
assert!(!cache.contains("A"));
assert!(cache.get("A").is_none(), "The item should have been removed from the cache");
}
}