magnetar/src/service/emoji_cache.rs

124 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::io::AsyncWriteExt;
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)
}
}