refactor(sdk)!: Move media methods from Client to a new type

This commit is contained in:
Jonas Platte
2022-09-02 16:22:37 +02:00
committed by Jonas Platte
parent 79b5854c83
commit b769827313
10 changed files with 410 additions and 363 deletions

View File

@@ -251,7 +251,8 @@ impl Client {
let source = (*media_source).clone();
RUNTIME.block_on(async move {
Ok(l.get_media_content(&MediaRequest { source, format: MediaFormat::File }, true)
Ok(l.media()
.get_media_content(&MediaRequest { source, format: MediaFormat::File }, true)
.await?)
})
}

View File

@@ -177,7 +177,7 @@ impl Account {
pub async fn get_avatar(&self, format: MediaFormat) -> Result<Option<Vec<u8>>> {
if let Some(url) = self.get_avatar_url().await? {
let request = MediaRequest { source: MediaSource::Plain(url), format };
Ok(Some(self.client.get_media_content(&request, true).await?))
Ok(Some(self.client.media().get_media_content(&request, true).await?))
} else {
Ok(None)
}
@@ -189,7 +189,7 @@ impl Account {
/// content repository, and set the user's avatar to the MXC URI for the
/// uploaded file.
///
/// This is a convenience method for calling [`Client::upload()`],
/// This is a convenience method for calling [`Media::upload()`],
/// followed by [`Account::set_avatar_url()`].
///
/// Returns the MXC URI of the uploaded avatar.
@@ -208,8 +208,10 @@ impl Account {
/// client.account().upload_avatar(&mime::IMAGE_JPEG, &image).await?;
/// # anyhow::Ok(()) });
/// ```
///
/// [`Media::upload()`]: crate::Media::upload
pub async fn upload_avatar(&self, content_type: &Mime, data: &[u8]) -> Result<OwnedMxcUri> {
let upload_response = self.client.upload(content_type, data).await?;
let upload_response = self.client.media().upload(content_type, data).await?;
self.set_avatar_url(Some(&upload_response.content_uri)).await?;
Ok(upload_response.content_uri)
}

View File

@@ -14,8 +14,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#[cfg(feature = "e2e-encryption")]
use std::io::Read;
use std::{
fmt::{self, Debug},
future::Future,
@@ -31,15 +29,13 @@ use futures_core::stream::Stream;
use futures_signals::signal::Signal;
use futures_util::{SinkExt, StreamExt, TryStreamExt};
use matrix_sdk_base::{
deserialized_responses::SyncResponse,
media::{MediaEventContent, MediaFormat, MediaRequest, MediaThumbnailSize},
BaseClient, SendOutsideWasm, Session, SessionMeta, SessionTokens, StateStore, SyncOutsideWasm,
deserialized_responses::SyncResponse, BaseClient, SendOutsideWasm, Session, SessionMeta,
SessionTokens, StateStore, SyncOutsideWasm,
};
use matrix_sdk_common::{
instant::{Duration, Instant},
instant::Instant,
locks::{Mutex, RwLock, RwLockReadGuard},
};
use mime::{self, Mime};
#[cfg(feature = "appservice")]
use ruma::TransactionId;
use ruma::{
@@ -55,7 +51,6 @@ use ruma::{
},
error::ErrorKind,
filter::{create_filter::v3::Request as FilterUploadRequest, FilterDefinition},
media::{create_content, get_content, get_content_thumbnail},
membership::{join_room_by_id, join_room_by_id_or_alias},
push::get_notifications::v3::Notification,
room::create_room,
@@ -71,13 +66,12 @@ use ruma::{
room::{
create::RoomCreateEventContent,
member::{MembershipState, RoomMemberEventContent},
MediaSource,
},
SyncStateEvent,
},
presence::PresenceState,
DeviceId, MxcUri, OwnedDeviceId, OwnedRoomId, OwnedServerName, RoomAliasId, RoomId,
RoomOrAliasId, ServerName, UInt, UserId,
DeviceId, OwnedDeviceId, OwnedRoomId, OwnedServerName, RoomAliasId, RoomId, RoomOrAliasId,
ServerName, UInt, UserId,
};
use serde::de::DeserializeOwned;
#[cfg(not(target_arch = "wasm32"))]
@@ -90,14 +84,13 @@ use url::Url;
#[cfg(feature = "e2e-encryption")]
use crate::encryption::Encryption;
use crate::{
attachment::{AttachmentInfo, Thumbnail},
config::RequestConfig,
error::{HttpError, HttpResult},
event_handler::{
EventHandler, EventHandlerHandle, EventHandlerResult, EventHandlerStore, SyncEvent,
},
http_client::HttpClient,
room, Account, Error, RefreshTokenError, Result, RumaApiError,
room, Account, Error, Media, RefreshTokenError, Result, RumaApiError,
};
mod builder;
@@ -110,11 +103,6 @@ pub use self::{
login_builder::LoginBuilder,
};
/// A conservative upload speed of 1Mbps
const DEFAULT_UPLOAD_SPEED: u64 = 125_000;
/// 5 min minimal upload request timeout, used to clamp the request timeout.
const MIN_UPLOAD_REQUEST_TIMEOUT: Duration = Duration::from_secs(60 * 5);
#[cfg(not(target_arch = "wasm32"))]
type NotificationHandlerFut = Pin<Box<dyn Future<Output = ()> + Send>>;
#[cfg(target_arch = "wasm32")]
@@ -528,6 +516,11 @@ impl Client {
Encryption::new(self.clone())
}
/// Get the media manager of the client.
pub fn media(&self) -> Media {
Media::new(self.clone())
}
/// Register a handler for a specific event type.
///
/// The handler is a function or closure with one or more arguments. The
@@ -1876,52 +1869,6 @@ impl Client {
self.send(request, None).await
}
/// Upload some media to the server.
///
/// # Arguments
///
/// * `content_type` - The type of the media, this will be used as the
/// content-type header.
///
/// * `reader` - A `Reader` that will be used to fetch the raw bytes of the
/// media.
///
/// # Examples
///
/// ```no_run
/// # use std::fs;
/// # use matrix_sdk::{Client, ruma::room_id};
/// # use url::Url;
/// # use futures::executor::block_on;
/// # use mime;
/// # block_on(async {
/// # let homeserver = Url::parse("http://localhost:8080")?;
/// # let mut client = Client::new(homeserver).await?;
/// let image = fs::read("/home/example/my-cat.jpg")?;
///
/// let response = client.upload(&mime::IMAGE_JPEG, &image).await?;
///
/// println!("Cat URI: {}", response.content_uri);
/// # anyhow::Ok(()) });
/// ```
pub async fn upload(
&self,
content_type: &Mime,
data: &[u8],
) -> Result<create_content::v3::Response> {
let timeout = std::cmp::max(
Duration::from_secs(data.len() as u64 / DEFAULT_UPLOAD_SPEED),
MIN_UPLOAD_REQUEST_TIMEOUT,
);
let request = assign!(create_content::v3::Request::new(data), {
content_type: Some(content_type.essence_str()),
});
let request_config = self.request_config().timeout(timeout);
Ok(self.send(request, Some(request_config)).await?)
}
/// Send an arbitrary request to the server, without updating client state.
///
/// **Warning:** Because this method *does not* update the client state, it
@@ -2048,7 +1995,7 @@ impl Client {
}
}
async fn server_versions(&self) -> HttpResult<&[MatrixVersion]> {
pub(crate) async fn server_versions(&self) -> HttpResult<&[MatrixVersion]> {
#[cfg(target_arch = "wasm32")]
let server_versions =
self.inner.server_versions.get_or_try_init(self.request_server_versions()).await?;
@@ -2481,294 +2428,11 @@ impl Client {
self.inner.base_client.sync_token().await
}
/// Get a media file's content.
///
/// If the content is encrypted and encryption is enabled, the content will
/// be decrypted.
///
/// # Arguments
///
/// * `request` - The `MediaRequest` of the content.
///
/// * `use_cache` - If we should use the media cache for this request.
pub async fn get_media_content(
&self,
request: &MediaRequest,
use_cache: bool,
) -> Result<Vec<u8>> {
let content = if use_cache {
self.inner.base_client.store().get_media_content(request).await?
} else {
None
};
if let Some(content) = content {
Ok(content)
} else {
let content: Vec<u8> = match &request.source {
MediaSource::Encrypted(file) => {
let content: Vec<u8> =
self.send(get_content::v3::Request::from_url(&file.url)?, None).await?.file;
#[cfg(feature = "e2e-encryption")]
let content = {
let mut cursor = std::io::Cursor::new(content);
let mut reader = matrix_sdk_base::crypto::AttachmentDecryptor::new(
&mut cursor,
file.as_ref().clone().into(),
)?;
let mut decrypted = Vec::new();
reader.read_to_end(&mut decrypted)?;
decrypted
};
content
}
MediaSource::Plain(uri) => {
if let MediaFormat::Thumbnail(size) = &request.format {
self.send(
get_content_thumbnail::v3::Request::from_url(
uri,
size.width,
size.height,
)?,
None,
)
.await?
.file
} else {
self.send(get_content::v3::Request::from_url(uri)?, None).await?.file
}
}
};
if use_cache {
self.inner.base_client.store().add_media_content(request, content.clone()).await?;
}
Ok(content)
}
}
/// Remove a media file's content from the store.
///
/// # Arguments
///
/// * `request` - The `MediaRequest` of the content.
pub async fn remove_media_content(&self, request: &MediaRequest) -> Result<()> {
Ok(self.inner.base_client.store().remove_media_content(request).await?)
}
/// Delete all the media content corresponding to the given
/// uri from the store.
///
/// # Arguments
///
/// * `uri` - The `MxcUri` of the files.
pub async fn remove_media_content_for_uri(&self, uri: &MxcUri) -> Result<()> {
Ok(self.inner.base_client.store().remove_media_content_for_uri(uri).await?)
}
/// Get the file of the given media event content.
///
/// If the content is encrypted and encryption is enabled, the content will
/// be decrypted.
///
/// Returns `Ok(None)` if the event content has no file.
///
/// This is a convenience method that calls the
/// [`get_media_content`](#method.get_media_content) method.
///
/// # Arguments
///
/// * `event_content` - The media event content.
///
/// * `use_cache` - If we should use the media cache for this file.
pub async fn get_file(
&self,
event_content: impl MediaEventContent,
use_cache: bool,
) -> Result<Option<Vec<u8>>> {
if let Some(source) = event_content.source() {
Ok(Some(
self.get_media_content(
&MediaRequest { source, format: MediaFormat::File },
use_cache,
)
.await?,
))
} else {
Ok(None)
}
}
/// Remove the file of the given media event content from the cache.
///
/// This is a convenience method that calls the
/// [`remove_media_content`](#method.remove_media_content) method.
///
/// # Arguments
///
/// * `event_content` - The media event content.
pub async fn remove_file(&self, event_content: impl MediaEventContent) -> Result<()> {
if let Some(source) = event_content.source() {
self.remove_media_content(&MediaRequest { source, format: MediaFormat::File }).await?
}
Ok(())
}
/// Get a thumbnail of the given media event content.
///
/// If the content is encrypted and encryption is enabled, the content will
/// be decrypted.
///
/// Returns `Ok(None)` if the event content has no thumbnail.
///
/// This is a convenience method that calls the
/// [`get_media_content`](#method.get_media_content) method.
///
/// # Arguments
///
/// * `event_content` - The media event content.
///
/// * `size` - The _desired_ size of the thumbnail. The actual thumbnail may
/// not match the size specified.
///
/// * `use_cache` - If we should use the media cache for this thumbnail.
pub async fn get_thumbnail(
&self,
event_content: impl MediaEventContent,
size: MediaThumbnailSize,
use_cache: bool,
) -> Result<Option<Vec<u8>>> {
if let Some(source) = event_content.thumbnail_source() {
Ok(Some(
self.get_media_content(
&MediaRequest { source, format: MediaFormat::Thumbnail(size) },
use_cache,
)
.await?,
))
} else {
Ok(None)
}
}
/// Remove the thumbnail of the given media event content from the cache.
///
/// This is a convenience method that calls the
/// [`remove_media_content`](#method.remove_media_content) method.
///
/// # Arguments
///
/// * `event_content` - The media event content.
///
/// * `size` - The _desired_ size of the thumbnail. Must match the size
/// requested with [`get_thumbnail`](#method.get_thumbnail).
pub async fn remove_thumbnail(
&self,
event_content: impl MediaEventContent,
size: MediaThumbnailSize,
) -> Result<()> {
if let Some(source) = event_content.source() {
self.remove_media_content(&MediaRequest {
source,
format: MediaFormat::Thumbnail(size),
})
.await?
}
Ok(())
}
/// Gets information about the owner of a given access token.
pub async fn whoami(&self) -> HttpResult<whoami::v3::Response> {
let request = whoami::v3::Request::new();
self.send(request, None).await
}
/// Upload the file bytes in `data` and construct an attachment
/// message with `body`, `content_type`, `info` and `thumbnail`.
pub(crate) async fn prepare_attachment_message(
&self,
body: &str,
content_type: &Mime,
data: &[u8],
info: Option<AttachmentInfo>,
thumbnail: Option<Thumbnail<'_>>,
) -> Result<ruma::events::room::message::MessageType> {
let (thumbnail_source, thumbnail_info) = if let Some(thumbnail) = thumbnail {
let response = self.upload(thumbnail.content_type, thumbnail.data).await?;
let url = response.content_uri;
use ruma::events::room::ThumbnailInfo;
let thumbnail_info = assign!(
thumbnail.info.as_ref().map(|info| ThumbnailInfo::from(info.clone())).unwrap_or_default(),
{ mimetype: Some(thumbnail.content_type.as_ref().to_owned()) }
);
(Some(MediaSource::Plain(url)), Some(Box::new(thumbnail_info)))
} else {
(None, None)
};
let response = self.upload(content_type, data).await?;
let url = response.content_uri;
use ruma::events::room::{self, message};
Ok(match content_type.type_() {
mime::IMAGE => {
let info = assign!(info.map(room::ImageInfo::from).unwrap_or_default(), {
mimetype: Some(content_type.as_ref().to_owned()),
thumbnail_source,
thumbnail_info,
});
message::MessageType::Image(message::ImageMessageEventContent::plain(
body.to_owned(),
url,
Some(Box::new(info)),
))
}
mime::AUDIO => {
let info = assign!(info.map(message::AudioInfo::from).unwrap_or_default(), {
mimetype: Some(content_type.as_ref().to_owned()),
});
message::MessageType::Audio(message::AudioMessageEventContent::plain(
body.to_owned(),
url,
Some(Box::new(info)),
))
}
mime::VIDEO => {
let info = assign!(info.map(message::VideoInfo::from).unwrap_or_default(), {
mimetype: Some(content_type.as_ref().to_owned()),
thumbnail_source,
thumbnail_info
});
message::MessageType::Video(message::VideoMessageEventContent::plain(
body.to_owned(),
url,
Some(Box::new(info)),
))
}
_ => {
let info = assign!(info.map(message::FileInfo::from).unwrap_or_default(), {
mimetype: Some(content_type.as_ref().to_owned()),
thumbnail_source,
thumbnail_info
});
message::MessageType::File(message::FileMessageEventContent::plain(
body.to_owned(),
url,
Some(Box::new(info)),
))
}
})
}
}
// The http mocking library is not supported for wasm32

View File

@@ -127,7 +127,7 @@ impl Client {
let mut buf = Vec::new();
encryptor.read_to_end(&mut buf)?;
let response = self.upload(thumbnail.content_type, &buf).await?;
let response = self.media().upload(thumbnail.content_type, &buf).await?;
let file: ruma::events::room::EncryptedFile = {
let keys = encryptor.finish();
@@ -159,7 +159,7 @@ impl Client {
let mut buf = Vec::new();
encryptor.read_to_end(&mut buf)?;
let response = self.upload(content_type, &buf).await?;
let response = self.media().upload(content_type, &buf).await?;
let file: ruma::events::room::EncryptedFile = {
let keys = encryptor.finish();

View File

@@ -20,8 +20,8 @@
pub use async_trait::async_trait;
pub use bytes;
pub use matrix_sdk_base::{
media, DisplayName, Room as BaseRoom, RoomInfo, RoomMember as BaseRoomMember, RoomType,
Session, StateChanges, StoreError,
DisplayName, Room as BaseRoom, RoomInfo, RoomMember as BaseRoomMember, RoomType, Session,
StateChanges, StoreError,
};
pub use matrix_sdk_common::*;
pub use reqwest;
@@ -36,6 +36,7 @@ pub mod config;
mod error;
pub mod event_handler;
mod http_client;
pub mod media;
/// High-level room API
pub mod room;
pub mod store;
@@ -52,6 +53,7 @@ pub use client::{Client, ClientBuildError, ClientBuilder, LoginBuilder, LoopCtrl
pub use error::ImageError;
pub use error::{Error, HttpError, HttpResult, RefreshTokenError, Result, RumaApiError};
pub use http_client::HttpSend;
pub use media::Media;
#[cfg(test)]
mod test_utils;

View File

@@ -0,0 +1,375 @@
// Copyright 2021 Kévin Commaille
// Copyright 2022 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! High-level media API.
#[cfg(feature = "e2e-encryption")]
use std::io::Read;
use std::time::Duration;
pub use matrix_sdk_base::media::*;
use mime::Mime;
use ruma::{
api::client::media::{create_content, get_content, get_content_thumbnail},
assign,
events::room::MediaSource,
MxcUri,
};
use crate::{
attachment::{AttachmentInfo, Thumbnail},
Client, Result,
};
/// A conservative upload speed of 1Mbps
const DEFAULT_UPLOAD_SPEED: u64 = 125_000;
/// 5 min minimal upload request timeout, used to clamp the request timeout.
const MIN_UPLOAD_REQUEST_TIMEOUT: Duration = Duration::from_secs(60 * 5);
/// A high-level API to interact with the media API.
#[derive(Debug, Clone)]
pub struct Media {
/// The underlying HTTP client.
client: Client,
}
impl Media {
pub(crate) fn new(client: Client) -> Self {
Self { client }
}
/// Upload some media to the server.
///
/// # Arguments
///
/// * `content_type` - The type of the media, this will be used as the
/// content-type header.
///
/// * `reader` - A `Reader` that will be used to fetch the raw bytes of the
/// media.
///
/// # Examples
///
/// ```no_run
/// # use std::fs;
/// # use matrix_sdk::{Client, ruma::room_id};
/// # use url::Url;
/// # use futures::executor::block_on;
/// # use mime;
/// # block_on(async {
/// # let homeserver = Url::parse("http://localhost:8080")?;
/// # let mut client = Client::new(homeserver).await?;
/// let image = fs::read("/home/example/my-cat.jpg")?;
///
/// let response = client.media().upload(&mime::IMAGE_JPEG, &image).await?;
///
/// println!("Cat URI: {}", response.content_uri);
/// # anyhow::Ok(()) });
/// ```
pub async fn upload(
&self,
content_type: &Mime,
data: &[u8],
) -> Result<create_content::v3::Response> {
let timeout = std::cmp::max(
Duration::from_secs(data.len() as u64 / DEFAULT_UPLOAD_SPEED),
MIN_UPLOAD_REQUEST_TIMEOUT,
);
let request = assign!(create_content::v3::Request::new(data), {
content_type: Some(content_type.essence_str()),
});
let request_config = self.client.request_config().timeout(timeout);
Ok(self.client.send(request, Some(request_config)).await?)
}
/// Get a media file's content.
///
/// If the content is encrypted and encryption is enabled, the content will
/// be decrypted.
///
/// # Arguments
///
/// * `request` - The `MediaRequest` of the content.
///
/// * `use_cache` - If we should use the media cache for this request.
pub async fn get_media_content(
&self,
request: &MediaRequest,
use_cache: bool,
) -> Result<Vec<u8>> {
let content =
if use_cache { self.client.store().get_media_content(request).await? } else { None };
if let Some(content) = content {
Ok(content)
} else {
let content: Vec<u8> = match &request.source {
MediaSource::Encrypted(file) => {
let request = get_content::v3::Request::from_url(&file.url)?;
let content: Vec<u8> = self.client.send(request, None).await?.file;
#[cfg(feature = "e2e-encryption")]
let content = {
let mut cursor = std::io::Cursor::new(content);
let mut reader = matrix_sdk_base::crypto::AttachmentDecryptor::new(
&mut cursor,
file.as_ref().clone().into(),
)?;
let mut decrypted = Vec::new();
reader.read_to_end(&mut decrypted)?;
decrypted
};
content
}
MediaSource::Plain(uri) => {
if let MediaFormat::Thumbnail(size) = &request.format {
let request = get_content_thumbnail::v3::Request::from_url(
uri,
size.width,
size.height,
)?;
self.client.send(request, None).await?.file
} else {
let request = get_content::v3::Request::from_url(uri)?;
self.client.send(request, None).await?.file
}
}
};
if use_cache {
self.client.store().add_media_content(request, content.clone()).await?;
}
Ok(content)
}
}
/// Remove a media file's content from the store.
///
/// # Arguments
///
/// * `request` - The `MediaRequest` of the content.
pub async fn remove_media_content(&self, request: &MediaRequest) -> Result<()> {
Ok(self.client.store().remove_media_content(request).await?)
}
/// Delete all the media content corresponding to the given
/// uri from the store.
///
/// # Arguments
///
/// * `uri` - The `MxcUri` of the files.
pub async fn remove_media_content_for_uri(&self, uri: &MxcUri) -> Result<()> {
Ok(self.client.store().remove_media_content_for_uri(uri).await?)
}
/// Get the file of the given media event content.
///
/// If the content is encrypted and encryption is enabled, the content will
/// be decrypted.
///
/// Returns `Ok(None)` if the event content has no file.
///
/// This is a convenience method that calls the
/// [`get_media_content`](#method.get_media_content) method.
///
/// # Arguments
///
/// * `event_content` - The media event content.
///
/// * `use_cache` - If we should use the media cache for this file.
pub async fn get_file(
&self,
event_content: impl MediaEventContent,
use_cache: bool,
) -> Result<Option<Vec<u8>>> {
if let Some(source) = event_content.source() {
Ok(Some(
self.get_media_content(
&MediaRequest { source, format: MediaFormat::File },
use_cache,
)
.await?,
))
} else {
Ok(None)
}
}
/// Remove the file of the given media event content from the cache.
///
/// This is a convenience method that calls the
/// [`remove_media_content`](#method.remove_media_content) method.
///
/// # Arguments
///
/// * `event_content` - The media event content.
pub async fn remove_file(&self, event_content: impl MediaEventContent) -> Result<()> {
if let Some(source) = event_content.source() {
self.remove_media_content(&MediaRequest { source, format: MediaFormat::File }).await?
}
Ok(())
}
/// Get a thumbnail of the given media event content.
///
/// If the content is encrypted and encryption is enabled, the content will
/// be decrypted.
///
/// Returns `Ok(None)` if the event content has no thumbnail.
///
/// This is a convenience method that calls the
/// [`get_media_content`](#method.get_media_content) method.
///
/// # Arguments
///
/// * `event_content` - The media event content.
///
/// * `size` - The _desired_ size of the thumbnail. The actual thumbnail may
/// not match the size specified.
///
/// * `use_cache` - If we should use the media cache for this thumbnail.
pub async fn get_thumbnail(
&self,
event_content: impl MediaEventContent,
size: MediaThumbnailSize,
use_cache: bool,
) -> Result<Option<Vec<u8>>> {
if let Some(source) = event_content.thumbnail_source() {
Ok(Some(
self.get_media_content(
&MediaRequest { source, format: MediaFormat::Thumbnail(size) },
use_cache,
)
.await?,
))
} else {
Ok(None)
}
}
/// Remove the thumbnail of the given media event content from the cache.
///
/// This is a convenience method that calls the
/// [`remove_media_content`](#method.remove_media_content) method.
///
/// # Arguments
///
/// * `event_content` - The media event content.
///
/// * `size` - The _desired_ size of the thumbnail. Must match the size
/// requested with [`get_thumbnail`](#method.get_thumbnail).
pub async fn remove_thumbnail(
&self,
event_content: impl MediaEventContent,
size: MediaThumbnailSize,
) -> Result<()> {
if let Some(source) = event_content.source() {
self.remove_media_content(&MediaRequest {
source,
format: MediaFormat::Thumbnail(size),
})
.await?
}
Ok(())
}
/// Upload the file bytes in `data` and construct an attachment
/// message with `body`, `content_type`, `info` and `thumbnail`.
pub(crate) async fn prepare_attachment_message(
&self,
body: &str,
content_type: &Mime,
data: &[u8],
info: Option<AttachmentInfo>,
thumbnail: Option<Thumbnail<'_>>,
) -> Result<ruma::events::room::message::MessageType> {
let (thumbnail_source, thumbnail_info) = if let Some(thumbnail) = thumbnail {
let response = self.upload(thumbnail.content_type, thumbnail.data).await?;
let url = response.content_uri;
use ruma::events::room::ThumbnailInfo;
let thumbnail_info = assign!(
thumbnail.info.as_ref().map(|info| ThumbnailInfo::from(info.clone())).unwrap_or_default(),
{ mimetype: Some(thumbnail.content_type.as_ref().to_owned()) }
);
(Some(MediaSource::Plain(url)), Some(Box::new(thumbnail_info)))
} else {
(None, None)
};
let response = self.upload(content_type, data).await?;
let url = response.content_uri;
use ruma::events::room::{self, message};
Ok(match content_type.type_() {
mime::IMAGE => {
let info = assign!(info.map(room::ImageInfo::from).unwrap_or_default(), {
mimetype: Some(content_type.as_ref().to_owned()),
thumbnail_source,
thumbnail_info,
});
message::MessageType::Image(message::ImageMessageEventContent::plain(
body.to_owned(),
url,
Some(Box::new(info)),
))
}
mime::AUDIO => {
let info = assign!(info.map(message::AudioInfo::from).unwrap_or_default(), {
mimetype: Some(content_type.as_ref().to_owned()),
});
message::MessageType::Audio(message::AudioMessageEventContent::plain(
body.to_owned(),
url,
Some(Box::new(info)),
))
}
mime::VIDEO => {
let info = assign!(info.map(message::VideoInfo::from).unwrap_or_default(), {
mimetype: Some(content_type.as_ref().to_owned()),
thumbnail_source,
thumbnail_info
});
message::MessageType::Video(message::VideoMessageEventContent::plain(
body.to_owned(),
url,
Some(Box::new(info)),
))
}
_ => {
let info = assign!(info.map(message::FileInfo::from).unwrap_or_default(), {
mimetype: Some(content_type.as_ref().to_owned()),
thumbnail_source,
thumbnail_info
});
message::MessageType::File(message::FileMessageEventContent::plain(
body.to_owned(),
url,
Some(Box::new(info)),
))
}
})
}
}

View File

@@ -205,7 +205,7 @@ impl Common {
pub async fn avatar(&self, format: MediaFormat) -> Result<Option<Vec<u8>>> {
if let Some(url) = self.avatar_url() {
let request = MediaRequest { source: MediaSource::Plain(url.to_owned()), format };
Ok(Some(self.client.get_media_content(&request, true).await?))
Ok(Some(self.client.media().get_media_content(&request, true).await?))
} else {
Ok(None)
}

View File

@@ -743,6 +743,7 @@ impl Joined {
.await?
} else {
self.client
.media()
.prepare_attachment_message(body, content_type, data, config.info, config.thumbnail)
.await?
};
@@ -750,6 +751,7 @@ impl Joined {
#[cfg(not(feature = "e2e-encryption"))]
let content = self
.client
.media()
.prepare_attachment_message(body, content_type, data, config.info, config.thumbnail)
.await?;

View File

@@ -61,7 +61,7 @@ impl RoomMember {
pub async fn avatar(&self, format: MediaFormat) -> Result<Option<Vec<u8>>> {
if let Some(url) = self.avatar_url() {
let request = MediaRequest { source: MediaSource::Plain(url.to_owned()), format };
Ok(Some(self.client.get_media_content(&request, true).await?))
Ok(Some(self.client.media().get_media_content(&request, true).await?))
} else {
Ok(None)
}

View File

@@ -538,9 +538,9 @@ async fn get_media_content() {
.mount(&server)
.await;
client.get_media_content(&request, true).await.unwrap();
client.get_media_content(&request, true).await.unwrap();
client.get_media_content(&request, false).await.unwrap();
client.media().get_media_content(&request, true).await.unwrap();
client.media().get_media_content(&request, true).await.unwrap();
client.media().get_media_content(&request, false).await.unwrap();
}
#[async_test]
@@ -566,8 +566,8 @@ async fn get_media_file() {
.mount(&server)
.await;
client.get_file(event_content.clone(), true).await.unwrap();
client.get_file(event_content.clone(), true).await.unwrap();
client.media().get_file(event_content.clone(), true).await.unwrap();
client.media().get_file(event_content.clone(), true).await.unwrap();
Mock::given(method("GET"))
.and(path("/_matrix/media/r0/thumbnail/example%2Eorg/image"))
@@ -580,6 +580,7 @@ async fn get_media_file() {
.await;
client
.media()
.get_thumbnail(
event_content,
MediaThumbnailSize { method: Method::Scale, width: uint!(100), height: uint!(100) },