use std::borrow::Borrow;
use std::collections::{hash_map, HashMap, VecDeque};
#[cfg(feature = "ttl")]
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
struct CacheEntry<V> {
value: V,
#[cfg(feature = "ttl")]
expires_at: Instant,
}
#[derive(Debug)]
pub struct FifoCache<K, V> {
map: HashMap<K, CacheEntry<V>>,
order: VecDeque<K>,
max_size: usize,
#[cfg(feature = "ttl")]
default_ttl: Duration,
}
impl<K, V> FifoCache<K, V>
where
K: Clone + Eq + std::hash::Hash,
V: Clone,
{
pub fn new(
max_size: usize,
#[cfg(feature = "ttl")]
default_ttl: Duration
) -> Self {
Self {
map: HashMap::with_capacity(max_size + 1),
order: VecDeque::with_capacity(max_size + 1),
max_size,
#[cfg(feature = "ttl")]
default_ttl,
}
}
pub fn get<Q>(&self, key: &Q) -> Option<&V>
where
K: Borrow<Q>,
Q: ?Sized + std::hash::Hash + Eq,
{
#[cfg(feature = "ttl")] {
let now = Instant::now();
self.map
.get(key)
.filter(|entry| entry.expires_at > now)
.map(|entry| &entry.value)
}
#[cfg(not(feature = "ttl"))] {
self.map.get(key).map(|entry| &entry.value)
}
}
pub fn insert(&mut self, key: K, value: V) {
#[cfg(feature = "ttl")] {
let expires_at = Instant::now() + self.default_ttl;
match self.map.entry(key.clone()) {
hash_map::Entry::Occupied(mut entry) => {
entry.insert(CacheEntry { value, expires_at });
}
hash_map::Entry::Vacant(entry) => {
entry.insert(CacheEntry { value, expires_at });
self.order.push_back(key);
self.prune();
}
}
}
#[cfg(not(feature = "ttl"))] {
match self.map.entry(key.clone()) {
hash_map::Entry::Occupied(mut entry) => {
entry.insert(CacheEntry { value });
}
hash_map::Entry::Vacant(entry) => {
entry.insert(CacheEntry { value });
self.order.push_back(key);
self.prune();
}
}
}
}
pub fn insert_lazy<Kinto: Into<K>, Vinto: Into<V>>(&mut self, key: Kinto, value: Vinto) {
self.insert(key.into(), value.into())
}
pub fn remove<Q>(&mut self, key: &Q) -> Option<V>
where
K: Borrow<Q>,
Q: ?Sized + std::hash::Hash + Eq,
{
if let Some(entry) = self.map.remove(key) {
self.order.retain(|k| k.borrow() != key);
Some(entry.value)
} else {
None
}
}
pub fn len(&self) -> usize {
self.map.len()
}
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
#[cfg(feature = "ttl")]
pub fn cleanup_expired(&mut self) {
let now = Instant::now();
self.order.retain(|key| {
if let Some(entry) = self.map.get(key) {
if entry.expires_at <= now {
self.map.remove(key);
false
} else {
true
}
} else {
false
}
});
}
pub fn clear(&mut self) {
self.map.clear();
self.order.clear();
}
pub fn max_size(&self) -> usize {
self.max_size
}
pub fn set_max_size(&mut self, max_size: usize, prune: bool) {
self.max_size = max_size;
if prune {
self.prune();
}
}
#[cfg(feature = "ttl")]
pub fn default_ttl(&self) -> Duration {
self.default_ttl
}
#[cfg(feature = "ttl")]
pub fn set_default_ttl(&mut self, default_ttl: Duration) {
self.default_ttl = default_ttl;
}
fn prune(&mut self) {
while self.order.len() > self.max_size {
if let Some(old_key) = self.order.pop_front() {
self.map.remove(&old_key);
}
}
}
}
impl<K, V> Default for FifoCache<K, V>
where
K: Clone + Eq + std::hash::Hash,
V: Clone,
{
fn default() -> Self {
Self::new(
1000,
#[cfg(feature = "ttl")]
Duration::from_secs(300)
)
}
}