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:
Klemens Schölhorn 2023-04-06 22:11:12 +02:00
parent 6206d0ea58
commit 9af4831efa

View File

@ -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<minijinja::Environment<'static>>;
type ImageDir = PathBuf;
type SecretKey = [u8; 64];
type ImageListCache = Arc<RwLock<Vec<ImageInfo>>>;
type ImageCache = Arc<RwLock<HashMap<OsString, (DateTime<Utc>, Vec<u8>)>>>;
#[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<ImageDir>,
State(image_list_cache): State<ImageListCache>,
State(secret_key): State<SecretKey>,
session: ReadableSession,
) -> Result<impl IntoResponse, AppError> {
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())?;