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.
This commit is contained in:
parent
6206d0ea58
commit
9af4831efa
52
src/main.rs
52
src/main.rs
@ -24,11 +24,12 @@ static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc;
|
|||||||
// * Polish the css
|
// * Polish the css
|
||||||
// * Add configuration file
|
// * Add configuration file
|
||||||
// * Support for headlines
|
// * Support for headlines
|
||||||
// * Cache generated images (+ cache cleanup)
|
// * Image cache cleanup
|
||||||
|
|
||||||
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 ImageListCache = Arc<RwLock<Vec<ImageInfo>>>;
|
||||||
type ImageCache = Arc<RwLock<HashMap<OsString, (DateTime<Utc>, Vec<u8>)>>>;
|
type ImageCache = Arc<RwLock<HashMap<OsString, (DateTime<Utc>, Vec<u8>)>>>;
|
||||||
|
|
||||||
#[derive(Clone, extract::FromRef)]
|
#[derive(Clone, extract::FromRef)]
|
||||||
@ -36,6 +37,7 @@ struct ApplicationState {
|
|||||||
engine: TemplateEngine,
|
engine: TemplateEngine,
|
||||||
image_cache: ImageCache,
|
image_cache: ImageCache,
|
||||||
image_dir: ImageDir,
|
image_dir: ImageDir,
|
||||||
|
image_list_cache: ImageListCache,
|
||||||
secret_key: SecretKey,
|
secret_key: SecretKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,6 +58,15 @@ async fn main() {
|
|||||||
.with(tracing_formatter)
|
.with(tracing_formatter)
|
||||||
.init();
|
.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();
|
let mut jinja = minijinja::Environment::new();
|
||||||
jinja.add_template("base", include_str!("../templates/base.html")).unwrap();
|
jinja.add_template("base", include_str!("../templates/base.html")).unwrap();
|
||||||
jinja.add_template("index", include_str!("../templates/index.html")).unwrap();
|
jinja.add_template("index", include_str!("../templates/index.html")).unwrap();
|
||||||
@ -76,8 +87,9 @@ 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_cache,
|
||||||
image_dir: PathBuf::from(image_path),
|
image_dir,
|
||||||
|
image_list_cache,
|
||||||
secret_key,
|
secret_key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -89,7 +101,33 @@ async fn main() {
|
|||||||
.unwrap();
|
.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 {
|
pub struct ImageInfo {
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
@ -108,19 +146,19 @@ pub struct IndexTempalte {
|
|||||||
|
|
||||||
async fn index(
|
async fn index(
|
||||||
engine: TemplateEngine,
|
engine: TemplateEngine,
|
||||||
State(image_dir): State<ImageDir>,
|
State(image_list_cache): State<ImageListCache>,
|
||||||
State(secret_key): State<SecretKey>,
|
State(secret_key): State<SecretKey>,
|
||||||
session: ReadableSession,
|
session: ReadableSession,
|
||||||
) -> Result<impl IntoResponse, AppError> {
|
) -> Result<impl IntoResponse, AppError> {
|
||||||
let logged_in = session.get::<()>("logged_in").is_some();
|
let logged_in = session.get::<()>("logged_in").is_some();
|
||||||
|
|
||||||
if logged_in {
|
if logged_in {
|
||||||
let images = read_images(&image_dir)?;
|
let images = image_list_cache.read().unwrap();
|
||||||
|
|
||||||
// Encrypt image names
|
// Encrypt image names
|
||||||
let chacha_key = chacha20poly1305::Key::from_slice(&secret_key[0..32]);
|
let chacha_key = chacha20poly1305::Key::from_slice(&secret_key[0..32]);
|
||||||
let chacha = XChaCha20Poly1305::new(chacha_key);
|
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 nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
|
||||||
let mut ciphertext = chacha.encrypt(&nonce, image_info.name.as_bytes())?;
|
let mut ciphertext = chacha.encrypt(&nonce, image_info.name.as_bytes())?;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user