Skip to content

Commit 0c3161b

Browse files
committed
feat(send_queue): Implement sending of MSC4274 galleries
Signed-off-by: Johannes Marbach <[email protected]>
1 parent afb6627 commit 0c3161b

File tree

10 files changed

+1363
-6
lines changed

10 files changed

+1363
-6
lines changed

crates/matrix-sdk-base/src/store/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ mod send_queue;
7272
#[cfg(any(test, feature = "testing"))]
7373
pub use self::integration_tests::StateStoreIntegrationTests;
7474
#[cfg(feature = "unstable-msc4274")]
75-
pub use self::send_queue::AccumulatedSentMediaInfo;
75+
pub use self::send_queue::{AccumulatedSentMediaInfo, FinishGalleryItemInfo};
7676
pub use self::{
7777
memory_store::MemoryStore,
7878
send_queue::{

crates/matrix-sdk-base/src/store/send_queue.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,19 @@ pub enum DependentQueuedRequestKind {
258258
/// Information about the thumbnail, if present.
259259
thumbnail_info: Option<FinishUploadThumbnailInfo>,
260260
},
261+
262+
/// Finish a gallery upload by updating references to the media cache and
263+
/// sending the final gallery event with the remote MXC URIs.
264+
#[cfg(feature = "unstable-msc4274")]
265+
FinishGallery {
266+
/// Local echo for the event (containing the local MXC URIs).
267+
///
268+
/// `Box` the local echo so that it reduces the size of the whole enum.
269+
local_echo: Box<RoomMessageEventContent>,
270+
271+
/// Metadata about the gallery items
272+
item_infos: Vec<FinishGalleryItemInfo>,
273+
},
261274
}
262275

263276
/// If parent_is_thumbnail_upload is missing, we assume the request is for a
@@ -285,6 +298,17 @@ pub struct FinishUploadThumbnailInfo {
285298
pub height: Option<UInt>,
286299
}
287300

301+
#[cfg(feature = "unstable-msc4274")]
302+
#[derive(Clone, Debug, Serialize, Deserialize)]
303+
/// Detailed record about a file and thumbnail used when finishing a gallery
304+
/// upload.
305+
pub struct FinishGalleryItemInfo {
306+
/// Transaction id for the file upload.
307+
pub file_upload: OwnedTransactionId,
308+
/// Information about the thumbnail, if present.
309+
pub thumbnail_info: Option<FinishUploadThumbnailInfo>,
310+
}
311+
288312
/// A transaction id identifying a [`DependentQueuedRequest`] rather than its
289313
/// parent [`QueuedRequest`].
290314
///
@@ -442,6 +466,11 @@ impl DependentQueuedRequest {
442466
// This one graduates into a new media event.
443467
true
444468
}
469+
#[cfg(feature = "unstable-msc4274")]
470+
DependentQueuedRequestKind::FinishGallery { .. } => {
471+
// This one graduates into a new gallery event.
472+
true
473+
}
445474
}
446475
}
447476
}

crates/matrix-sdk/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ All notable changes to this project will be documented in this file.
88

99
### Features
1010

11+
- `RoomSendQueue::send_gallery` has been added to allow sending MSC4274-style media galleries
12+
via the send queue under the `unstable-msc4274` feature.
13+
([#4977](https://github.com/matrix-org/matrix-rust-sdk/pull/4977))
14+
1115
### Bug fixes
1216

1317
### Refactor

crates/matrix-sdk/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ docsrs = ["e2e-encryption", "sqlite", "indexeddb", "sso-login", "qrcode"]
5151
experimental-share-history-on-invite = []
5252

5353
# Add support for inline media galleries via msgtypes
54-
unstable-msc4274 = ["matrix-sdk-base/unstable-msc4274"]
54+
unstable-msc4274 = ["ruma/unstable-msc4274", "matrix-sdk-base/unstable-msc4274"]
5555

5656
[dependencies]
5757
anyhow = { workspace = true, optional = true }

crates/matrix-sdk/src/attachment.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,3 +276,95 @@ impl AttachmentConfig {
276276
self
277277
}
278278
}
279+
280+
/// Configuration for sending a gallery.
281+
#[cfg(feature = "unstable-msc4274")]
282+
#[derive(Debug, Default)]
283+
pub struct GalleryConfig {
284+
pub(crate) txn_id: Option<OwnedTransactionId>,
285+
pub(crate) caption: Option<String>,
286+
pub(crate) formatted_caption: Option<FormattedBody>,
287+
pub(crate) mentions: Option<Mentions>,
288+
pub(crate) reply: Option<Reply>,
289+
}
290+
291+
#[cfg(feature = "unstable-msc4274")]
292+
impl GalleryConfig {
293+
/// Create a new empty `GalleryConfig`.
294+
pub fn new() -> Self {
295+
Self::default()
296+
}
297+
298+
/// Set the transaction ID to send.
299+
///
300+
/// # Arguments
301+
///
302+
/// * `txn_id` - A unique ID that can be attached to a `MessageEvent` held
303+
/// in its unsigned field as `transaction_id`. If not given, one is
304+
/// created for the message.
305+
#[must_use]
306+
pub fn txn_id(mut self, txn_id: &TransactionId) -> Self {
307+
self.txn_id = Some(txn_id.to_owned());
308+
self
309+
}
310+
311+
/// Set the optional caption
312+
///
313+
/// # Arguments
314+
///
315+
/// * `caption` - The optional caption
316+
pub fn caption(mut self, caption: Option<String>) -> Self {
317+
self.caption = caption;
318+
self
319+
}
320+
321+
/// Set the optional formatted caption
322+
///
323+
/// # Arguments
324+
///
325+
/// * `formatted_caption` - The optional formatted caption
326+
pub fn formatted_caption(mut self, formatted_caption: Option<FormattedBody>) -> Self {
327+
self.formatted_caption = formatted_caption;
328+
self
329+
}
330+
331+
/// Set the mentions of the message.
332+
///
333+
/// # Arguments
334+
///
335+
/// * `mentions` - The mentions of the message
336+
pub fn mentions(mut self, mentions: Option<Mentions>) -> Self {
337+
self.mentions = mentions;
338+
self
339+
}
340+
341+
/// Set the reply information of the message.
342+
///
343+
/// # Arguments
344+
///
345+
/// * `reply` - The reply information of the message
346+
pub fn reply(mut self, reply: Option<Reply>) -> Self {
347+
self.reply = reply;
348+
self
349+
}
350+
}
351+
352+
#[cfg(feature = "unstable-msc4274")]
353+
#[derive(Debug)]
354+
/// Metadata for a gallery item
355+
pub struct GalleryItemInfo {
356+
/// The filename
357+
pub filename: String,
358+
/// The mime type
359+
pub content_type: mime::Mime,
360+
/// The binary data
361+
pub data: Vec<u8>,
362+
/// The attachment info
363+
pub attachment_info: AttachmentInfo,
364+
/// The caption
365+
pub caption: Option<String>,
366+
/// The formatted caption
367+
pub formatted_caption: Option<FormattedBody>,
368+
/// The thumbnail
369+
pub thumbnail: Option<Thumbnail>,
370+
}

crates/matrix-sdk/src/room/edit.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,12 @@ pub(crate) fn update_media_caption(
269269
event.formatted = formatted_caption;
270270
true
271271
}
272+
#[cfg(feature = "unstable-msc4274")]
273+
MessageType::Gallery(event) => {
274+
event.body = caption.unwrap_or_default();
275+
event.formatted = formatted_caption;
276+
true
277+
}
272278
MessageType::Image(event) => {
273279
set_caption!(event, caption);
274280
event.formatted = formatted_caption;

crates/matrix-sdk/src/room/mod.rs

Lines changed: 97 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ use matrix_sdk_common::{
5656
};
5757
use mime::Mime;
5858
use reply::Reply;
59+
#[cfg(feature = "unstable-msc4274")]
60+
use ruma::events::room::message::GalleryItemType;
5961
#[cfg(feature = "e2e-encryption")]
6062
use ruma::events::{
6163
room::encrypted::OriginalSyncRoomEncryptedEvent, AnySyncMessageLikeEvent, AnySyncTimelineEvent,
@@ -2160,7 +2162,7 @@ impl Room {
21602162
}
21612163

21622164
let content = self
2163-
.make_attachment_event(
2165+
.make_message_event(
21642166
self.make_attachment_type(
21652167
content_type,
21662168
filename,
@@ -2276,7 +2278,7 @@ impl Room {
22762278

22772279
/// Creates the [`RoomMessageEventContent`] based on the message type,
22782280
/// mentions and reply information.
2279-
pub(crate) async fn make_attachment_event(
2281+
pub(crate) async fn make_message_event(
22802282
&self,
22812283
msg_type: MessageType,
22822284
mentions: Option<Mentions>,
@@ -2294,6 +2296,99 @@ impl Room {
22942296
Ok(content)
22952297
}
22962298

2299+
/// Creates the inner [`GalleryItemType`] for an already-uploaded media file
2300+
/// provided by its source.
2301+
#[cfg(feature = "unstable-msc4274")]
2302+
#[allow(clippy::too_many_arguments)]
2303+
pub(crate) fn make_gallery_item_type(
2304+
&self,
2305+
content_type: &Mime,
2306+
filename: String,
2307+
source: MediaSource,
2308+
caption: Option<String>,
2309+
formatted_caption: Option<FormattedBody>,
2310+
info: Option<AttachmentInfo>,
2311+
thumbnail: Option<(MediaSource, Box<ThumbnailInfo>)>,
2312+
) -> GalleryItemType {
2313+
// If caption is set, use it as body, and filename as the file name; otherwise,
2314+
// body is the filename, and the filename is not set.
2315+
// https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/2530-body-as-caption.md
2316+
let (body, filename) = match caption {
2317+
Some(caption) => (caption, Some(filename)),
2318+
None => (filename, None),
2319+
};
2320+
2321+
let (thumbnail_source, thumbnail_info) = thumbnail.unzip();
2322+
2323+
match content_type.type_() {
2324+
mime::IMAGE => {
2325+
let info = assign!(info.map(ImageInfo::from).unwrap_or_default(), {
2326+
mimetype: Some(content_type.as_ref().to_owned()),
2327+
thumbnail_source,
2328+
thumbnail_info
2329+
});
2330+
let content = assign!(ImageMessageEventContent::new(body, source), {
2331+
info: Some(Box::new(info)),
2332+
formatted: formatted_caption,
2333+
filename
2334+
});
2335+
GalleryItemType::Image(content)
2336+
}
2337+
2338+
mime::AUDIO => {
2339+
let mut content = assign!(AudioMessageEventContent::new(body, source), {
2340+
formatted: formatted_caption,
2341+
filename
2342+
});
2343+
2344+
if let Some(AttachmentInfo::Voice { audio_info, waveform: Some(waveform_vec) }) =
2345+
&info
2346+
{
2347+
if let Some(duration) = audio_info.duration {
2348+
let waveform = waveform_vec.iter().map(|v| (*v).into()).collect();
2349+
content.audio =
2350+
Some(UnstableAudioDetailsContentBlock::new(duration, waveform));
2351+
}
2352+
content.voice = Some(UnstableVoiceContentBlock::new());
2353+
}
2354+
2355+
let mut audio_info = info.map(AudioInfo::from).unwrap_or_default();
2356+
audio_info.mimetype = Some(content_type.as_ref().to_owned());
2357+
let content = content.info(Box::new(audio_info));
2358+
2359+
GalleryItemType::Audio(content)
2360+
}
2361+
2362+
mime::VIDEO => {
2363+
let info = assign!(info.map(VideoInfo::from).unwrap_or_default(), {
2364+
mimetype: Some(content_type.as_ref().to_owned()),
2365+
thumbnail_source,
2366+
thumbnail_info
2367+
});
2368+
let content = assign!(VideoMessageEventContent::new(body, source), {
2369+
info: Some(Box::new(info)),
2370+
formatted: formatted_caption,
2371+
filename
2372+
});
2373+
GalleryItemType::Video(content)
2374+
}
2375+
2376+
_ => {
2377+
let info = assign!(info.map(FileInfo::from).unwrap_or_default(), {
2378+
mimetype: Some(content_type.as_ref().to_owned()),
2379+
thumbnail_source,
2380+
thumbnail_info
2381+
});
2382+
let content = assign!(FileMessageEventContent::new(body, source), {
2383+
info: Some(Box::new(info)),
2384+
formatted: formatted_caption,
2385+
filename,
2386+
});
2387+
GalleryItemType::File(content)
2388+
}
2389+
}
2390+
}
2391+
22972392
/// Update the power levels of a select set of users of this room.
22982393
///
22992394
/// Issue a `power_levels` state event request to the server, changing the

0 commit comments

Comments
 (0)