diff --git a/mediarepo-api/Cargo.toml b/mediarepo-api/Cargo.toml index adbb3fc..113ce18 100644 --- a/mediarepo-api/Cargo.toml +++ b/mediarepo-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mediarepo-api" -version = "0.6.0" +version = "0.7.0" edition = "2018" license = "gpl-3" @@ -16,6 +16,8 @@ serde_json = {version="1.0.68", optional=true} directories = {version="4.0.1", optional=true} mime_guess = {version = "2.0.3", optional=true} serde_piecewise_default = "0.2.0" +futures = {version = "0.3.17", optional=true} +url = {version = "2.2.2", optional=true } [dependencies.serde] version = "1.0.130" @@ -41,5 +43,5 @@ version = "0.5.8" optional = true [features] -tauri-plugin = ["client-api","tauri", "rmp-ipc", "parking_lot", "serde_json", "tokio", "toml", "directories", "mime_guess"] +tauri-plugin = ["client-api","tauri", "rmp-ipc", "parking_lot", "serde_json", "tokio", "toml", "directories", "mime_guess", "futures", "url"] client-api = ["rmp-ipc", "async-trait", "tokio"] \ No newline at end of file diff --git a/mediarepo-api/src/client_api/file.rs b/mediarepo-api/src/client_api/file.rs index 52a044b..27f7657 100644 --- a/mediarepo-api/src/client_api/file.rs +++ b/mediarepo-api/src/client_api/file.rs @@ -55,6 +55,11 @@ where self.emit_and_get("all_files", ()).await } + /// Returns a file by identifier + pub async fn get_file(&self, id: FileIdentifier) -> ApiResult { + self.emit_and_get("get_file", id).await + } + /// Searches for a file by a list of tags #[tracing::instrument(level = "debug", skip(self))] pub async fn find_files( diff --git a/mediarepo-api/src/tauri_plugin/custom_schemes.rs b/mediarepo-api/src/tauri_plugin/custom_schemes.rs index a5b4bd2..8d87c95 100644 --- a/mediarepo-api/src/tauri_plugin/custom_schemes.rs +++ b/mediarepo-api/src/tauri_plugin/custom_schemes.rs @@ -1,24 +1,104 @@ -use crate::tauri_plugin::state::BufferState; -use tauri::http::ResponseBuilder; -use tauri::{Builder, Manager, Runtime}; +use crate::tauri_plugin::state::{ApiState, BufferState}; +use crate::types::identifier::FileIdentifier; +use std::borrow::Cow; +use std::collections::HashMap; +use tauri::http::{Request, Response, ResponseBuilder}; +use tauri::{AppHandle, Builder, Manager, Runtime}; +use url::Url; + +type Result = std::result::Result>; pub fn register_custom_uri_schemes(builder: Builder) -> Builder { - builder.register_uri_scheme_protocol("once", |app, request| { - let buf_state = app.state::(); - let resource_key = request.uri().trim_start_matches("once://"); - - let buffer = buf_state.get_entry(resource_key); - - if let Some(buffer) = buffer { - ResponseBuilder::new() - .mimetype(&buffer.mime) - .status(200) - .body(buffer.buf) - } else { - ResponseBuilder::new() - .mimetype("text/plain") - .status(404) - .body("Resource not found".as_bytes().to_vec()) - } - }) + builder + .register_uri_scheme_protocol("once", once_scheme) + .register_uri_scheme_protocol("content", content_scheme) + .register_uri_scheme_protocol("thumb", thumb_scheme) +} + +fn once_scheme(app: &AppHandle, request: &Request) -> Result { + let buf_state = app.state::(); + let resource_key = request.uri().trim_start_matches("once://"); + + let buffer = buf_state.get_entry(resource_key); + + if let Some(buffer) = buffer { + ResponseBuilder::new() + .mimetype(&buffer.mime) + .status(200) + .body(buffer.buf) + } else { + ResponseBuilder::new() + .mimetype("text/plain") + .status(404) + .body("Resource not found".as_bytes().to_vec()) + } +} + +fn content_scheme(app: &AppHandle, request: &Request) -> Result { + let api_state = app.state::(); + let buf_state = app.state::(); + let hash = request.uri().trim_start_matches("content://"); + + if let Some(buffer) = buf_state.get_entry(hash) { + ResponseBuilder::new() + .status(200) + .mimetype(&buffer.mime) + .body(buffer.buf) + } else { + let api = api_state.api_sync()?; + let file = + futures::executor::block_on(api.file.get_file(FileIdentifier::Hash(hash.to_string())))?; + let mime = file.mime_type.unwrap_or("image/png".to_string()); + let bytes = futures::executor::block_on( + api.file + .read_file_by_hash(FileIdentifier::Hash(hash.to_string())), + )?; + buf_state.add_entry(hash.to_string(), mime.clone(), bytes.clone()); + ResponseBuilder::new() + .mimetype(&mime) + .status(200) + .body(bytes) + } +} + +fn thumb_scheme(app: &AppHandle, request: &Request) -> Result { + let api_state = app.state::(); + let buf_state = app.state::(); + + let url = Url::parse(request.uri())?; + let hash = url.path(); + + let query_pairs = url + .query_pairs() + .collect::, Cow<'_, str>>>(); + + let height = query_pairs + .get("height") + .and_then(|h| h.parse::().ok()) + .unwrap_or(250); + + let width = query_pairs + .get("width") + .and_then(|w| w.parse::().ok()) + .unwrap_or(250); + + if let Some(buffer) = buf_state.get_entry(hash) { + ResponseBuilder::new() + .status(200) + .mimetype(&buffer.mime) + .body(buffer.buf) + } else { + let api = api_state.api_sync()?; + let (thumb, bytes) = futures::executor::block_on(api.file.get_thumbnail_of_size( + FileIdentifier::Hash(hash.to_string()), + ((height as f32 * 0.8) as u32, (width as f32 * 0.8) as u32), + ((height as f32 * 1.2) as u32, (width as f32 * 1.2) as u32), + ))?; + let mime = thumb.mime_type.unwrap_or(String::from("image/png")); + buf_state.add_entry(hash.to_string(), mime.clone(), bytes.clone()); + ResponseBuilder::new() + .mimetype(&mime) + .status(200) + .body(bytes) + } } diff --git a/mediarepo-api/src/tauri_plugin/state.rs b/mediarepo-api/src/tauri_plugin/state.rs index dba0be4..821975b 100644 --- a/mediarepo-api/src/tauri_plugin/state.rs +++ b/mediarepo-api/src/tauri_plugin/state.rs @@ -54,6 +54,10 @@ impl ApiState { .clone() .ok_or_else(|| PluginError::from("Not connected")) } + + pub fn api_sync(&self) -> PluginResult> { + futures::executor::block_on(self.api()) + } } #[derive(Clone)] @@ -79,6 +83,13 @@ pub struct BufferState { } impl BufferState { + /// Adds a cached buffer to the buffer state + pub fn add_entry(&self, key: String, mime: String, bytes: Vec) { + let mut buffers = self.buffer.write(); + let buffer = VolatileBuffer::new(mime, bytes); + buffers.insert(key, Mutex::new(buffer)); + } + /// 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 {