mirror of
https://gitlab.computer.surgery/matrix/grapevine.git
synced 2025-12-18 16:21:24 +01:00
Use MxcData in media service
This commit is contained in:
parent
391fc2e51a
commit
c1ea25e1e0
6 changed files with 89 additions and 91 deletions
|
|
@ -20,10 +20,8 @@ use ruma::{
|
||||||
use tracing::{debug, error, info, warn};
|
use tracing::{debug, error, info, warn};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
service::media::FileMeta,
|
service::media::{FileMeta, MxcData},
|
||||||
services,
|
services, utils, Ar, Error, Ra, Result,
|
||||||
utils::{self, MxcData},
|
|
||||||
Ar, Error, Ra, Result,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const MXC_LENGTH: usize = 32;
|
const MXC_LENGTH: usize = 32;
|
||||||
|
|
@ -159,7 +157,7 @@ pub(crate) async fn create_content_route(
|
||||||
services()
|
services()
|
||||||
.media
|
.media
|
||||||
.create(
|
.create(
|
||||||
mxc.to_string(),
|
mxc,
|
||||||
body.filename
|
body.filename
|
||||||
.clone()
|
.clone()
|
||||||
.map(|filename| ContentDisposition {
|
.map(|filename| ContentDisposition {
|
||||||
|
|
@ -350,7 +348,7 @@ pub(crate) async fn get_remote_content(
|
||||||
services()
|
services()
|
||||||
.media
|
.media
|
||||||
.create(
|
.create(
|
||||||
mxc.to_string(),
|
mxc,
|
||||||
response.content.content_disposition.as_ref(),
|
response.content.content_disposition.as_ref(),
|
||||||
response.content.content_type.clone(),
|
response.content.content_type.clone(),
|
||||||
&response.content.file,
|
&response.content.file,
|
||||||
|
|
@ -455,7 +453,7 @@ async fn get_content_route_ruma(
|
||||||
..
|
..
|
||||||
},
|
},
|
||||||
file,
|
file,
|
||||||
)) = services().media.get(mxc.to_string()).await?
|
)) = services().media.get(mxc).await?
|
||||||
{
|
{
|
||||||
Ok(authenticated_media_client::get_content::v1::Response {
|
Ok(authenticated_media_client::get_content::v1::Response {
|
||||||
file,
|
file,
|
||||||
|
|
@ -581,7 +579,7 @@ async fn get_content_as_filename_route_ruma(
|
||||||
..
|
..
|
||||||
},
|
},
|
||||||
file,
|
file,
|
||||||
)) = services().media.get(mxc.to_string()).await?
|
)) = services().media.get(mxc).await?
|
||||||
{
|
{
|
||||||
Ok(authenticated_media_client::get_content_as_filename::v1::Response {
|
Ok(authenticated_media_client::get_content_as_filename::v1::Response {
|
||||||
file,
|
file,
|
||||||
|
|
@ -842,8 +840,7 @@ async fn get_content_thumbnail_route_ruma(
|
||||||
..
|
..
|
||||||
},
|
},
|
||||||
file,
|
file,
|
||||||
)) =
|
)) = services().media.get_thumbnail(mxc, width, height).await?
|
||||||
services().media.get_thumbnail(mxc.to_string(), width, height).await?
|
|
||||||
{
|
{
|
||||||
return Ok(make_response(file, content_type));
|
return Ok(make_response(file, content_type));
|
||||||
}
|
}
|
||||||
|
|
@ -872,7 +869,7 @@ async fn get_content_thumbnail_route_ruma(
|
||||||
services()
|
services()
|
||||||
.media
|
.media
|
||||||
.upload_thumbnail(
|
.upload_thumbnail(
|
||||||
mxc.to_string(),
|
mxc,
|
||||||
None,
|
None,
|
||||||
resp.content.content_type.clone(),
|
resp.content.content_type.clone(),
|
||||||
width,
|
width,
|
||||||
|
|
@ -901,10 +898,7 @@ async fn get_content_thumbnail_route_ruma(
|
||||||
..
|
..
|
||||||
},
|
},
|
||||||
file,
|
file,
|
||||||
)) = services()
|
)) = services().media.get_thumbnail(mxc, width, height).await?
|
||||||
.media
|
|
||||||
.get_thumbnail(mxc.to_string(), width, height)
|
|
||||||
.await?
|
|
||||||
{
|
{
|
||||||
return Ok(make_response(file, content_type));
|
return Ok(make_response(file, content_type));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,9 +70,12 @@ use super::appservice_server;
|
||||||
use crate::{
|
use crate::{
|
||||||
api::client_server::{self, claim_keys_helper, get_keys_helper},
|
api::client_server::{self, claim_keys_helper, get_keys_helper},
|
||||||
observability::{FoundIn, Lookup, METRICS},
|
observability::{FoundIn, Lookup, METRICS},
|
||||||
service::pdu::{gen_event_id_canonical_json, PduBuilder},
|
service::{
|
||||||
|
media::MxcData,
|
||||||
|
pdu::{gen_event_id_canonical_json, PduBuilder},
|
||||||
|
},
|
||||||
services,
|
services,
|
||||||
utils::{self, dbg_truncate_str, MxcData},
|
utils::{self, dbg_truncate_str},
|
||||||
Ar, Error, PduEvent, Ra, Result,
|
Ar, Error, PduEvent, Ra, Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -2050,7 +2053,7 @@ pub(crate) async fn media_download_route(
|
||||||
content_type,
|
content_type,
|
||||||
},
|
},
|
||||||
file,
|
file,
|
||||||
)) = services().media.get(mxc.to_string()).await?
|
)) = services().media.get(mxc).await?
|
||||||
else {
|
else {
|
||||||
return Err(Error::BadRequest(
|
return Err(Error::BadRequest(
|
||||||
ErrorKind::NotYetUploaded,
|
ErrorKind::NotYetUploaded,
|
||||||
|
|
@ -2097,7 +2100,7 @@ pub(crate) async fn media_thumbnail_route(
|
||||||
..
|
..
|
||||||
},
|
},
|
||||||
file,
|
file,
|
||||||
)) = services().media.get_thumbnail(mxc.to_string(), width, height).await?
|
)) = services().media.get_thumbnail(mxc, width, height).await?
|
||||||
else {
|
else {
|
||||||
return Err(Error::BadRequest(
|
return Err(Error::BadRequest(
|
||||||
ErrorKind::NotYetUploaded,
|
ErrorKind::NotYetUploaded,
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use crate::{
|
||||||
database::KeyValueDatabase,
|
database::KeyValueDatabase,
|
||||||
service::{
|
service::{
|
||||||
self,
|
self,
|
||||||
media::{FileMeta, MediaFileKey},
|
media::{FileMeta, MediaFileKey, MxcData},
|
||||||
},
|
},
|
||||||
utils, Error, Result,
|
utils, Error, Result,
|
||||||
};
|
};
|
||||||
|
|
@ -12,12 +12,12 @@ use crate::{
|
||||||
impl service::media::Data for KeyValueDatabase {
|
impl service::media::Data for KeyValueDatabase {
|
||||||
fn create_file_metadata(
|
fn create_file_metadata(
|
||||||
&self,
|
&self,
|
||||||
mxc: String,
|
mxc: MxcData<'_>,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
meta: &FileMeta,
|
meta: &FileMeta,
|
||||||
) -> Result<MediaFileKey> {
|
) -> Result<MediaFileKey> {
|
||||||
let mut key = mxc.as_bytes().to_vec();
|
let mut key = mxc.to_string().as_bytes().to_vec();
|
||||||
key.push(0xFF);
|
key.push(0xFF);
|
||||||
key.extend_from_slice(&width.to_be_bytes());
|
key.extend_from_slice(&width.to_be_bytes());
|
||||||
key.extend_from_slice(&height.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(
|
fn search_file_metadata(
|
||||||
&self,
|
&self,
|
||||||
mxc: String,
|
mxc: MxcData<'_>,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
) -> Result<(FileMeta, MediaFileKey)> {
|
) -> 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.push(0xFF);
|
||||||
prefix.extend_from_slice(&width.to_be_bytes());
|
prefix.extend_from_slice(&width.to_be_bytes());
|
||||||
prefix.extend_from_slice(&height.to_be_bytes());
|
prefix.extend_from_slice(&height.to_be_bytes());
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
use std::io::Cursor;
|
use std::{fmt, io::Cursor};
|
||||||
|
|
||||||
use image::imageops::FilterType;
|
use image::imageops::FilterType;
|
||||||
use ruma::http_headers::ContentDisposition;
|
use ruma::{
|
||||||
|
api::client::error::ErrorKind, http_headers::ContentDisposition, MxcUri,
|
||||||
|
MxcUriError, OwnedMxcUri,
|
||||||
|
};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{AsyncReadExt, AsyncWriteExt},
|
io::{AsyncReadExt, AsyncWriteExt},
|
||||||
};
|
};
|
||||||
use tracing::{debug, warn};
|
use tracing::{debug, warn};
|
||||||
|
|
||||||
use crate::{services, Result};
|
use crate::{services, Error, Result};
|
||||||
|
|
||||||
mod data;
|
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<Self> {
|
||||||
|
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<MxcData<'_>> 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<Self, Self::Error> {
|
||||||
|
Ok(Self::new(value.server_name()?, value.media_id()?)
|
||||||
|
.expect("validated MxcUri should always be valid MxcData"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) struct Service {
|
pub(crate) struct Service {
|
||||||
pub(crate) db: &'static dyn Data,
|
pub(crate) db: &'static dyn Data,
|
||||||
}
|
}
|
||||||
|
|
@ -46,7 +100,7 @@ impl Service {
|
||||||
#[tracing::instrument(skip(self, file))]
|
#[tracing::instrument(skip(self, file))]
|
||||||
pub(crate) async fn create(
|
pub(crate) async fn create(
|
||||||
&self,
|
&self,
|
||||||
mxc: String,
|
mxc: MxcData<'_>,
|
||||||
content_disposition: Option<&ContentDisposition>,
|
content_disposition: Option<&ContentDisposition>,
|
||||||
content_type: Option<String>,
|
content_type: Option<String>,
|
||||||
file: &[u8],
|
file: &[u8],
|
||||||
|
|
@ -69,7 +123,7 @@ impl Service {
|
||||||
#[tracing::instrument(skip(self, file))]
|
#[tracing::instrument(skip(self, file))]
|
||||||
pub(crate) async fn upload_thumbnail(
|
pub(crate) async fn upload_thumbnail(
|
||||||
&self,
|
&self,
|
||||||
mxc: String,
|
mxc: MxcData<'_>,
|
||||||
content_disposition: Option<String>,
|
content_disposition: Option<String>,
|
||||||
content_type: Option<String>,
|
content_type: Option<String>,
|
||||||
width: u32,
|
width: u32,
|
||||||
|
|
@ -93,7 +147,7 @@ impl Service {
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
pub(crate) async fn get(
|
pub(crate) async fn get(
|
||||||
&self,
|
&self,
|
||||||
mxc: String,
|
mxc: MxcData<'_>,
|
||||||
) -> Result<Option<(FileMeta, Vec<u8>)>> {
|
) -> Result<Option<(FileMeta, Vec<u8>)>> {
|
||||||
if let Ok((meta, key)) = self.db.search_file_metadata(mxc, 0, 0) {
|
if let Ok((meta, key)) = self.db.search_file_metadata(mxc, 0, 0) {
|
||||||
let path = services().globals.get_media_file(&key);
|
let path = services().globals.get_media_file(&key);
|
||||||
|
|
@ -224,7 +278,7 @@ impl Service {
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
pub(crate) async fn get_thumbnail(
|
pub(crate) async fn get_thumbnail(
|
||||||
&self,
|
&self,
|
||||||
mxc: String,
|
mxc: MxcData<'_>,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
) -> Result<Option<(FileMeta, Vec<u8>)>> {
|
) -> Result<Option<(FileMeta, Vec<u8>)>> {
|
||||||
|
|
@ -233,7 +287,7 @@ impl Service {
|
||||||
Self::thumbnail_properties(width, height).unwrap_or((0, 0, false));
|
Self::thumbnail_properties(width, height).unwrap_or((0, 0, false));
|
||||||
|
|
||||||
if let Ok((meta, key)) =
|
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");
|
debug!("Using saved thumbnail");
|
||||||
let path = services().globals.get_media_file(&key);
|
let path = services().globals.get_media_file(&key);
|
||||||
|
|
@ -243,8 +297,7 @@ impl Service {
|
||||||
return Ok(Some((meta, file.clone())));
|
return Ok(Some((meta, file.clone())));
|
||||||
}
|
}
|
||||||
|
|
||||||
let Ok((meta, key)) = self.db.search_file_metadata(mxc.clone(), 0, 0)
|
let Ok((meta, key)) = self.db.search_file_metadata(mxc, 0, 0) else {
|
||||||
else {
|
|
||||||
debug!("Original image not found, can't generate thumbnail");
|
debug!("Original image not found, can't generate thumbnail");
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use super::{FileMeta, MediaFileKey};
|
use super::{FileMeta, MediaFileKey, MxcData};
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
|
|
||||||
pub(crate) trait Data: Send + Sync {
|
pub(crate) trait Data: Send + Sync {
|
||||||
fn create_file_metadata(
|
fn create_file_metadata(
|
||||||
&self,
|
&self,
|
||||||
mxc: String,
|
mxc: MxcData<'_>,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
meta: &FileMeta,
|
meta: &FileMeta,
|
||||||
|
|
@ -12,7 +12,7 @@ pub(crate) trait Data: Send + Sync {
|
||||||
|
|
||||||
fn search_file_metadata(
|
fn search_file_metadata(
|
||||||
&self,
|
&self,
|
||||||
mxc: String,
|
mxc: MxcData<'_>,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
) -> Result<(FileMeta, MediaFileKey)>;
|
) -> Result<(FileMeta, MediaFileKey)>;
|
||||||
|
|
|
||||||
56
src/utils.rs
56
src/utils.rs
|
|
@ -13,11 +13,10 @@ use cmp::Ordering;
|
||||||
use rand::{prelude::*, rngs::OsRng};
|
use rand::{prelude::*, rngs::OsRng};
|
||||||
use ring::digest;
|
use ring::digest;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
api::client::error::ErrorKind, canonical_json::try_from_json_map,
|
canonical_json::try_from_json_map, CanonicalJsonError, CanonicalJsonObject,
|
||||||
CanonicalJsonError, CanonicalJsonObject, MxcUri, MxcUriError, OwnedMxcUri,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{Error, Result};
|
use crate::Result;
|
||||||
|
|
||||||
// Hopefully we have a better chat protocol in 530 years
|
// Hopefully we have a better chat protocol in 530 years
|
||||||
#[allow(clippy::as_conversions, clippy::cast_possible_truncation)]
|
#[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<Self> {
|
|
||||||
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<MxcData<'_>> 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<Self, Self::Error> {
|
|
||||||
Ok(Self::new(value.server_name()?, value.media_id()?)
|
|
||||||
.expect("validated MxcUri should always be valid MxcData"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::utils::dbg_truncate_str;
|
use crate::utils::dbg_truncate_str;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue