2023-10-27 19:55:08 +00:00
|
|
|
use crate::web::ApiError;
|
|
|
|
use lru::LruCache;
|
2023-10-30 22:00:46 +00:00
|
|
|
use magnetar_calckey_model::emoji::{EmojiResolver, EmojiTag};
|
2023-10-27 19:55:08 +00:00
|
|
|
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>>>,
|
2023-10-30 22:00:46 +00:00
|
|
|
db: EmojiResolver,
|
2023-10-27 19:55:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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())),
|
2023-10-30 22:00:46 +00:00
|
|
|
db: EmojiResolver::new(db),
|
2023-10-27 19:55:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2023-10-30 22:00:46 +00:00
|
|
|
.iter()
|
2023-10-27 19:55:08 +00:00
|
|
|
.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)
|
|
|
|
}
|
2023-10-30 22:00:46 +00:00
|
|
|
|
|
|
|
pub async fn get_many_tagged(
|
|
|
|
&self,
|
|
|
|
tags: &[EmojiTag<'_>],
|
|
|
|
) -> Result<Vec<Arc<ck::emoji::Model>>, EmojiCacheError> {
|
|
|
|
let locs = tags
|
|
|
|
.iter()
|
|
|
|
.map(|tag| EmojiLocator {
|
|
|
|
name: tag.name.to_string(),
|
|
|
|
host: tag.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.iter() {
|
|
|
|
if let Some(emoji) = read.get(loc) {
|
|
|
|
resolved.push(emoji.clone());
|
|
|
|
} else {
|
|
|
|
to_resolve.push(EmojiTag {
|
|
|
|
name: &loc.name,
|
|
|
|
host: loc.host.as_deref(),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
drop(read);
|
|
|
|
|
|
|
|
let emoji = self
|
|
|
|
.db
|
|
|
|
.fetch_many_tagged_emojis(&to_resolve)
|
|
|
|
.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)
|
|
|
|
}
|
2023-10-27 19:55:08 +00:00
|
|
|
}
|