diff --git a/mediarepo-api/src/tauri_plugin/commands/file.rs b/mediarepo-api/src/tauri_plugin/commands/file.rs index 3b8bee2..43be28e 100644 --- a/mediarepo-api/src/tauri_plugin/commands/file.rs +++ b/mediarepo-api/src/tauri_plugin/commands/file.rs @@ -29,11 +29,15 @@ pub async fn read_file_by_hash( api_state: ApiAccess<'_>, buffer_state: BufferAccess<'_>, ) -> PluginResult { - let api = api_state.api().await?; - let content = api.file.read_file_by_hash(hash.clone()).await?; - let uri = add_once_buffer(buffer_state, hash, mime_type, content); + if buffer_state.reserve_entry(&hash) { + Ok(format!("once://{}", hash)) // entry has been cached + } else { + let api = api_state.api().await?; + let content = api.file.read_file_by_hash(hash.clone()).await?; + let uri = add_once_buffer(buffer_state, hash, mime_type, content); - Ok(uri) + Ok(uri) + } } #[tauri::command] @@ -54,9 +58,13 @@ pub async fn read_thumbnail( api_state: ApiAccess<'_>, buffer_state: BufferAccess<'_>, ) -> PluginResult { - let api = api_state.api().await?; - let content = api.file.read_thumbnail(hash.clone()).await?; - let uri = add_once_buffer(buffer_state, hash, mime_type, content); + if buffer_state.reserve_entry(&hash) { + Ok(format!("once://{}", hash)) // entry has been cached + } else { + let api = api_state.api().await?; + let content = api.file.read_thumbnail(hash.clone()).await?; + let uri = add_once_buffer(buffer_state, hash, mime_type, content); - Ok(uri) + Ok(uri) + } } diff --git a/mediarepo-api/src/tauri_plugin/commands/mod.rs b/mediarepo-api/src/tauri_plugin/commands/mod.rs index 845dc1f..f58ab56 100644 --- a/mediarepo-api/src/tauri_plugin/commands/mod.rs +++ b/mediarepo-api/src/tauri_plugin/commands/mod.rs @@ -4,7 +4,7 @@ pub use file::*; pub use repo::*; pub use tag::*; -use crate::tauri_plugin::state::{ApiState, AppState, BufferState, OnceBuffer}; +use crate::tauri_plugin::state::{ApiState, AppState, BufferState, VolatileBuffer}; pub mod file; pub mod repo; @@ -22,7 +22,7 @@ fn add_once_buffer( buf: Vec, ) -> String { let uri = format!("once://{}", key); - let once_buffer = OnceBuffer::new(mime, buf); + let once_buffer = VolatileBuffer::new(mime, buf); let mut once_buffers = buffer_state.buffer.lock(); once_buffers.insert(key, once_buffer); diff --git a/mediarepo-api/src/tauri_plugin/custom_schemes.rs b/mediarepo-api/src/tauri_plugin/custom_schemes.rs index 219d3b0..6ad31c4 100644 --- a/mediarepo-api/src/tauri_plugin/custom_schemes.rs +++ b/mediarepo-api/src/tauri_plugin/custom_schemes.rs @@ -6,10 +6,9 @@ pub fn register_custom_uri_schemes(builder: Builder) -> Builder(); let resource_key = request.uri().trim_start_matches("once://"); - let buffer = { - let mut buffers = buf_state.buffer.lock(); - buffers.remove(resource_key) - }; + + let buffer = buf_state.get_entry(resource_key); + buf_state.clear_expired(); if let Some(buffer) = buffer { ResponseBuilder::new() .mimetype(&buffer.mime) diff --git a/mediarepo-api/src/tauri_plugin/state.rs b/mediarepo-api/src/tauri_plugin/state.rs index 6833f99..3179f42 100644 --- a/mediarepo-api/src/tauri_plugin/state.rs +++ b/mediarepo-api/src/tauri_plugin/state.rs @@ -1,9 +1,11 @@ use std::collections::HashMap; use std::mem; use std::sync::Arc; +use std::time::Duration; use parking_lot::Mutex; use tauri::async_runtime::RwLock; +use tokio::time::Instant; use crate::client_api::ApiClient; use crate::tauri_plugin::error::{PluginError, PluginResult}; @@ -37,20 +39,75 @@ impl ApiState { } } -pub struct OnceBuffer { +#[derive(Clone)] +pub struct VolatileBuffer { + pub accessed: bool, + pub valid_until: Instant, pub mime: String, pub buf: Vec, } -impl OnceBuffer { +impl VolatileBuffer { pub fn new(mime: String, buf: Vec) -> Self { - Self { mime, buf } + Self { + accessed: false, + valid_until: Instant::now() + Duration::from_secs(60), + mime, + buf, + } } } #[derive(Default)] pub struct BufferState { - pub buffer: Arc>>, + pub buffer: Arc>>, +} + +impl BufferState { + /// Checks if an entry for the specific key exists and resets + /// its state so that it can safely be accessed again. + pub fn reserve_entry(&self, key: &String) -> bool { + let mut buffers = self.buffer.lock(); + let entry = buffers.get_mut(key); + + if let Some(entry) = entry { + entry.accessed = false; // reset that it has been accessed so it can be reused + true + } else { + false + } + } + + /// Returns the cloned buffer entry and flags it for expiration + pub fn get_entry(&self, key: &str) -> Option { + let mut buffers = self.buffer.lock(); + let entry = buffers.get_mut(key); + + if let Some(entry) = entry { + entry.accessed = true; + entry.valid_until = Instant::now() + Duration::from_secs(10); // time to live is 10 seconds + + Some(entry.clone()) + } else { + None + } + } + + /// Clears all expired entries + pub fn clear_expired(&self) { + let mut buffer = self.buffer.lock(); + let keys: Vec = buffer.keys().cloned().collect(); + + for key in keys { + let (accessed, valid_until) = { + let entry = buffer.get(&key).unwrap(); + (entry.accessed, entry.valid_until.clone()) + }; + if accessed && valid_until < Instant::now() { + buffer.remove(&key); + } + } + } } pub struct AppState {