From 9af4831efa0b21f31901854742ada1955f685118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klemens=20Sch=C3=B6lhorn?= Date: Thu, 6 Apr 2023 22:11:12 +0200 Subject: [PATCH] Add cache for image info list Preparing the images list involves reading a directory (which might be slow on non-local directories) and opening every file to read the exif data. To avoid having to do this on every request, we do it every 60s in a background job and only read from the cache on requests. --- src/main.rs | 52 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index eaa65bf..ca1ef26 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,11 +24,12 @@ static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc; // * Polish the css // * Add configuration file // * Support for headlines -// * Cache generated images (+ cache cleanup) +// * Image cache cleanup type TemplateEngine = Engine>; type ImageDir = PathBuf; type SecretKey = [u8; 64]; +type ImageListCache = Arc>>; type ImageCache = Arc, Vec)>>>; #[derive(Clone, extract::FromRef)] @@ -36,6 +37,7 @@ struct ApplicationState { engine: TemplateEngine, image_cache: ImageCache, image_dir: ImageDir, + image_list_cache: ImageListCache, secret_key: SecretKey, } @@ -56,6 +58,15 @@ async fn main() { .with(tracing_formatter) .init(); + let image_dir = PathBuf::from(image_path); + let image_cache = ImageCache::default(); + let image_list_cache = ImageListCache::default(); + + tokio::spawn(update_image_list_cache_job( + image_dir.clone(), + image_list_cache.clone() + )); + let mut jinja = minijinja::Environment::new(); jinja.add_template("base", include_str!("../templates/base.html")).unwrap(); jinja.add_template("index", include_str!("../templates/index.html")).unwrap(); @@ -76,8 +87,9 @@ async fn main() { .layer(session_layer) .with_state(ApplicationState { engine: Engine::from(jinja), - image_cache: Default::default(), - image_dir: PathBuf::from(image_path), + image_cache, + image_dir, + image_list_cache, secret_key, }); @@ -89,7 +101,33 @@ async fn main() { .unwrap(); } -#[derive(Debug, Serialize, Default)] +async fn update_image_list_cache_job(image_dir: ImageDir, image_cache: ImageListCache) { + let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(60)); + interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + + loop { + interval.tick().await; + + let image_dir = image_dir.clone(); + let images = task::spawn_blocking(move || { + // TODO: Only update images with changed modification times + read_images(&image_dir) + }).await + .unwrap_or_else(|error| { + tracing::error!("Could not read images due to panic: {:#}", error); + Ok(Vec::new()) + }) + .unwrap_or_else(|error| { + tracing::error!("Could not read images: {:#}", error); + Vec::new() + }); + + tracing::debug!("{} images in the image list cache", images.len()); + *image_cache.write().unwrap() = images; + } +} + +#[derive(Debug, Serialize, Default, Clone)] pub struct ImageInfo { width: u32, height: u32, @@ -108,19 +146,19 @@ pub struct IndexTempalte { async fn index( engine: TemplateEngine, - State(image_dir): State, + State(image_list_cache): State, State(secret_key): State, session: ReadableSession, ) -> Result { let logged_in = session.get::<()>("logged_in").is_some(); if logged_in { - let images = read_images(&image_dir)?; + let images = image_list_cache.read().unwrap(); // Encrypt image names let chacha_key = chacha20poly1305::Key::from_slice(&secret_key[0..32]); let chacha = XChaCha20Poly1305::new(chacha_key); - let mut images = images.into_iter().map(|mut image_info| { + let mut images = images.iter().cloned().map(|mut image_info| { let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng); let mut ciphertext = chacha.encrypt(&nonce, image_info.name.as_bytes())?;