add cache
This commit is contained in:
parent
96cd6ed0ef
commit
d7652b8800
4 changed files with 161 additions and 7 deletions
116
src/cache.rs
Normal file
116
src/cache.rs
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use rocket::tokio::sync::RwLock;
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! use_api_cache {
|
||||||
|
($route:literal, $id:ident, $cache:ident) => {
|
||||||
|
if let Some(ret) = $cache.get_only($route, $id).await {
|
||||||
|
return Ok(serde_json::from_str(&ret).unwrap());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RouteCache {
|
||||||
|
inner: RwLock<HashMap<String, HashMap<String, Option<String>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RouteCache {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
inner: RwLock::new(HashMap::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get<F, Fut>(&self, route: &str, id: &str, generator: F) -> String
|
||||||
|
where
|
||||||
|
F: FnOnce() -> Fut,
|
||||||
|
Fut: std::future::Future<Output = String>,
|
||||||
|
{
|
||||||
|
{
|
||||||
|
// Try to get a read lock first.
|
||||||
|
let lock = self.inner.read().await;
|
||||||
|
if let Some(inner_map) = lock.get(route) {
|
||||||
|
if let Some(cached_value) = inner_map.get(id) {
|
||||||
|
log::trace!("Using cached value for {route} / {id}");
|
||||||
|
return cached_value.clone().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the value was not found, acquire a write lock to insert the computed value.
|
||||||
|
let mut lock = self.inner.write().await;
|
||||||
|
|
||||||
|
log::trace!("Computing value for {route} / {id}");
|
||||||
|
let computed = generator().await;
|
||||||
|
|
||||||
|
lock.entry(route.to_string())
|
||||||
|
.or_insert_with(HashMap::new)
|
||||||
|
.insert(id.to_string(), Some(computed.clone()));
|
||||||
|
|
||||||
|
computed
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_only(&self, route: &str, id: &str) -> Option<String> {
|
||||||
|
let lock = self.inner.read().await;
|
||||||
|
if let Some(inner_map) = lock.get(route) {
|
||||||
|
if let Some(cached_value) = inner_map.get(id) {
|
||||||
|
log::trace!("Using cached value for {route} / {id}");
|
||||||
|
return cached_value.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_option<F, Fut>(&self, route: &str, id: &str, generator: F) -> Option<String>
|
||||||
|
where
|
||||||
|
F: FnOnce() -> Fut,
|
||||||
|
Fut: std::future::Future<Output = Option<String>>,
|
||||||
|
{
|
||||||
|
{
|
||||||
|
// Try to get a read lock first.
|
||||||
|
let lock = self.inner.read().await;
|
||||||
|
if let Some(inner_map) = lock.get(route) {
|
||||||
|
if let Some(cached_value) = inner_map.get(id) {
|
||||||
|
log::trace!("Using cached value for {route} / {id}");
|
||||||
|
return cached_value.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the value was not found, acquire a write lock to insert the computed value.
|
||||||
|
let mut lock = self.inner.write().await;
|
||||||
|
|
||||||
|
log::trace!("Computing value for {route} / {id}");
|
||||||
|
let computed = generator().await;
|
||||||
|
|
||||||
|
lock.entry(route.to_string())
|
||||||
|
.or_insert_with(HashMap::new)
|
||||||
|
.insert(id.to_string(), computed.clone());
|
||||||
|
|
||||||
|
computed
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn insert(&self, route: &str, id: &str, value: String) {
|
||||||
|
let mut lock = self.inner.write().await;
|
||||||
|
|
||||||
|
log::trace!("Inserting value for {route} / {id}");
|
||||||
|
|
||||||
|
lock.entry(route.to_string())
|
||||||
|
.or_insert_with(HashMap::new)
|
||||||
|
.insert(id.to_string(), Some(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn invalidate(&self, route: &str, id: &str) {
|
||||||
|
let mut lock = self.inner.write().await;
|
||||||
|
if let Some(inner_map) = lock.get_mut(route) {
|
||||||
|
inner_map.remove(id);
|
||||||
|
|
||||||
|
// If the inner map is empty, remove the route entry as well.
|
||||||
|
if inner_map.is_empty() {
|
||||||
|
lock.remove(route);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
use library::Libary;
|
use library::Libary;
|
||||||
|
|
||||||
|
mod cache;
|
||||||
mod library;
|
mod library;
|
||||||
mod route;
|
mod route;
|
||||||
|
|
||||||
|
@ -30,6 +31,8 @@ async fn rocket() -> _ {
|
||||||
|
|
||||||
User::create("admin", "admin", UserRole::Admin).await;
|
User::create("admin", "admin", UserRole::Admin).await;
|
||||||
|
|
||||||
|
let cache = cache::RouteCache::new();
|
||||||
|
|
||||||
rocket::build()
|
rocket::build()
|
||||||
.mount(
|
.mount(
|
||||||
"/",
|
"/",
|
||||||
|
@ -49,5 +52,6 @@ async fn rocket() -> _ {
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.manage(lib)
|
.manage(lib)
|
||||||
|
.manage(cache)
|
||||||
.attach(cors)
|
.attach(cors)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,11 @@ use rocket::fs::NamedFile;
|
||||||
use rocket::*;
|
use rocket::*;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
|
use crate::cache::RouteCache;
|
||||||
use crate::library::album::Album;
|
use crate::library::album::Album;
|
||||||
use crate::library::Libary;
|
use crate::library::Libary;
|
||||||
use crate::route::to_api;
|
use crate::route::to_api;
|
||||||
|
use crate::use_api_cache;
|
||||||
|
|
||||||
#[get("/artist/<artist_id>/albums")]
|
#[get("/artist/<artist_id>/albums")]
|
||||||
pub async fn albums_route(artist_id: &str, lib: &State<Libary>) -> FallibleApiResponse {
|
pub async fn albums_route(artist_id: &str, lib: &State<Libary>) -> FallibleApiResponse {
|
||||||
|
@ -37,13 +39,31 @@ fn sort_by_tracknumber(a: &serde_json::Value, b: &serde_json::Value) -> Ordering
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/album/<album_id>/cover")]
|
#[get("/album/<album_id>/cover")]
|
||||||
pub async fn album_cover_route(album_id: &str, lib: &State<Libary>) -> Option<NamedFile> {
|
pub async fn album_cover_route(
|
||||||
let album = lib.get_album_by_id(album_id).await?;
|
album_id: &str,
|
||||||
NamedFile::open(album.get_cover().await?).await.ok()
|
lib: &State<Libary>,
|
||||||
|
cache: &State<RouteCache>,
|
||||||
|
) -> Option<NamedFile> {
|
||||||
|
NamedFile::open(
|
||||||
|
cache
|
||||||
|
.get_option("album_cover_route", album_id, || async {
|
||||||
|
let album = lib.get_album_by_id(album_id).await?;
|
||||||
|
album.get_cover().await
|
||||||
|
})
|
||||||
|
.await?,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/album/<album_id>")]
|
#[get("/album/<album_id>")]
|
||||||
pub async fn album_route(album_id: &str, lib: &State<Libary>) -> FallibleApiResponse {
|
pub async fn album_route(
|
||||||
|
album_id: &str,
|
||||||
|
lib: &State<Libary>,
|
||||||
|
cache: &State<RouteCache>,
|
||||||
|
) -> FallibleApiResponse {
|
||||||
|
use_api_cache!("album_route", album_id, cache);
|
||||||
|
|
||||||
let album = lib
|
let album = lib
|
||||||
.get_album_by_id(album_id)
|
.get_album_by_id(album_id)
|
||||||
.await
|
.await
|
||||||
|
@ -69,5 +89,13 @@ pub async fn album_route(album_id: &str, lib: &State<Libary>) -> FallibleApiResp
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.insert("tracks".into(), tracks.into());
|
.insert("tracks".into(), tracks.into());
|
||||||
|
|
||||||
|
cache
|
||||||
|
.insert(
|
||||||
|
"album_route",
|
||||||
|
album_id,
|
||||||
|
serde_json::to_string(&album).unwrap(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
Ok(album)
|
Ok(album)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ use mongod::Model;
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
use rocket::*;
|
use rocket::*;
|
||||||
|
|
||||||
|
use crate::cache::RouteCache;
|
||||||
use crate::library::artist::Artist;
|
use crate::library::artist::Artist;
|
||||||
use crate::library::Libary;
|
use crate::library::Libary;
|
||||||
|
|
||||||
|
@ -18,9 +19,14 @@ pub async fn artists_route(lib: &State<Libary>) -> FallibleApiResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/artist/<id>/image")]
|
#[get("/artist/<id>/image")]
|
||||||
pub async fn artist_image_route(id: &str) -> Option<NamedFile> {
|
pub async fn artist_image_route(id: &str, cache: &State<RouteCache>) -> Option<NamedFile> {
|
||||||
let image = Artist::get_image_of(id).await?;
|
let image = cache
|
||||||
NamedFile::open(image).await.ok()
|
.get_option("artist_image_route", id, || async {
|
||||||
|
Artist::get_image_of(id).await
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
NamedFile::open(image?).await.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/artist/<id>")]
|
#[get("/artist/<id>")]
|
||||||
|
|
Loading…
Reference in a new issue