1#[cfg(feature = "unstable-msc4274")]
18use std::{collections::HashMap, iter::zip};
19
20use matrix_sdk_base::{
21 RoomState,
22 media::{
23 MediaFormat, MediaRequestParameters, MediaThumbnailSettings,
24 store::IgnoreMediaRetentionPolicy,
25 },
26 store::{
27 ChildTransactionId, DependentQueuedRequestKind, FinishUploadThumbnailInfo,
28 QueuedRequestKind, SentMediaInfo, SentRequestKey, SerializableEventContent,
29 },
30};
31#[cfg(feature = "unstable-msc4274")]
32use matrix_sdk_base::{
33 media::UniqueKey,
34 store::{AccumulatedSentMediaInfo, FinishGalleryItemInfo},
35};
36use mime::Mime;
37#[cfg(feature = "unstable-msc4274")]
38use ruma::events::room::message::{GalleryItemType, GalleryMessageEventContent};
39use ruma::{
40 EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, TransactionId,
41 events::{
42 AnyMessageLikeEventContent, Mentions,
43 room::{
44 MediaSource, ThumbnailInfo,
45 message::{
46 FormattedBody, MessageType, Relation, ReplacementMetadata, RoomMessageEventContent,
47 RoomMessageEventContentWithoutRelation,
48 },
49 },
50 },
51};
52use tracing::{Span, debug, error, instrument, trace, warn};
53
54use super::{QueueStorage, QueueThumbnailInfo, RoomSendQueue, RoomSendQueueError};
55use crate::{
56 Client, Media, Room,
57 attachment::{AttachmentConfig, Thumbnail},
58 room::edit::{update_media_caption, validate_attachment_edit},
59 send_queue::{
60 LocalEcho, LocalEchoContent, MediaHandles, RoomSendQueueStorageError, RoomSendQueueUpdate,
61 SendHandle,
62 },
63};
64#[cfg(feature = "unstable-msc4274")]
65use crate::{
66 attachment::{GalleryConfig, GalleryItemInfo},
67 send_queue::GalleryItemQueueInfo,
68};
69
70fn update_media_event_after_upload(echo: &mut RoomMessageEventContent, sent: SentMediaInfo) {
73 update_media_msgtype_after_upload(&mut echo.msgtype, &sent);
74
75 if let Some(Relation::Replacement(replacement)) = &mut echo.relates_to {
79 update_media_msgtype_after_upload(&mut replacement.new_content.msgtype, &sent);
80 }
81}
82
83fn update_media_msgtype_after_upload(msgtype: &mut MessageType, sent: &SentMediaInfo) {
86 match msgtype {
89 MessageType::Audio(event) => {
90 event.source = sent.file.clone();
91 }
92 MessageType::File(event) => {
93 event.source = sent.file.clone();
94 if let Some(info) = event.info.as_mut() {
95 info.thumbnail_source = sent.thumbnail.clone();
96 }
97 }
98 MessageType::Image(event) => {
99 event.source = sent.file.clone();
100 if let Some(info) = event.info.as_mut() {
101 info.thumbnail_source = sent.thumbnail.clone();
102 }
103 }
104 MessageType::Video(event) => {
105 event.source = sent.file.clone();
106 if let Some(info) = event.info.as_mut() {
107 info.thumbnail_source = sent.thumbnail.clone();
108 }
109 }
110
111 _ => {
112 error!("Invalid message type in database: {}", msgtype.msgtype());
116 debug_assert!(false, "invalid message type in database");
118 }
119 }
120}
121
122#[cfg(feature = "unstable-msc4274")]
125fn update_gallery_event_after_upload(
126 echo: &mut RoomMessageEventContent,
127 sent: HashMap<String, AccumulatedSentMediaInfo>,
128) {
129 let MessageType::Gallery(gallery) = &mut echo.msgtype else {
130 error!("Invalid gallery item types in database");
134 debug_assert!(false, "invalid item type in database {:?}", echo.msgtype());
136 return;
137 };
138
139 for itemtype in gallery.itemtypes.iter_mut() {
142 match itemtype {
143 GalleryItemType::Audio(event) => match sent.get(&event.source.unique_key()) {
144 Some(sent) => event.source = sent.file.clone(),
145 None => error!("key for item {:?} does not exist on gallery event", event.source),
146 },
147 GalleryItemType::File(event) => match sent.get(&event.source.unique_key()) {
148 Some(sent) => {
149 event.source = sent.file.clone();
150 if let Some(info) = event.info.as_mut() {
151 info.thumbnail_source = sent.thumbnail.clone();
152 }
153 }
154 None => error!("key for item {:?} does not exist on gallery event", event.source),
155 },
156 GalleryItemType::Image(event) => match sent.get(&event.source.unique_key()) {
157 Some(sent) => {
158 event.source = sent.file.clone();
159 if let Some(info) = event.info.as_mut() {
160 info.thumbnail_source = sent.thumbnail.clone();
161 }
162 }
163 None => error!("key for item {:?} does not exist on gallery event", event.source),
164 },
165 GalleryItemType::Video(event) => match sent.get(&event.source.unique_key()) {
166 Some(sent) => {
167 event.source = sent.file.clone();
168 if let Some(info) = event.info.as_mut() {
169 info.thumbnail_source = sent.thumbnail.clone();
170 }
171 }
172 None => error!("key for item {:?} does not exist on gallery event", event.source),
173 },
174
175 _ => {
176 error!("Invalid gallery item types in database");
181 debug_assert!(false, "invalid gallery item type in database {itemtype:?}");
183 }
184 }
185 }
186}
187
188#[derive(Default)]
189struct MediaCacheResult {
190 upload_thumbnail_txn: Option<OwnedTransactionId>,
191 event_thumbnail_info: Option<(MediaSource, Box<ThumbnailInfo>)>,
192 queue_thumbnail_info: Option<QueueThumbnailInfo>,
193}
194
195impl RoomSendQueue {
196 #[instrument(skip_all, fields(event_txn))]
216 pub async fn send_attachment(
217 &self,
218 filename: impl Into<String>,
219 content_type: Mime,
220 data: Vec<u8>,
221 config: AttachmentConfig,
222 ) -> Result<SendHandle, RoomSendQueueError> {
223 self.send_attachment_impl(filename.into(), content_type, data, config, None).await
224 }
225
226 #[instrument(skip_all, fields(event_txn, %edited_event_id))]
242 pub async fn edit_with_attachment(
243 &self,
244 edited_event_id: &EventId,
245 filename: impl Into<String>,
246 content_type: Mime,
247 data: Vec<u8>,
248 mut config: AttachmentConfig,
249 ) -> Result<SendHandle, RoomSendQueueError> {
250 let Some(room) = self.inner.room.get() else {
251 return Err(RoomSendQueueError::RoomDisappeared);
252 };
253
254 if room.state() != RoomState::Joined {
255 return Err(RoomSendQueueError::RoomNotJoined);
256 }
257
258 let original_mentions =
259 validate_attachment_edit(&room, room.own_user_id(), edited_event_id).await?;
260
261 config.reply = None;
263
264 self.send_attachment_impl(
265 filename.into(),
266 content_type,
267 data,
268 config,
269 Some((edited_event_id.to_owned(), original_mentions)),
270 )
271 .await
272 }
273
274 async fn send_attachment_impl(
278 &self,
279 filename: String,
280 content_type: Mime,
281 data: Vec<u8>,
282 mut config: AttachmentConfig,
283 replaces: Option<(OwnedEventId, Option<Mentions>)>,
284 ) -> Result<SendHandle, RoomSendQueueError> {
285 let Some(room) = self.inner.room.get() else {
286 return Err(RoomSendQueueError::RoomDisappeared);
287 };
288
289 if room.state() != RoomState::Joined {
290 return Err(RoomSendQueueError::RoomNotJoined);
291 }
292
293 let extra_content = config.extra_content.take();
294 let upload_file_txn = TransactionId::new();
295 let send_event_txn = config.txn_id.map_or_else(ChildTransactionId::new, Into::into);
296
297 Span::current().record("event_txn", tracing::field::display(&*send_event_txn));
298 debug!(filename, %content_type, %upload_file_txn, "sending an attachment");
299
300 let file_media_request = Media::make_local_file_media_request(&upload_file_txn);
301
302 let MediaCacheResult { upload_thumbnail_txn, event_thumbnail_info, queue_thumbnail_info } =
303 RoomSendQueue::cache_media(&room, data, config.thumbnail.take(), &file_media_request)
304 .await?;
305
306 let event_content = room
308 .make_media_event(
309 Room::make_attachment_type(
310 &content_type,
311 filename,
312 file_media_request.source.clone(),
313 config.caption,
314 config.info,
315 event_thumbnail_info,
316 ),
317 config.mentions,
318 config.reply,
319 )
320 .await
321 .map_err(|_| RoomSendQueueError::FailedToCreateAttachment)?;
322
323 let event_content = if let Some((edited_event_id, original_mentions)) = replaces {
328 RoomMessageEventContentWithoutRelation::from(event_content)
329 .make_replacement(ReplacementMetadata::new(edited_event_id, original_mentions))
330 } else {
331 event_content
332 };
333
334 let created_at = MilliSecondsSinceUnixEpoch::now();
335
336 self.inner
338 .queue
339 .push_media(
340 event_content.clone(),
341 content_type,
342 send_event_txn.clone().into(),
343 created_at,
344 upload_file_txn.clone(),
345 file_media_request,
346 queue_thumbnail_info,
347 extra_content.clone(),
348 )
349 .await?;
350
351 trace!("manager sends a media to the background task");
352
353 self.inner.notifier.notify_one();
354
355 let send_handle = SendHandle {
356 room: self.clone(),
357 transaction_id: send_event_txn.clone().into(),
358 media_handles: vec![MediaHandles { upload_thumbnail_txn, upload_file_txn }],
359 created_at,
360 };
361
362 self.send_update(RoomSendQueueUpdate::NewLocalEvent(LocalEcho {
363 transaction_id: send_event_txn.clone().into(),
364 content: LocalEchoContent::Event {
365 serialized_event: merge_extra_content(
366 SerializableEventContent::new(&event_content.into())
367 .map_err(RoomSendQueueStorageError::JsonSerialization)?,
368 extra_content,
369 )?,
370 send_handle: send_handle.clone(),
371 send_error: None,
372 },
373 }));
374
375 Ok(send_handle)
376 }
377
378 #[cfg(feature = "unstable-msc4274")]
398 #[instrument(skip_all, fields(event_txn))]
399 pub async fn send_gallery(
400 &self,
401 gallery: GalleryConfig,
402 ) -> Result<SendHandle, RoomSendQueueError> {
403 let Some(room) = self.inner.room.get() else {
404 return Err(RoomSendQueueError::RoomDisappeared);
405 };
406
407 if room.state() != RoomState::Joined {
408 return Err(RoomSendQueueError::RoomNotJoined);
409 }
410
411 if gallery.is_empty() {
412 return Err(RoomSendQueueError::EmptyGallery);
413 }
414
415 let send_event_txn =
416 gallery.txn_id.clone().map_or_else(ChildTransactionId::new, Into::into);
417
418 Span::current().record("event_txn", tracing::field::display(&*send_event_txn));
419
420 let mut item_types = Vec::with_capacity(gallery.len());
421 let mut item_queue_infos = Vec::with_capacity(gallery.len());
422 let mut media_handles = Vec::with_capacity(gallery.len());
423
424 for item_info in gallery.items {
425 let GalleryItemInfo { filename, content_type, data, .. } = item_info;
426
427 let upload_file_txn = TransactionId::new();
428
429 debug!(filename, %content_type, %upload_file_txn, "uploading a gallery attachment");
430
431 let file_media_request = Media::make_local_file_media_request(&upload_file_txn);
432
433 let MediaCacheResult {
434 upload_thumbnail_txn,
435 event_thumbnail_info,
436 queue_thumbnail_info,
437 } = RoomSendQueue::cache_media(&room, data, item_info.thumbnail, &file_media_request)
438 .await?;
439
440 item_types.push(Room::make_gallery_item_type(
441 &content_type,
442 filename,
443 file_media_request.source.clone(),
444 item_info.caption,
445 Some(item_info.attachment_info),
446 event_thumbnail_info,
447 ));
448
449 item_queue_infos.push(GalleryItemQueueInfo {
450 content_type,
451 upload_file_txn: upload_file_txn.clone(),
452 file_media_request,
453 thumbnail: queue_thumbnail_info,
454 });
455
456 media_handles.push(MediaHandles { upload_file_txn, upload_thumbnail_txn });
457 }
458
459 let (body, formatted) =
461 gallery.caption.map(|caption| (caption.body, caption.formatted)).unwrap_or_default();
462 let event_content = room
463 .make_media_event(
464 MessageType::Gallery(GalleryMessageEventContent::new(body, formatted, item_types)),
465 gallery.mentions,
466 gallery.reply,
467 )
468 .await
469 .map_err(|_| RoomSendQueueError::FailedToCreateGallery)?;
470
471 let created_at = MilliSecondsSinceUnixEpoch::now();
472
473 self.inner
475 .queue
476 .push_gallery(
477 event_content.clone(),
478 send_event_txn.clone().into(),
479 created_at,
480 item_queue_infos,
481 )
482 .await?;
483
484 trace!("manager sends a gallery to the background task");
485
486 self.inner.notifier.notify_one();
487
488 let send_handle = SendHandle {
489 room: self.clone(),
490 transaction_id: send_event_txn.clone().into(),
491 media_handles,
492 created_at,
493 };
494
495 self.send_update(RoomSendQueueUpdate::NewLocalEvent(LocalEcho {
496 transaction_id: send_event_txn.clone().into(),
497 content: LocalEchoContent::Event {
498 serialized_event: SerializableEventContent::new(&event_content.into())
499 .map_err(RoomSendQueueStorageError::JsonSerialization)?,
500 send_handle: send_handle.clone(),
501 send_error: None,
502 },
503 }));
504
505 Ok(send_handle)
506 }
507
508 async fn cache_media(
509 room: &Room,
510 data: Vec<u8>,
511 thumbnail: Option<Thumbnail>,
512 file_media_request: &MediaRequestParameters,
513 ) -> Result<MediaCacheResult, RoomSendQueueError> {
514 let client = room.client();
515 let media_store =
516 client.media_store().lock().await.map_err(RoomSendQueueStorageError::LockError)?;
517
518 media_store
520 .add_media_content(
521 file_media_request,
522 data,
523 IgnoreMediaRetentionPolicy::Yes,
525 )
526 .await
527 .map_err(RoomSendQueueStorageError::MediaStoreError)?;
528
529 if let Some(thumbnail) = thumbnail {
531 let txn = TransactionId::new();
532 trace!(upload_thumbnail_txn = %txn, "media has a thumbnail");
533
534 let (data, content_type, thumbnail_info) = thumbnail.into_parts();
537 let file_size = data.len();
538
539 let thumbnail_height = thumbnail_info.height;
540 let thumbnail_width = thumbnail_info.width;
541
542 let thumbnail_media_request = Media::make_local_file_media_request(&txn);
544 media_store
545 .add_media_content(
546 &thumbnail_media_request,
547 data,
548 IgnoreMediaRetentionPolicy::Yes,
550 )
551 .await
552 .map_err(RoomSendQueueStorageError::MediaStoreError)?;
553
554 Ok(MediaCacheResult {
555 upload_thumbnail_txn: Some(txn.clone()),
556 event_thumbnail_info: Some((
557 thumbnail_media_request.source.clone(),
558 thumbnail_info,
559 )),
560 queue_thumbnail_info: Some(QueueThumbnailInfo {
561 finish_upload_thumbnail_info: FinishUploadThumbnailInfo {
562 txn,
563 width: thumbnail_width,
564 height: thumbnail_height,
565 },
566 media_request_parameters: thumbnail_media_request,
567 content_type,
568 file_size,
569 }),
570 })
571 } else {
572 Ok(Default::default())
573 }
574 }
575}
576
577pub(super) fn merge_extra_content(
582 content: SerializableEventContent,
583 extra_content: Option<serde_json::Map<String, serde_json::Value>>,
584) -> Result<SerializableEventContent, RoomSendQueueStorageError> {
585 let Some(extra_content) = extra_content else {
586 return Ok(content);
587 };
588 if extra_content.is_empty() {
589 return Ok(content);
590 }
591
592 let (raw, event_type) = content.into_raw();
593 let mut object: serde_json::Map<String, serde_json::Value> =
594 raw.deserialize_as().map_err(RoomSendQueueStorageError::JsonSerialization)?;
595
596 for (key, value) in extra_content {
597 match object.entry(key) {
598 serde_json::map::Entry::Occupied(entry) => {
599 warn!(key = entry.key(), "extra content field shadowed by the event's own field");
600 }
601 serde_json::map::Entry::Vacant(entry) => {
602 entry.insert(value);
603 }
604 }
605 }
606
607 let raw = ruma::serde::Raw::from_json(
608 serde_json::value::to_raw_value(&object)
609 .map_err(RoomSendQueueStorageError::JsonSerialization)?,
610 );
611
612 Ok(SerializableEventContent::from_raw(raw, event_type))
613}
614
615impl QueueStorage {
616 #[allow(clippy::too_many_arguments)]
618 pub(super) async fn handle_dependent_finish_upload(
619 &self,
620 client: &Client,
621 event_txn: OwnedTransactionId,
622 parent_key: SentRequestKey,
623 mut local_echo: RoomMessageEventContent,
624 file_upload_txn: OwnedTransactionId,
625 thumbnail_info: Option<FinishUploadThumbnailInfo>,
626 extra_content: Option<serde_json::Map<String, serde_json::Value>>,
627 new_updates: &mut Vec<RoomSendQueueUpdate>,
628 ) -> Result<(), RoomSendQueueError> {
629 let sent_media = parent_key
631 .into_media()
632 .ok_or(RoomSendQueueError::StorageError(RoomSendQueueStorageError::InvalidParentKey))?;
633
634 update_media_cache_keys_after_upload(client, &file_upload_txn, thumbnail_info, &sent_media)
635 .await?;
636 update_media_event_after_upload(&mut local_echo, sent_media);
637
638 let new_content = merge_extra_content(
639 SerializableEventContent::new(&local_echo.into())
640 .map_err(RoomSendQueueStorageError::JsonSerialization)?,
641 extra_content,
642 )?;
643
644 new_updates.push(RoomSendQueueUpdate::ReplacedLocalEvent {
647 transaction_id: event_txn.clone(),
648 new_content: new_content.clone(),
649 });
650
651 trace!(%event_txn, "queueing media event after successfully uploading media(s)");
652
653 client
654 .state_store()
655 .save_send_queue_request(
656 &self.room_id,
657 event_txn,
658 MilliSecondsSinceUnixEpoch::now(),
659 new_content.into(),
660 Self::HIGH_PRIORITY,
661 )
662 .await
663 .map_err(RoomSendQueueStorageError::StateStoreError)?;
664
665 Ok(())
666 }
667
668 #[cfg(feature = "unstable-msc4274")]
671 #[allow(clippy::too_many_arguments)]
672 pub(super) async fn handle_dependent_finish_gallery_upload(
673 &self,
674 client: &Client,
675 event_txn: OwnedTransactionId,
676 parent_key: SentRequestKey,
677 mut local_echo: RoomMessageEventContent,
678 item_infos: Vec<FinishGalleryItemInfo>,
679 new_updates: &mut Vec<RoomSendQueueUpdate>,
680 ) -> Result<(), RoomSendQueueError> {
681 let sent_gallery = parent_key
683 .into_media()
684 .ok_or(RoomSendQueueError::StorageError(RoomSendQueueStorageError::InvalidParentKey))?;
685
686 let mut sent_media_vec = sent_gallery.accumulated;
687 sent_media_vec.push(AccumulatedSentMediaInfo {
688 file: sent_gallery.file,
689 thumbnail: sent_gallery.thumbnail,
690 });
691
692 let mut sent_infos = HashMap::new();
693
694 for (item_info, sent_media) in zip(item_infos, sent_media_vec) {
695 let FinishGalleryItemInfo { file_upload: file_upload_txn, thumbnail_info } = item_info;
696
697 let from_req = Media::make_local_file_media_request(&file_upload_txn);
700 sent_infos.insert(from_req.source.unique_key(), sent_media.clone());
701
702 update_media_cache_keys_after_upload(
703 client,
704 &file_upload_txn,
705 thumbnail_info,
706 &sent_media.into(),
707 )
708 .await?;
709 }
710
711 update_gallery_event_after_upload(&mut local_echo, sent_infos);
712
713 let new_content = SerializableEventContent::new(&local_echo.into())
714 .map_err(RoomSendQueueStorageError::JsonSerialization)?;
715
716 new_updates.push(RoomSendQueueUpdate::ReplacedLocalEvent {
719 transaction_id: event_txn.clone(),
720 new_content: new_content.clone(),
721 });
722
723 trace!(%event_txn, "queueing media event after successfully uploading media(s)");
724
725 client
726 .state_store()
727 .save_send_queue_request(
728 &self.room_id,
729 event_txn,
730 MilliSecondsSinceUnixEpoch::now(),
731 new_content.into(),
732 Self::HIGH_PRIORITY,
733 )
734 .await
735 .map_err(RoomSendQueueStorageError::StateStoreError)?;
736
737 Ok(())
738 }
739
740 #[allow(clippy::too_many_arguments)]
743 pub(super) async fn handle_dependent_file_or_thumbnail_upload(
744 &self,
745 client: &Client,
746 next_upload_txn: OwnedTransactionId,
747 parent_key: SentRequestKey,
748 content_type: String,
749 cache_key: MediaRequestParameters,
750 event_txn: OwnedTransactionId,
751 parent_is_thumbnail_upload: bool,
752 ) -> Result<(), RoomSendQueueError> {
753 let sent_media = parent_key
756 .into_media()
757 .ok_or(RoomSendQueueError::StorageError(RoomSendQueueStorageError::InvalidParentKey))?;
758
759 if parent_is_thumbnail_upload {
762 debug_assert!(sent_media.thumbnail.is_none());
763 if sent_media.thumbnail.is_some() {
764 warn!("unexpected thumbnail for a thumbnail!");
765 }
766 }
767
768 trace!(
769 related_to = %event_txn,
770 "done uploading file or thumbnail, now queuing the dependent file \
771 or thumbnail upload request",
772 );
773
774 #[cfg(feature = "unstable-msc4274")]
780 let accumulated = if parent_is_thumbnail_upload {
781 sent_media.accumulated
782 } else {
783 let mut accumulated = sent_media.accumulated;
784 accumulated.push(AccumulatedSentMediaInfo {
785 file: sent_media.file.clone(),
786 thumbnail: sent_media.thumbnail,
787 });
788 accumulated
789 };
790
791 let request = QueuedRequestKind::MediaUpload {
792 content_type,
793 cache_key,
794 thumbnail_source: parent_is_thumbnail_upload.then_some(sent_media.file),
797 related_to: event_txn,
798 #[cfg(feature = "unstable-msc4274")]
799 accumulated,
800 };
801
802 client
803 .state_store()
804 .save_send_queue_request(
805 &self.room_id,
806 next_upload_txn,
807 MilliSecondsSinceUnixEpoch::now(),
808 request,
809 Self::HIGH_PRIORITY,
810 )
811 .await
812 .map_err(RoomSendQueueStorageError::StateStoreError)?;
813
814 Ok(())
815 }
816
817 #[instrument(skip(self, handles))]
824 pub(super) async fn abort_upload(
825 &self,
826 event_txn: &TransactionId,
827 handles: &MediaHandles,
828 ) -> Result<bool, RoomSendQueueStorageError> {
829 let mut guard = self.store.lock().await;
830 let client = guard.client()?;
831
832 debug!("trying to abort an upload");
834
835 let store = client.state_store();
836
837 let upload_file_as_dependent = ChildTransactionId::from(handles.upload_file_txn.clone());
838 let event_as_dependent = ChildTransactionId::from(event_txn.to_owned());
839
840 let mut removed_dependent_upload = false;
841 let mut removed_dependent_event = false;
842
843 if let Some(thumbnail_txn) = &handles.upload_thumbnail_txn
844 && store.remove_send_queue_request(&self.room_id, thumbnail_txn).await?
845 {
846 trace!("could remove thumbnail request, removing 2 dependent requests now");
849
850 if let Some(info) = guard.being_sent.as_ref()
852 && info.transaction_id == *thumbnail_txn
853 {
854 let info = guard.being_sent.take().unwrap();
856 if info.cancel_upload() {
857 trace!("aborted ongoing thumbnail upload");
858 }
859 }
860
861 removed_dependent_upload = store
863 .remove_dependent_queued_request(&self.room_id, &upload_file_as_dependent)
864 .await?;
865
866 if !removed_dependent_upload {
867 warn!("unable to find the dependent file upload request");
868 }
869
870 removed_dependent_event =
871 store.remove_dependent_queued_request(&self.room_id, &event_as_dependent).await?;
872
873 if !removed_dependent_event {
874 warn!("unable to find the dependent media event upload request");
875 }
876 }
877
878 if !removed_dependent_upload {
886 if store.remove_send_queue_request(&self.room_id, &handles.upload_file_txn).await? {
887 trace!("could remove file upload request, removing 1 dependent request");
891
892 if let Some(info) = guard.being_sent.as_ref()
894 && info.transaction_id == handles.upload_file_txn
895 {
896 let info = guard.being_sent.take().unwrap();
898 if info.cancel_upload() {
899 trace!("aborted ongoing file upload");
900 }
901 }
902
903 if !store
905 .remove_dependent_queued_request(&self.room_id, &event_as_dependent)
906 .await?
907 {
908 warn!("unable to find the dependent media event upload request");
909 }
910 } else {
911 if !removed_dependent_event
916 && !store
917 .remove_dependent_queued_request(&self.room_id, &event_as_dependent)
918 .await?
919 {
920 debug!("uploads already happened => deferring to aborting an event sending");
924 return Ok(false);
925 }
926 }
927 }
928
929 {
933 let media_store = client.media_store().lock().await?;
934 media_store
935 .remove_media_content_for_uri(&Media::make_local_uri(&handles.upload_file_txn))
936 .await?;
937 if let Some(txn) = &handles.upload_thumbnail_txn {
938 media_store.remove_media_content_for_uri(&Media::make_local_uri(txn)).await?;
939 }
940 }
941
942 debug!("successfully aborted!");
943 Ok(true)
944 }
945
946 #[instrument(skip(self, caption, formatted_caption))]
947 pub(super) async fn edit_media_caption(
948 &self,
949 txn: &TransactionId,
950 caption: Option<String>,
951 formatted_caption: Option<FormattedBody>,
952 mentions: Option<Mentions>,
953 ) -> Result<Option<AnyMessageLikeEventContent>, RoomSendQueueStorageError> {
954 use RoomSendQueueStorageError::InvalidMediaCaptionEdit;
956
957 let guard = self.store.lock().await;
958 let client = guard.client()?;
959 let store = client.state_store();
960
961 {
970 let dependent_requests = store.load_dependent_queued_requests(&self.room_id).await?;
973
974 if let Some(found) =
975 dependent_requests.into_iter().find(|req| *req.own_transaction_id == *txn)
976 {
977 trace!("found the caption to edit in a dependent request");
978
979 let DependentQueuedRequestKind::FinishUpload {
980 mut local_echo,
981 file_upload,
982 thumbnail_info,
983 extra_content,
984 } = found.kind
985 else {
986 return Err(InvalidMediaCaptionEdit);
987 };
988
989 if !update_media_caption(&mut local_echo, caption, formatted_caption, mentions) {
990 return Err(InvalidMediaCaptionEdit);
991 }
992
993 let new_dependent_request = DependentQueuedRequestKind::FinishUpload {
994 local_echo: local_echo.clone(),
995 file_upload,
996 thumbnail_info,
997 extra_content,
998 };
999 store
1000 .update_dependent_queued_request(
1001 &self.room_id,
1002 &found.own_transaction_id,
1003 new_dependent_request,
1004 )
1005 .await?;
1006
1007 trace!("caption successfully updated");
1008 return Ok(Some((*local_echo).into()));
1009 }
1010 }
1011
1012 let requests = store.load_send_queue_requests(&self.room_id).await?;
1013 let Some(found) = requests.into_iter().find(|req| req.transaction_id == *txn) else {
1014 return Ok(None);
1016 };
1017
1018 trace!("found the caption to edit as a request");
1019
1020 let QueuedRequestKind::Event { content: serialized_content, .. } = found.kind else {
1021 return Err(InvalidMediaCaptionEdit);
1022 };
1023
1024 let deserialized = serialized_content.deserialize()?;
1025 let AnyMessageLikeEventContent::RoomMessage(mut content) = deserialized else {
1026 return Err(InvalidMediaCaptionEdit);
1027 };
1028
1029 if !update_media_caption(&mut content, caption, formatted_caption, mentions) {
1030 return Err(InvalidMediaCaptionEdit);
1031 }
1032
1033 let any_content: AnyMessageLikeEventContent = content.into();
1034 let new_serialized = SerializableEventContent::new(&any_content.clone())?;
1035
1036 if let Some(being_sent) = guard.being_sent.as_ref()
1038 && being_sent.transaction_id == *txn
1039 {
1040 store
1042 .save_dependent_queued_request(
1043 &self.room_id,
1044 txn,
1045 ChildTransactionId::new(),
1046 MilliSecondsSinceUnixEpoch::now(),
1047 DependentQueuedRequestKind::EditEvent { new_content: new_serialized },
1048 )
1049 .await?;
1050
1051 trace!("media event was being sent, pushed a dependent edit");
1052 return Ok(Some(any_content));
1053 }
1054
1055 store.update_send_queue_request(&self.room_id, txn, new_serialized.into()).await?;
1057
1058 trace!("media event was not being sent, updated local echo");
1059 Ok(Some(any_content))
1060 }
1061}
1062
1063async fn update_media_cache_keys_after_upload(
1066 client: &Client,
1067 file_upload_txn: &OwnedTransactionId,
1068 thumbnail_info: Option<FinishUploadThumbnailInfo>,
1069 sent_media: &SentMediaInfo,
1070) -> Result<(), RoomSendQueueError> {
1071 let from_req = Media::make_local_file_media_request(file_upload_txn);
1073
1074 trace!(from = ?from_req.source, to = ?sent_media.file, "renaming media file key in cache store");
1075 let media_store =
1076 client.media_store().lock().await.map_err(RoomSendQueueStorageError::LockError)?;
1077
1078 media_store
1080 .set_ignore_media_retention_policy(&from_req, IgnoreMediaRetentionPolicy::No)
1081 .await
1082 .map_err(RoomSendQueueStorageError::MediaStoreError)?;
1083
1084 media_store
1085 .replace_media_key(
1086 &from_req,
1087 &MediaRequestParameters { source: sent_media.file.clone(), format: MediaFormat::File },
1088 )
1089 .await
1090 .map_err(RoomSendQueueStorageError::MediaStoreError)?;
1091
1092 if let Some((info, remote_thumbnail_source)) =
1094 thumbnail_info.as_ref().zip(sent_media.thumbnail.clone())
1095 {
1096 let from_request_params = Media::make_local_file_media_request(&info.txn);
1097
1098 if let Some((height, width)) = info.height.zip(info.width) {
1099 trace!(
1100 from = ?from_req.source,
1101 to = ?remote_thumbnail_source,
1102 height = u64::from(height),
1103 width = u64::from(width),
1104 "storing thumbnail as a thumbnail for the uploaded media, and a file in itself, in cache store"
1105 );
1106
1107 match media_store
1112 .get_media_content(&MediaRequestParameters {
1113 source: from_request_params.source.clone(),
1114 format: MediaFormat::File,
1115 })
1116 .await
1117 {
1118 Ok(Some(thumbnail_content)) => {
1119 media_store
1123 .add_media_content(
1124 &MediaRequestParameters {
1125 source: remote_thumbnail_source.clone(),
1126 format: MediaFormat::Thumbnail(MediaThumbnailSettings::new(
1127 width, height,
1128 )),
1129 },
1130 thumbnail_content,
1131 IgnoreMediaRetentionPolicy::No,
1132 )
1133 .await
1134 .map_err(RoomSendQueueStorageError::MediaStoreError)?;
1135 }
1136
1137 Ok(None) => {
1138 warn!(
1139 from = ?from_request_params.source,
1140 "unable to reload thumbnail content from media store: no content found",
1141 );
1142 }
1143
1144 Err(err) => {
1145 error!(
1148 from = ?from_request_params.source,
1149 "unable to reload thumbnail content from media store: {err}"
1150 );
1151 }
1152 }
1153 } else {
1154 trace!(from = ?from_req.source, to = ?remote_thumbnail_source, "only renaming thumbnail key to file in cache store");
1155 }
1156
1157 media_store
1159 .set_ignore_media_retention_policy(&from_request_params, IgnoreMediaRetentionPolicy::No)
1160 .await
1161 .map_err(RoomSendQueueStorageError::MediaStoreError)?;
1162
1163 media_store
1165 .replace_media_key(
1166 &from_request_params,
1167 &MediaRequestParameters {
1168 source: remote_thumbnail_source,
1169 format: MediaFormat::File,
1170 },
1171 )
1172 .await
1173 .map_err(RoomSendQueueStorageError::MediaStoreError)?;
1174 }
1175
1176 Ok(())
1177}