123 lines
3.2 KiB
Rust
123 lines
3.2 KiB
Rust
|
use crate::web::ApiError;
|
||
|
use lru::LruCache;
|
||
|
use magnetar_calckey_model::{ck, CalckeyDbError, CalckeyModel};
|
||
|
use std::collections::HashSet;
|
||
|
use std::sync::Arc;
|
||
|
use strum::EnumVariantNames;
|
||
|
use thiserror::Error;
|
||
|
use tokio::sync::Mutex;
|
||
|
|
||
|
#[derive(Debug, Error, EnumVariantNames)]
|
||
|
pub enum EmojiCacheError {
|
||
|
#[error("Database error: {0}")]
|
||
|
DbError(#[from] CalckeyDbError),
|
||
|
}
|
||
|
|
||
|
impl From<EmojiCacheError> for ApiError {
|
||
|
fn from(err: EmojiCacheError) -> Self {
|
||
|
let mut api_error: ApiError = match err {
|
||
|
EmojiCacheError::DbError(err) => err.into(),
|
||
|
};
|
||
|
|
||
|
api_error.message = format!("Emoji cache error: {}", api_error.message);
|
||
|
|
||
|
api_error
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
|
||
|
struct EmojiLocator {
|
||
|
name: String,
|
||
|
host: Option<String>,
|
||
|
}
|
||
|
|
||
|
pub struct EmojiCacheService {
|
||
|
cache: Mutex<LruCache<EmojiLocator, Arc<ck::emoji::Model>>>,
|
||
|
db: CalckeyModel,
|
||
|
}
|
||
|
|
||
|
impl EmojiCacheService {
|
||
|
pub(super) fn new(db: CalckeyModel) -> Self {
|
||
|
const CACHE_SIZE: usize = 4096;
|
||
|
Self {
|
||
|
cache: Mutex::new(LruCache::new(CACHE_SIZE.try_into().unwrap())),
|
||
|
db,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub async fn get(
|
||
|
&self,
|
||
|
name: &str,
|
||
|
host: Option<&str>,
|
||
|
) -> Result<Option<Arc<ck::emoji::Model>>, EmojiCacheError> {
|
||
|
let loc = EmojiLocator {
|
||
|
name: name.to_string(),
|
||
|
host: host.map(str::to_string),
|
||
|
};
|
||
|
let mut read = self.cache.lock().await;
|
||
|
if let Some(emoji) = read.get(&loc) {
|
||
|
return Ok(Some(emoji.clone()));
|
||
|
}
|
||
|
drop(read);
|
||
|
|
||
|
let emoji = self.db.fetch_emoji(name, host).await?;
|
||
|
|
||
|
if emoji.is_none() {
|
||
|
return Ok(None);
|
||
|
}
|
||
|
|
||
|
let mut write = self.cache.lock().await;
|
||
|
let emoji = Arc::new(emoji.unwrap());
|
||
|
write.put(loc, emoji.clone());
|
||
|
Ok(Some(emoji))
|
||
|
}
|
||
|
|
||
|
pub async fn get_many(
|
||
|
&self,
|
||
|
names: &[String],
|
||
|
host: Option<&str>,
|
||
|
) -> Result<Vec<Arc<ck::emoji::Model>>, EmojiCacheError> {
|
||
|
let locs = names
|
||
|
.into_iter()
|
||
|
.map(|n| EmojiLocator {
|
||
|
name: n.clone(),
|
||
|
host: host.map(str::to_string),
|
||
|
})
|
||
|
.collect::<HashSet<_>>();
|
||
|
let mut to_resolve = Vec::new();
|
||
|
let mut resolved = Vec::new();
|
||
|
|
||
|
let mut read = self.cache.lock().await;
|
||
|
for loc in locs {
|
||
|
if let Some(emoji) = read.get(&loc) {
|
||
|
resolved.push(emoji.clone());
|
||
|
} else {
|
||
|
to_resolve.push(loc.name);
|
||
|
}
|
||
|
}
|
||
|
drop(read);
|
||
|
|
||
|
let emoji = self
|
||
|
.db
|
||
|
.fetch_many_emojis(&to_resolve, host)
|
||
|
.await?
|
||
|
.into_iter()
|
||
|
.map(Arc::new)
|
||
|
.collect::<Vec<_>>();
|
||
|
resolved.extend(emoji.iter().cloned());
|
||
|
|
||
|
let mut write = self.cache.lock().await;
|
||
|
emoji.iter().for_each(|e| {
|
||
|
write.put(
|
||
|
EmojiLocator {
|
||
|
name: e.name.clone(),
|
||
|
host: e.host.clone(),
|
||
|
},
|
||
|
e.clone(),
|
||
|
);
|
||
|
});
|
||
|
|
||
|
Ok(resolved)
|
||
|
}
|
||
|
}
|