feat: media
This commit is contained in:
parent
42ae433b25
commit
821c608c6a
|
@ -16,7 +16,7 @@ use ruma_client_api::{
|
||||||
directory::{self, get_public_rooms_filtered},
|
directory::{self, get_public_rooms_filtered},
|
||||||
filter::{self, create_filter, get_filter},
|
filter::{self, create_filter, get_filter},
|
||||||
keys::{claim_keys, get_keys, upload_keys},
|
keys::{claim_keys, get_keys, upload_keys},
|
||||||
media::get_media_config,
|
media::{create_content, get_content_thumbnail, get_content, get_media_config},
|
||||||
membership::{
|
membership::{
|
||||||
forget_room, get_member_events, invite_user, join_room_by_id, join_room_by_id_or_alias,
|
forget_room, get_member_events, invite_user, join_room_by_id, join_room_by_id_or_alias,
|
||||||
leave_room,
|
leave_room,
|
||||||
|
@ -53,6 +53,7 @@ const GUEST_NAME_LENGTH: usize = 10;
|
||||||
const DEVICE_ID_LENGTH: usize = 10;
|
const DEVICE_ID_LENGTH: usize = 10;
|
||||||
const SESSION_ID_LENGTH: usize = 256;
|
const SESSION_ID_LENGTH: usize = 256;
|
||||||
const TOKEN_LENGTH: usize = 256;
|
const TOKEN_LENGTH: usize = 256;
|
||||||
|
const MXC_LENGTH: usize = 256;
|
||||||
|
|
||||||
#[get("/_matrix/client/versions")]
|
#[get("/_matrix/client/versions")]
|
||||||
pub fn get_supported_versions_route() -> MatrixResult<get_supported_versions::Response> {
|
pub fn get_supported_versions_route() -> MatrixResult<get_supported_versions::Response> {
|
||||||
|
@ -1259,7 +1260,7 @@ pub fn create_message_event_route(
|
||||||
body.room_id.clone(),
|
body.room_id.clone(),
|
||||||
user_id.clone(),
|
user_id.clone(),
|
||||||
body.event_type.clone(),
|
body.event_type.clone(),
|
||||||
body.json_body.clone(),
|
body.json_body.clone().unwrap(),
|
||||||
Some(unsigned),
|
Some(unsigned),
|
||||||
None,
|
None,
|
||||||
&db.globals,
|
&db.globals,
|
||||||
|
@ -1291,7 +1292,7 @@ pub fn create_state_event_for_key_route(
|
||||||
body.room_id.clone(),
|
body.room_id.clone(),
|
||||||
user_id.clone(),
|
user_id.clone(),
|
||||||
body.event_type.clone(),
|
body.event_type.clone(),
|
||||||
body.json_body.clone(),
|
body.json_body.clone().unwrap(),
|
||||||
None,
|
None,
|
||||||
Some(body.state_key.clone()),
|
Some(body.state_key.clone()),
|
||||||
&db.globals,
|
&db.globals,
|
||||||
|
@ -1322,7 +1323,7 @@ pub fn create_state_event_for_empty_key_route(
|
||||||
body.room_id.clone(),
|
body.room_id.clone(),
|
||||||
user_id.clone(),
|
user_id.clone(),
|
||||||
body.event_type.clone(),
|
body.event_type.clone(),
|
||||||
body.json_body.clone(),
|
body.json_body.clone().unwrap(),
|
||||||
None,
|
None,
|
||||||
Some("".to_owned()),
|
Some("".to_owned()),
|
||||||
&db.globals,
|
&db.globals,
|
||||||
|
@ -1766,10 +1767,68 @@ pub fn send_event_to_device_route(
|
||||||
pub fn get_media_config_route() -> MatrixResult<get_media_config::Response> {
|
pub fn get_media_config_route() -> MatrixResult<get_media_config::Response> {
|
||||||
warn!("TODO: get_media_config_route");
|
warn!("TODO: get_media_config_route");
|
||||||
MatrixResult(Ok(get_media_config::Response {
|
MatrixResult(Ok(get_media_config::Response {
|
||||||
upload_size: 0_u32.into(),
|
upload_size: (20_u32 * 1024 * 1024).into(), // 20 MB
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[post("/_matrix/media/r0/upload", data = "<body>")]
|
||||||
|
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));
|
||||||
|
db.media
|
||||||
|
.create(mxc.clone(), body.filename.as_ref(), &body.content_type, &body.file)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
MatrixResult(Ok(create_content::Response {
|
||||||
|
content_uri: mxc,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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() {
|
||||||
|
MatrixResult(Ok(get_content::Response {
|
||||||
|
file,
|
||||||
|
content_type,
|
||||||
|
content_disposition: filename.unwrap_or_default(), // TODO: Spec says this should be optional
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
MatrixResult(Err(Error {
|
||||||
|
kind: ErrorKind::NotFound,
|
||||||
|
message: "Media not found.".to_owned(),
|
||||||
|
status_code: http::StatusCode::NOT_FOUND,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
MatrixResult(Err(Error {
|
||||||
|
kind: ErrorKind::NotFound,
|
||||||
|
message: "Media not found.".to_owned(),
|
||||||
|
status_code: http::StatusCode::NOT_FOUND,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[options("/<_segments..>")]
|
#[options("/<_segments..>")]
|
||||||
pub fn options_route(
|
pub fn options_route(
|
||||||
_segments: rocket::http::uri::Segments<'_>,
|
_segments: rocket::http::uri::Segments<'_>,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
pub(self) mod account_data;
|
pub(self) mod account_data;
|
||||||
pub(self) mod global_edus;
|
pub(self) mod global_edus;
|
||||||
pub(self) mod globals;
|
pub(self) mod globals;
|
||||||
|
pub(self) mod media;
|
||||||
pub(self) mod rooms;
|
pub(self) mod rooms;
|
||||||
pub(self) mod users;
|
pub(self) mod users;
|
||||||
|
|
||||||
|
@ -15,6 +16,7 @@ pub struct Database {
|
||||||
pub rooms: rooms::Rooms,
|
pub rooms: rooms::Rooms,
|
||||||
pub account_data: account_data::AccountData,
|
pub account_data: account_data::AccountData,
|
||||||
pub global_edus: global_edus::GlobalEdus,
|
pub global_edus: global_edus::GlobalEdus,
|
||||||
|
pub media: media::Media,
|
||||||
pub _db: sled::Db,
|
pub _db: sled::Db,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,6 +90,9 @@ impl Database {
|
||||||
//globalallid_globalall: db.open_tree("globalallid_globalall").unwrap(),
|
//globalallid_globalall: db.open_tree("globalallid_globalall").unwrap(),
|
||||||
globallatestid_globallatest: db.open_tree("globallatestid_globallatest").unwrap(), // Presence
|
globallatestid_globallatest: db.open_tree("globallatestid_globallatest").unwrap(), // Presence
|
||||||
},
|
},
|
||||||
|
media: media::Media {
|
||||||
|
mediaid_file: db.open_tree("mediaid_file").unwrap(),
|
||||||
|
},
|
||||||
_db: db,
|
_db: db,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
use crate::{utils, Error, Result};
|
||||||
|
|
||||||
|
pub struct Media {
|
||||||
|
pub(super) mediaid_file: sled::Tree, // MediaId = MXC + Filename + ContentType
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Media {
|
||||||
|
/// Uploads or replaces a file.
|
||||||
|
pub fn create(
|
||||||
|
&self,
|
||||||
|
mxc: String,
|
||||||
|
filename: Option<&String>,
|
||||||
|
content_type: &str,
|
||||||
|
file: &[u8],
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut key = mxc.as_bytes().to_vec();
|
||||||
|
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());
|
||||||
|
|
||||||
|
self.mediaid_file.insert(key, file)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downloads a file.
|
||||||
|
pub fn get(&self, mxc: String) -> Result<Option<(Option<String>, String, Vec<u8>)>> {
|
||||||
|
let mut prefix = mxc.as_bytes().to_vec();
|
||||||
|
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 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)?)
|
||||||
|
};
|
||||||
|
|
||||||
|
let content_type = utils::string_from_bytes(
|
||||||
|
parts
|
||||||
|
.next()
|
||||||
|
.ok_or(Error::BadDatabase("mediaid is invalid"))?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Some((filename, content_type, file.to_vec())))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -71,6 +71,9 @@ fn setup_rocket() -> rocket::Rocket {
|
||||||
client_server::publicised_groups_route,
|
client_server::publicised_groups_route,
|
||||||
client_server::send_event_to_device_route,
|
client_server::send_event_to_device_route,
|
||||||
client_server::get_media_config_route,
|
client_server::get_media_config_route,
|
||||||
|
client_server::create_content_route,
|
||||||
|
client_server::get_content_route,
|
||||||
|
client_server::get_content_thumbnail_route,
|
||||||
client_server::options_route,
|
client_server::options_route,
|
||||||
server_server::well_known_server,
|
server_server::well_known_server,
|
||||||
server_server::get_server_version,
|
server_server::get_server_version,
|
||||||
|
|
|
@ -11,7 +11,7 @@ use ruma_identifiers::UserId;
|
||||||
use std::{convert::TryInto, io::Cursor, ops::Deref};
|
use std::{convert::TryInto, io::Cursor, ops::Deref};
|
||||||
use tokio::io::AsyncReadExt;
|
use tokio::io::AsyncReadExt;
|
||||||
|
|
||||||
const MESSAGE_LIMIT: u64 = 65535;
|
const MESSAGE_LIMIT: u64 = 20 * 1024 * 1024; // 20 MB
|
||||||
|
|
||||||
/// This struct converts rocket requests into ruma structs by converting them into http requests
|
/// This struct converts rocket requests into ruma structs by converting them into http requests
|
||||||
/// first.
|
/// first.
|
||||||
|
@ -19,7 +19,7 @@ pub struct Ruma<T> {
|
||||||
body: T,
|
body: T,
|
||||||
pub user_id: Option<UserId>,
|
pub user_id: Option<UserId>,
|
||||||
pub device_id: Option<String>,
|
pub device_id: Option<String>,
|
||||||
pub json_body: serde_json::Value,
|
pub json_body: Option<serde_json::Value>, // This is None if parsing failed (for raw byte bodies)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: Endpoint> FromData<'a> for Ruma<T> {
|
impl<'a, T: Endpoint> FromData<'a> for Ruma<T> {
|
||||||
|
@ -85,12 +85,8 @@ impl<'a, T: Endpoint> FromData<'a> for Ruma<T> {
|
||||||
body: t,
|
body: t,
|
||||||
user_id,
|
user_id,
|
||||||
device_id,
|
device_id,
|
||||||
// TODO: Can we avoid parsing it again?
|
// TODO: Can we avoid parsing it again? (We only need this for append_pdu)
|
||||||
json_body: if !body.is_empty() {
|
json_body: serde_json::from_slice(&body).ok()
|
||||||
serde_json::from_slice(&body).expect("Ruma already parsed it successfully")
|
|
||||||
} else {
|
|
||||||
serde_json::Value::default()
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("{:?}", e);
|
warn!("{:?}", e);
|
||||||
|
|
Loading…
Reference in New Issue