97 lines
2.6 KiB
Rust
97 lines
2.6 KiB
Rust
use crate::web::ApiError;
|
|
use lru::LruCache;
|
|
use magnetar_model::sea_orm::{EntityTrait, PrimaryKeyTrait};
|
|
use magnetar_model::{CalckeyDbError, CalckeyModel};
|
|
use std::marker::PhantomData;
|
|
use std::sync::Arc;
|
|
use std::time::{Duration, Instant};
|
|
use strum::EnumVariantNames;
|
|
use thiserror::Error;
|
|
use tokio::sync::Mutex;
|
|
|
|
#[derive(Debug, Error, EnumVariantNames)]
|
|
pub enum GenericIdCacheError {
|
|
#[error("Database error: {0}")]
|
|
DbError(#[from] CalckeyDbError),
|
|
}
|
|
|
|
impl From<GenericIdCacheError> for ApiError {
|
|
fn from(err: GenericIdCacheError) -> Self {
|
|
let mut api_error: ApiError = match err {
|
|
GenericIdCacheError::DbError(err) => err.into(),
|
|
};
|
|
|
|
api_error.message = format!("Generic ID cache error: {}", api_error.message);
|
|
|
|
api_error
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct CacheEntry<E: EntityTrait> {
|
|
created: Instant,
|
|
data: Arc<E::Model>,
|
|
}
|
|
|
|
impl<E: EntityTrait> CacheEntry<E> {
|
|
fn new(data: Arc<E::Model>) -> Self {
|
|
Self {
|
|
created: Instant::now(),
|
|
data,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct GenericIdCacheService<E: EntityTrait> {
|
|
cache: Mutex<LruCache<String, CacheEntry<E>>>,
|
|
lifetime_max: Duration,
|
|
db: CalckeyModel,
|
|
_entity_type: PhantomData<E>,
|
|
}
|
|
|
|
impl<E: EntityTrait> GenericIdCacheService<E> {
|
|
pub(super) fn new(db: CalckeyModel, cache_size: usize, entry_lifetime: Duration) -> Self {
|
|
const CACHE_SIZE: usize = 4096;
|
|
|
|
Self {
|
|
cache: Mutex::new(LruCache::new(
|
|
cache_size
|
|
.try_into()
|
|
.unwrap_or(CACHE_SIZE.try_into().unwrap()),
|
|
)),
|
|
lifetime_max: entry_lifetime,
|
|
db,
|
|
_entity_type: PhantomData,
|
|
}
|
|
}
|
|
|
|
pub async fn get<'a>(&self, id: &'a str) -> Result<Option<Arc<E::Model>>, GenericIdCacheError>
|
|
where
|
|
<<E as EntityTrait>::PrimaryKey as PrimaryKeyTrait>::ValueType: From<&'a str>,
|
|
{
|
|
let mut read = self.cache.lock().await;
|
|
if let Some(item) = read.peek(id) {
|
|
if item.created + self.lifetime_max >= Instant::now() {
|
|
let data = item.data.clone();
|
|
read.promote(id);
|
|
return Ok(Some(data));
|
|
}
|
|
}
|
|
drop(read);
|
|
|
|
let val = E::find_by_id(id)
|
|
.one(self.db.inner())
|
|
.await
|
|
.map_err(CalckeyDbError::from)?;
|
|
|
|
if val.is_none() {
|
|
return Ok(None);
|
|
}
|
|
|
|
let mut write = self.cache.lock().await;
|
|
let data = Arc::new(val.unwrap());
|
|
write.put(id.to_string(), CacheEntry::new(data.clone()));
|
|
Ok(Some(data))
|
|
}
|
|
}
|