1use std::{
138 collections::{BTreeMap, HashMap},
139 future::IntoFuture,
140 ops::Not,
141 str::FromStr as _,
142 sync::{
143 Arc, RwLock,
144 atomic::{AtomicBool, Ordering},
145 },
146 time::Duration,
147};
148
149use eyeball::SharedObservable;
150#[cfg(feature = "e2e-encryption")]
151use matrix_sdk_base::crypto::{OlmError, SessionRecipientCollectionError};
152#[cfg(feature = "unstable-msc4274")]
153use matrix_sdk_base::store::FinishGalleryItemInfo;
154use matrix_sdk_base::{
155 RoomState, StoreError,
156 cross_process_lock::CrossProcessLockError,
157 deserialized_responses::{EncryptionInfo, TimelineEvent},
158 event_cache::store::EventCacheStoreError,
159 media::{MediaRequestParameters, store::MediaStoreError},
160 store::{
161 ChildTransactionId, DependentQueuedRequest, DependentQueuedRequestKind, DynStateStore,
162 FinishUploadThumbnailInfo, QueueWedgeError, QueuedRequest, QueuedRequestKind,
163 SentMediaInfo, SentRequestKey, SerializableEventContent,
164 },
165 task_monitor::BackgroundTaskHandle,
166};
167use matrix_sdk_common::{boxed_into_future, locks::Mutex as SyncMutex};
168use mime::Mime;
169use ruma::{
170 MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, OwnedTransactionId, RoomId,
171 TransactionId,
172 events::{
173 AnyMessageLikeEventContent, Mentions, MessageLikeEventContent as _, TimelineEventType,
174 reaction::ReactionEventContent,
175 relation::Annotation,
176 room::{
177 MediaSource,
178 message::{FormattedBody, RoomMessageEventContent},
179 },
180 },
181 serde::Raw,
182};
183use tokio::sync::{Mutex, Notify, OwnedMutexGuard, broadcast, oneshot};
184use tracing::{debug, error, info, instrument, trace, warn};
185
186use crate::{
187 Client, Media, Room, TransmissionProgress,
188 client::WeakClient,
189 config::RequestConfig,
190 error::RetryKind,
191 room::{WeakRoom, edit::EditedContent},
192};
193
194mod progress;
195mod upload;
196
197pub use progress::AbstractProgress;
198
199const STORE_ERROR_BACKOFF: Duration = Duration::from_millis(250);
202
203pub struct SendQueue {
205 client: Client,
206}
207
208#[cfg(not(tarpaulin_include))]
209impl std::fmt::Debug for SendQueue {
210 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211 f.debug_struct("SendQueue").finish_non_exhaustive()
212 }
213}
214
215impl SendQueue {
216 pub(super) fn new(client: Client) -> Self {
217 Self { client }
218 }
219
220 pub async fn respawn_tasks_for_rooms_with_unsent_requests(&self) {
223 if !self.is_enabled() {
224 return;
225 }
226
227 let room_ids =
228 self.client.state_store().load_rooms_with_unsent_requests().await.unwrap_or_else(
229 |err| {
230 warn!("error when loading rooms with unsent requests: {err}");
231 Vec::new()
232 },
233 );
234
235 for room_id in room_ids {
237 if let Some(room) = self.client.get_room(&room_id) {
238 let _ = self.for_room(room);
239 }
240 }
241 }
242
243 #[inline(always)]
245 fn data(&self) -> &SendQueueData {
246 &self.client.inner.send_queue_data
247 }
248
249 pub(crate) fn for_room(&self, room: Room) -> RoomSendQueue {
252 let data = self.data();
253
254 let mut map = data.rooms.write().unwrap();
255
256 let room_id = room.room_id();
257 if let Some(room_q) = map.get(room_id).cloned() {
258 return room_q;
259 }
260
261 let owned_room_id = room_id.to_owned();
262 let room_q = RoomSendQueue::new(
263 self.is_enabled(),
264 data.global_update_sender.clone(),
265 data.error_sender.clone(),
266 data.is_dropping.clone(),
267 &self.client,
268 owned_room_id.clone(),
269 data.report_media_upload_progress.clone(),
270 );
271
272 map.insert(owned_room_id, room_q.clone());
273
274 room_q
275 }
276
277 pub async fn set_enabled(&self, enabled: bool) {
287 debug!(?enabled, "setting global send queue enablement");
288
289 self.data().globally_enabled.store(enabled, Ordering::SeqCst);
290
291 for room in self.data().rooms.read().unwrap().values() {
293 room.set_enabled(enabled);
294 }
295
296 self.respawn_tasks_for_rooms_with_unsent_requests().await;
299 }
300
301 pub fn is_enabled(&self) -> bool {
304 self.data().globally_enabled.load(Ordering::SeqCst)
305 }
306
307 pub fn enable_upload_progress(&self, enabled: bool) {
309 self.data().report_media_upload_progress.store(enabled, Ordering::SeqCst);
310 }
311
312 pub fn subscribe(&self) -> broadcast::Receiver<SendQueueUpdate> {
317 self.data().global_update_sender.subscribe()
318 }
319
320 pub async fn local_echoes(
322 &self,
323 ) -> Result<BTreeMap<OwnedRoomId, Vec<LocalEcho>>, RoomSendQueueError> {
324 let room_ids =
325 self.client.state_store().load_rooms_with_unsent_requests().await.unwrap_or_else(
326 |err| {
327 warn!("error when loading rooms with unsent requests: {err}");
328 Vec::new()
329 },
330 );
331
332 let mut local_echoes: BTreeMap<OwnedRoomId, Vec<LocalEcho>> = BTreeMap::new();
333
334 for room_id in room_ids {
335 if let Some(room) = self.client.get_room(&room_id) {
336 let queue = self.for_room(room);
337 local_echoes
338 .insert(room_id.to_owned(), queue.inner.queue.local_echoes(&queue).await?);
339 }
340 }
341
342 Ok(local_echoes)
343 }
344
345 pub fn subscribe_errors(&self) -> broadcast::Receiver<SendQueueRoomError> {
348 self.data().error_sender.subscribe()
349 }
350}
351
352#[derive(Clone, Debug)]
355struct QueueThumbnailInfo {
356 finish_upload_thumbnail_info: FinishUploadThumbnailInfo,
358
359 media_request_parameters: MediaRequestParameters,
361
362 content_type: Mime,
364
365 file_size: usize,
367}
368
369#[derive(Clone, Debug)]
371pub struct SendQueueRoomError {
372 pub room_id: OwnedRoomId,
374
375 pub error: Arc<crate::Error>,
377
378 pub is_recoverable: bool,
384}
385
386impl Client {
387 pub fn send_queue(&self) -> SendQueue {
390 SendQueue::new(self.clone())
391 }
392}
393
394pub(super) struct SendQueueData {
395 rooms: RwLock<BTreeMap<OwnedRoomId, RoomSendQueue>>,
397
398 globally_enabled: AtomicBool,
403
404 global_update_sender: broadcast::Sender<SendQueueUpdate>,
408
409 error_sender: broadcast::Sender<SendQueueRoomError>,
411
412 is_dropping: Arc<AtomicBool>,
414
415 report_media_upload_progress: Arc<AtomicBool>,
417}
418
419impl SendQueueData {
420 pub fn new(globally_enabled: bool) -> Self {
422 let (global_update_sender, _) = broadcast::channel(32);
423 let (error_sender, _) = broadcast::channel(32);
424
425 Self {
426 rooms: Default::default(),
427 globally_enabled: AtomicBool::new(globally_enabled),
428 global_update_sender,
429 error_sender,
430 is_dropping: Arc::new(false.into()),
431 report_media_upload_progress: Arc::new(false.into()),
432 }
433 }
434}
435
436impl Drop for SendQueueData {
437 fn drop(&mut self) {
438 debug!("globally dropping the send queue");
441 self.is_dropping.store(true, Ordering::SeqCst);
442
443 let rooms = self.rooms.read().unwrap();
444 for room in rooms.values() {
445 room.inner.notifier.notify_one();
446 }
447 }
448}
449
450impl Room {
451 pub fn send_queue(&self) -> RoomSendQueue {
453 self.client.send_queue().for_room(self.clone())
454 }
455}
456
457#[derive(Clone)]
461pub struct RoomSendQueue {
462 inner: Arc<RoomSendQueueInner>,
463}
464
465#[cfg(not(tarpaulin_include))]
466impl std::fmt::Debug for RoomSendQueue {
467 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
468 f.debug_struct("RoomSendQueue").finish_non_exhaustive()
469 }
470}
471
472impl RoomSendQueue {
473 fn new(
474 globally_enabled: bool,
475 global_update_sender: broadcast::Sender<SendQueueUpdate>,
476 global_error_sender: broadcast::Sender<SendQueueRoomError>,
477 is_dropping: Arc<AtomicBool>,
478 client: &Client,
479 room_id: OwnedRoomId,
480 report_media_upload_progress: Arc<AtomicBool>,
481 ) -> Self {
482 let (update_sender, _) = broadcast::channel(32);
483
484 let queue = QueueStorage::new(WeakClient::from_client(client), room_id.clone());
485 let notifier = Arc::new(Notify::new());
486
487 let weak_room = WeakRoom::new(WeakClient::from_client(client), room_id);
488 let locally_enabled = Arc::new(AtomicBool::new(globally_enabled));
489
490 let task = client.task_monitor().spawn_infinite_task(
491 "send_queue",
492 Self::sending_task(
493 weak_room.clone(),
494 queue.clone(),
495 notifier.clone(),
496 global_update_sender.clone(),
497 update_sender.clone(),
498 locally_enabled.clone(),
499 global_error_sender,
500 is_dropping,
501 report_media_upload_progress,
502 ),
503 );
504
505 Self {
506 inner: Arc::new(RoomSendQueueInner {
507 room: weak_room,
508 global_update_sender,
509 update_sender,
510 _task: task,
511 queue,
512 notifier,
513 locally_enabled,
514 }),
515 }
516 }
517
518 pub async fn send_raw(
533 &self,
534 content: Raw<AnyMessageLikeEventContent>,
535 event_type: String,
536 ) -> Result<SendHandle, RoomSendQueueError> {
537 let Some(room) = self.inner.room.get() else {
538 return Err(RoomSendQueueError::RoomDisappeared);
539 };
540 if room.state() != RoomState::Joined {
541 return Err(RoomSendQueueError::RoomNotJoined);
542 }
543
544 let content = SerializableEventContent::from_raw(content, event_type);
545
546 let created_at = MilliSecondsSinceUnixEpoch::now();
547 let transaction_id = self.inner.queue.push(content.clone().into(), created_at).await?;
548 trace!(%transaction_id, "manager sends a raw event to the background task");
549
550 self.inner.notifier.notify_one();
551
552 let send_handle = SendHandle {
553 room: self.clone(),
554 transaction_id: transaction_id.clone(),
555 media_handles: vec![],
556 created_at,
557 };
558
559 self.send_update(RoomSendQueueUpdate::NewLocalEvent(LocalEcho {
560 transaction_id,
561 content: LocalEchoContent::Event {
562 serialized_event: content,
563 send_handle: send_handle.clone(),
564 send_error: None,
565 },
566 }));
567
568 Ok(send_handle)
569 }
570
571 pub fn send(&self, content: AnyMessageLikeEventContent) -> SendEvent<'_> {
586 SendEvent { queue: self, content, extra_content: None }
587 }
588
589 pub async fn redact(
604 &self,
605 redacts: OwnedEventId,
606 reason: Option<&str>,
607 ) -> Result<SendRedactionHandle, RoomSendQueueError> {
608 let Some(room) = self.inner.room.get() else {
609 return Err(RoomSendQueueError::RoomDisappeared);
610 };
611 if room.state() != RoomState::Joined {
612 return Err(RoomSendQueueError::RoomNotJoined);
613 }
614
615 let request = QueuedRequestKind::Redaction {
616 redacts: redacts.clone(),
617 reason: reason.map(str::to_owned),
618 };
619
620 let created_at = MilliSecondsSinceUnixEpoch::now();
621 let transaction_id = self.inner.queue.push(request, created_at).await?;
622 trace!(%transaction_id, "manager sends a redaction event to the background task");
623
624 self.inner.notifier.notify_one();
625
626 let send_handle =
627 SendRedactionHandle { room: self.clone(), transaction_id: transaction_id.clone() };
628
629 self.send_update(RoomSendQueueUpdate::NewLocalEvent(LocalEcho {
630 transaction_id,
631 content: LocalEchoContent::Redaction {
632 redacts,
633 reason: reason.map(str::to_owned),
634 send_handle: send_handle.clone(),
635 send_error: None,
636 },
637 }));
638
639 Ok(send_handle)
640 }
641
642 pub async fn subscribe(
648 &self,
649 ) -> Result<(Vec<LocalEcho>, broadcast::Receiver<RoomSendQueueUpdate>), RoomSendQueueError>
650 {
651 let local_echoes = self.inner.queue.local_echoes(self).await?;
652
653 Ok((local_echoes, self.inner.update_sender.subscribe()))
654 }
655
656 #[allow(clippy::too_many_arguments)]
662 #[instrument(skip_all, fields(room_id = %room.room_id()))]
663 async fn sending_task(
664 room: WeakRoom,
665 queue: QueueStorage,
666 notifier: Arc<Notify>,
667 global_update_sender: broadcast::Sender<SendQueueUpdate>,
668 update_sender: broadcast::Sender<RoomSendQueueUpdate>,
669 locally_enabled: Arc<AtomicBool>,
670 global_error_sender: broadcast::Sender<SendQueueRoomError>,
671 is_dropping: Arc<AtomicBool>,
672 report_media_upload_progress: Arc<AtomicBool>,
673 ) {
674 trace!("spawned the sending task");
675
676 let room_id = room.room_id();
677
678 loop {
679 if is_dropping.load(Ordering::SeqCst) {
681 trace!("shutting down!");
682 break;
683 }
684
685 let mut new_updates = Vec::new();
688 if let Err(err) = queue.apply_dependent_requests(&mut new_updates).await {
689 warn!("errors when applying dependent requests: {err}");
690 }
691
692 for up in new_updates {
693 send_update(&global_update_sender, &update_sender, room_id, up);
694 }
695
696 if !locally_enabled.load(Ordering::SeqCst) {
697 trace!("not enabled, sleeping");
698 notifier.notified().await;
700 continue;
701 }
702
703 let (queued_request, cancel_upload_rx) = match queue.peek_next_to_send().await {
704 Ok(Some(request)) => request,
705
706 Ok(None) => {
707 trace!("queue is empty or blocked on a wedged request, sleeping");
708 notifier.notified().await;
710 continue;
711 }
712
713 Err(err) => {
714 warn!("error when loading next request to send: {err}");
715 matrix_sdk_common::sleep::sleep(STORE_ERROR_BACKOFF).await;
717 continue;
718 }
719 };
720
721 let txn_id = queued_request.transaction_id.clone();
722 trace!(txn_id = %txn_id, "received a request to send!");
723
724 let Some(room) = room.get() else {
725 if is_dropping.load(Ordering::SeqCst) {
726 break;
727 }
728 error!("the weak room couldn't be upgraded but we're not shutting down?");
729 continue;
730 };
731
732 let (related_txn_id, media_upload_progress_info, http_progress) =
737 if let QueuedRequestKind::MediaUpload {
738 cache_key,
739 thumbnail_source,
740 #[cfg(feature = "unstable-msc4274")]
741 accumulated,
742 related_to,
743 ..
744 } = &queued_request.kind
745 {
746 let (media_upload_progress_info, http_progress) =
749 if report_media_upload_progress.load(Ordering::SeqCst) {
750 let media_upload_progress_info =
751 RoomSendQueue::create_media_upload_progress_info(
752 &queued_request.transaction_id,
753 related_to,
754 cache_key,
755 thumbnail_source.as_ref(),
756 #[cfg(feature = "unstable-msc4274")]
757 accumulated,
758 &room,
759 &queue,
760 )
761 .await;
762
763 let progress = RoomSendQueue::create_media_upload_progress_observable(
764 &media_upload_progress_info,
765 related_to,
766 &update_sender,
767 );
768
769 (Some(media_upload_progress_info), Some(progress))
770 } else {
771 Default::default()
772 };
773
774 (Some(related_to.clone()), media_upload_progress_info, http_progress)
775 } else {
776 Default::default()
777 };
778
779 match Self::handle_request(&room, queued_request, cancel_upload_rx, http_progress).await
780 {
781 Ok((Some(parent_key), encryption_info)) => match queue
782 .mark_as_sent(&txn_id, parent_key.clone())
783 .await
784 {
785 Ok(()) => match parent_key {
786 SentRequestKey::Event { event_id, event, event_type } => {
787 send_update(
788 &global_update_sender,
789 &update_sender,
790 room_id,
791 RoomSendQueueUpdate::SentEvent {
792 transaction_id: txn_id,
793 event_id: event_id.clone(),
794 },
795 );
796
797 if let Ok((room_event_cache, _drop_handles)) = room.event_cache().await
817 {
818 let timeline_event = match Raw::from_json_string(
819 format!(
821 "{{\
822 \"event_id\":\"{event_id}\",\
823 \"origin_server_ts\":{ts},\
824 \"sender\":\"{sender}\",\
825 \"type\":\"{type}\",\
826 \"content\":{content}\
827 }}",
828 event_id = event_id,
829 ts = MilliSecondsSinceUnixEpoch::now().get(),
830 sender = room.client().user_id().expect("Client must be logged-in"),
831 type = event_type,
832 content = event.into_json(),
833 ),
834 ) {
835 Ok(event) => match encryption_info {
836 #[cfg(feature = "e2e-encryption")]
837 Some(encryption_info) => {
838 use matrix_sdk_base::deserialized_responses::DecryptedRoomEvent;
839 let decrypted_event = DecryptedRoomEvent {
840 event: event.cast_unchecked(),
841 encryption_info: Arc::new(encryption_info),
842 unsigned_encryption_info: None,
843 };
844 Some(TimelineEvent::from_decrypted(
845 decrypted_event,
846 None,
847 ))
848 }
849 _ => Some(TimelineEvent::from_plaintext(event)),
850 },
851 Err(err) => {
852 error!(
853 ?err,
854 "Failed to build the (sync) event before the saving in the Event Cache"
855 );
856 None
857 }
858 };
859
860 if let Some(timeline_event) = timeline_event
863 && let Err(err) = room_event_cache
864 .insert_sent_event_from_send_queue(timeline_event)
865 .await
866 {
867 error!(
868 ?err,
869 "Failed to save the sent event in the Event Cache"
870 );
871 }
872 } else {
873 info!(
874 "Cannot insert the sent event in the Event Cache because \
875 either the room no longer exists, or the Room Event Cache cannot be retrieved"
876 );
877 }
878 }
879
880 SentRequestKey::Media(sent_media_info) => {
881 let index =
884 media_upload_progress_info.as_ref().map_or(0, |info| info.index);
885 let progress = media_upload_progress_info
886 .as_ref()
887 .map(|info| {
888 AbstractProgress { current: info.bytes, total: info.bytes }
889 + info.offsets
890 })
891 .unwrap_or(AbstractProgress { current: 1, total: 1 });
892
893 let _ = update_sender.send(RoomSendQueueUpdate::MediaUpload {
896 related_to: related_txn_id.as_ref().unwrap_or(&txn_id).clone(),
897 file: Some(sent_media_info.file),
898 index,
899 progress,
900 });
901 }
902
903 SentRequestKey::Redaction { event_id, redacts, reason } => {
904 send_update(
905 &global_update_sender,
906 &update_sender,
907 room_id,
908 RoomSendQueueUpdate::SentEvent {
909 transaction_id: txn_id,
910 event_id: event_id.clone(),
911 },
912 );
913
914 if let Ok((room_event_cache, _drop_handles)) = room.event_cache().await
919 {
920 let content_field_redacts = room.version().is_some_and(|id| {
921 id.rules()
922 .is_some_and(|rules| rules.redaction.content_field_redacts)
923 });
924 let redacts = if content_field_redacts.not() {
925 format!("\"redacts\":\"{redacts}\",")
926 } else {
927 "".to_owned()
928 };
929 let reason = reason.map_or_else(
930 || "".to_owned(),
931 |r| format!("\"reason\": \"{r}\""),
932 );
933 let content = if content_field_redacts {
934 format!("\"redacts\":\"{redacts}\",{reason}")
935 } else {
936 reason
937 };
938
939 let timeline_event = match Raw::from_json_string(
940 format!(
942 "{{\
943 {redacts}\
944 \"event_id\":\"{event_id}\",\
945 \"origin_server_ts\":{ts},\
946 \"sender\":\"{sender}\",\
947 \"type\":\"{type}\",\
948 \"content\":{{{content}}}\
949 }}",
950 redacts = redacts,
951 event_id = event_id,
952 ts = MilliSecondsSinceUnixEpoch::now().get(),
953 sender = room.client().user_id().expect("Client must be logged-in"),
954 type = TimelineEventType::RoomRedaction,
955 content = content
956 ),
957 ) {
958 Ok(event) => Some(TimelineEvent::from_plaintext(event)),
959 Err(err) => {
960 error!(
961 ?err,
962 "Failed to build the (sync) redaction event before the saving in the Event Cache"
963 );
964 None
965 }
966 };
967
968 if let Some(timeline_event) = timeline_event
971 && let Err(err) = room_event_cache
972 .insert_sent_event_from_send_queue(timeline_event)
973 .await
974 {
975 error!(
976 ?err,
977 "Failed to save the sent redaction event in the Event Cache"
978 );
979 }
980 } else {
981 info!(
982 "Cannot insert the sent redaction event in the Event Cache because \
983 either the room no longer exists, or the Room Event Cache cannot be retrieved"
984 );
985 }
986 }
987 },
988
989 Err(err) => {
990 warn!("unable to mark queued request as sent: {err}");
991 }
992 },
993
994 Ok((None, _)) => {
995 debug!("Request has been aborted while running, continuing.");
996 }
997
998 Err(err) => {
999 let is_recoverable = match err {
1000 crate::Error::Http(ref http_err) => {
1001 matches!(
1003 http_err.retry_kind(),
1004 RetryKind::Transient { .. } | RetryKind::NetworkFailure
1005 )
1006 }
1007
1008 crate::Error::ConcurrentRequestFailed => true,
1013
1014 _ => false,
1016 };
1017
1018 locally_enabled.store(false, Ordering::SeqCst);
1020
1021 if is_recoverable {
1022 warn!(txn_id = %txn_id, error = ?err, "Recoverable error when sending request: {err}, disabling send queue");
1023
1024 queue.mark_as_not_being_sent(&txn_id).await;
1027
1028 } else {
1034 warn!(txn_id = %txn_id, error = ?err, "Unrecoverable error when sending request: {err}");
1035
1036 if let Err(storage_error) =
1040 queue.mark_as_wedged(&txn_id, QueueWedgeError::from(&err)).await
1041 {
1042 warn!("unable to mark request as wedged: {storage_error}");
1043 }
1044 }
1045
1046 let error = Arc::new(err);
1047
1048 let _ = global_error_sender.send(SendQueueRoomError {
1049 room_id: room_id.to_owned(),
1050 error: error.clone(),
1051 is_recoverable,
1052 });
1053
1054 send_update(
1055 &global_update_sender,
1056 &update_sender,
1057 room_id,
1058 RoomSendQueueUpdate::SendError {
1059 transaction_id: related_txn_id.unwrap_or(txn_id),
1060 error,
1061 is_recoverable,
1062 },
1063 );
1064 }
1065 }
1066 }
1067
1068 info!("exited sending task");
1069 }
1070
1071 async fn handle_request(
1075 room: &Room,
1076 request: QueuedRequest,
1077 cancel_upload_rx: Option<oneshot::Receiver<()>>,
1078 progress: Option<SharedObservable<TransmissionProgress>>,
1079 ) -> Result<(Option<SentRequestKey>, Option<EncryptionInfo>), crate::Error> {
1080 match request.kind {
1081 QueuedRequestKind::Event { content } => {
1082 let (event, event_type) = content.into_raw();
1083
1084 let result = room
1085 .send_raw(&event_type, &event)
1086 .with_transaction_id(&request.transaction_id)
1087 .with_request_config(RequestConfig::short_retry())
1088 .await?;
1089
1090 trace!(txn_id = %request.transaction_id, event_id = %result.response.event_id, "event successfully sent");
1091
1092 Ok((
1093 Some(SentRequestKey::Event {
1094 event_id: result.response.event_id,
1095 event,
1096 event_type,
1097 }),
1098 result.encryption_info,
1099 ))
1100 }
1101
1102 QueuedRequestKind::MediaUpload {
1103 content_type,
1104 cache_key,
1105 thumbnail_source,
1106 related_to: relates_to,
1107 #[cfg(feature = "unstable-msc4274")]
1108 accumulated,
1109 } => {
1110 trace!(%relates_to, "uploading media related to event");
1111
1112 let fut = async move {
1113 let data = room
1114 .client()
1115 .media_store()
1116 .lock()
1117 .await?
1118 .get_media_content(&cache_key)
1119 .await?
1120 .ok_or(crate::Error::SendQueueWedgeError(Box::new(
1121 QueueWedgeError::MissingMediaContent,
1122 )))?;
1123
1124 let mime = Mime::from_str(&content_type).map_err(|_| {
1125 crate::Error::SendQueueWedgeError(Box::new(
1126 QueueWedgeError::InvalidMimeType { mime_type: content_type.clone() },
1127 ))
1128 })?;
1129
1130 #[cfg(feature = "e2e-encryption")]
1131 let media_source = if room.latest_encryption_state().await?.is_encrypted() {
1132 trace!("upload will be encrypted (encrypted room)");
1133
1134 let mut cursor = std::io::Cursor::new(data);
1135 let mut req = room
1136 .client
1137 .upload_encrypted_file(&mut cursor)
1138 .with_request_config(RequestConfig::short_retry());
1139 if let Some(progress) = progress {
1140 req = req.with_send_progress_observable(progress);
1141 }
1142 let encrypted_file = req.await?;
1143
1144 MediaSource::Encrypted(Box::new(encrypted_file))
1145 } else {
1146 trace!("upload will be in clear text (room without encryption)");
1147
1148 let request_config = RequestConfig::short_retry()
1149 .timeout(Media::reasonable_upload_timeout(&data));
1150 let mut req =
1151 room.client().media().upload(&mime, data, Some(request_config));
1152 if let Some(progress) = progress {
1153 req = req.with_send_progress_observable(progress);
1154 }
1155 let res = req.await?;
1156
1157 MediaSource::Plain(res.content_uri)
1158 };
1159
1160 #[cfg(not(feature = "e2e-encryption"))]
1161 let media_source = {
1162 let request_config = RequestConfig::short_retry()
1163 .timeout(Media::reasonable_upload_timeout(&data));
1164 let mut req =
1165 room.client().media().upload(&mime, data, Some(request_config));
1166 if let Some(progress) = progress {
1167 req = req.with_send_progress_observable(progress);
1168 }
1169 let res = req.await?;
1170 MediaSource::Plain(res.content_uri)
1171 };
1172
1173 let uri = match &media_source {
1174 MediaSource::Plain(uri) => uri,
1175 MediaSource::Encrypted(encrypted_file) => &encrypted_file.url,
1176 };
1177 trace!(%relates_to, mxc_uri = %uri, "media successfully uploaded");
1178
1179 Ok((
1180 Some(SentRequestKey::Media(SentMediaInfo {
1181 file: media_source,
1182 thumbnail: thumbnail_source,
1183 #[cfg(feature = "unstable-msc4274")]
1184 accumulated,
1185 })),
1186 None,
1187 ))
1188 };
1189
1190 let wait_for_cancel = async move {
1191 if let Some(rx) = cancel_upload_rx {
1192 rx.await
1193 } else {
1194 std::future::pending().await
1195 }
1196 };
1197
1198 tokio::select! {
1199 biased;
1200
1201 _ = wait_for_cancel => {
1202 Ok((None, None))
1203 }
1204
1205 res = fut => {
1206 res
1207 }
1208 }
1209 }
1210
1211 QueuedRequestKind::Redaction { redacts, reason } => {
1212 let result = room
1213 .redact(&redacts, reason.as_deref(), Some(request.transaction_id.clone()))
1214 .await?;
1215
1216 trace!(txn_id = %request.transaction_id, event_id = %result.event_id, "redaction successfully sent");
1217
1218 Ok((
1219 Some(SentRequestKey::Redaction { event_id: result.event_id, redacts, reason }),
1220 None,
1221 ))
1222 }
1223 }
1224 }
1225
1226 pub fn is_enabled(&self) -> bool {
1228 self.inner.locally_enabled.load(Ordering::SeqCst)
1229 }
1230
1231 pub fn set_enabled(&self, enabled: bool) {
1233 self.inner.locally_enabled.store(enabled, Ordering::SeqCst);
1234
1235 if enabled {
1238 self.inner.notifier.notify_one();
1239 }
1240 }
1241
1242 fn send_update(&self, update: RoomSendQueueUpdate) {
1246 let _ = self.inner.update_sender.send(update.clone());
1247 let _ = self
1248 .inner
1249 .global_update_sender
1250 .send(SendQueueUpdate { room_id: self.inner.room.room_id().to_owned(), update });
1251 }
1252}
1253
1254fn send_update(
1255 global_update_sender: &broadcast::Sender<SendQueueUpdate>,
1256 update_sender: &broadcast::Sender<RoomSendQueueUpdate>,
1257 room_id: &RoomId,
1258 update: RoomSendQueueUpdate,
1259) {
1260 let _ = update_sender.send(update.clone());
1261 let _ = global_update_sender.send(SendQueueUpdate { room_id: room_id.to_owned(), update });
1262}
1263
1264impl From<&crate::Error> for QueueWedgeError {
1265 fn from(value: &crate::Error) -> Self {
1266 match value {
1267 #[cfg(feature = "e2e-encryption")]
1268 crate::Error::OlmError(error) => match &**error {
1269 OlmError::SessionRecipientCollectionError(error) => match error {
1270 SessionRecipientCollectionError::VerifiedUserHasUnsignedDevice(user_map) => {
1271 QueueWedgeError::InsecureDevices { user_device_map: user_map.clone() }
1272 }
1273
1274 SessionRecipientCollectionError::VerifiedUserChangedIdentity(users) => {
1275 QueueWedgeError::IdentityViolations { users: users.clone() }
1276 }
1277
1278 SessionRecipientCollectionError::CrossSigningNotSetup
1279 | SessionRecipientCollectionError::SendingFromUnverifiedDevice => {
1280 QueueWedgeError::CrossVerificationRequired
1281 }
1282 },
1283 _ => QueueWedgeError::GenericApiError { msg: value.to_string() },
1284 },
1285
1286 crate::Error::SendQueueWedgeError(error) => *error.clone(),
1288
1289 _ => QueueWedgeError::GenericApiError { msg: value.to_string() },
1290 }
1291 }
1292}
1293
1294struct RoomSendQueueInner {
1295 room: WeakRoom,
1297
1298 global_update_sender: broadcast::Sender<SendQueueUpdate>,
1302
1303 update_sender: broadcast::Sender<RoomSendQueueUpdate>,
1309
1310 queue: QueueStorage,
1317
1318 notifier: Arc<Notify>,
1321
1322 locally_enabled: Arc<AtomicBool>,
1325
1326 _task: BackgroundTaskHandle,
1329}
1330
1331struct BeingSentInfo {
1333 transaction_id: OwnedTransactionId,
1335
1336 cancel_upload: Option<oneshot::Sender<()>>,
1339}
1340
1341impl BeingSentInfo {
1342 fn cancel_upload(self) -> bool {
1347 if let Some(cancel_upload) = self.cancel_upload {
1348 let _ = cancel_upload.send(());
1349 true
1350 } else {
1351 false
1352 }
1353 }
1354}
1355
1356#[derive(Clone)]
1359struct StoreLock {
1360 client: WeakClient,
1362
1363 being_sent: Arc<Mutex<Option<BeingSentInfo>>>,
1368}
1369
1370impl StoreLock {
1371 async fn lock(&self) -> StoreLockGuard {
1373 StoreLockGuard {
1374 client: self.client.clone(),
1375 being_sent: self.being_sent.clone().lock_owned().await,
1376 }
1377 }
1378}
1379
1380struct StoreLockGuard {
1383 client: WeakClient,
1385
1386 being_sent: OwnedMutexGuard<Option<BeingSentInfo>>,
1389}
1390
1391impl StoreLockGuard {
1392 fn client(&self) -> Result<Client, RoomSendQueueStorageError> {
1394 self.client.get().ok_or(RoomSendQueueStorageError::ClientShuttingDown)
1395 }
1396}
1397
1398#[derive(Clone)]
1399struct QueueStorage {
1400 store: StoreLock,
1403
1404 room_id: OwnedRoomId,
1406
1407 thumbnail_file_sizes: Arc<SyncMutex<HashMap<OwnedTransactionId, Vec<Option<usize>>>>>,
1420}
1421
1422impl QueueStorage {
1423 const LOW_PRIORITY: usize = 0;
1425
1426 const HIGH_PRIORITY: usize = 10;
1428
1429 fn new(client: WeakClient, room: OwnedRoomId) -> Self {
1431 Self {
1432 room_id: room,
1433 store: StoreLock { client, being_sent: Default::default() },
1434 thumbnail_file_sizes: Default::default(),
1435 }
1436 }
1437
1438 async fn push(
1442 &self,
1443 request: QueuedRequestKind,
1444 created_at: MilliSecondsSinceUnixEpoch,
1445 ) -> Result<OwnedTransactionId, RoomSendQueueStorageError> {
1446 let transaction_id = TransactionId::new();
1447
1448 self.store
1449 .lock()
1450 .await
1451 .client()?
1452 .state_store()
1453 .save_send_queue_request(
1454 &self.room_id,
1455 transaction_id.clone(),
1456 created_at,
1457 request,
1458 Self::LOW_PRIORITY,
1459 )
1460 .await?;
1461
1462 Ok(transaction_id)
1463 }
1464
1465 async fn peek_next_to_send(
1470 &self,
1471 ) -> Result<Option<(QueuedRequest, Option<oneshot::Receiver<()>>)>, RoomSendQueueStorageError>
1472 {
1473 let mut guard = self.store.lock().await;
1474 let queued_requests =
1475 guard.client()?.state_store().load_send_queue_requests(&self.room_id).await?;
1476
1477 if let Some(request) = queued_requests.first().filter(|queued| !queued.is_wedged()) {
1483 let (cancel_upload_tx, cancel_upload_rx) =
1484 if matches!(request.kind, QueuedRequestKind::MediaUpload { .. }) {
1485 let (tx, rx) = oneshot::channel();
1486 (Some(tx), Some(rx))
1487 } else {
1488 Default::default()
1489 };
1490
1491 let prev = guard.being_sent.replace(BeingSentInfo {
1492 transaction_id: request.transaction_id.clone(),
1493 cancel_upload: cancel_upload_tx,
1494 });
1495
1496 if let Some(prev) = prev {
1497 error!(
1498 prev_txn = ?prev.transaction_id,
1499 "a previous request was still active while picking a new one"
1500 );
1501 }
1502
1503 Ok(Some((request.clone(), cancel_upload_rx)))
1504 } else {
1505 Ok(None)
1506 }
1507 }
1508
1509 async fn mark_as_not_being_sent(&self, transaction_id: &TransactionId) {
1513 let was_being_sent = self.store.lock().await.being_sent.take();
1514
1515 let prev_txn = was_being_sent.as_ref().map(|info| info.transaction_id.as_ref());
1516 if prev_txn != Some(transaction_id) {
1517 error!(prev_txn = ?prev_txn, "previous active request didn't match that we expect (after transient error)");
1518 }
1519 }
1520
1521 async fn mark_as_wedged(
1525 &self,
1526 transaction_id: &TransactionId,
1527 reason: QueueWedgeError,
1528 ) -> Result<(), RoomSendQueueStorageError> {
1529 let mut guard = self.store.lock().await;
1531 let was_being_sent = guard.being_sent.take();
1532
1533 let prev_txn = was_being_sent.as_ref().map(|info| info.transaction_id.as_ref());
1534 if prev_txn != Some(transaction_id) {
1535 error!(
1536 ?prev_txn,
1537 "previous active request didn't match that we expect (after permanent error)",
1538 );
1539 }
1540
1541 Ok(guard
1542 .client()?
1543 .state_store()
1544 .update_send_queue_request_status(&self.room_id, transaction_id, Some(reason))
1545 .await?)
1546 }
1547
1548 async fn mark_as_unwedged(
1551 &self,
1552 transaction_id: &TransactionId,
1553 ) -> Result<(), RoomSendQueueStorageError> {
1554 Ok(self
1555 .store
1556 .lock()
1557 .await
1558 .client()?
1559 .state_store()
1560 .update_send_queue_request_status(&self.room_id, transaction_id, None)
1561 .await?)
1562 }
1563
1564 async fn mark_as_sent(
1567 &self,
1568 transaction_id: &TransactionId,
1569 parent_key: SentRequestKey,
1570 ) -> Result<(), RoomSendQueueStorageError> {
1571 let mut guard = self.store.lock().await;
1573 let was_being_sent = guard.being_sent.take();
1574
1575 let prev_txn = was_being_sent.as_ref().map(|info| info.transaction_id.as_ref());
1576 if prev_txn != Some(transaction_id) {
1577 error!(
1578 ?prev_txn,
1579 "previous active request didn't match that we expect (after successful send)",
1580 );
1581 }
1582
1583 let client = guard.client()?;
1584 let store = client.state_store();
1585
1586 store
1588 .mark_dependent_queued_requests_as_ready(&self.room_id, transaction_id, parent_key)
1589 .await?;
1590
1591 let removed = store.remove_send_queue_request(&self.room_id, transaction_id).await?;
1592
1593 if !removed {
1594 warn!(txn_id = %transaction_id, "request marked as sent was missing from storage");
1595 }
1596
1597 self.thumbnail_file_sizes.lock().remove(transaction_id);
1598
1599 Ok(())
1600 }
1601
1602 async fn cancel_event(
1609 &self,
1610 transaction_id: &TransactionId,
1611 reason: Option<String>,
1612 ) -> Result<bool, RoomSendQueueStorageError> {
1613 let guard = self.store.lock().await;
1614
1615 if guard.being_sent.as_ref().map(|info| info.transaction_id.as_ref())
1616 == Some(transaction_id)
1617 {
1618 guard
1620 .client()?
1621 .state_store()
1622 .save_dependent_queued_request(
1623 &self.room_id,
1624 transaction_id,
1625 ChildTransactionId::new(),
1626 MilliSecondsSinceUnixEpoch::now(),
1627 DependentQueuedRequestKind::RedactEventWithReason { reason },
1628 )
1629 .await?;
1630
1631 return Ok(true);
1632 }
1633
1634 let removed = guard
1635 .client()?
1636 .state_store()
1637 .remove_send_queue_request(&self.room_id, transaction_id)
1638 .await?;
1639
1640 self.thumbnail_file_sizes.lock().remove(transaction_id);
1641
1642 Ok(removed)
1643 }
1644
1645 async fn replace_event(
1652 &self,
1653 transaction_id: &TransactionId,
1654 serializable: SerializableEventContent,
1655 ) -> Result<bool, RoomSendQueueStorageError> {
1656 let guard = self.store.lock().await;
1657
1658 if guard.being_sent.as_ref().map(|info| info.transaction_id.as_ref())
1659 == Some(transaction_id)
1660 {
1661 guard
1663 .client()?
1664 .state_store()
1665 .save_dependent_queued_request(
1666 &self.room_id,
1667 transaction_id,
1668 ChildTransactionId::new(),
1669 MilliSecondsSinceUnixEpoch::now(),
1670 DependentQueuedRequestKind::EditEvent { new_content: serializable },
1671 )
1672 .await?;
1673
1674 return Ok(true);
1675 }
1676
1677 let edited = guard
1678 .client()?
1679 .state_store()
1680 .update_send_queue_request(&self.room_id, transaction_id, serializable.into())
1681 .await?;
1682
1683 Ok(edited)
1684 }
1685
1686 #[allow(clippy::too_many_arguments)]
1690 async fn push_media(
1691 &self,
1692 event: RoomMessageEventContent,
1693 content_type: Mime,
1694 send_event_txn: OwnedTransactionId,
1695 created_at: MilliSecondsSinceUnixEpoch,
1696 upload_file_txn: OwnedTransactionId,
1697 file_media_request: MediaRequestParameters,
1698 thumbnail: Option<QueueThumbnailInfo>,
1699 extra_content: Option<serde_json::Map<String, serde_json::Value>>,
1700 ) -> Result<(), RoomSendQueueStorageError> {
1701 let guard = self.store.lock().await;
1702 let client = guard.client()?;
1703 let store = client.state_store();
1704
1705 let thumbnail_file_sizes = vec![thumbnail.as_ref().map(|t| t.file_size)];
1707
1708 let thumbnail_info = self
1709 .push_thumbnail_and_media_uploads(
1710 store,
1711 &content_type,
1712 send_event_txn.clone(),
1713 created_at,
1714 upload_file_txn.clone(),
1715 file_media_request,
1716 thumbnail,
1717 )
1718 .await?;
1719
1720 store
1722 .save_dependent_queued_request(
1723 &self.room_id,
1724 &upload_file_txn,
1725 send_event_txn.clone().into(),
1726 created_at,
1727 DependentQueuedRequestKind::FinishUpload {
1728 local_echo: Box::new(event),
1729 file_upload: upload_file_txn.clone(),
1730 thumbnail_info,
1731 extra_content,
1732 },
1733 )
1734 .await?;
1735
1736 self.thumbnail_file_sizes.lock().insert(send_event_txn, thumbnail_file_sizes);
1737
1738 Ok(())
1739 }
1740
1741 #[cfg(feature = "unstable-msc4274")]
1745 #[allow(clippy::too_many_arguments)]
1746 async fn push_gallery(
1747 &self,
1748 event: RoomMessageEventContent,
1749 send_event_txn: OwnedTransactionId,
1750 created_at: MilliSecondsSinceUnixEpoch,
1751 item_queue_infos: Vec<GalleryItemQueueInfo>,
1752 ) -> Result<(), RoomSendQueueStorageError> {
1753 let guard = self.store.lock().await;
1754 let client = guard.client()?;
1755 let store = client.state_store();
1756
1757 let mut finish_item_infos = Vec::with_capacity(item_queue_infos.len());
1758 let mut thumbnail_file_sizes = Vec::with_capacity(item_queue_infos.len());
1759
1760 let Some((first, rest)) = item_queue_infos.split_first() else {
1761 return Ok(());
1762 };
1763
1764 let GalleryItemQueueInfo { content_type, upload_file_txn, file_media_request, thumbnail } =
1765 first;
1766
1767 let thumbnail_info = self
1768 .push_thumbnail_and_media_uploads(
1769 store,
1770 content_type,
1771 send_event_txn.clone(),
1772 created_at,
1773 upload_file_txn.clone(),
1774 file_media_request.clone(),
1775 thumbnail.clone(),
1776 )
1777 .await?;
1778
1779 finish_item_infos
1780 .push(FinishGalleryItemInfo { file_upload: upload_file_txn.clone(), thumbnail_info });
1781 thumbnail_file_sizes.push(thumbnail.as_ref().map(|t| t.file_size));
1782
1783 let mut last_upload_file_txn = upload_file_txn.clone();
1784
1785 for item_queue_info in rest {
1786 let GalleryItemQueueInfo {
1787 content_type,
1788 upload_file_txn,
1789 file_media_request,
1790 thumbnail,
1791 } = item_queue_info;
1792
1793 let thumbnail_info = if let Some(QueueThumbnailInfo {
1794 finish_upload_thumbnail_info: thumbnail_info,
1795 media_request_parameters: thumbnail_media_request,
1796 content_type: thumbnail_content_type,
1797 ..
1798 }) = thumbnail
1799 {
1800 let upload_thumbnail_txn = thumbnail_info.txn.clone();
1801
1802 store
1805 .save_dependent_queued_request(
1806 &self.room_id,
1807 &last_upload_file_txn,
1808 upload_thumbnail_txn.clone().into(),
1809 created_at,
1810 DependentQueuedRequestKind::UploadFileOrThumbnail {
1811 content_type: thumbnail_content_type.to_string(),
1812 cache_key: thumbnail_media_request.clone(),
1813 related_to: send_event_txn.clone(),
1814 parent_is_thumbnail_upload: false,
1815 },
1816 )
1817 .await?;
1818
1819 last_upload_file_txn = upload_thumbnail_txn;
1820
1821 Some(thumbnail_info)
1822 } else {
1823 None
1824 };
1825
1826 store
1828 .save_dependent_queued_request(
1829 &self.room_id,
1830 &last_upload_file_txn,
1831 upload_file_txn.clone().into(),
1832 created_at,
1833 DependentQueuedRequestKind::UploadFileOrThumbnail {
1834 content_type: content_type.to_string(),
1835 cache_key: file_media_request.clone(),
1836 related_to: send_event_txn.clone(),
1837 parent_is_thumbnail_upload: thumbnail.is_some(),
1838 },
1839 )
1840 .await?;
1841
1842 finish_item_infos.push(FinishGalleryItemInfo {
1843 file_upload: upload_file_txn.clone(),
1844 thumbnail_info: thumbnail_info.cloned(),
1845 });
1846 thumbnail_file_sizes.push(thumbnail.as_ref().map(|t| t.file_size));
1847
1848 last_upload_file_txn = upload_file_txn.clone();
1849 }
1850
1851 store
1854 .save_dependent_queued_request(
1855 &self.room_id,
1856 &last_upload_file_txn,
1857 send_event_txn.clone().into(),
1858 created_at,
1859 DependentQueuedRequestKind::FinishGallery {
1860 local_echo: Box::new(event),
1861 item_infos: finish_item_infos,
1862 },
1863 )
1864 .await?;
1865
1866 self.thumbnail_file_sizes.lock().insert(send_event_txn, thumbnail_file_sizes);
1867
1868 Ok(())
1869 }
1870
1871 #[allow(clippy::too_many_arguments)]
1877 async fn push_thumbnail_and_media_uploads(
1878 &self,
1879 store: &DynStateStore,
1880 content_type: &Mime,
1881 send_event_txn: OwnedTransactionId,
1882 created_at: MilliSecondsSinceUnixEpoch,
1883 upload_file_txn: OwnedTransactionId,
1884 file_media_request: MediaRequestParameters,
1885 thumbnail: Option<QueueThumbnailInfo>,
1886 ) -> Result<Option<FinishUploadThumbnailInfo>, RoomSendQueueStorageError> {
1887 if let Some(QueueThumbnailInfo {
1888 finish_upload_thumbnail_info: thumbnail_info,
1889 media_request_parameters: thumbnail_media_request,
1890 content_type: thumbnail_content_type,
1891 ..
1892 }) = thumbnail
1893 {
1894 let upload_thumbnail_txn = thumbnail_info.txn.clone();
1895
1896 store
1898 .save_send_queue_request(
1899 &self.room_id,
1900 upload_thumbnail_txn.clone(),
1901 created_at,
1902 QueuedRequestKind::MediaUpload {
1903 content_type: thumbnail_content_type.to_string(),
1904 cache_key: thumbnail_media_request,
1905 thumbnail_source: None, related_to: send_event_txn.clone(),
1907 #[cfg(feature = "unstable-msc4274")]
1908 accumulated: vec![],
1909 },
1910 Self::LOW_PRIORITY,
1911 )
1912 .await?;
1913
1914 store
1916 .save_dependent_queued_request(
1917 &self.room_id,
1918 &upload_thumbnail_txn,
1919 upload_file_txn.into(),
1920 created_at,
1921 DependentQueuedRequestKind::UploadFileOrThumbnail {
1922 content_type: content_type.to_string(),
1923 cache_key: file_media_request,
1924 related_to: send_event_txn,
1925 parent_is_thumbnail_upload: true,
1926 },
1927 )
1928 .await?;
1929
1930 Ok(Some(thumbnail_info))
1931 } else {
1932 store
1934 .save_send_queue_request(
1935 &self.room_id,
1936 upload_file_txn,
1937 created_at,
1938 QueuedRequestKind::MediaUpload {
1939 content_type: content_type.to_string(),
1940 cache_key: file_media_request,
1941 thumbnail_source: None,
1942 related_to: send_event_txn,
1943 #[cfg(feature = "unstable-msc4274")]
1944 accumulated: vec![],
1945 },
1946 Self::LOW_PRIORITY,
1947 )
1948 .await?;
1949
1950 Ok(None)
1951 }
1952 }
1953
1954 #[instrument(skip(self))]
1956 async fn react(
1957 &self,
1958 transaction_id: &TransactionId,
1959 key: String,
1960 created_at: MilliSecondsSinceUnixEpoch,
1961 ) -> Result<Option<ChildTransactionId>, RoomSendQueueStorageError> {
1962 let guard = self.store.lock().await;
1963 let client = guard.client()?;
1964 let store = client.state_store();
1965
1966 let requests = store.load_send_queue_requests(&self.room_id).await?;
1967
1968 if !requests.iter().any(|item| item.transaction_id == transaction_id) {
1970 let dependent_requests = store.load_dependent_queued_requests(&self.room_id).await?;
1973 if !dependent_requests
1974 .into_iter()
1975 .filter_map(|item| item.is_own_event().then_some(item.own_transaction_id))
1976 .any(|child_txn| *child_txn == *transaction_id)
1977 {
1978 return Ok(None);
1980 }
1981 }
1982
1983 let reaction_txn_id = ChildTransactionId::new();
1985 store
1986 .save_dependent_queued_request(
1987 &self.room_id,
1988 transaction_id,
1989 reaction_txn_id.clone(),
1990 created_at,
1991 DependentQueuedRequestKind::ReactEvent { key },
1992 )
1993 .await?;
1994
1995 Ok(Some(reaction_txn_id))
1996 }
1997
1998 async fn local_echoes(
2001 &self,
2002 room: &RoomSendQueue,
2003 ) -> Result<Vec<LocalEcho>, RoomSendQueueStorageError> {
2004 let guard = self.store.lock().await;
2005 let client = guard.client()?;
2006 let store = client.state_store();
2007
2008 let queued_requests = store.load_send_queue_requests(&self.room_id).await?;
2009
2010 let mut media_upload_errors: HashMap<OwnedTransactionId, QueueWedgeError> = queued_requests
2014 .iter()
2015 .filter_map(|queued| match queued.kind {
2016 QueuedRequestKind::MediaUpload { .. } => {
2017 queued.error.clone().map(|error| (queued.transaction_id.clone(), error))
2018 }
2019 _ => None,
2020 })
2021 .collect();
2022
2023 let local_requests = queued_requests.into_iter().filter_map(|queued| {
2024 Some(LocalEcho {
2025 transaction_id: queued.transaction_id.clone(),
2026 content: match queued.kind {
2027 QueuedRequestKind::Event { content } => LocalEchoContent::Event {
2028 serialized_event: content,
2029 send_handle: SendHandle {
2030 room: room.clone(),
2031 transaction_id: queued.transaction_id,
2032 media_handles: vec![],
2033 created_at: queued.created_at,
2034 },
2035 send_error: queued.error,
2036 },
2037
2038 QueuedRequestKind::MediaUpload { .. } => {
2039 return None;
2042 }
2043
2044 QueuedRequestKind::Redaction { redacts, reason } => {
2045 LocalEchoContent::Redaction {
2046 redacts,
2047 reason,
2048 send_handle: SendRedactionHandle {
2049 room: room.clone(),
2050 transaction_id: queued.transaction_id,
2051 },
2052 send_error: queued.error,
2053 }
2054 }
2055 },
2056 })
2057 });
2058
2059 let dependent_requests = store.load_dependent_queued_requests(&self.room_id).await?;
2060
2061 let reactions_and_medias =
2062 dependent_requests.into_iter().filter_map(|dep| match dep.kind {
2063 DependentQueuedRequestKind::EditEvent { .. }
2064 | DependentQueuedRequestKind::RedactEvent
2065 | DependentQueuedRequestKind::RedactEventWithReason { .. } => {
2066 None
2068 }
2069
2070 DependentQueuedRequestKind::ReactEvent { key } => Some(LocalEcho {
2071 transaction_id: dep.own_transaction_id.clone().into(),
2072 content: LocalEchoContent::React {
2073 key,
2074 send_handle: SendReactionHandle {
2075 room: room.clone(),
2076 transaction_id: dep.own_transaction_id,
2077 },
2078 applies_to: dep.parent_transaction_id,
2079 },
2080 }),
2081
2082 DependentQueuedRequestKind::UploadFileOrThumbnail { .. } => {
2083 None
2085 }
2086
2087 DependentQueuedRequestKind::FinishUpload {
2088 local_echo,
2089 file_upload,
2090 thumbnail_info,
2091 extra_content,
2092 } => {
2093 let upload_thumbnail_txn = thumbnail_info.map(|info| info.txn);
2094
2095 let send_error = media_upload_errors.remove(&file_upload).or_else(|| {
2097 upload_thumbnail_txn
2098 .as_ref()
2099 .and_then(|txn| media_upload_errors.remove(&**txn))
2100 });
2101
2102 Some(LocalEcho {
2104 transaction_id: dep.own_transaction_id.clone().into(),
2105 content: LocalEchoContent::Event {
2106 serialized_event: upload::merge_extra_content(
2107 SerializableEventContent::new(&(*local_echo).into()).ok()?,
2108 extra_content,
2109 )
2110 .ok()?,
2111 send_handle: SendHandle {
2112 room: room.clone(),
2113 transaction_id: dep.own_transaction_id.into(),
2114 media_handles: vec![MediaHandles {
2115 upload_thumbnail_txn,
2116 upload_file_txn: file_upload,
2117 }],
2118 created_at: dep.created_at,
2119 },
2120 send_error,
2121 },
2122 })
2123 }
2124
2125 #[cfg(feature = "unstable-msc4274")]
2126 DependentQueuedRequestKind::FinishGallery { local_echo, item_infos } => {
2127 self.create_gallery_local_echo(
2129 dep.own_transaction_id,
2130 room,
2131 dep.created_at,
2132 local_echo,
2133 item_infos,
2134 &mut media_upload_errors,
2135 )
2136 }
2137 });
2138
2139 Ok(local_requests.chain(reactions_and_medias).collect())
2140 }
2141
2142 #[cfg(feature = "unstable-msc4274")]
2144 fn create_gallery_local_echo(
2145 &self,
2146 transaction_id: ChildTransactionId,
2147 room: &RoomSendQueue,
2148 created_at: MilliSecondsSinceUnixEpoch,
2149 local_echo: Box<RoomMessageEventContent>,
2150 item_infos: Vec<FinishGalleryItemInfo>,
2151 media_upload_errors: &mut HashMap<OwnedTransactionId, QueueWedgeError>,
2152 ) -> Option<LocalEcho> {
2153 let send_error = item_infos.iter().find_map(|i| {
2155 media_upload_errors.remove(&i.file_upload).or_else(|| {
2156 i.thumbnail_info.as_ref().and_then(|info| media_upload_errors.remove(&*info.txn))
2157 })
2158 });
2159
2160 Some(LocalEcho {
2161 transaction_id: transaction_id.clone().into(),
2162 content: LocalEchoContent::Event {
2163 serialized_event: SerializableEventContent::new(&(*local_echo).into()).ok()?,
2164 send_handle: SendHandle {
2165 room: room.clone(),
2166 transaction_id: transaction_id.into(),
2167 media_handles: item_infos
2168 .into_iter()
2169 .map(|i| MediaHandles {
2170 upload_thumbnail_txn: i.thumbnail_info.map(|info| info.txn),
2171 upload_file_txn: i.file_upload,
2172 })
2173 .collect(),
2174 created_at,
2175 },
2176 send_error,
2177 },
2178 })
2179 }
2180
2181 #[instrument(skip_all)]
2189 async fn try_apply_single_dependent_request(
2190 &self,
2191 client: &Client,
2192 dependent_request: DependentQueuedRequest,
2193 new_updates: &mut Vec<RoomSendQueueUpdate>,
2194 ) -> Result<bool, RoomSendQueueError> {
2195 let store = client.state_store();
2196
2197 let parent_key = dependent_request.parent_key;
2198
2199 match dependent_request.kind {
2200 DependentQueuedRequestKind::EditEvent { new_content } => {
2201 if let Some(parent_key) = parent_key {
2202 let Some(event_id) = parent_key.into_event_id() else {
2203 return Err(RoomSendQueueError::StorageError(
2204 RoomSendQueueStorageError::InvalidParentKey,
2205 ));
2206 };
2207
2208 let room = client
2210 .get_room(&self.room_id)
2211 .ok_or(RoomSendQueueError::RoomDisappeared)?;
2212
2213 let edited_content = match new_content.deserialize() {
2217 Ok(AnyMessageLikeEventContent::RoomMessage(c)) => {
2218 EditedContent::RoomMessage(c.into())
2220 }
2221
2222 Ok(AnyMessageLikeEventContent::UnstablePollStart(c)) => {
2223 let poll_start = c.poll_start().clone();
2224 EditedContent::PollStart {
2225 fallback_text: poll_start.question.text.clone(),
2226 new_content: poll_start,
2227 }
2228 }
2229
2230 Ok(c) => {
2231 warn!("Unsupported edit content type: {:?}", c.event_type());
2232 return Ok(true);
2233 }
2234
2235 Err(err) => {
2236 warn!("Unable to deserialize: {err}");
2237 return Ok(true);
2238 }
2239 };
2240
2241 let edit_event = match room.make_edit_event(&event_id, edited_content).await {
2242 Ok(e) => e,
2243 Err(err) => {
2244 warn!("couldn't create edited event: {err}");
2245 return Ok(true);
2246 }
2247 };
2248
2249 let serializable = SerializableEventContent::from_raw(
2251 Raw::new(&edit_event)
2252 .map_err(RoomSendQueueStorageError::JsonSerialization)?,
2253 edit_event.event_type().to_string(),
2254 );
2255
2256 store
2257 .save_send_queue_request(
2258 &self.room_id,
2259 dependent_request.own_transaction_id.into(),
2260 dependent_request.created_at,
2261 serializable.into(),
2262 Self::HIGH_PRIORITY,
2263 )
2264 .await
2265 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2266 } else {
2267 let edited = store
2269 .update_send_queue_request(
2270 &self.room_id,
2271 &dependent_request.parent_transaction_id,
2272 new_content.into(),
2273 )
2274 .await
2275 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2276
2277 if !edited {
2278 warn!("missing local echo upon dependent edit");
2279 }
2280 }
2281 }
2282
2283 kind @ (DependentQueuedRequestKind::RedactEvent
2284 | DependentQueuedRequestKind::RedactEventWithReason { .. }) => {
2285 let reason = match kind {
2286 DependentQueuedRequestKind::RedactEventWithReason { reason } => reason,
2287 _ => None,
2289 };
2290
2291 if let Some(parent_key) = parent_key {
2292 let Some(event_id) = parent_key.into_event_id() else {
2293 return Err(RoomSendQueueError::StorageError(
2294 RoomSendQueueStorageError::InvalidParentKey,
2295 ));
2296 };
2297
2298 let room = client
2300 .get_room(&self.room_id)
2301 .ok_or(RoomSendQueueError::RoomDisappeared)?;
2302
2303 if let Err(err) = room
2308 .redact(
2309 &event_id,
2310 reason.as_deref(),
2311 Some(dependent_request.own_transaction_id.into()),
2312 )
2313 .await
2314 {
2315 warn!("error when sending a redact for {event_id}: {err}");
2316 return Ok(false);
2317 }
2318 } else {
2319 let removed = store
2322 .remove_send_queue_request(
2323 &self.room_id,
2324 &dependent_request.parent_transaction_id,
2325 )
2326 .await
2327 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2328
2329 if !removed {
2330 warn!("missing local echo upon dependent redact");
2331 }
2332 }
2333 }
2334
2335 DependentQueuedRequestKind::ReactEvent { key } => {
2336 if let Some(parent_key) = parent_key {
2337 let Some(parent_event_id) = parent_key.into_event_id() else {
2338 return Err(RoomSendQueueError::StorageError(
2339 RoomSendQueueStorageError::InvalidParentKey,
2340 ));
2341 };
2342
2343 let react_event =
2345 ReactionEventContent::new(Annotation::new(parent_event_id, key)).into();
2346 let serializable = SerializableEventContent::from_raw(
2347 Raw::new(&react_event)
2348 .map_err(RoomSendQueueStorageError::JsonSerialization)?,
2349 react_event.event_type().to_string(),
2350 );
2351
2352 store
2353 .save_send_queue_request(
2354 &self.room_id,
2355 dependent_request.own_transaction_id.into(),
2356 dependent_request.created_at,
2357 serializable.into(),
2358 Self::HIGH_PRIORITY,
2359 )
2360 .await
2361 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2362 } else {
2363 return Ok(false);
2365 }
2366 }
2367
2368 DependentQueuedRequestKind::UploadFileOrThumbnail {
2369 content_type,
2370 cache_key,
2371 related_to,
2372 parent_is_thumbnail_upload,
2373 } => {
2374 let Some(parent_key) = parent_key else {
2375 return Ok(false);
2377 };
2378 self.handle_dependent_file_or_thumbnail_upload(
2379 client,
2380 dependent_request.own_transaction_id.into(),
2381 parent_key,
2382 content_type,
2383 cache_key,
2384 related_to,
2385 parent_is_thumbnail_upload,
2386 )
2387 .await?;
2388 }
2389
2390 DependentQueuedRequestKind::FinishUpload {
2391 local_echo,
2392 file_upload,
2393 thumbnail_info,
2394 extra_content,
2395 } => {
2396 let Some(parent_key) = parent_key else {
2397 return Ok(false);
2399 };
2400 self.handle_dependent_finish_upload(
2401 client,
2402 dependent_request.own_transaction_id.into(),
2403 parent_key,
2404 *local_echo,
2405 file_upload,
2406 thumbnail_info,
2407 extra_content,
2408 new_updates,
2409 )
2410 .await?;
2411 }
2412
2413 #[cfg(feature = "unstable-msc4274")]
2414 DependentQueuedRequestKind::FinishGallery { local_echo, item_infos } => {
2415 let Some(parent_key) = parent_key else {
2416 return Ok(false);
2418 };
2419 self.handle_dependent_finish_gallery_upload(
2420 client,
2421 dependent_request.own_transaction_id.into(),
2422 parent_key,
2423 *local_echo,
2424 item_infos,
2425 new_updates,
2426 )
2427 .await?;
2428 }
2429 }
2430
2431 Ok(true)
2432 }
2433
2434 #[instrument(skip(self))]
2435 async fn apply_dependent_requests(
2436 &self,
2437 new_updates: &mut Vec<RoomSendQueueUpdate>,
2438 ) -> Result<(), RoomSendQueueError> {
2439 let guard = self.store.lock().await;
2440
2441 let client = guard.client()?;
2442 let store = client.state_store();
2443
2444 let dependent_requests = store
2445 .load_dependent_queued_requests(&self.room_id)
2446 .await
2447 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2448
2449 let num_initial_dependent_requests = dependent_requests.len();
2450 if num_initial_dependent_requests == 0 {
2451 return Ok(());
2453 }
2454
2455 let canonicalized_dependent_requests = canonicalize_dependent_requests(&dependent_requests);
2456
2457 for original in &dependent_requests {
2459 if !canonicalized_dependent_requests
2460 .iter()
2461 .any(|canonical| canonical.own_transaction_id == original.own_transaction_id)
2462 {
2463 store
2464 .remove_dependent_queued_request(&self.room_id, &original.own_transaction_id)
2465 .await
2466 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2467 }
2468 }
2469
2470 let mut num_dependent_requests = canonicalized_dependent_requests.len();
2471
2472 debug!(
2473 num_dependent_requests,
2474 num_initial_dependent_requests, "starting handling of dependent requests"
2475 );
2476
2477 for dependent in canonicalized_dependent_requests {
2478 let dependent_id = dependent.own_transaction_id.clone();
2479
2480 match self.try_apply_single_dependent_request(&client, dependent, new_updates).await {
2481 Ok(should_remove) => {
2482 if should_remove {
2483 store
2485 .remove_dependent_queued_request(&self.room_id, &dependent_id)
2486 .await
2487 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2488
2489 num_dependent_requests -= 1;
2490 }
2491 }
2492
2493 Err(err) => {
2494 warn!("error when applying single dependent request: {err}");
2495 }
2496 }
2497 }
2498
2499 debug!(
2500 leftover_dependent_requests = num_dependent_requests,
2501 "stopped handling dependent request"
2502 );
2503
2504 Ok(())
2505 }
2506
2507 async fn remove_dependent_send_queue_request(
2509 &self,
2510 dependent_event_id: &ChildTransactionId,
2511 ) -> Result<bool, RoomSendQueueStorageError> {
2512 Ok(self
2513 .store
2514 .lock()
2515 .await
2516 .client()?
2517 .state_store()
2518 .remove_dependent_queued_request(&self.room_id, dependent_event_id)
2519 .await?)
2520 }
2521}
2522
2523#[cfg(feature = "unstable-msc4274")]
2524struct GalleryItemQueueInfo {
2526 content_type: Mime,
2527 upload_file_txn: OwnedTransactionId,
2528 file_media_request: MediaRequestParameters,
2529 thumbnail: Option<QueueThumbnailInfo>,
2530}
2531
2532#[derive(Clone, Debug)]
2534pub enum LocalEchoContent {
2535 Event {
2537 serialized_event: SerializableEventContent,
2540 send_handle: SendHandle,
2542 send_error: Option<QueueWedgeError>,
2545 },
2546
2547 React {
2549 key: String,
2551 send_handle: SendReactionHandle,
2553 applies_to: OwnedTransactionId,
2555 },
2556
2557 Redaction {
2559 redacts: OwnedEventId,
2561 reason: Option<String>,
2563 send_handle: SendRedactionHandle,
2565 send_error: Option<QueueWedgeError>,
2568 },
2569}
2570
2571#[derive(Clone, Debug)]
2574pub struct LocalEcho {
2575 pub transaction_id: OwnedTransactionId,
2577 pub content: LocalEchoContent,
2579}
2580
2581#[derive(Clone, Debug)]
2584pub enum RoomSendQueueUpdate {
2585 NewLocalEvent(LocalEcho),
2590
2591 CancelledLocalEvent {
2594 transaction_id: OwnedTransactionId,
2596 },
2597
2598 ReplacedLocalEvent {
2600 transaction_id: OwnedTransactionId,
2602
2603 new_content: SerializableEventContent,
2605 },
2606
2607 SendError {
2612 transaction_id: OwnedTransactionId,
2614 error: Arc<crate::Error>,
2616 is_recoverable: bool,
2622 },
2623
2624 RetryEvent {
2626 transaction_id: OwnedTransactionId,
2628 },
2629
2630 SentEvent {
2633 transaction_id: OwnedTransactionId,
2635 event_id: OwnedEventId,
2637 },
2638
2639 MediaUpload {
2642 related_to: OwnedTransactionId,
2644
2645 file: Option<MediaSource>,
2647
2648 index: u64,
2652
2653 progress: AbstractProgress,
2657 },
2658}
2659
2660#[derive(Clone, Debug)]
2665pub struct SendQueueUpdate {
2666 pub room_id: OwnedRoomId,
2668
2669 pub update: RoomSendQueueUpdate,
2671}
2672
2673#[derive(Debug, thiserror::Error)]
2675pub enum RoomSendQueueError {
2676 #[error("the room isn't in the joined state")]
2678 RoomNotJoined,
2679
2680 #[error("the room is now missing from the client")]
2684 RoomDisappeared,
2685
2686 #[error(transparent)]
2688 StorageError(#[from] RoomSendQueueStorageError),
2689
2690 #[error("the attachment event could not be created")]
2692 FailedToCreateAttachment,
2693
2694 #[cfg(feature = "unstable-msc4274")]
2696 #[error("the gallery contains no items")]
2697 EmptyGallery,
2698
2699 #[cfg(feature = "unstable-msc4274")]
2701 #[error("the gallery event could not be created")]
2702 FailedToCreateGallery,
2703}
2704
2705#[derive(Debug, thiserror::Error)]
2707pub enum RoomSendQueueStorageError {
2708 #[error(transparent)]
2710 StateStoreError(#[from] StoreError),
2711
2712 #[error(transparent)]
2714 EventCacheStoreError(#[from] EventCacheStoreError),
2715
2716 #[error(transparent)]
2718 MediaStoreError(#[from] MediaStoreError),
2719
2720 #[error(transparent)]
2722 LockError(#[from] CrossProcessLockError),
2723
2724 #[error(transparent)]
2726 JsonSerialization(#[from] serde_json::Error),
2727
2728 #[error("a dependent event had an invalid parent key type")]
2731 InvalidParentKey,
2732
2733 #[error("The client is shutting down.")]
2735 ClientShuttingDown,
2736
2737 #[error("This operation is not implemented for media uploads")]
2739 OperationNotImplementedYet,
2740
2741 #[error("Can't edit a media caption when the underlying event isn't a media")]
2743 InvalidMediaCaptionEdit,
2744}
2745
2746#[derive(Clone, Debug)]
2748struct MediaHandles {
2749 upload_thumbnail_txn: Option<OwnedTransactionId>,
2753
2754 upload_file_txn: OwnedTransactionId,
2756}
2757
2758#[allow(missing_debug_implementations)]
2760pub struct SendEvent<'a> {
2761 queue: &'a RoomSendQueue,
2762 content: AnyMessageLikeEventContent,
2763 extra_content: Option<serde_json::Map<String, serde_json::Value>>,
2764}
2765
2766impl<'a> SendEvent<'a> {
2767 pub fn with_extra_content(
2771 mut self,
2772 extra_content: serde_json::Map<String, serde_json::Value>,
2773 ) -> Self {
2774 self.extra_content = Some(extra_content);
2775 self
2776 }
2777}
2778
2779impl<'a> IntoFuture for SendEvent<'a> {
2780 type Output = Result<SendHandle, RoomSendQueueError>;
2781 boxed_into_future!(extra_bounds: 'a);
2782
2783 fn into_future(self) -> Self::IntoFuture {
2784 Box::pin(async move {
2785 let serialized = upload::merge_extra_content(
2786 SerializableEventContent::new(&self.content)
2787 .map_err(RoomSendQueueStorageError::JsonSerialization)?,
2788 self.extra_content,
2789 )?;
2790 let (raw, event_type) = serialized.into_raw();
2791 self.queue.send_raw(raw, event_type).await
2792 })
2793 }
2794}
2795
2796#[derive(Clone, Debug)]
2798pub struct SendHandle {
2799 room: RoomSendQueue,
2801
2802 transaction_id: OwnedTransactionId,
2807
2808 media_handles: Vec<MediaHandles>,
2810
2811 pub created_at: MilliSecondsSinceUnixEpoch,
2813}
2814
2815impl SendHandle {
2816 #[cfg(test)]
2818 pub(crate) fn new(
2819 room: RoomSendQueue,
2820 transaction_id: OwnedTransactionId,
2821 created_at: MilliSecondsSinceUnixEpoch,
2822 ) -> Self {
2823 Self { room, transaction_id, media_handles: vec![], created_at }
2824 }
2825
2826 fn nyi_for_uploads(&self) -> Result<(), RoomSendQueueStorageError> {
2827 if !self.media_handles.is_empty() {
2828 Err(RoomSendQueueStorageError::OperationNotImplementedYet)
2829 } else {
2830 Ok(())
2831 }
2832 }
2833
2834 pub async fn abort(&self) -> Result<bool, RoomSendQueueStorageError> {
2839 self.abort_with_reason(None).await
2840 }
2841
2842 #[instrument(skip(self), fields(room_id = %self.room.inner.room.room_id(), txn_id = %self.transaction_id))]
2853 pub async fn abort_with_reason(
2854 &self,
2855 reason: Option<String>,
2856 ) -> Result<bool, RoomSendQueueStorageError> {
2857 trace!("received an abort request");
2858
2859 let queue = &self.room.inner.queue;
2860
2861 for handles in &self.media_handles {
2862 if queue.abort_upload(&self.transaction_id, handles).await? {
2863 self.room.inner.notifier.notify_one();
2865
2866 self.room.send_update(RoomSendQueueUpdate::CancelledLocalEvent {
2868 transaction_id: self.transaction_id.clone(),
2869 });
2870
2871 return Ok(true);
2872 }
2873
2874 }
2878
2879 if queue.cancel_event(&self.transaction_id, reason).await? {
2880 trace!("successful abort");
2881
2882 self.room.inner.notifier.notify_one();
2884
2885 self.room.send_update(RoomSendQueueUpdate::CancelledLocalEvent {
2887 transaction_id: self.transaction_id.clone(),
2888 });
2889
2890 Ok(true)
2891 } else {
2892 debug!("local echo didn't exist anymore, can't abort");
2893 Ok(false)
2894 }
2895 }
2896
2897 #[instrument(skip(self, new_content), fields(room_id = %self.room.inner.room.room_id(), txn_id = %self.transaction_id))]
2902 pub async fn edit_raw(
2903 &self,
2904 new_content: Raw<AnyMessageLikeEventContent>,
2905 event_type: String,
2906 ) -> Result<bool, RoomSendQueueStorageError> {
2907 trace!("received an edit request");
2908 self.nyi_for_uploads()?;
2909
2910 let serializable = SerializableEventContent::from_raw(new_content, event_type);
2911
2912 if self.room.inner.queue.replace_event(&self.transaction_id, serializable.clone()).await? {
2913 trace!("successful edit");
2914
2915 self.room.inner.notifier.notify_one();
2917
2918 self.room.send_update(RoomSendQueueUpdate::ReplacedLocalEvent {
2920 transaction_id: self.transaction_id.clone(),
2921 new_content: serializable,
2922 });
2923
2924 Ok(true)
2925 } else {
2926 debug!("local echo doesn't exist anymore, can't edit");
2927 Ok(false)
2928 }
2929 }
2930
2931 pub async fn edit(
2936 &self,
2937 new_content: AnyMessageLikeEventContent,
2938 ) -> Result<bool, RoomSendQueueStorageError> {
2939 self.edit_raw(
2940 Raw::new(&new_content).map_err(RoomSendQueueStorageError::JsonSerialization)?,
2941 new_content.event_type().to_string(),
2942 )
2943 .await
2944 }
2945
2946 pub async fn edit_media_caption(
2951 &self,
2952 caption: Option<String>,
2953 formatted_caption: Option<FormattedBody>,
2954 mentions: Option<Mentions>,
2955 ) -> Result<bool, RoomSendQueueStorageError> {
2956 if let Some(new_content) = self
2957 .room
2958 .inner
2959 .queue
2960 .edit_media_caption(&self.transaction_id, caption, formatted_caption, mentions)
2961 .await?
2962 {
2963 trace!("successful edit of media caption");
2964
2965 self.room.inner.notifier.notify_one();
2967
2968 let new_content = SerializableEventContent::new(&new_content)
2969 .map_err(RoomSendQueueStorageError::JsonSerialization)?;
2970
2971 self.room.send_update(RoomSendQueueUpdate::ReplacedLocalEvent {
2973 transaction_id: self.transaction_id.clone(),
2974 new_content,
2975 });
2976
2977 Ok(true)
2978 } else {
2979 debug!("local echo doesn't exist anymore, can't edit media caption");
2980 Ok(false)
2981 }
2982 }
2983
2984 pub async fn unwedge(&self) -> Result<(), RoomSendQueueError> {
2987 let room = &self.room.inner;
2988 room.queue
2989 .mark_as_unwedged(&self.transaction_id)
2990 .await
2991 .map_err(RoomSendQueueError::StorageError)?;
2992
2993 for handles in &self.media_handles {
3001 room.queue
3002 .mark_as_unwedged(&handles.upload_file_txn)
3003 .await
3004 .map_err(RoomSendQueueError::StorageError)?;
3005
3006 if let Some(txn) = &handles.upload_thumbnail_txn {
3007 room.queue.mark_as_unwedged(txn).await.map_err(RoomSendQueueError::StorageError)?;
3008 }
3009 }
3010
3011 room.notifier.notify_one();
3013
3014 self.room.send_update(RoomSendQueueUpdate::RetryEvent {
3015 transaction_id: self.transaction_id.clone(),
3016 });
3017
3018 Ok(())
3019 }
3020
3021 #[instrument(skip(self), fields(room_id = %self.room.inner.room.room_id(), txn_id = %self.transaction_id))]
3026 pub async fn react(
3027 &self,
3028 key: String,
3029 ) -> Result<Option<SendReactionHandle>, RoomSendQueueStorageError> {
3030 trace!("received an intent to react");
3031
3032 let created_at = MilliSecondsSinceUnixEpoch::now();
3033 if let Some(reaction_txn_id) =
3034 self.room.inner.queue.react(&self.transaction_id, key.clone(), created_at).await?
3035 {
3036 trace!("successfully queued react");
3037
3038 self.room.inner.notifier.notify_one();
3040
3041 let send_handle = SendReactionHandle {
3043 room: self.room.clone(),
3044 transaction_id: reaction_txn_id.clone(),
3045 };
3046
3047 self.room.send_update(RoomSendQueueUpdate::NewLocalEvent(LocalEcho {
3048 transaction_id: reaction_txn_id.into(),
3051 content: LocalEchoContent::React {
3052 key,
3053 send_handle: send_handle.clone(),
3054 applies_to: self.transaction_id.clone(),
3055 },
3056 }));
3057
3058 Ok(Some(send_handle))
3059 } else {
3060 debug!("local echo doesn't exist anymore, can't react");
3061 Ok(None)
3062 }
3063 }
3064}
3065
3066#[derive(Clone, Debug)]
3068pub struct SendReactionHandle {
3069 room: RoomSendQueue,
3071 transaction_id: ChildTransactionId,
3073}
3074
3075impl SendReactionHandle {
3076 #[cfg(test)]
3078 pub(crate) fn new(room: RoomSendQueue, transaction_id: ChildTransactionId) -> Self {
3079 Self { room, transaction_id }
3080 }
3081
3082 pub async fn abort(&self) -> Result<bool, RoomSendQueueStorageError> {
3087 if self.room.inner.queue.remove_dependent_send_queue_request(&self.transaction_id).await? {
3088 self.room.send_update(RoomSendQueueUpdate::CancelledLocalEvent {
3092 transaction_id: self.transaction_id.clone().into(),
3093 });
3094
3095 return Ok(true);
3096 }
3097
3098 let handle = SendHandle {
3101 room: self.room.clone(),
3102 transaction_id: self.transaction_id.clone().into(),
3103 media_handles: vec![],
3104 created_at: MilliSecondsSinceUnixEpoch::now(),
3105 };
3106
3107 handle.abort().await
3108 }
3109
3110 pub fn transaction_id(&self) -> &TransactionId {
3112 &self.transaction_id
3113 }
3114}
3115
3116#[derive(Clone, Debug)]
3119pub struct SendRedactionHandle {
3120 room: RoomSendQueue,
3122
3123 transaction_id: OwnedTransactionId,
3125}
3126
3127impl SendRedactionHandle {
3128 #[cfg(test)]
3130 pub(crate) fn new(room: RoomSendQueue, transaction_id: OwnedTransactionId) -> Self {
3131 Self { room, transaction_id }
3132 }
3133
3134 pub async fn abort(&self) -> Result<bool, RoomSendQueueStorageError> {
3139 trace!("received a redaction abort request");
3140
3141 let queue = &self.room.inner.queue;
3142
3143 if queue.cancel_event(&self.transaction_id, None).await? {
3144 trace!("successful redaction abort");
3145
3146 self.room.inner.notifier.notify_one();
3148
3149 self.room.send_update(RoomSendQueueUpdate::CancelledLocalEvent {
3151 transaction_id: self.transaction_id.clone(),
3152 });
3153
3154 Ok(true)
3155 } else {
3156 debug!("local echo of redaction didn't exist anymore, can't abort");
3157 Ok(false)
3158 }
3159 }
3160}
3161
3162fn canonicalize_dependent_requests(
3166 dependent: &[DependentQueuedRequest],
3167) -> Vec<DependentQueuedRequest> {
3168 let mut by_txn = HashMap::<OwnedTransactionId, Vec<&DependentQueuedRequest>>::new();
3169
3170 for d in dependent {
3171 let prevs = by_txn.entry(d.parent_transaction_id.clone()).or_default();
3172
3173 if prevs.iter().any(|prev| {
3174 matches!(
3175 prev.kind,
3176 DependentQueuedRequestKind::RedactEvent
3177 | DependentQueuedRequestKind::RedactEventWithReason { .. }
3178 )
3179 }) {
3180 continue;
3183 }
3184
3185 match &d.kind {
3186 DependentQueuedRequestKind::EditEvent { .. } => {
3187 if let Some(prev_edit) = prevs
3189 .iter_mut()
3190 .find(|prev| matches!(prev.kind, DependentQueuedRequestKind::EditEvent { .. }))
3191 {
3192 *prev_edit = d;
3193 } else {
3194 prevs.insert(0, d);
3195 }
3196 }
3197
3198 DependentQueuedRequestKind::UploadFileOrThumbnail { .. }
3199 | DependentQueuedRequestKind::FinishUpload { .. }
3200 | DependentQueuedRequestKind::ReactEvent { .. } => {
3201 prevs.push(d);
3203 }
3204
3205 #[cfg(feature = "unstable-msc4274")]
3206 DependentQueuedRequestKind::FinishGallery { .. } => {
3207 prevs.push(d);
3209 }
3210
3211 DependentQueuedRequestKind::RedactEvent
3212 | DependentQueuedRequestKind::RedactEventWithReason { .. } => {
3213 prevs.clear();
3215 prevs.push(d);
3216 }
3217 }
3218 }
3219
3220 by_txn.into_values().flat_map(|entries| entries.into_iter().cloned()).collect()
3221}
3222
3223#[cfg(all(test, not(target_family = "wasm")))]
3224mod tests {
3225 use std::{sync::Arc, time::Duration};
3226
3227 use assert_matches2::{assert_let, assert_matches};
3228 use matrix_sdk_base::store::{
3229 ChildTransactionId, DependentQueuedRequest, DependentQueuedRequestKind,
3230 SerializableEventContent,
3231 };
3232 use matrix_sdk_test::{JoinedRoomBuilder, SyncResponseBuilder, async_test};
3233 use ruma::{
3234 MilliSecondsSinceUnixEpoch, TransactionId,
3235 events::{AnyMessageLikeEventContent, room::message::RoomMessageEventContent},
3236 room_id,
3237 };
3238
3239 use super::canonicalize_dependent_requests;
3240 use crate::{client::WeakClient, test_utils::logged_in_client};
3241
3242 #[test]
3243 fn test_canonicalize_dependent_events_created_at() {
3244 let txn = TransactionId::new();
3247 let created_at = MilliSecondsSinceUnixEpoch::now();
3248
3249 let edit = DependentQueuedRequest {
3250 own_transaction_id: ChildTransactionId::new(),
3251 parent_transaction_id: txn.clone(),
3252 kind: DependentQueuedRequestKind::EditEvent {
3253 new_content: SerializableEventContent::new(
3254 &RoomMessageEventContent::text_plain("edit").into(),
3255 )
3256 .unwrap(),
3257 },
3258 parent_key: None,
3259 created_at,
3260 };
3261
3262 let res = canonicalize_dependent_requests(&[edit]);
3263
3264 assert_eq!(res.len(), 1);
3265 assert_let!(DependentQueuedRequestKind::EditEvent { new_content } = &res[0].kind);
3266 assert_let!(
3267 AnyMessageLikeEventContent::RoomMessage(msg) = new_content.deserialize().unwrap()
3268 );
3269 assert_eq!(msg.body(), "edit");
3270 assert_eq!(res[0].parent_transaction_id, txn);
3271 assert_eq!(res[0].created_at, created_at);
3272 }
3273
3274 #[async_test]
3275 async fn test_client_no_cycle_with_send_queue() {
3276 for enabled in [true, false] {
3277 let client = logged_in_client(None).await;
3278 let weak_client = WeakClient::from_client(&client);
3279
3280 {
3281 let mut sync_response_builder = SyncResponseBuilder::new();
3282
3283 let room_id = room_id!("!a:b.c");
3284
3285 client
3287 .base_client()
3288 .receive_sync_response(
3289 sync_response_builder
3290 .add_joined_room(JoinedRoomBuilder::new(room_id))
3291 .build_sync_response(),
3292 )
3293 .await
3294 .unwrap();
3295
3296 let room = client.get_room(room_id).unwrap();
3297 let q = room.send_queue();
3298
3299 let _watcher = q.subscribe().await;
3300
3301 client.send_queue().set_enabled(enabled).await;
3302 }
3303
3304 drop(client);
3305
3306 tokio::time::sleep(Duration::from_millis(500)).await;
3308
3309 let client = weak_client.get();
3311 assert!(
3312 client.is_none(),
3313 "too many strong references to the client: {}",
3314 Arc::strong_count(&client.unwrap().inner)
3315 );
3316 }
3317 }
3318
3319 #[test]
3320 fn test_canonicalize_dependent_events_smoke_test() {
3321 let txn = TransactionId::new();
3323
3324 let edit = DependentQueuedRequest {
3325 own_transaction_id: ChildTransactionId::new(),
3326 parent_transaction_id: txn.clone(),
3327 kind: DependentQueuedRequestKind::EditEvent {
3328 new_content: SerializableEventContent::new(
3329 &RoomMessageEventContent::text_plain("edit").into(),
3330 )
3331 .unwrap(),
3332 },
3333 parent_key: None,
3334 created_at: MilliSecondsSinceUnixEpoch::now(),
3335 };
3336 let res = canonicalize_dependent_requests(&[edit]);
3337
3338 assert_eq!(res.len(), 1);
3339 assert_matches!(&res[0].kind, DependentQueuedRequestKind::EditEvent { .. });
3340 assert_eq!(res[0].parent_transaction_id, txn);
3341 assert!(res[0].parent_key.is_none());
3342 }
3343
3344 #[test]
3345 fn test_canonicalize_dependent_events_redaction_preferred() {
3346 let txn = TransactionId::new();
3348
3349 let mut inputs = Vec::with_capacity(100);
3350 let redact = DependentQueuedRequest {
3351 own_transaction_id: ChildTransactionId::new(),
3352 parent_transaction_id: txn.clone(),
3353 kind: DependentQueuedRequestKind::RedactEventWithReason { reason: None },
3354 parent_key: None,
3355 created_at: MilliSecondsSinceUnixEpoch::now(),
3356 };
3357
3358 let edit = DependentQueuedRequest {
3359 own_transaction_id: ChildTransactionId::new(),
3360 parent_transaction_id: txn.clone(),
3361 kind: DependentQueuedRequestKind::EditEvent {
3362 new_content: SerializableEventContent::new(
3363 &RoomMessageEventContent::text_plain("edit").into(),
3364 )
3365 .unwrap(),
3366 },
3367 parent_key: None,
3368 created_at: MilliSecondsSinceUnixEpoch::now(),
3369 };
3370
3371 inputs.push({
3372 let mut edit = edit.clone();
3373 edit.own_transaction_id = ChildTransactionId::new();
3374 edit
3375 });
3376
3377 inputs.push(redact);
3378
3379 for _ in 0..98 {
3380 let mut edit = edit.clone();
3381 edit.own_transaction_id = ChildTransactionId::new();
3382 inputs.push(edit);
3383 }
3384
3385 let res = canonicalize_dependent_requests(&inputs);
3386
3387 assert_eq!(res.len(), 1);
3388 assert_matches!(&res[0].kind, DependentQueuedRequestKind::RedactEventWithReason { .. });
3389 assert_eq!(res[0].parent_transaction_id, txn);
3390 }
3391
3392 #[test]
3393 fn test_canonicalize_dependent_events_last_edit_preferred() {
3394 let parent_txn = TransactionId::new();
3395
3396 let inputs = (0..10)
3398 .map(|i| DependentQueuedRequest {
3399 own_transaction_id: ChildTransactionId::new(),
3400 parent_transaction_id: parent_txn.clone(),
3401 kind: DependentQueuedRequestKind::EditEvent {
3402 new_content: SerializableEventContent::new(
3403 &RoomMessageEventContent::text_plain(format!("edit{i}")).into(),
3404 )
3405 .unwrap(),
3406 },
3407 parent_key: None,
3408 created_at: MilliSecondsSinceUnixEpoch::now(),
3409 })
3410 .collect::<Vec<_>>();
3411
3412 let txn = inputs[9].parent_transaction_id.clone();
3413
3414 let res = canonicalize_dependent_requests(&inputs);
3415
3416 assert_eq!(res.len(), 1);
3417 assert_let!(DependentQueuedRequestKind::EditEvent { new_content } = &res[0].kind);
3418 assert_let!(
3419 AnyMessageLikeEventContent::RoomMessage(msg) = new_content.deserialize().unwrap()
3420 );
3421 assert_eq!(msg.body(), "edit9");
3422 assert_eq!(res[0].parent_transaction_id, txn);
3423 }
3424
3425 #[test]
3426 fn test_canonicalize_multiple_local_echoes() {
3427 let txn1 = TransactionId::new();
3428 let txn2 = TransactionId::new();
3429
3430 let child1 = ChildTransactionId::new();
3431 let child2 = ChildTransactionId::new();
3432
3433 let inputs = vec![
3434 DependentQueuedRequest {
3436 own_transaction_id: child1.clone(),
3437 kind: DependentQueuedRequestKind::RedactEventWithReason { reason: None },
3438 parent_transaction_id: txn1.clone(),
3439 parent_key: None,
3440 created_at: MilliSecondsSinceUnixEpoch::now(),
3441 },
3442 DependentQueuedRequest {
3444 own_transaction_id: child2,
3445 kind: DependentQueuedRequestKind::EditEvent {
3446 new_content: SerializableEventContent::new(
3447 &RoomMessageEventContent::text_plain("edit").into(),
3448 )
3449 .unwrap(),
3450 },
3451 parent_transaction_id: txn2.clone(),
3452 parent_key: None,
3453 created_at: MilliSecondsSinceUnixEpoch::now(),
3454 },
3455 ];
3456
3457 let res = canonicalize_dependent_requests(&inputs);
3458
3459 assert_eq!(res.len(), 2);
3461
3462 for dependent in res {
3463 if dependent.own_transaction_id == child1 {
3464 assert_eq!(dependent.parent_transaction_id, txn1);
3465 assert_matches!(
3466 dependent.kind,
3467 DependentQueuedRequestKind::RedactEventWithReason { .. }
3468 );
3469 } else {
3470 assert_eq!(dependent.parent_transaction_id, txn2);
3471 assert_matches!(dependent.kind, DependentQueuedRequestKind::EditEvent { .. });
3472 }
3473 }
3474 }
3475
3476 #[test]
3477 fn test_canonicalize_reactions_after_edits() {
3478 let txn = TransactionId::new();
3480
3481 let react_id = ChildTransactionId::new();
3482 let react = DependentQueuedRequest {
3483 own_transaction_id: react_id.clone(),
3484 kind: DependentQueuedRequestKind::ReactEvent { key: "🧠".to_owned() },
3485 parent_transaction_id: txn.clone(),
3486 parent_key: None,
3487 created_at: MilliSecondsSinceUnixEpoch::now(),
3488 };
3489
3490 let edit_id = ChildTransactionId::new();
3491 let edit = DependentQueuedRequest {
3492 own_transaction_id: edit_id.clone(),
3493 kind: DependentQueuedRequestKind::EditEvent {
3494 new_content: SerializableEventContent::new(
3495 &RoomMessageEventContent::text_plain("edit").into(),
3496 )
3497 .unwrap(),
3498 },
3499 parent_transaction_id: txn,
3500 parent_key: None,
3501 created_at: MilliSecondsSinceUnixEpoch::now(),
3502 };
3503
3504 let res = canonicalize_dependent_requests(&[react, edit]);
3505
3506 assert_eq!(res.len(), 2);
3507 assert_eq!(res[0].own_transaction_id, edit_id);
3508 assert_eq!(res[1].own_transaction_id, react_id);
3509 }
3510}