diff --git a/bindings/matrix-sdk-ffi/src/api.udl b/bindings/matrix-sdk-ffi/src/api.udl index 1ed73c945..33053361d 100644 --- a/bindings/matrix-sdk-ffi/src/api.udl +++ b/bindings/matrix-sdk-ffi/src/api.udl @@ -13,10 +13,3 @@ interface RoomMessageEventContentWithoutRelation { interface ClientError { Generic(string msg); }; - -interface MediaSource { - [Name=from_json, Throws=ClientError] - constructor(string json); - string to_json(); - string url(); -}; diff --git a/bindings/matrix-sdk-ffi/src/client.rs b/bindings/matrix-sdk-ffi/src/client.rs index 0a50f9716..444ee1cd8 100644 --- a/bindings/matrix-sdk-ffi/src/client.rs +++ b/bindings/matrix-sdk-ffi/src/client.rs @@ -32,9 +32,7 @@ use matrix_sdk::{ user_directory::search_users, }, events::{ - room::{ - avatar::RoomAvatarEventContent, encryption::RoomEncryptionEventContent, MediaSource, - }, + room::{avatar::RoomAvatarEventContent, encryption::RoomEncryptionEventContent}, AnyInitialStateEvent, AnyToDeviceEvent, InitialStateEvent, }, serde::Raw, @@ -81,7 +79,7 @@ use crate::{ notification_settings::NotificationSettings, room_directory_search::RoomDirectorySearch, room_preview::RoomPreview, - ruma::AuthData, + ruma::{AuthData, MediaSource}, sync_service::{SyncService, SyncServiceBuilder}, task_handle::TaskHandle, utils::AsyncRuntimeDropped, @@ -455,7 +453,7 @@ impl Client { .inner .media() .get_media_file( - &MediaRequestParameters { source, format: MediaFormat::File }, + &MediaRequestParameters { source: source.media_source, format: MediaFormat::File }, filename, &mime_type, use_cache, @@ -728,7 +726,7 @@ impl Client { &self, media_source: Arc, ) -> Result, ClientError> { - let source = (*media_source).clone(); + let source = (*media_source).clone().media_source; debug!(?source, "requesting media file"); Ok(self @@ -744,9 +742,9 @@ impl Client { width: u64, height: u64, ) -> Result, ClientError> { - let source = (*media_source).clone(); + let source = (*media_source).clone().media_source; - debug!(source = ?media_source, width, height, "requesting media thumbnail"); + debug!(?source, width, height, "requesting media thumbnail"); Ok(self .inner .media() diff --git a/bindings/matrix-sdk-ffi/src/event.rs b/bindings/matrix-sdk-ffi/src/event.rs index 8771693ab..1fbc423d5 100644 --- a/bindings/matrix-sdk-ffi/src/event.rs +++ b/bindings/matrix-sdk-ffi/src/event.rs @@ -202,7 +202,7 @@ impl TryFrom for MessageLikeEventContent { _ => None, }); MessageLikeEventContent::RoomMessage { - message_type: original_content.msgtype.into(), + message_type: original_content.msgtype.try_into()?, in_reply_to_event_id, } } diff --git a/bindings/matrix-sdk-ffi/src/lib.rs b/bindings/matrix-sdk-ffi/src/lib.rs index 9ed31580e..a648947c2 100644 --- a/bindings/matrix-sdk-ffi/src/lib.rs +++ b/bindings/matrix-sdk-ffi/src/lib.rs @@ -33,13 +33,11 @@ mod utils; mod widget; use async_compat::TOKIO1 as RUNTIME; -use matrix_sdk::ruma::events::room::{ - message::RoomMessageEventContentWithoutRelation, MediaSource, -}; +use matrix_sdk::ruma::events::room::message::RoomMessageEventContentWithoutRelation; use self::{ error::ClientError, - ruma::{MediaSourceExt, Mentions, RoomMessageEventContentWithoutRelationExt}, + ruma::{Mentions, RoomMessageEventContentWithoutRelationExt}, task_handle::TaskHandle, }; diff --git a/bindings/matrix-sdk-ffi/src/room.rs b/bindings/matrix-sdk-ffi/src/room.rs index ed67a1d4f..dcaf6f2ef 100644 --- a/bindings/matrix-sdk-ffi/src/room.rs +++ b/bindings/matrix-sdk-ffi/src/room.rs @@ -973,7 +973,7 @@ impl TryFrom for RumaAvatarImageInfo { fn try_from(value: ImageInfo) -> Result { let thumbnail_url = if let Some(media_source) = value.thumbnail_source { - match media_source.as_ref() { + match &media_source.as_ref().media_source { MediaSource::Plain(mxc_uri) => Some(mxc_uri.clone()), MediaSource::Encrypted(_) => return Err(MediaInfoError::InvalidField), } diff --git a/bindings/matrix-sdk-ffi/src/ruma.rs b/bindings/matrix-sdk-ffi/src/ruma.rs index 195fae191..7d70f6bd1 100644 --- a/bindings/matrix-sdk-ffi/src/ruma.rs +++ b/bindings/matrix-sdk-ffi/src/ruma.rs @@ -42,7 +42,8 @@ use ruma::{ VideoInfo as RumaVideoInfo, VideoMessageEventContent as RumaVideoMessageEventContent, }, - ImageInfo as RumaImageInfo, MediaSource, ThumbnailInfo as RumaThumbnailInfo, + ImageInfo as RumaImageInfo, MediaSource as RumaMediaSource, + ThumbnailInfo as RumaThumbnailInfo, }, }, matrix_uri::MatrixId as RumaMatrixId, @@ -156,7 +157,7 @@ impl From<&RumaMatrixId> for MatrixId { #[matrix_sdk_ffi_macros::export] pub fn media_source_from_url(url: String) -> Arc { - Arc::new(MediaSource::Plain(url.into())) + Arc::new(MediaSource { media_source: RumaMediaSource::Plain(url.into()) }) } #[matrix_sdk_ffi_macros::export] @@ -200,21 +201,61 @@ pub fn message_event_content_from_html_as_emote( ))) } -#[extension_trait] -pub impl MediaSourceExt for MediaSource { - fn from_json(json: String) -> Result { - let res = serde_json::from_str(&json)?; - Ok(res) - } +#[derive(Clone, uniffi::Object)] +pub struct MediaSource { + pub(crate) media_source: RumaMediaSource, +} - fn to_json(&self) -> String { - serde_json::to_string(self).expect("Media source should always be serializable ") +#[matrix_sdk_ffi_macros::export] +impl MediaSource { + pub fn url(&self) -> String { + self.media_source.url() + } +} + +impl TryFrom for MediaSource { + type Error = ClientError; + + fn try_from(value: RumaMediaSource) -> Result { + value.verify()?; + Ok(Self { media_source: value }) + } +} + +impl TryFrom<&RumaMediaSource> for MediaSource { + type Error = ClientError; + + fn try_from(value: &RumaMediaSource) -> Result { + value.verify()?; + Ok(Self { media_source: value.clone() }) + } +} + +impl From for RumaMediaSource { + fn from(value: MediaSource) -> Self { + value.media_source + } +} + +#[extension_trait] +pub impl MediaSourceExt for RumaMediaSource { + fn verify(&self) -> Result<(), ClientError> { + match self { + RumaMediaSource::Plain(url) => { + url.validate().map_err(|e| ClientError::Generic { msg: e.to_string() })?; + } + RumaMediaSource::Encrypted(file) => { + file.url.validate().map_err(|e| ClientError::Generic { msg: e.to_string() })?; + } + } + + Ok(()) } fn url(&self) -> String { match self { - MediaSource::Plain(url) => url.to_string(), - MediaSource::Encrypted(file) => file.url.to_string(), + RumaMediaSource::Plain(url) => url.to_string(), + RumaMediaSource::Encrypted(file) => file.url.to_string(), } } } @@ -280,7 +321,7 @@ fn get_body_and_filename(filename: String, caption: Option) -> (String, } impl TryFrom for RumaMessageType { - type Error = serde_json::Error; + type Error = ClientError; fn try_from(value: MessageType) -> Result { Ok(match value { @@ -292,7 +333,7 @@ impl TryFrom for RumaMessageType { MessageType::Image { content } => { let (body, filename) = get_body_and_filename(content.filename, content.caption); let mut event_content = - RumaImageMessageEventContent::new(body, (*content.source).clone()) + RumaImageMessageEventContent::new(body, (*content.source).clone().into()) .info(content.info.map(Into::into).map(Box::new)); event_content.formatted = content.formatted_caption.map(Into::into); event_content.filename = filename; @@ -301,7 +342,7 @@ impl TryFrom for RumaMessageType { MessageType::Audio { content } => { let (body, filename) = get_body_and_filename(content.filename, content.caption); let mut event_content = - RumaAudioMessageEventContent::new(body, (*content.source).clone()) + RumaAudioMessageEventContent::new(body, (*content.source).clone().into()) .info(content.info.map(Into::into).map(Box::new)); event_content.formatted = content.formatted_caption.map(Into::into); event_content.filename = filename; @@ -310,7 +351,7 @@ impl TryFrom for RumaMessageType { MessageType::Video { content } => { let (body, filename) = get_body_and_filename(content.filename, content.caption); let mut event_content = - RumaVideoMessageEventContent::new(body, (*content.source).clone()) + RumaVideoMessageEventContent::new(body, (*content.source).clone().into()) .info(content.info.map(Into::into).map(Box::new)); event_content.formatted = content.formatted_caption.map(Into::into); event_content.filename = filename; @@ -319,7 +360,7 @@ impl TryFrom for RumaMessageType { MessageType::File { content } => { let (body, filename) = get_body_and_filename(content.filename, content.caption); let mut event_content = - RumaFileMessageEventContent::new(body, (*content.source).clone()) + RumaFileMessageEventContent::new(body, (*content.source).clone().into()) .info(content.info.map(Into::into).map(Box::new)); event_content.formatted = content.formatted_caption.map(Into::into); event_content.filename = filename; @@ -345,9 +386,11 @@ impl TryFrom for RumaMessageType { } } -impl From for MessageType { - fn from(value: RumaMessageType) -> Self { - match value { +impl TryFrom for MessageType { + type Error = ClientError; + + fn try_from(value: RumaMessageType) -> Result { + Ok(match value { RumaMessageType::Emote(c) => MessageType::Emote { content: EmoteMessageContent { body: c.body.clone(), @@ -359,16 +402,17 @@ impl From for MessageType { filename: c.filename().to_owned(), caption: c.caption().map(ToString::to_string), formatted_caption: c.formatted_caption().map(Into::into), - source: Arc::new(c.source.clone()), - info: c.info.as_deref().map(Into::into), + source: Arc::new(c.source.try_into()?), + info: c.info.as_deref().map(TryInto::try_into).transpose()?, }, }, + RumaMessageType::Audio(c) => MessageType::Audio { content: AudioMessageContent { filename: c.filename().to_owned(), caption: c.caption().map(ToString::to_string), formatted_caption: c.formatted_caption().map(Into::into), - source: Arc::new(c.source.clone()), + source: Arc::new(c.source.try_into()?), info: c.info.as_deref().map(Into::into), audio: c.audio.map(Into::into), voice: c.voice.map(Into::into), @@ -379,8 +423,8 @@ impl From for MessageType { filename: c.filename().to_owned(), caption: c.caption().map(ToString::to_string), formatted_caption: c.formatted_caption().map(Into::into), - source: Arc::new(c.source.clone()), - info: c.info.as_deref().map(Into::into), + source: Arc::new(c.source.try_into()?), + info: c.info.as_deref().map(TryInto::try_into).transpose()?, }, }, RumaMessageType::File(c) => MessageType::File { @@ -388,8 +432,8 @@ impl From for MessageType { filename: c.filename().to_owned(), caption: c.caption().map(ToString::to_string), formatted_caption: c.formatted_caption().map(Into::into), - source: Arc::new(c.source.clone()), - info: c.info.as_deref().map(Into::into), + source: Arc::new(c.source.try_into()?), + info: c.info.as_deref().map(TryInto::try_into).transpose()?, }, }, RumaMessageType::Notice(c) => MessageType::Notice { @@ -425,7 +469,7 @@ impl From for MessageType { msgtype: value.msgtype().to_owned(), body: value.body().to_owned(), }, - } + }) } } @@ -520,7 +564,7 @@ impl From for RumaImageInfo { mimetype: value.mimetype, size: value.size.map(u64_to_uint), thumbnail_info: value.thumbnail_info.map(Into::into).map(Box::new), - thumbnail_source: value.thumbnail_source.map(|source| (*source).clone()), + thumbnail_source: value.thumbnail_source.map(|source| (*source).clone().into()), blurhash: value.blurhash, }) } @@ -625,7 +669,7 @@ impl From for RumaVideoInfo { mimetype: value.mimetype, size: value.size.map(u64_to_uint), thumbnail_info: value.thumbnail_info.map(Into::into).map(Box::new), - thumbnail_source: value.thumbnail_source.map(|source| (*source).clone()), + thumbnail_source: value.thumbnail_source.map(|source| (*source).clone().into()), blurhash: value.blurhash, }) } @@ -668,7 +712,7 @@ impl From for RumaFileInfo { mimetype: value.mimetype, size: value.size.map(u64_to_uint), thumbnail_info: value.thumbnail_info.map(Into::into).map(Box::new), - thumbnail_source: value.thumbnail_source.map(|source| (*source).clone()), + thumbnail_source: value.thumbnail_source.map(|source| (*source).clone().into()), }) } } @@ -790,8 +834,10 @@ pub enum MessageFormat { Unknown { format: String }, } -impl From<&matrix_sdk::ruma::events::room::ImageInfo> for ImageInfo { - fn from(info: &matrix_sdk::ruma::events::room::ImageInfo) -> Self { +impl TryFrom<&matrix_sdk::ruma::events::room::ImageInfo> for ImageInfo { + type Error = ClientError; + + fn try_from(info: &matrix_sdk::ruma::events::room::ImageInfo) -> Result { let thumbnail_info = info.thumbnail_info.as_ref().map(|info| ThumbnailInfo { height: info.height.map(Into::into), width: info.width.map(Into::into), @@ -799,15 +845,20 @@ impl From<&matrix_sdk::ruma::events::room::ImageInfo> for ImageInfo { size: info.size.map(Into::into), }); - Self { + Ok(Self { height: info.height.map(Into::into), width: info.width.map(Into::into), mimetype: info.mimetype.clone(), size: info.size.map(Into::into), thumbnail_info, - thumbnail_source: info.thumbnail_source.clone().map(Arc::new), + thumbnail_source: info + .thumbnail_source + .as_ref() + .map(TryInto::try_into) + .transpose()? + .map(Arc::new), blurhash: info.blurhash.clone(), - } + }) } } @@ -821,8 +872,10 @@ impl From<&RumaAudioInfo> for AudioInfo { } } -impl From<&RumaVideoInfo> for VideoInfo { - fn from(info: &RumaVideoInfo) -> Self { +impl TryFrom<&RumaVideoInfo> for VideoInfo { + type Error = ClientError; + + fn try_from(info: &RumaVideoInfo) -> Result { let thumbnail_info = info.thumbnail_info.as_ref().map(|info| ThumbnailInfo { height: info.height.map(Into::into), width: info.width.map(Into::into), @@ -830,21 +883,28 @@ impl From<&RumaVideoInfo> for VideoInfo { size: info.size.map(Into::into), }); - Self { + Ok(Self { duration: info.duration, height: info.height.map(Into::into), width: info.width.map(Into::into), mimetype: info.mimetype.clone(), size: info.size.map(Into::into), thumbnail_info, - thumbnail_source: info.thumbnail_source.clone().map(Arc::new), + thumbnail_source: info + .thumbnail_source + .as_ref() + .map(TryInto::try_into) + .transpose()? + .map(Arc::new), blurhash: info.blurhash.clone(), - } + }) } } -impl From<&RumaFileInfo> for FileInfo { - fn from(info: &RumaFileInfo) -> Self { +impl TryFrom<&RumaFileInfo> for FileInfo { + type Error = ClientError; + + fn try_from(info: &RumaFileInfo) -> Result { let thumbnail_info = info.thumbnail_info.as_ref().map(|info| ThumbnailInfo { height: info.height.map(Into::into), width: info.width.map(Into::into), @@ -852,12 +912,17 @@ impl From<&RumaFileInfo> for FileInfo { size: info.size.map(Into::into), }); - Self { + Ok(Self { mimetype: info.mimetype.clone(), size: info.size.map(Into::into), thumbnail_info, - thumbnail_source: info.thumbnail_source.clone().map(Arc::new), - } + thumbnail_source: info + .thumbnail_source + .as_ref() + .map(TryInto::try_into) + .transpose()? + .map(Arc::new), + }) } } diff --git a/bindings/matrix-sdk-ffi/src/timeline/content.rs b/bindings/matrix-sdk-ffi/src/timeline/content.rs index 0b01ef500..3c182c65e 100644 --- a/bindings/matrix-sdk-ffi/src/timeline/content.rs +++ b/bindings/matrix-sdk-ffi/src/timeline/content.rs @@ -16,26 +16,55 @@ use std::{collections::HashMap, sync::Arc}; use matrix_sdk::{crypto::types::events::UtdCause, room::power_levels::power_level_user_changes}; use matrix_sdk_ui::timeline::{PollResult, RoomPinnedEventsChange, TimelineDetails}; -use ruma::events::{room::MediaSource, FullStateEventContent}; +use ruma::events::{room::MediaSource as RumaMediaSource, EventContent, FullStateEventContent}; use super::ProfileDetails; -use crate::ruma::{ImageInfo, Mentions, MessageType, PollKind}; +use crate::{ + error::ClientError, + ruma::{ImageInfo, MediaSource, MediaSourceExt, Mentions, MessageType, PollKind}, +}; impl From for TimelineItemContent { fn from(value: matrix_sdk_ui::timeline::TimelineItemContent) -> Self { use matrix_sdk_ui::timeline::TimelineItemContent as Content; match value { - Content::Message(message) => TimelineItemContent::Message { content: message.into() }, + Content::Message(message) => { + let msgtype = message.msgtype().msgtype().to_owned(); + + match TryInto::::try_into(message) { + Ok(message) => TimelineItemContent::Message { content: message }, + Err(error) => TimelineItemContent::FailedToParseMessageLike { + event_type: msgtype, + error: error.to_string(), + }, + } + } Content::RedactedMessage => TimelineItemContent::RedactedMessage, Content::Sticker(sticker) => { let content = sticker.content(); - TimelineItemContent::Sticker { - body: content.body.clone(), - info: (&content.info).into(), - source: Arc::new(MediaSource::from(content.source.clone())), + + let media_source = RumaMediaSource::from(content.source.clone()); + + if let Err(error) = media_source.verify() { + return TimelineItemContent::FailedToParseMessageLike { + event_type: sticker.content().event_type().to_string(), + error: error.to_string(), + }; + } + + match TryInto::::try_into(&content.info) { + Ok(info) => TimelineItemContent::Sticker { + body: content.body.clone(), + info, + source: Arc::new(MediaSource { media_source }), + }, + Err(error) => TimelineItemContent::FailedToParseMessageLike { + event_type: sticker.content().event_type().to_string(), + error: error.to_string(), + }, } } @@ -117,16 +146,18 @@ pub struct MessageContent { pub mentions: Option, } -impl From for MessageContent { - fn from(value: matrix_sdk_ui::timeline::Message) -> Self { - Self { - msg_type: value.msgtype().clone().into(), +impl TryFrom for MessageContent { + type Error = ClientError; + + fn try_from(value: matrix_sdk_ui::timeline::Message) -> Result { + Ok(Self { + msg_type: value.msgtype().clone().try_into()?, body: value.body().to_owned(), in_reply_to: value.in_reply_to().map(|r| Arc::new(r.clone().into())), is_edited: value.is_edited(), thread_root: value.thread_root().map(|id| id.to_string()), mentions: value.mentions().cloned().map(|m| m.into()), - } + }) } }