use std::time::{Duration, SystemTime};
use crate::engine::ns;
use crate::{Result, Store, from_ms, to_ms};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CacheEntry {
pub value: Vec<u8>,
pub stored_at: SystemTime,
}
impl CacheEntry {
pub fn age_at(&self, now: SystemTime) -> Duration {
now.duration_since(self.stored_at).unwrap_or_default()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CachePolicy {
pub max_age: Duration,
pub min_age: Option<Duration>,
pub store_in_cache: bool,
}
impl Default for CachePolicy {
fn default() -> Self {
Self {
max_age: Duration::from_millis(172_800_000),
min_age: None,
store_in_cache: true,
}
}
}
impl CachePolicy {
pub fn from_firecrawl(
max_age_ms: Option<u64>,
min_age_ms: Option<u64>,
store_in_cache: Option<bool>,
) -> Self {
let d = Self::default();
Self {
max_age: max_age_ms.map_or(d.max_age, Duration::from_millis),
min_age: min_age_ms.map(Duration::from_millis),
store_in_cache: store_in_cache.unwrap_or(d.store_in_cache),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CacheLookup {
Hit(CacheEntry),
Miss,
NoCachedData,
}
pub struct Cache<'a> {
store: &'a Store,
}
impl<'a> Cache<'a> {
pub(crate) fn new(store: &'a Store) -> Self {
Self { store }
}
pub fn entry(&self, key: impl AsRef<[u8]>) -> Result<Option<CacheEntry>> {
Ok(self
.store
.get(ns::CACHE, key.as_ref())?
.map(|(ts, value)| CacheEntry {
value,
stored_at: from_ms(ts),
}))
}
pub fn get(&self, key: impl AsRef<[u8]>, max_age: Duration) -> Result<Option<CacheEntry>> {
self.get_at(key, max_age, SystemTime::now())
}
pub fn get_at(
&self,
key: impl AsRef<[u8]>,
max_age: Duration,
now: SystemTime,
) -> Result<Option<CacheEntry>> {
if max_age.is_zero() {
return Ok(None);
}
Ok(self.entry(key)?.filter(|e| e.age_at(now) < max_age))
}
pub fn get_min_age(
&self,
key: impl AsRef<[u8]>,
min_age: Duration,
now: SystemTime,
) -> Result<Option<CacheEntry>> {
Ok(self.entry(key)?.filter(|e| e.age_at(now) >= min_age))
}
pub fn lookup(&self, key: impl AsRef<[u8]>, policy: &CachePolicy) -> Result<CacheLookup> {
let now = SystemTime::now();
if let Some(min) = policy.min_age {
return Ok(match self.get_min_age(key, min, now)? {
Some(e) => CacheLookup::Hit(e),
None => CacheLookup::NoCachedData,
});
}
Ok(match self.get_at(key, policy.max_age, now)? {
Some(e) => CacheLookup::Hit(e),
None => CacheLookup::Miss,
})
}
pub fn put(&self, key: impl AsRef<[u8]>, value: &[u8]) -> Result<()> {
self.put_at(key, value, SystemTime::now())
}
pub fn put_at(&self, key: impl AsRef<[u8]>, value: &[u8], stored_at: SystemTime) -> Result<()> {
self.store
.put(ns::CACHE, key.as_ref(), value, to_ms(stored_at))
.map(drop)
}
pub fn store(&self, key: impl AsRef<[u8]>, value: &[u8], policy: &CachePolicy) -> Result<bool> {
if policy.store_in_cache {
self.put(key, value)?;
}
Ok(policy.store_in_cache)
}
pub fn remove(&self, key: impl AsRef<[u8]>) -> Result<bool> {
self.store.delete(ns::CACHE, key.as_ref())
}
}