feat: image thumbnails
This commit is contained in:
parent
61f4f2c716
commit
d544d28b6e
6 changed files with 365 additions and 112 deletions
|
@ -16,7 +16,7 @@ use ruma_client_api::{
|
|||
directory::{self, get_public_rooms, get_public_rooms_filtered},
|
||||
filter::{self, create_filter, get_filter},
|
||||
keys::{claim_keys, get_keys, upload_keys},
|
||||
media::{create_content, get_content_thumbnail, get_content, get_media_config},
|
||||
media::{create_content, get_content, get_content_thumbnail, get_media_config},
|
||||
membership::{
|
||||
forget_room, get_member_events, invite_user, join_room_by_id, join_room_by_id_or_alias,
|
||||
leave_room,
|
||||
|
@ -1307,9 +1307,7 @@ pub fn create_message_event_route(
|
|||
)
|
||||
.expect("message events are always okay");
|
||||
|
||||
MatrixResult(Ok(create_message_event::Response {
|
||||
event_id: Some(event_id),
|
||||
}))
|
||||
MatrixResult(Ok(create_message_event::Response { event_id }))
|
||||
}
|
||||
|
||||
#[put(
|
||||
|
@ -1339,9 +1337,7 @@ pub fn create_state_event_for_key_route(
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
MatrixResult(Ok(create_state_event_for_key::Response {
|
||||
event_id: Some(event_id),
|
||||
}))
|
||||
MatrixResult(Ok(create_state_event_for_key::Response { event_id }))
|
||||
}
|
||||
|
||||
#[put(
|
||||
|
@ -1370,9 +1366,7 @@ pub fn create_state_event_for_empty_key_route(
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
MatrixResult(Ok(create_state_event_for_empty_key::Response {
|
||||
event_id: Some(event_id),
|
||||
}))
|
||||
MatrixResult(Ok(create_state_event_for_empty_key::Response { event_id }))
|
||||
}
|
||||
|
||||
#[get("/_matrix/client/r0/rooms/<_room_id>/state", data = "<body>")]
|
||||
|
@ -1805,7 +1799,6 @@ pub fn send_event_to_device_route(
|
|||
|
||||
#[get("/_matrix/media/r0/config")]
|
||||
pub fn get_media_config_route() -> MatrixResult<get_media_config::Response> {
|
||||
warn!("TODO: get_media_config_route");
|
||||
MatrixResult(Ok(get_media_config::Response {
|
||||
upload_size: (20_u32 * 1024 * 1024).into(), // 20 MB
|
||||
}))
|
||||
|
@ -1816,24 +1809,38 @@ pub fn create_content_route(
|
|||
db: State<'_, Database>,
|
||||
body: Ruma<create_content::Request>,
|
||||
) -> MatrixResult<create_content::Response> {
|
||||
let mxc = format!("mxc://{}/{}", db.globals.server_name(), utils::random_string(MXC_LENGTH));
|
||||
let mxc = format!(
|
||||
"mxc://{}/{}",
|
||||
db.globals.server_name(),
|
||||
utils::random_string(MXC_LENGTH)
|
||||
);
|
||||
db.media
|
||||
.create(mxc.clone(), body.filename.as_ref(), &body.content_type, &body.file)
|
||||
.create(
|
||||
mxc.clone(),
|
||||
body.filename.as_ref(),
|
||||
&body.content_type,
|
||||
&body.file,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
MatrixResult(Ok(create_content::Response {
|
||||
content_uri: mxc,
|
||||
}))
|
||||
MatrixResult(Ok(create_content::Response { content_uri: mxc }))
|
||||
}
|
||||
|
||||
#[get("/_matrix/media/r0/download/<_server_name>/<_media_id>", data = "<body>")]
|
||||
#[get(
|
||||
"/_matrix/media/r0/download/<_server_name>/<_media_id>",
|
||||
data = "<body>"
|
||||
)]
|
||||
pub fn get_content_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_content::Request>,
|
||||
_server_name: String,
|
||||
_media_id: String,
|
||||
) -> MatrixResult<get_content::Response> {
|
||||
if let Some((filename, content_type, file)) = db.media.get(format!("mxc://{}/{}", body.server_name, body.media_id)).unwrap() {
|
||||
if let Some((filename, content_type, file)) = db
|
||||
.media
|
||||
.get(format!("mxc://{}/{}", body.server_name, body.media_id))
|
||||
.unwrap()
|
||||
{
|
||||
MatrixResult(Ok(get_content::Response {
|
||||
file,
|
||||
content_type,
|
||||
|
@ -1848,18 +1855,26 @@ pub fn get_content_route(
|
|||
}
|
||||
}
|
||||
|
||||
#[get("/_matrix/media/r0/thumbnail/<_server_name>/<_media_id>", data = "<body>")]
|
||||
#[get(
|
||||
"/_matrix/media/r0/thumbnail/<_server_name>/<_media_id>",
|
||||
data = "<body>"
|
||||
)]
|
||||
pub fn get_content_thumbnail_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_content_thumbnail::Request>,
|
||||
_server_name: String,
|
||||
_media_id: String,
|
||||
) -> MatrixResult<get_content_thumbnail::Response> {
|
||||
if let Some((_, content_type, file)) = db.media.get(format!("mxc://{}/{}", body.server_name, body.media_id)).unwrap() {
|
||||
MatrixResult(Ok(get_content_thumbnail::Response {
|
||||
file,
|
||||
content_type,
|
||||
}))
|
||||
if let Some((_, content_type, file)) = db
|
||||
.media
|
||||
.get_thumbnail(
|
||||
format!("mxc://{}/{}", body.server_name, body.media_id),
|
||||
body.width.try_into().unwrap(),
|
||||
body.height.try_into().unwrap(),
|
||||
)
|
||||
.unwrap()
|
||||
{
|
||||
MatrixResult(Ok(get_content_thumbnail::Response { file, content_type }))
|
||||
} else {
|
||||
MatrixResult(Err(Error {
|
||||
kind: ErrorKind::NotFound,
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use crate::{utils, Error, Result};
|
||||
use std::mem;
|
||||
|
||||
pub struct Media {
|
||||
pub(super) mediaid_file: sled::Tree, // MediaId = MXC + Filename + ContentType
|
||||
pub(super) mediaid_file: sled::Tree, // MediaId = MXC + WidthHeight + Filename + ContentType
|
||||
}
|
||||
|
||||
impl Media {
|
||||
|
@ -15,6 +16,9 @@ impl Media {
|
|||
) -> Result<()> {
|
||||
let mut key = mxc.as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(&0_u32.to_be_bytes()); // Width = 0 if it's not a thumbnail
|
||||
key.extend_from_slice(&0_u32.to_be_bytes()); // Height = 0 if it's not a thumbnail
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(filename.map(|f| f.as_bytes()).unwrap_or_default());
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(content_type.as_bytes());
|
||||
|
@ -28,10 +32,19 @@ impl Media {
|
|||
pub fn get(&self, mxc: String) -> Result<Option<(Option<String>, String, Vec<u8>)>> {
|
||||
let mut prefix = mxc.as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
prefix.extend_from_slice(&0_u32.to_be_bytes()); // Width = 0 if it's not a thumbnail
|
||||
prefix.extend_from_slice(&0_u32.to_be_bytes()); // Height = 0 if it's not a thumbnail
|
||||
prefix.push(0xff);
|
||||
|
||||
if let Some(r) = self.mediaid_file.scan_prefix(&prefix).next() {
|
||||
let (key, file) = r?;
|
||||
let mut parts = key.split(|&b| b == 0xff).skip(1);
|
||||
let mut parts = key.rsplit(|&b| b == 0xff);
|
||||
|
||||
let content_type = utils::string_from_bytes(
|
||||
parts
|
||||
.next()
|
||||
.ok_or(Error::BadDatabase("mediaid is invalid"))?,
|
||||
)?;
|
||||
|
||||
let filename_bytes = parts
|
||||
.next()
|
||||
|
@ -42,13 +55,99 @@ impl Media {
|
|||
Some(utils::string_from_bytes(filename_bytes)?)
|
||||
};
|
||||
|
||||
Ok(Some((filename, content_type, file.to_vec())))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Downloads a file's thumbnail.
|
||||
pub fn get_thumbnail(
|
||||
&self,
|
||||
mxc: String,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<Option<(Option<String>, String, Vec<u8>)>> {
|
||||
let mut main_prefix = mxc.as_bytes().to_vec();
|
||||
main_prefix.push(0xff);
|
||||
|
||||
let mut thumbnail_prefix = main_prefix.clone();
|
||||
thumbnail_prefix.extend_from_slice(&width.to_be_bytes());
|
||||
thumbnail_prefix.extend_from_slice(&height.to_be_bytes());
|
||||
thumbnail_prefix.push(0xff);
|
||||
|
||||
let mut original_prefix = main_prefix;
|
||||
original_prefix.extend_from_slice(&0_u32.to_be_bytes()); // Width = 0 if it's not a thumbnail
|
||||
original_prefix.extend_from_slice(&0_u32.to_be_bytes()); // Height = 0 if it's not a thumbnail
|
||||
original_prefix.push(0xff);
|
||||
|
||||
if let Some(r) = self.mediaid_file.scan_prefix(&thumbnail_prefix).next() {
|
||||
// Using saved thumbnail
|
||||
let (key, file) = r?;
|
||||
let mut parts = key.rsplit(|&b| b == 0xff);
|
||||
|
||||
let content_type = utils::string_from_bytes(
|
||||
parts
|
||||
.next()
|
||||
.ok_or(Error::BadDatabase("mediaid is invalid"))?,
|
||||
)?;
|
||||
|
||||
let filename_bytes = parts
|
||||
.next()
|
||||
.ok_or(Error::BadDatabase("mediaid is invalid"))?;
|
||||
let filename = if filename_bytes.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(utils::string_from_bytes(filename_bytes)?)
|
||||
};
|
||||
|
||||
Ok(Some((filename, content_type, file.to_vec())))
|
||||
} else if let Some(r) = self.mediaid_file.scan_prefix(&original_prefix).next() {
|
||||
// Generate a thumbnail
|
||||
let (key, file) = r?;
|
||||
let mut parts = key.rsplit(|&b| b == 0xff);
|
||||
|
||||
let content_type = utils::string_from_bytes(
|
||||
parts
|
||||
.next()
|
||||
.ok_or(Error::BadDatabase("mediaid is invalid"))?,
|
||||
)?;
|
||||
|
||||
let filename_bytes = parts
|
||||
.next()
|
||||
.ok_or(Error::BadDatabase("mediaid is invalid"))?;
|
||||
let filename = if filename_bytes.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(utils::string_from_bytes(filename_bytes)?)
|
||||
};
|
||||
|
||||
if let Ok(image) = image::load_from_memory(&file) {
|
||||
let thumbnail = image.thumbnail(width, height);
|
||||
let mut thumbnail_bytes = Vec::new();
|
||||
thumbnail.write_to(&mut thumbnail_bytes, image::ImageOutputFormat::Jpeg(75))?;
|
||||
|
||||
// Save thumbnail in database so we don't have to generate it again next time
|
||||
let mut thumbnail_key = key.to_vec();
|
||||
let width_index = thumbnail_key
|
||||
.iter()
|
||||
.position(|&b| b == 0xff)
|
||||
.ok_or(Error::BadDatabase("mediaid is invalid"))?
|
||||
+ 1;
|
||||
let mut widthheight = width.to_be_bytes().to_vec();
|
||||
widthheight.extend_from_slice(&height.to_be_bytes());
|
||||
|
||||
thumbnail_key.splice(
|
||||
width_index..width_index + 2 * mem::size_of::<u32>(),
|
||||
widthheight,
|
||||
);
|
||||
|
||||
self.mediaid_file.insert(thumbnail_key, &*thumbnail_bytes)?;
|
||||
|
||||
Ok(Some((filename, content_type, thumbnail_bytes)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
|
|
|
@ -29,6 +29,11 @@ pub enum Error {
|
|||
#[from]
|
||||
source: ruma_events::InvalidEvent,
|
||||
},
|
||||
#[error("could not generate image")]
|
||||
ImageError {
|
||||
#[from]
|
||||
source: image::error::ImageError,
|
||||
},
|
||||
#[error("bad request")]
|
||||
BadRequest(&'static str),
|
||||
#[error("problem in that database")]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue