From aa5e9e607ecc739d0d991ea7221dadd0125f6d64 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timo=20K=C3=B6sters?= <timo@koesters.xyz>
Date: Mon, 14 Sep 2020 14:20:38 +0200
Subject: [PATCH] feat: download media and thumbnails over federation

---
 src/client_server/media.rs | 69 +++++++++++++++++++++++++++++++++++---
 src/database/media.rs      | 28 ++++++++++++++--
 2 files changed, 90 insertions(+), 7 deletions(-)

diff --git a/src/client_server/media.rs b/src/client_server/media.rs
index d07744727..8f3374352 100644
--- a/src/client_server/media.rs
+++ b/src/client_server/media.rs
@@ -1,5 +1,7 @@
 use super::State;
-use crate::{database::media::FileMeta, utils, ConduitResult, Database, Error, Ruma};
+use crate::{
+    database::media::FileMeta, server_server, utils, ConduitResult, Database, Error, Ruma,
+};
 use ruma::api::client::{
     error::ErrorKind,
     r0::media::{create_content, get_content, get_content_thumbnail, get_media_config},
@@ -35,7 +37,7 @@ pub fn create_content_route(
         utils::random_string(MXC_LENGTH)
     );
     db.media
-        .create(mxc.clone(), &body.filename, &body.content_type, &body.file)?;
+        .create(mxc.clone(), &body.filename.as_deref(), &body.content_type, &body.file)?;
 
     Ok(create_content::Response { content_uri: mxc }.into())
 }
@@ -47,19 +49,25 @@ pub fn create_content_route(
         data = "<body>"
     )
 )]
-pub fn get_content_route(
+pub async fn get_content_route(
     db: State<'_, Database>,
     body: Ruma<get_content::Request<'_>>,
     _server_name: String,
     _media_id: String,
 ) -> ConduitResult<get_content::Response> {
+        let mxc = format!(
+            "mxc://{}/{}",
+            db.globals.server_name(),
+            utils::random_string(MXC_LENGTH)
+        );
+
     if let Some(FileMeta {
         filename,
         content_type,
         file,
     }) = db
         .media
-        .get(format!("mxc://{}/{}", body.server_name, body.media_id))?
+        .get(&mxc)?
     {
         Ok(get_content::Response {
             file,
@@ -67,6 +75,26 @@ pub fn get_content_route(
             content_disposition: filename.unwrap_or_default(), // TODO: Spec says this should be optional
         }
         .into())
+    } else if body.allow_remote {
+        let get_content_response = server_server::send_request(
+            &db,
+            body.server_name.as_ref(),
+            get_content::Request {
+                allow_remote: false,
+                server_name: &body.server_name,
+                media_id: &body.media_id,
+            },
+        )
+        .await?;
+
+        db.media.create(
+            mxc,
+            &Some(&get_content_response.content_disposition),
+            &get_content_response.content_type,
+            &get_content_response.file,
+        )?;
+
+        Ok(get_content_response.into())
     } else {
         Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."))
     }
@@ -79,7 +107,7 @@ pub fn get_content_route(
         data = "<body>"
     )
 )]
-pub fn get_content_thumbnail_route(
+pub async fn get_content_thumbnail_route(
     db: State<'_, Database>,
     body: Ruma<get_content_thumbnail::Request<'_>>,
     _server_name: String,
@@ -97,6 +125,37 @@ pub fn get_content_thumbnail_route(
             .map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid."))?,
     )? {
         Ok(get_content_thumbnail::Response { file, content_type }.into())
+    } else if body.allow_remote {
+        let get_thumbnail_response = server_server::send_request(
+            &db,
+            body.server_name.as_ref(),
+            get_content_thumbnail::Request {
+                allow_remote: false,
+                height: body.height,
+                width: body.width,
+                method: body.method,
+                server_name: &body.server_name,
+                media_id: &body.media_id,
+            },
+        )
+        .await?;
+
+        let mxc = format!(
+            "mxc://{}/{}",
+            db.globals.server_name(),
+            utils::random_string(MXC_LENGTH)
+        );
+
+        db.media.upload_thumbnail(
+            mxc,
+            &None,
+            &get_thumbnail_response.content_type,
+            body.width.try_into().expect("all UInts are valid u32s"),
+            body.height.try_into().expect("all UInts are valid u32s"),
+            &get_thumbnail_response.file,
+        )?;
+
+        Ok(get_thumbnail_response.into())
     } else {
         Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."))
     }
diff --git a/src/database/media.rs b/src/database/media.rs
index 63fa11c64..869d5d807 100644
--- a/src/database/media.rs
+++ b/src/database/media.rs
@@ -16,7 +16,7 @@ impl Media {
     pub fn create(
         &self,
         mxc: String,
-        filename: &Option<String>,
+        filename: &Option<&str>,
         content_type: &str,
         file: &[u8],
     ) -> Result<()> {
@@ -34,8 +34,32 @@ pub fn create(
         Ok(())
     }
 
+    /// Uploads or replaces a file thumbnail.
+    pub fn upload_thumbnail(
+        &self,
+        mxc: String,
+        filename: &Option<String>,
+        content_type: &str,
+        width: u32,
+        height: u32,
+        file: &[u8],
+    ) -> Result<()> {
+        let mut key = mxc.as_bytes().to_vec();
+        key.push(0xff);
+        key.extend_from_slice(&width.to_be_bytes());
+        key.extend_from_slice(&height.to_be_bytes());
+        key.push(0xff);
+        key.extend_from_slice(filename.as_ref().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<FileMeta>> {
+    pub fn get(&self, mxc: &str) -> Result<Option<FileMeta>> {
         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
-- 
GitLab