Add simple in-memory cache for encoded images
This commit is contained in:
		
							
								
								
									
										50
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										50
									
								
								src/main.rs
									
									
									
									
									
								
							| @@ -1,4 +1,4 @@ | |||||||
| use std::{net::SocketAddr, path::{Path, PathBuf}, ffi::{OsStr, OsString}, fs::File, io::{BufReader, Seek, Cursor, BufRead}, env::args_os, os::unix::prelude::OsStrExt, cmp::Reverse}; | use std::{net::SocketAddr, path::{Path, PathBuf}, ffi::{OsStr, OsString}, fs::File, io::{BufReader, Seek, Cursor, BufRead}, env::args_os, os::unix::prelude::OsStrExt, cmp::Reverse, sync::Arc, collections::HashMap}; | ||||||
|  |  | ||||||
| use anyhow::{Context, Result, anyhow}; | use anyhow::{Context, Result, anyhow}; | ||||||
| use axum::{Router, routing::{get, post}, response::{IntoResponse, Redirect}, http::{StatusCode, header}, extract::{self, State}, Form, handler::Handler}; | use axum::{Router, routing::{get, post}, response::{IntoResponse, Redirect}, http::{StatusCode, header}, extract::{self, State}, Form, handler::Handler}; | ||||||
| @@ -10,7 +10,7 @@ use error::AppError; | |||||||
| use exif::Exif; | use exif::Exif; | ||||||
| use image::{imageops::FilterType, DynamicImage}; | use image::{imageops::FilterType, DynamicImage}; | ||||||
| use serde::{Serialize, Deserialize}; | use serde::{Serialize, Deserialize}; | ||||||
| use tokio::task; | use tokio::{task, sync::RwLock}; | ||||||
| use tower_http::{trace::{self, TraceLayer}, compression::CompressionLayer}; | use tower_http::{trace::{self, TraceLayer}, compression::CompressionLayer}; | ||||||
| use tracing::Level; | use tracing::Level; | ||||||
| use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt}; | use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt}; | ||||||
| @@ -29,10 +29,12 @@ static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc; | |||||||
| type TemplateEngine = Engine<minijinja::Environment<'static>>; | type TemplateEngine = Engine<minijinja::Environment<'static>>; | ||||||
| type ImageDir = PathBuf; | type ImageDir = PathBuf; | ||||||
| type SecretKey = [u8; 64]; | type SecretKey = [u8; 64]; | ||||||
|  | type ImageCache = Arc<RwLock<HashMap<OsString, (DateTime<Utc>, Vec<u8>)>>>; | ||||||
|  |  | ||||||
| #[derive(Clone, extract::FromRef)] | #[derive(Clone, extract::FromRef)] | ||||||
| struct ApplicationState { | struct ApplicationState { | ||||||
|     engine: TemplateEngine, |     engine: TemplateEngine, | ||||||
|  |     image_cache: ImageCache, | ||||||
|     image_dir: ImageDir, |     image_dir: ImageDir, | ||||||
|     secret_key: SecretKey, |     secret_key: SecretKey, | ||||||
| } | } | ||||||
| @@ -74,6 +76,7 @@ async fn main() { | |||||||
|         .layer(session_layer) |         .layer(session_layer) | ||||||
|         .with_state(ApplicationState { |         .with_state(ApplicationState { | ||||||
|             engine: Engine::from(jinja), |             engine: Engine::from(jinja), | ||||||
|  |             image_cache: Default::default(), | ||||||
|             image_dir: PathBuf::from(image_path), |             image_dir: PathBuf::from(image_path), | ||||||
|             secret_key, |             secret_key, | ||||||
|         }); |         }); | ||||||
| @@ -158,6 +161,7 @@ async fn authenticate( | |||||||
|  |  | ||||||
| async fn converted_image( | async fn converted_image( | ||||||
|     extract::Path(encrypted_filename_hex): extract::Path<String>, |     extract::Path(encrypted_filename_hex): extract::Path<String>, | ||||||
|  |     State(image_cache): State<ImageCache>, | ||||||
|     State(image_dir): State<ImageDir>, |     State(image_dir): State<ImageDir>, | ||||||
|     State(secret_key): State<SecretKey>, |     State(secret_key): State<SecretKey>, | ||||||
|     session: ReadableSession, |     session: ReadableSession, | ||||||
| @@ -175,18 +179,50 @@ async fn converted_image( | |||||||
|         let chacha = XChaCha20Poly1305::new(chacha_key); |         let chacha = XChaCha20Poly1305::new(chacha_key); | ||||||
|         chacha.decrypt(nonce, ciphertext) |         chacha.decrypt(nonce, ciphertext) | ||||||
|     }.with_context(|| format!("Could not decrypt filename {}", encrypted_filename_hex))?; |     }.with_context(|| format!("Could not decrypt filename {}", encrypted_filename_hex))?; | ||||||
|  |     let image_name = OsStr::from_bytes(&image_name); | ||||||
|  |  | ||||||
|     let image_path = image_dir.join(OsStr::from_bytes(&image_name)); |     let image_path = image_dir.join(image_name); | ||||||
|  |  | ||||||
|     image_path.exists() |     image_path.exists() | ||||||
|         .then_some(()) |         .then_some(()) | ||||||
|         .ok_or(anyhow!("Requested image not found!")) |         .ok_or(anyhow!("Requested image not found!")) | ||||||
|         .context(StatusCode::NOT_FOUND)?; |         .context(StatusCode::NOT_FOUND)?; | ||||||
|  |  | ||||||
|     let image_buffer = task::spawn_blocking(move || { |     // Check if we have the file already in cache | ||||||
|         convert_image(&image_path) |     let image_modified: Option<DateTime<Utc>> = std::fs::metadata(&image_path) | ||||||
|             .with_context(|| format!("Could not convert image {:?}", image_path)) |             .and_then(|m| m.modified()) | ||||||
|     }).await??; |             .map(DateTime::from) | ||||||
|  |             .ok(); | ||||||
|  |     let cached_buffer = if let Some(image_modified) = image_modified { | ||||||
|  |         image_cache | ||||||
|  |             .read().await | ||||||
|  |             .get(image_name) | ||||||
|  |             .filter(|(cache_modified, _)| *cache_modified == image_modified) | ||||||
|  |             .map(|(_, image_buffer)| image_buffer) | ||||||
|  |             .cloned() | ||||||
|  |     } else { None }; | ||||||
|  |  | ||||||
|  |     let image_buffer = match cached_buffer { | ||||||
|  |         Some(image_buffer) =>  { | ||||||
|  |             tracing::debug!("Read {:?} from cache", image_name); | ||||||
|  |             image_buffer | ||||||
|  |         } | ||||||
|  |         None => { | ||||||
|  |             let image_buffer = task::spawn_blocking(move || { | ||||||
|  |                 convert_image(&image_path) | ||||||
|  |                     .with_context(|| format!("Could not convert image {:?}", image_path)) | ||||||
|  |             }).await??; | ||||||
|  |  | ||||||
|  |             if let Some(image_modified) = image_modified { | ||||||
|  |                 tracing::debug!("Add {:?} ({}k) to cache", image_name, image_buffer.len() / 1024); | ||||||
|  |                 image_cache | ||||||
|  |                     .write().await | ||||||
|  |                     .insert(image_name.to_owned(), (image_modified, image_buffer.clone())); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             image_buffer | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  |  | ||||||
|     Ok(( |     Ok(( | ||||||
|         [ |         [ | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user