From c1ea25e1e0ced94bd67335b20668519c063d2ef7 Mon Sep 17 00:00:00 2001 From: Lambda Date: Sun, 1 Sep 2024 15:35:35 +0000 Subject: [PATCH] Use MxcData in media service --- src/api/client_server/media.rs | 24 ++++------- src/api/server_server.rs | 11 +++-- src/database/key_value/media.rs | 10 ++--- src/service/media.rs | 73 ++++++++++++++++++++++++++++----- src/service/media/data.rs | 6 +-- src/utils.rs | 56 +------------------------ 6 files changed, 89 insertions(+), 91 deletions(-) diff --git a/src/api/client_server/media.rs b/src/api/client_server/media.rs index a06d443b..640060c7 100644 --- a/src/api/client_server/media.rs +++ b/src/api/client_server/media.rs @@ -20,10 +20,8 @@ use ruma::{ use tracing::{debug, error, info, warn}; use crate::{ - service::media::FileMeta, - services, - utils::{self, MxcData}, - Ar, Error, Ra, Result, + service::media::{FileMeta, MxcData}, + services, utils, Ar, Error, Ra, Result, }; const MXC_LENGTH: usize = 32; @@ -159,7 +157,7 @@ pub(crate) async fn create_content_route( services() .media .create( - mxc.to_string(), + mxc, body.filename .clone() .map(|filename| ContentDisposition { @@ -350,7 +348,7 @@ pub(crate) async fn get_remote_content( services() .media .create( - mxc.to_string(), + mxc, response.content.content_disposition.as_ref(), response.content.content_type.clone(), &response.content.file, @@ -455,7 +453,7 @@ async fn get_content_route_ruma( .. }, file, - )) = services().media.get(mxc.to_string()).await? + )) = services().media.get(mxc).await? { Ok(authenticated_media_client::get_content::v1::Response { file, @@ -581,7 +579,7 @@ async fn get_content_as_filename_route_ruma( .. }, file, - )) = services().media.get(mxc.to_string()).await? + )) = services().media.get(mxc).await? { Ok(authenticated_media_client::get_content_as_filename::v1::Response { file, @@ -842,8 +840,7 @@ async fn get_content_thumbnail_route_ruma( .. }, file, - )) = - services().media.get_thumbnail(mxc.to_string(), width, height).await? + )) = services().media.get_thumbnail(mxc, width, height).await? { return Ok(make_response(file, content_type)); } @@ -872,7 +869,7 @@ async fn get_content_thumbnail_route_ruma( services() .media .upload_thumbnail( - mxc.to_string(), + mxc, None, resp.content.content_type.clone(), width, @@ -901,10 +898,7 @@ async fn get_content_thumbnail_route_ruma( .. }, file, - )) = services() - .media - .get_thumbnail(mxc.to_string(), width, height) - .await? + )) = services().media.get_thumbnail(mxc, width, height).await? { return Ok(make_response(file, content_type)); } diff --git a/src/api/server_server.rs b/src/api/server_server.rs index a3f2f19b..654e2528 100644 --- a/src/api/server_server.rs +++ b/src/api/server_server.rs @@ -70,9 +70,12 @@ use super::appservice_server; use crate::{ api::client_server::{self, claim_keys_helper, get_keys_helper}, observability::{FoundIn, Lookup, METRICS}, - service::pdu::{gen_event_id_canonical_json, PduBuilder}, + service::{ + media::MxcData, + pdu::{gen_event_id_canonical_json, PduBuilder}, + }, services, - utils::{self, dbg_truncate_str, MxcData}, + utils::{self, dbg_truncate_str}, Ar, Error, PduEvent, Ra, Result, }; @@ -2050,7 +2053,7 @@ pub(crate) async fn media_download_route( content_type, }, file, - )) = services().media.get(mxc.to_string()).await? + )) = services().media.get(mxc).await? else { return Err(Error::BadRequest( ErrorKind::NotYetUploaded, @@ -2097,7 +2100,7 @@ pub(crate) async fn media_thumbnail_route( .. }, file, - )) = services().media.get_thumbnail(mxc.to_string(), width, height).await? + )) = services().media.get_thumbnail(mxc, width, height).await? else { return Err(Error::BadRequest( ErrorKind::NotYetUploaded, diff --git a/src/database/key_value/media.rs b/src/database/key_value/media.rs index 314cd0a9..3beeaf4c 100644 --- a/src/database/key_value/media.rs +++ b/src/database/key_value/media.rs @@ -4,7 +4,7 @@ use crate::{ database::KeyValueDatabase, service::{ self, - media::{FileMeta, MediaFileKey}, + media::{FileMeta, MediaFileKey, MxcData}, }, utils, Error, Result, }; @@ -12,12 +12,12 @@ use crate::{ impl service::media::Data for KeyValueDatabase { fn create_file_metadata( &self, - mxc: String, + mxc: MxcData<'_>, width: u32, height: u32, meta: &FileMeta, ) -> Result { - let mut key = mxc.as_bytes().to_vec(); + let mut key = mxc.to_string().as_bytes().to_vec(); key.push(0xFF); key.extend_from_slice(&width.to_be_bytes()); key.extend_from_slice(&height.to_be_bytes()); @@ -45,11 +45,11 @@ impl service::media::Data for KeyValueDatabase { fn search_file_metadata( &self, - mxc: String, + mxc: MxcData<'_>, width: u32, height: u32, ) -> Result<(FileMeta, MediaFileKey)> { - let mut prefix = mxc.as_bytes().to_vec(); + let mut prefix = mxc.to_string().as_bytes().to_vec(); prefix.push(0xFF); prefix.extend_from_slice(&width.to_be_bytes()); prefix.extend_from_slice(&height.to_be_bytes()); diff --git a/src/service/media.rs b/src/service/media.rs index c03fd92f..4877bb7a 100644 --- a/src/service/media.rs +++ b/src/service/media.rs @@ -1,14 +1,17 @@ -use std::io::Cursor; +use std::{fmt, io::Cursor}; use image::imageops::FilterType; -use ruma::http_headers::ContentDisposition; +use ruma::{ + api::client::error::ErrorKind, http_headers::ContentDisposition, MxcUri, + MxcUriError, OwnedMxcUri, +}; use tokio::{ fs::File, io::{AsyncReadExt, AsyncWriteExt}, }; use tracing::{debug, warn}; -use crate::{services, Result}; +use crate::{services, Error, Result}; mod data; @@ -37,6 +40,57 @@ impl MediaFileKey { } } +/// Data that makes up an `mxc://` URL. +#[derive(Debug, Clone, Copy)] +pub(crate) struct MxcData<'a> { + pub(crate) server_name: &'a ruma::ServerName, + pub(crate) media_id: &'a str, +} + +impl<'a> MxcData<'a> { + pub(crate) fn new( + server_name: &'a ruma::ServerName, + media_id: &'a str, + ) -> Result { + if !media_id.bytes().all(|b| { + matches!(b, + b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z' | b'-' | b'_' + ) + }) { + return Err(Error::BadRequest( + ErrorKind::InvalidParam, + "Invalid MXC media id", + )); + } + + Ok(Self { + server_name, + media_id, + }) + } +} + +impl fmt::Display for MxcData<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "mxc://{}/{}", self.server_name, self.media_id) + } +} + +impl From> for OwnedMxcUri { + fn from(value: MxcData<'_>) -> Self { + value.to_string().into() + } +} + +impl<'a> TryFrom<&'a MxcUri> for MxcData<'a> { + type Error = MxcUriError; + + fn try_from(value: &'a MxcUri) -> Result { + Ok(Self::new(value.server_name()?, value.media_id()?) + .expect("validated MxcUri should always be valid MxcData")) + } +} + pub(crate) struct Service { pub(crate) db: &'static dyn Data, } @@ -46,7 +100,7 @@ impl Service { #[tracing::instrument(skip(self, file))] pub(crate) async fn create( &self, - mxc: String, + mxc: MxcData<'_>, content_disposition: Option<&ContentDisposition>, content_type: Option, file: &[u8], @@ -69,7 +123,7 @@ impl Service { #[tracing::instrument(skip(self, file))] pub(crate) async fn upload_thumbnail( &self, - mxc: String, + mxc: MxcData<'_>, content_disposition: Option, content_type: Option, width: u32, @@ -93,7 +147,7 @@ impl Service { #[tracing::instrument(skip(self))] pub(crate) async fn get( &self, - mxc: String, + mxc: MxcData<'_>, ) -> Result)>> { if let Ok((meta, key)) = self.db.search_file_metadata(mxc, 0, 0) { let path = services().globals.get_media_file(&key); @@ -224,7 +278,7 @@ impl Service { #[tracing::instrument(skip(self))] pub(crate) async fn get_thumbnail( &self, - mxc: String, + mxc: MxcData<'_>, width: u32, height: u32, ) -> Result)>> { @@ -233,7 +287,7 @@ impl Service { Self::thumbnail_properties(width, height).unwrap_or((0, 0, false)); if let Ok((meta, key)) = - self.db.search_file_metadata(mxc.clone(), width, height) + self.db.search_file_metadata(mxc, width, height) { debug!("Using saved thumbnail"); let path = services().globals.get_media_file(&key); @@ -243,8 +297,7 @@ impl Service { return Ok(Some((meta, file.clone()))); } - let Ok((meta, key)) = self.db.search_file_metadata(mxc.clone(), 0, 0) - else { + let Ok((meta, key)) = self.db.search_file_metadata(mxc, 0, 0) else { debug!("Original image not found, can't generate thumbnail"); return Ok(None); }; diff --git a/src/service/media/data.rs b/src/service/media/data.rs index 64fbdbbf..a7d81428 100644 --- a/src/service/media/data.rs +++ b/src/service/media/data.rs @@ -1,10 +1,10 @@ -use super::{FileMeta, MediaFileKey}; +use super::{FileMeta, MediaFileKey, MxcData}; use crate::Result; pub(crate) trait Data: Send + Sync { fn create_file_metadata( &self, - mxc: String, + mxc: MxcData<'_>, width: u32, height: u32, meta: &FileMeta, @@ -12,7 +12,7 @@ pub(crate) trait Data: Send + Sync { fn search_file_metadata( &self, - mxc: String, + mxc: MxcData<'_>, width: u32, height: u32, ) -> Result<(FileMeta, MediaFileKey)>; diff --git a/src/utils.rs b/src/utils.rs index 136e9b5f..18a3aa59 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -13,11 +13,10 @@ use cmp::Ordering; use rand::{prelude::*, rngs::OsRng}; use ring::digest; use ruma::{ - api::client::error::ErrorKind, canonical_json::try_from_json_map, - CanonicalJsonError, CanonicalJsonObject, MxcUri, MxcUriError, OwnedMxcUri, + canonical_json::try_from_json_map, CanonicalJsonError, CanonicalJsonObject, }; -use crate::{Error, Result}; +use crate::Result; // Hopefully we have a better chat protocol in 530 years #[allow(clippy::as_conversions, clippy::cast_possible_truncation)] @@ -255,57 +254,6 @@ pub(crate) fn dbg_truncate_str(s: &str, mut max_len: usize) -> Cow<'_, str> { } } -/// Data that makes up an `mxc://` URL. -#[derive(Debug, Clone, Copy)] -pub(crate) struct MxcData<'a> { - pub(crate) server_name: &'a ruma::ServerName, - pub(crate) media_id: &'a str, -} - -impl<'a> MxcData<'a> { - pub(crate) fn new( - server_name: &'a ruma::ServerName, - media_id: &'a str, - ) -> Result { - if !media_id.bytes().all(|b| { - matches!(b, - b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z' | b'-' | b'_' - ) - }) { - return Err(Error::BadRequest( - ErrorKind::InvalidParam, - "Invalid MXC media id", - )); - } - - Ok(Self { - server_name, - media_id, - }) - } -} - -impl fmt::Display for MxcData<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "mxc://{}/{}", self.server_name, self.media_id) - } -} - -impl From> for OwnedMxcUri { - fn from(value: MxcData<'_>) -> Self { - value.to_string().into() - } -} - -impl<'a> TryFrom<&'a MxcUri> for MxcData<'a> { - type Error = MxcUriError; - - fn try_from(value: &'a MxcUri) -> Result { - Ok(Self::new(value.server_name()?, value.media_id()?) - .expect("validated MxcUri should always be valid MxcData")) - } -} - #[cfg(test)] mod tests { use crate::utils::dbg_truncate_str;