use bytes::Bytes;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::{Rc, Weak};
use std::time::Instant;
#[derive(PartialEq, Clone, Copy)]
pub enum CacheItemTTL {
ExpiresAfterSeconds(u64),
Persist,
}
pub(crate) struct CacheItem {
last_access: Instant,
key: String,
value: Bytes,
expires_at: CacheItemTTL,
prev: Option<Weak<RefCell<CacheItem>>>,
next: Option<Rc<RefCell<CacheItem>>>,
}
pub(crate) struct LRUCache {
capacity: usize,
ttl: u64,
map: HashMap<String, Rc<RefCell<CacheItem>>>,
head: Option<Rc<RefCell<CacheItem>>>,
tail: Option<Rc<RefCell<CacheItem>>>,
}
impl LRUCache {
pub(crate) fn new(capacity: usize, ttl_seconds: u64) -> Self {
LRUCache {
capacity,
ttl: ttl_seconds,
map: HashMap::new(),
head: None,
tail: None,
}
}
pub(crate) fn add_item(&mut self, key: String, value: Bytes) {
if let Some(node) = self.map.get(&key) {
node.borrow_mut().value = value.clone();
self.move_to_head(&Rc::clone(node));
} else {
let new_node = self.create_node(key.clone(), value);
self.add_to_head(Rc::clone(&new_node));
self.map.insert(key.clone(), Rc::clone(&new_node));
if self.map.len() > self.capacity {
if let Some(tail_node) = self.tail.take() {
let tail_key = tail_node.borrow().key.clone();
self.remove_node(Rc::clone(&tail_node));
self.map.remove(&tail_key);
}
}
}
}
pub(crate) fn get_item(&mut self, key: &str) -> Option<Bytes> {
let node = self.map.get(key)?;
let (value, expires_at) = {
let node_borrow = node.borrow();
(node_borrow.value.clone(), node_borrow.expires_at)
};
match expires_at {
CacheItemTTL::Persist => {
self.move_to_head(&Rc::clone(node));
Some(value)
}
CacheItemTTL::ExpiresAfterSeconds(seconds_to_expire)
if node.borrow().last_access.elapsed().as_secs() > seconds_to_expire =>
{
self.remove_node(Rc::clone(node));
self.map.remove(key);
None
}
CacheItemTTL::ExpiresAfterSeconds(_) => {
self.move_to_head(&Rc::clone(node));
Some(value)
}
}
}
fn move_to_head(&mut self, node: &Rc<RefCell<CacheItem>>) {
self.remove_node(Rc::clone(node));
self.add_to_head(Rc::clone(node));
node.borrow_mut().last_access = Instant::now();
}
fn remove_node(&mut self, node: Rc<RefCell<CacheItem>>) {
let prev_weak = node.borrow_mut().prev.take();
let next_opt = node.borrow_mut().next.take();
if let Some(ref prev_weak_ref) = prev_weak {
if let Some(prev_rc) = prev_weak_ref.upgrade() {
prev_rc.borrow_mut().next = next_opt.clone();
}
} else {
self.head = next_opt.clone();
}
if let Some(next_rc) = next_opt {
next_rc.borrow_mut().prev = prev_weak.clone();
} else {
if let Some(ref prev_weak_ref) = prev_weak {
self.tail = prev_weak_ref.upgrade();
} else {
self.tail = None;
}
}
}
fn add_to_head(&mut self, node: Rc<RefCell<CacheItem>>) {
node.borrow_mut().prev = None;
node.borrow_mut().next = self.head.clone();
if let Some(old_head) = &self.head {
old_head.borrow_mut().prev = Some(Rc::downgrade(&node));
} else {
self.tail = Some(Rc::clone(&node));
}
self.head = Some(node);
}
fn create_node(&self, key: String, value: Bytes) -> Rc<RefCell<CacheItem>> {
let expires_at = if self.ttl < 1 {
CacheItemTTL::Persist
} else {
CacheItemTTL::ExpiresAfterSeconds(self.ttl)
};
Rc::new(RefCell::new(CacheItem {
last_access: Instant::now(),
key: key.clone(),
value: value.clone(),
expires_at,
prev: None,
next: None,
}))
}
}