1use std::{
138 collections::{BTreeMap, HashMap},
139 future::IntoFuture,
140 str::FromStr as _,
141 sync::{
142 Arc, OnceLock, RwLock,
143 atomic::{AtomicBool, Ordering},
144 },
145 time::Duration,
146};
147
148use eyeball::SharedObservable;
149#[cfg(feature = "e2e-encryption")]
150use matrix_sdk_base::crypto::{OlmError, SessionRecipientCollectionError};
151#[cfg(feature = "unstable-msc4274")]
152use matrix_sdk_base::store::FinishGalleryItemInfo;
153use matrix_sdk_base::{
154 RoomState, StoreError,
155 cross_process_lock::CrossProcessLockError,
156 deserialized_responses::{EncryptionInfo, TimelineEvent},
157 event_cache::store::EventCacheStoreError,
158 media::{MediaRequestParameters, store::MediaStoreError},
159 store::{
160 ChildTransactionId, DependentQueuedRequest, DependentQueuedRequestKind, DynStateStore,
161 FinishUploadThumbnailInfo, QueueWedgeError, QueuedRequest, QueuedRequestKind,
162 SentMediaInfo, SentRequestKey, SerializableEventContent,
163 },
164 task_monitor::BackgroundTaskHandle,
165};
166use matrix_sdk_common::{boxed_into_future, locks::Mutex as SyncMutex};
167use mime::Mime;
168#[cfg(feature = "unstable-msc4354")]
169use ruma::events::sticky::StickyDurationMs;
170use ruma::{
171 EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, OwnedTransactionId, RoomId,
172 TransactionId, UInt, UserId,
173 api::error::ErrorKind,
174 events::{
175 AnyMessageLikeEventContent, Mentions, MessageLikeEventContent as _, TimelineEventType,
176 reaction::ReactionEventContent,
177 relation::Annotation,
178 room::{
179 MediaSource,
180 message::{FormattedBody, RoomMessageEventContent},
181 },
182 },
183 serde::Raw,
184};
185use tokio::sync::{Mutex, Notify, OwnedMutexGuard, broadcast, oneshot};
186use tracing::{debug, error, info, instrument, trace, warn};
187
188#[cfg(feature = "unstable-msc4354")]
189use crate::utils::sticky_duration_ms;
190use crate::{
191 Client, Media, Room, SessionChange, TransmissionProgress,
192 client::WeakClient,
193 config::RequestConfig,
194 error::RetryKind,
195 room::{WeakRoom, edit::EditedContent},
196};
197
198mod progress;
199mod upload;
200
201pub use progress::AbstractProgress;
202
203const STORE_ERROR_BACKOFF: Duration = Duration::from_millis(250);
206
207pub struct SendQueue {
209 client: Client,
210}
211
212#[cfg(not(tarpaulin_include))]
213impl std::fmt::Debug for SendQueue {
214 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215 f.debug_struct("SendQueue").finish_non_exhaustive()
216 }
217}
218
219impl SendQueue {
220 pub(super) fn new(client: Client) -> Self {
221 Self { client }
222 }
223
224 pub async fn respawn_tasks_for_rooms_with_unsent_requests(&self) {
227 if !self.is_enabled() {
228 return;
229 }
230
231 let room_ids =
232 self.client.state_store().load_rooms_with_unsent_requests().await.unwrap_or_else(
233 |err| {
234 warn!("error when loading rooms with unsent requests: {err}");
235 Vec::new()
236 },
237 );
238
239 for room_id in room_ids {
242 if let Some(room) = self.client.get_room(&room_id) {
243 let _ = self.for_room(room);
244 }
245 }
246 }
247
248 async fn session_change_task(client: WeakClient) {
251 let Some(strong_client) = client.get() else { return };
252 let mut session_changes = strong_client.subscribe_to_session_changes();
253 drop(strong_client);
255
256 loop {
257 match session_changes.recv().await {
258 Ok(SessionChange::TokensRefreshed) => {
259 let Some(client) = client.get() else { break };
260 client.send_queue().unwedge_requests_with_expired_token().await;
261 }
262
263 Ok(SessionChange::UnknownToken(_)) => {}
264
265 Err(broadcast::error::RecvError::Lagged(num)) => {
268 warn!(num, "missed some session changes, unwedging just in case");
269 let Some(client) = client.get() else { break };
270 client.send_queue().unwedge_requests_with_expired_token().await;
271 }
272
273 Err(broadcast::error::RecvError::Closed) => break,
274 }
275 }
276 }
277
278 async fn unwedge_requests_with_expired_token(&self) {
281 let room_ids = match self.client.state_store().load_rooms_with_unsent_requests().await {
282 Ok(room_ids) => room_ids,
283 Err(err) => {
284 warn!("error when loading rooms with unsent requests: {err}");
285 return;
286 }
287 };
288
289 for room_id in room_ids {
290 let Some(room) = self.client.get_room(&room_id) else { continue };
291 self.for_room(room).unwedge_requests_with_expired_token().await;
292 }
293 }
294
295 #[inline(always)]
297 fn data(&self) -> &SendQueueData {
298 &self.client.inner.send_queue_data
299 }
300
301 pub(crate) fn for_room(&self, room: Room) -> RoomSendQueue {
304 let data = self.data();
305
306 data.session_change_task.get_or_init(|| {
307 let client = self.client.downgrade();
308 self.client
309 .task_monitor()
310 .spawn_infinite_task("send_queue_session_change", Self::session_change_task(client))
311 .abort_on_drop()
312 });
313
314 let mut map = data.rooms.write().unwrap();
315
316 let room_id = room.room_id();
317 if let Some(room_q) = map.get(room_id).cloned() {
318 return room_q;
319 }
320
321 let owned_room_id = room_id.to_owned();
322 let room_q = RoomSendQueue::new(
323 self.is_enabled(),
324 data.global_update_sender.clone(),
325 data.error_sender.clone(),
326 data.is_dropping.clone(),
327 &self.client,
328 owned_room_id.clone(),
329 data.report_media_upload_progress.clone(),
330 );
331
332 map.insert(owned_room_id, room_q.clone());
333
334 room_q
335 }
336
337 pub async fn set_enabled(&self, enabled: bool) {
347 debug!(?enabled, "setting global send queue enablement");
348
349 self.data().globally_enabled.store(enabled, Ordering::SeqCst);
350
351 for room in self.data().rooms.read().unwrap().values() {
353 room.set_enabled(enabled);
354 }
355
356 self.respawn_tasks_for_rooms_with_unsent_requests().await;
359 }
360
361 pub fn is_enabled(&self) -> bool {
363 self.data().globally_enabled.load(Ordering::SeqCst)
364 }
365
366 pub fn enable_upload_progress(&self, enabled: bool) {
368 self.data().report_media_upload_progress.store(enabled, Ordering::SeqCst);
369 }
370
371 pub fn subscribe(&self) -> broadcast::Receiver<SendQueueUpdate> {
376 self.data().global_update_sender.subscribe()
377 }
378
379 pub async fn local_echoes(
381 &self,
382 ) -> Result<BTreeMap<OwnedRoomId, Vec<LocalEcho>>, RoomSendQueueError> {
383 let room_ids =
384 self.client.state_store().load_rooms_with_unsent_requests().await.unwrap_or_else(
385 |err| {
386 warn!("error when loading rooms with unsent requests: {err}");
387 Vec::new()
388 },
389 );
390
391 let mut local_echoes: BTreeMap<OwnedRoomId, Vec<LocalEcho>> = BTreeMap::new();
392
393 for room_id in room_ids {
394 if let Some(room) = self.client.get_room(&room_id) {
395 let queue = self.for_room(room);
396 local_echoes
397 .insert(room_id.to_owned(), queue.inner.queue.local_echoes(&queue).await?);
398 }
399 }
400
401 Ok(local_echoes)
402 }
403
404 pub fn subscribe_errors(&self) -> broadcast::Receiver<SendQueueRoomError> {
407 self.data().error_sender.subscribe()
408 }
409}
410
411#[derive(Clone, Debug)]
414struct QueueThumbnailInfo {
415 finish_upload_thumbnail_info: FinishUploadThumbnailInfo,
417
418 media_request_parameters: MediaRequestParameters,
420
421 content_type: Mime,
423
424 file_size: usize,
426}
427
428#[derive(Clone, Debug)]
430pub struct SendQueueRoomError {
431 pub room_id: OwnedRoomId,
433
434 pub error: Arc<crate::Error>,
436
437 pub is_recoverable: bool,
442}
443
444impl Client {
445 pub fn send_queue(&self) -> SendQueue {
448 SendQueue::new(self.clone())
449 }
450}
451
452pub(super) struct SendQueueData {
453 rooms: RwLock<BTreeMap<OwnedRoomId, RoomSendQueue>>,
455
456 globally_enabled: AtomicBool,
461
462 global_update_sender: broadcast::Sender<SendQueueUpdate>,
466
467 error_sender: broadcast::Sender<SendQueueRoomError>,
469
470 is_dropping: Arc<AtomicBool>,
472
473 report_media_upload_progress: Arc<AtomicBool>,
475
476 session_change_task: OnceLock<BackgroundTaskHandle>,
478}
479
480impl SendQueueData {
481 pub fn new(globally_enabled: bool) -> Self {
483 let (global_update_sender, _) = broadcast::channel(32);
484 let (error_sender, _) = broadcast::channel(32);
485
486 Self {
487 rooms: Default::default(),
488 globally_enabled: AtomicBool::new(globally_enabled),
489 global_update_sender,
490 error_sender,
491 is_dropping: Arc::new(false.into()),
492 report_media_upload_progress: Arc::new(false.into()),
493 session_change_task: OnceLock::new(),
494 }
495 }
496}
497
498impl Drop for SendQueueData {
499 fn drop(&mut self) {
500 debug!("globally dropping the send queue");
503 self.is_dropping.store(true, Ordering::SeqCst);
504
505 let rooms = self.rooms.read().unwrap();
506 for room in rooms.values() {
507 room.inner.notifier.notify_one();
508 }
509 }
510}
511
512impl Room {
513 pub fn send_queue(&self) -> RoomSendQueue {
515 self.client.send_queue().for_room(self.clone())
516 }
517}
518
519#[derive(Clone)]
523pub struct RoomSendQueue {
524 inner: Arc<RoomSendQueueInner>,
525}
526
527#[cfg(not(tarpaulin_include))]
528impl std::fmt::Debug for RoomSendQueue {
529 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
530 f.debug_struct("RoomSendQueue").finish_non_exhaustive()
531 }
532}
533
534impl RoomSendQueue {
535 fn new(
536 globally_enabled: bool,
537 global_update_sender: broadcast::Sender<SendQueueUpdate>,
538 global_error_sender: broadcast::Sender<SendQueueRoomError>,
539 is_dropping: Arc<AtomicBool>,
540 client: &Client,
541 room_id: OwnedRoomId,
542 report_media_upload_progress: Arc<AtomicBool>,
543 ) -> Self {
544 let (update_sender, _) = broadcast::channel(32);
545
546 let queue = QueueStorage::new(client.downgrade(), room_id.clone());
547 let notifier = Arc::new(Notify::new());
548
549 let weak_room = WeakRoom::new(client.downgrade(), room_id);
550 let locally_enabled = Arc::new(AtomicBool::new(globally_enabled));
551
552 let task = client.task_monitor().spawn_infinite_task(
553 "send_queue",
554 Self::sending_task(
555 weak_room.clone(),
556 queue.clone(),
557 notifier.clone(),
558 global_update_sender.clone(),
559 update_sender.clone(),
560 locally_enabled.clone(),
561 global_error_sender,
562 is_dropping,
563 report_media_upload_progress,
564 ),
565 );
566
567 Self {
568 inner: Arc::new(RoomSendQueueInner {
569 room: weak_room,
570 global_update_sender,
571 update_sender,
572 _task: task,
573 queue,
574 notifier,
575 locally_enabled,
576 }),
577 }
578 }
579
580 pub fn send_raw(
595 &self,
596 content: Raw<AnyMessageLikeEventContent>,
597 event_type: String,
598 ) -> SendRawEvent<'_> {
599 SendRawEvent {
600 queue: self,
601 content: SerializableEventContent::from_raw(content, event_type),
602 #[cfg(feature = "unstable-msc4354")]
603 sticky_duration: None,
604 }
605 }
606
607 async fn send_serialized(
609 &self,
610 content: SerializableEventContent,
611 #[cfg(feature = "unstable-msc4354")] sticky_duration: Option<StickyDurationMs>,
612 ) -> Result<SendHandle, RoomSendQueueError> {
613 let Some(room) = self.inner.room.get() else {
614 return Err(RoomSendQueueError::RoomDisappeared);
615 };
616 if room.state() != RoomState::Joined {
617 return Err(RoomSendQueueError::RoomNotJoined);
618 }
619
620 let request = QueuedRequestKind::Event {
621 content: content.clone(),
622 #[cfg(feature = "unstable-msc4354")]
623 sticky_duration,
624 };
625
626 let created_at = MilliSecondsSinceUnixEpoch::now();
627 let transaction_id = self.inner.queue.push(request, created_at).await?;
628 trace!(%transaction_id, "manager sends a raw event to the background task");
629
630 self.inner.notifier.notify_one();
631
632 let send_handle = SendHandle {
633 room: self.clone(),
634 transaction_id: transaction_id.clone(),
635 media_handles: vec![],
636 created_at,
637 };
638
639 self.send_update(RoomSendQueueUpdate::NewLocalEvent(LocalEcho {
640 transaction_id,
641 content: LocalEchoContent::Event {
642 serialized_event: content,
643 send_handle: send_handle.clone(),
644 send_error: None,
645 },
646 }));
647
648 Ok(send_handle)
649 }
650
651 pub fn send(&self, content: AnyMessageLikeEventContent) -> SendEvent<'_> {
666 SendEvent {
667 queue: self,
668 content,
669 extra_content: None,
670 #[cfg(feature = "unstable-msc4354")]
671 sticky_duration: None,
672 }
673 }
674
675 pub async fn redact(
690 &self,
691 redacts: OwnedEventId,
692 reason: Option<&str>,
693 ) -> Result<SendHandle, RoomSendQueueError> {
694 let Some(room) = self.inner.room.get() else {
695 return Err(RoomSendQueueError::RoomDisappeared);
696 };
697 if room.state() != RoomState::Joined {
698 return Err(RoomSendQueueError::RoomNotJoined);
699 }
700
701 let request = QueuedRequestKind::Redaction {
702 redacts: redacts.clone(),
703 reason: reason.map(str::to_owned),
704 };
705
706 let created_at = MilliSecondsSinceUnixEpoch::now();
707 let transaction_id = self.inner.queue.push(request, created_at).await?;
708 trace!(%transaction_id, "manager sends a redaction event to the background task");
709
710 self.inner.notifier.notify_one();
711
712 let send_handle = SendHandle {
713 room: self.clone(),
714 transaction_id: transaction_id.clone(),
715 media_handles: vec![],
716 created_at,
717 };
718
719 self.send_update(RoomSendQueueUpdate::NewLocalEvent(LocalEcho {
720 transaction_id,
721 content: LocalEchoContent::Redaction {
722 redacts,
723 reason: reason.map(str::to_owned),
724 send_handle: send_handle.clone(),
725 send_error: None,
726 },
727 }));
728
729 Ok(send_handle)
730 }
731
732 pub async fn subscribe(
738 &self,
739 ) -> Result<(Vec<LocalEcho>, broadcast::Receiver<RoomSendQueueUpdate>), RoomSendQueueError>
740 {
741 let local_echoes = self.inner.queue.local_echoes(self).await?;
742
743 Ok((local_echoes, self.inner.update_sender.subscribe()))
744 }
745
746 #[allow(clippy::too_many_arguments)]
752 #[instrument(skip_all, fields(room_id = %room.room_id()))]
753 async fn sending_task(
754 room: WeakRoom,
755 queue: QueueStorage,
756 notifier: Arc<Notify>,
757 global_update_sender: broadcast::Sender<SendQueueUpdate>,
758 update_sender: broadcast::Sender<RoomSendQueueUpdate>,
759 locally_enabled: Arc<AtomicBool>,
760 global_error_sender: broadcast::Sender<SendQueueRoomError>,
761 is_dropping: Arc<AtomicBool>,
762 report_media_upload_progress: Arc<AtomicBool>,
763 ) {
764 trace!("spawned the sending task");
765
766 let room_id = room.room_id();
767
768 loop {
769 if is_dropping.load(Ordering::SeqCst) {
771 trace!("shutting down!");
772 break;
773 }
774
775 let mut new_updates = Vec::new();
778 if let Err(err) = queue.apply_dependent_requests(&mut new_updates).await {
779 warn!("errors when applying dependent requests: {err}");
780 }
781
782 for up in new_updates {
783 send_update(&global_update_sender, &update_sender, room_id, up);
784 }
785
786 if !locally_enabled.load(Ordering::SeqCst) {
787 trace!("not enabled, sleeping");
788 notifier.notified().await;
790 continue;
791 }
792
793 let (queued_request, cancel_upload_rx) = match queue.peek_next_to_send().await {
794 Ok(Some(request)) => request,
795
796 Ok(None) => {
797 trace!("queue is empty or blocked on a wedged request, sleeping");
798 notifier.notified().await;
800 continue;
801 }
802
803 Err(err) => {
804 warn!("error when loading next request to send: {err}");
805 matrix_sdk_common::sleep::sleep(STORE_ERROR_BACKOFF).await;
808 continue;
809 }
810 };
811
812 let txn_id = queued_request.transaction_id.clone();
813 trace!(txn_id = %txn_id, "received a request to send!");
814
815 let Some(room) = room.get() else {
816 if is_dropping.load(Ordering::SeqCst) {
817 break;
818 }
819 error!("the weak room couldn't be upgraded but we're not shutting down?");
820 continue;
821 };
822
823 let (related_txn_id, media_upload_progress_info, http_progress) =
829 if let QueuedRequestKind::MediaUpload {
830 cache_key,
831 thumbnail_source,
832 #[cfg(feature = "unstable-msc4274")]
833 accumulated,
834 related_to,
835 ..
836 } = &queued_request.kind
837 {
838 let (media_upload_progress_info, http_progress) =
841 if report_media_upload_progress.load(Ordering::SeqCst) {
842 let media_upload_progress_info =
843 RoomSendQueue::create_media_upload_progress_info(
844 &queued_request.transaction_id,
845 related_to,
846 cache_key,
847 thumbnail_source.as_ref(),
848 #[cfg(feature = "unstable-msc4274")]
849 accumulated,
850 &room,
851 &queue,
852 )
853 .await;
854
855 let progress = RoomSendQueue::create_media_upload_progress_observable(
856 &media_upload_progress_info,
857 related_to,
858 &update_sender,
859 );
860
861 (Some(media_upload_progress_info), Some(progress))
862 } else {
863 Default::default()
864 };
865
866 (Some(related_to.clone()), media_upload_progress_info, http_progress)
867 } else {
868 Default::default()
869 };
870
871 match Self::handle_request(&room, queued_request, cancel_upload_rx, http_progress).await
872 {
873 Ok((Some(parent_key), encryption_info)) => match queue
874 .mark_as_sent(&txn_id, parent_key.clone())
875 .await
876 {
877 Ok(()) => match parent_key {
878 SentRequestKey::Event { event_id, event, event_type } => {
879 send_update(
880 &global_update_sender,
881 &update_sender,
882 room_id,
883 RoomSendQueueUpdate::SentEvent {
884 transaction_id: txn_id,
885 event_id: event_id.clone(),
886 },
887 );
888
889 if let Some(event_cache) = room.client().event_cache()
916 && let Ok((room_event_cache, _drop_handles)) =
917 event_cache.room(room.room_id()).await
918 {
919 let event = build_timeline_event(
920 &event_id,
921 MilliSecondsSinceUnixEpoch::now().get(),
922 room.client().user_id().expect("Client must be logged-in"),
923 &event_type,
924 event.into_json(),
925 );
926
927 let timeline_event = match serde_json::value::to_raw_value(&event)
928 .map(Raw::from_json)
929 {
930 Ok(event) => match encryption_info {
931 #[cfg(feature = "e2e-encryption")]
932 Some(encryption_info) => {
933 use matrix_sdk_base::deserialized_responses::DecryptedRoomEvent;
934 let decrypted_event = DecryptedRoomEvent {
935 event: event.cast_unchecked(),
936 encryption_info: Arc::new(encryption_info),
937 unsigned_encryption_info: None,
938 };
939 Some(TimelineEvent::from_decrypted(
940 decrypted_event,
941 None,
942 ))
943 }
944 _ => Some(TimelineEvent::from_plaintext(event)),
945 },
946 Err(err) => {
947 error!(
948 ?err,
949 "Failed to build the (sync) event before the saving in the Event Cache"
950 );
951 None
952 }
953 };
954
955 if let Some(timeline_event) = timeline_event
959 && let Err(err) = room_event_cache
960 .insert_sent_event_from_send_queue(timeline_event)
961 .await
962 {
963 error!(
964 ?err,
965 "Failed to save the sent event in the Event Cache"
966 );
967 }
968 } else {
969 info!(
970 "Cannot insert the sent event in the Event Cache because \
971 either the room no longer exists, or the Room Event Cache cannot be retrieved"
972 );
973 }
974 }
975
976 SentRequestKey::Media(sent_media_info) => {
977 let index =
980 media_upload_progress_info.as_ref().map_or(0, |info| info.index);
981 let progress = media_upload_progress_info
982 .as_ref()
983 .map(|info| {
984 AbstractProgress { current: info.bytes, total: info.bytes }
985 + info.offsets
986 })
987 .unwrap_or(AbstractProgress { current: 1, total: 1 });
988
989 let _ = update_sender.send(RoomSendQueueUpdate::MediaUpload {
993 related_to: related_txn_id.as_ref().unwrap_or(&txn_id).clone(),
994 file: Some(sent_media_info.file),
995 index,
996 progress,
997 });
998 }
999
1000 SentRequestKey::Redaction { event_id, redacts, reason } => {
1001 send_update(
1002 &global_update_sender,
1003 &update_sender,
1004 room_id,
1005 RoomSendQueueUpdate::SentEvent {
1006 transaction_id: txn_id,
1007 event_id: event_id.clone(),
1008 },
1009 );
1010
1011 if let Some(event_cache) = room.client().event_cache()
1017 && let Ok((room_event_cache, _drop_handles)) =
1018 event_cache.room(room.room_id()).await
1019 {
1020 let content_field_redacts = room.version().is_some_and(|id| {
1021 id.rules()
1022 .is_some_and(|rules| rules.redaction.content_field_redacts)
1023 });
1024
1025 let event = build_redaction_event(
1026 &event_id,
1027 MilliSecondsSinceUnixEpoch::now().get(),
1028 room.client().user_id().expect("Client must be logged-in"),
1029 &redacts,
1030 reason.as_deref(),
1031 content_field_redacts,
1032 );
1033
1034 let timeline_event = match serde_json::value::to_raw_value(&event)
1035 .map(Raw::from_json)
1036 {
1037 Ok(event) => Some(TimelineEvent::from_plaintext(event)),
1038 Err(err) => {
1039 error!(
1040 ?err,
1041 "Failed to build the (sync) redaction event before the saving in the Event Cache"
1042 );
1043 None
1044 }
1045 };
1046
1047 if let Some(timeline_event) = timeline_event
1051 && let Err(err) = room_event_cache
1052 .insert_sent_event_from_send_queue(timeline_event)
1053 .await
1054 {
1055 error!(
1056 ?err,
1057 "Failed to save the sent redaction event in the Event Cache"
1058 );
1059 }
1060 } else {
1061 info!(
1062 "Cannot insert the sent redaction event in the Event Cache because \
1063 either the room no longer exists, or the Room Event Cache cannot be retrieved"
1064 );
1065 }
1066 }
1067 },
1068
1069 Err(err) => {
1070 warn!("unable to mark queued request as sent: {err}");
1071 }
1072 },
1073
1074 Ok((None, _)) => {
1075 debug!("Request has been aborted while running, continuing.");
1076 }
1077
1078 Err(err)
1083 if err.client_api_error_kind() == Some(&ErrorKind::DuplicateAnnotation) =>
1084 {
1085 debug!(txn_id = %txn_id, "the server already has this reaction, dropping it");
1086
1087 queue.mark_as_not_being_sent(&txn_id).await;
1088
1089 match queue.cancel_event(&txn_id, None).await {
1090 Ok(true) => send_update(
1091 &global_update_sender,
1092 &update_sender,
1093 room_id,
1094 RoomSendQueueUpdate::CancelledLocalEvent { transaction_id: txn_id },
1095 ),
1096
1097 Ok(false) => {}
1100
1101 Err(storage_error) => {
1102 error!(
1105 txn_id = %txn_id,
1106 "couldn't drop the duplicate reaction, disabling the queue: {storage_error}"
1107 );
1108 locally_enabled.store(false, Ordering::SeqCst);
1109 }
1110 }
1111 }
1112
1113 Err(err) => {
1114 let is_recoverable = match err {
1115 crate::Error::Http(ref http_err) => {
1116 matches!(
1118 http_err.retry_kind(),
1119 RetryKind::Transient { .. } | RetryKind::NetworkFailure
1120 )
1121 }
1122
1123 crate::Error::ConcurrentRequestFailed => true,
1129
1130 _ => false,
1132 };
1133
1134 if is_recoverable {
1135 locally_enabled.store(false, Ordering::SeqCst);
1139
1140 warn!(txn_id = %txn_id, error = ?err, "Recoverable error when sending request: {err}, disabling send queue");
1141
1142 queue.mark_as_not_being_sent(&txn_id).await;
1145
1146 } else {
1152 warn!(txn_id = %txn_id, error = ?err, "Unrecoverable error when sending request: {err}");
1153
1154 if let Err(storage_error) =
1159 queue.mark_as_wedged(&txn_id, QueueWedgeError::from(&err)).await
1160 {
1161 error!(
1165 "unable to mark request as wedged, disabling the queue: {storage_error}"
1166 );
1167 locally_enabled.store(false, Ordering::SeqCst);
1168 }
1169 }
1170
1171 let error = Arc::new(err);
1172
1173 let _ = global_error_sender.send(SendQueueRoomError {
1174 room_id: room_id.to_owned(),
1175 error: error.clone(),
1176 is_recoverable,
1177 });
1178
1179 send_update(
1180 &global_update_sender,
1181 &update_sender,
1182 room_id,
1183 RoomSendQueueUpdate::SendError {
1184 transaction_id: related_txn_id.unwrap_or(txn_id),
1185 error,
1186 is_recoverable,
1187 },
1188 );
1189 }
1190 }
1191 }
1192
1193 info!("exited sending task");
1194 }
1195
1196 async fn handle_request(
1199 room: &Room,
1200 request: QueuedRequest,
1201 cancel_upload_rx: Option<oneshot::Receiver<()>>,
1202 progress: Option<SharedObservable<TransmissionProgress>>,
1203 ) -> Result<(Option<SentRequestKey>, Option<EncryptionInfo>), crate::Error> {
1204 match request.kind {
1205 QueuedRequestKind::Event {
1206 content,
1207 #[cfg(feature = "unstable-msc4354")]
1208 sticky_duration,
1209 } => {
1210 let (event, event_type) = content.into_raw();
1211
1212 let future = room
1213 .send_raw(&event_type, &event)
1214 .with_transaction_id(&request.transaction_id)
1215 .with_request_config(RequestConfig::short_retry());
1216
1217 #[cfg(feature = "unstable-msc4354")]
1218 let future = match sticky_duration {
1219 Some(duration) => {
1220 future.with_sticky_duration(Duration::from_millis(duration.get().into()))
1221 }
1222 None => future,
1223 };
1224
1225 let result = future.await?;
1226
1227 trace!(txn_id = %request.transaction_id, event_id = %result.response.event_id, "event successfully sent");
1228
1229 Ok((
1230 Some(SentRequestKey::Event {
1231 event_id: result.response.event_id,
1232 event,
1233 event_type,
1234 }),
1235 result.encryption_info,
1236 ))
1237 }
1238
1239 QueuedRequestKind::MediaUpload {
1240 content_type,
1241 cache_key,
1242 thumbnail_source,
1243 related_to: relates_to,
1244 #[cfg(feature = "unstable-msc4274")]
1245 accumulated,
1246 } => {
1247 trace!(%relates_to, "uploading media related to event");
1248
1249 let fut = async move {
1250 let data = room
1251 .client()
1252 .media_store()
1253 .lock()
1254 .await?
1255 .get_media_content(&cache_key)
1256 .await?
1257 .ok_or(crate::Error::SendQueueWedgeError(Box::new(
1258 QueueWedgeError::MissingMediaContent,
1259 )))?;
1260
1261 let mime = Mime::from_str(&content_type).map_err(|_| {
1262 crate::Error::SendQueueWedgeError(Box::new(
1263 QueueWedgeError::InvalidMimeType { mime_type: content_type.clone() },
1264 ))
1265 })?;
1266
1267 #[cfg(feature = "e2e-encryption")]
1268 let media_source = if room.latest_encryption_state().await?.is_encrypted() {
1269 trace!("upload will be encrypted (encrypted room)");
1270
1271 let mut cursor = std::io::Cursor::new(data);
1272 let mut req = room
1273 .client
1274 .upload_encrypted_file(&mut cursor)
1275 .with_request_config(RequestConfig::short_retry());
1276 if let Some(progress) = progress {
1277 req = req.with_send_progress_observable(progress);
1278 }
1279 let encrypted_file = req.await?;
1280
1281 MediaSource::Encrypted(Box::new(encrypted_file))
1282 } else {
1283 trace!("upload will be in clear text (room without encryption)");
1284
1285 let request_config = RequestConfig::short_retry()
1286 .timeout(Media::reasonable_upload_timeout(&data));
1287 let mut req =
1288 room.client().media().upload(&mime, data, Some(request_config));
1289 if let Some(progress) = progress {
1290 req = req.with_send_progress_observable(progress);
1291 }
1292 let res = req.await?;
1293
1294 MediaSource::Plain(res.content_uri)
1295 };
1296
1297 #[cfg(not(feature = "e2e-encryption"))]
1298 let media_source = {
1299 let request_config = RequestConfig::short_retry()
1300 .timeout(Media::reasonable_upload_timeout(&data));
1301 let mut req =
1302 room.client().media().upload(&mime, data, Some(request_config));
1303 if let Some(progress) = progress {
1304 req = req.with_send_progress_observable(progress);
1305 }
1306 let res = req.await?;
1307 MediaSource::Plain(res.content_uri)
1308 };
1309
1310 let uri = match &media_source {
1311 MediaSource::Plain(uri) => uri,
1312 MediaSource::Encrypted(encrypted_file) => &encrypted_file.url,
1313 };
1314 trace!(%relates_to, mxc_uri = %uri, "media successfully uploaded");
1315
1316 Ok((
1317 Some(SentRequestKey::Media(SentMediaInfo {
1318 file: media_source,
1319 thumbnail: thumbnail_source,
1320 #[cfg(feature = "unstable-msc4274")]
1321 accumulated,
1322 })),
1323 None,
1324 ))
1325 };
1326
1327 let wait_for_cancel = async move {
1328 if let Some(rx) = cancel_upload_rx {
1329 rx.await
1330 } else {
1331 std::future::pending().await
1332 }
1333 };
1334
1335 tokio::select! {
1336 biased;
1337
1338 _ = wait_for_cancel => {
1339 Ok((None, None))
1340 }
1341
1342 res = fut => {
1343 res
1344 }
1345 }
1346 }
1347
1348 QueuedRequestKind::Redaction { redacts, reason } => {
1349 let result = room
1350 .redact(&redacts, reason.as_deref(), Some(request.transaction_id.clone()))
1351 .await?;
1352
1353 trace!(txn_id = %request.transaction_id, event_id = %result.event_id, "redaction successfully sent");
1354
1355 Ok((
1356 Some(SentRequestKey::Redaction { event_id: result.event_id, redacts, reason }),
1357 None,
1358 ))
1359 }
1360 }
1361 }
1362
1363 pub fn is_enabled(&self) -> bool {
1365 self.inner.locally_enabled.load(Ordering::SeqCst)
1366 }
1367
1368 pub fn set_enabled(&self, enabled: bool) {
1370 self.inner.locally_enabled.store(enabled, Ordering::SeqCst);
1371
1372 if enabled {
1375 self.inner.notifier.notify_one();
1376 }
1377 }
1378
1379 fn send_update(&self, update: RoomSendQueueUpdate) {
1383 let _ = self.inner.update_sender.send(update.clone());
1384 let _ = self
1385 .inner
1386 .global_update_sender
1387 .send(SendQueueUpdate { room_id: self.inner.room.room_id().to_owned(), update });
1388 }
1389
1390 async fn unwedge_requests_with_expired_token(&self) {
1393 let transaction_ids = match self.inner.queue.requests_with_expired_token().await {
1394 Ok(transaction_ids) => transaction_ids,
1395 Err(err) => {
1396 warn!("error when looking for requests wedged by an expired token: {err}");
1397 return;
1398 }
1399 };
1400
1401 for transaction_id in transaction_ids {
1402 if let Err(err) = self.unwedge_request(&transaction_id).await {
1403 warn!(%transaction_id, "error when unwedging a request: {err}");
1404 }
1405 }
1406 }
1407
1408 async fn unwedge_request(
1411 &self,
1412 transaction_id: &TransactionId,
1413 ) -> Result<(), RoomSendQueueError> {
1414 self.inner
1415 .queue
1416 .mark_as_unwedged(transaction_id)
1417 .await
1418 .map_err(RoomSendQueueError::StorageError)?;
1419
1420 self.inner.notifier.notify_one();
1423
1424 self.send_update(RoomSendQueueUpdate::RetryEvent {
1425 transaction_id: transaction_id.to_owned(),
1426 });
1427
1428 Ok(())
1429 }
1430}
1431
1432fn send_update(
1433 global_update_sender: &broadcast::Sender<SendQueueUpdate>,
1434 update_sender: &broadcast::Sender<RoomSendQueueUpdate>,
1435 room_id: &RoomId,
1436 update: RoomSendQueueUpdate,
1437) {
1438 let _ = update_sender.send(update.clone());
1439 let _ = global_update_sender.send(SendQueueUpdate { room_id: room_id.to_owned(), update });
1440}
1441
1442impl From<&crate::Error> for QueueWedgeError {
1443 fn from(value: &crate::Error) -> Self {
1444 match value {
1445 #[cfg(feature = "e2e-encryption")]
1446 crate::Error::OlmError(error) => match &**error {
1447 OlmError::SessionRecipientCollectionError(error) => match error {
1448 SessionRecipientCollectionError::VerifiedUserHasUnsignedDevice(user_map) => {
1449 QueueWedgeError::InsecureDevices { user_device_map: user_map.clone() }
1450 }
1451
1452 SessionRecipientCollectionError::VerifiedUserChangedIdentity(users) => {
1453 QueueWedgeError::IdentityViolations { users: users.clone() }
1454 }
1455
1456 SessionRecipientCollectionError::CrossSigningNotSetup
1457 | SessionRecipientCollectionError::SendingFromUnverifiedDevice => {
1458 QueueWedgeError::CrossVerificationRequired
1459 }
1460 },
1461 _ => QueueWedgeError::GenericApiError { msg: value.to_string() },
1462 },
1463
1464 crate::Error::SendQueueWedgeError(error) => *error.clone(),
1466
1467 crate::Error::Http(error) if error.is_rejected_token() => {
1468 QueueWedgeError::ExpiredAccessToken
1469 }
1470
1471 _ => QueueWedgeError::GenericApiError { msg: value.to_string() },
1472 }
1473 }
1474}
1475
1476struct RoomSendQueueInner {
1477 room: WeakRoom,
1479
1480 global_update_sender: broadcast::Sender<SendQueueUpdate>,
1484
1485 update_sender: broadcast::Sender<RoomSendQueueUpdate>,
1491
1492 queue: QueueStorage,
1498
1499 notifier: Arc<Notify>,
1502
1503 locally_enabled: Arc<AtomicBool>,
1506
1507 _task: BackgroundTaskHandle,
1510}
1511
1512struct BeingSentInfo {
1514 transaction_id: OwnedTransactionId,
1516
1517 cancel_upload: Option<oneshot::Sender<()>>,
1520}
1521
1522impl BeingSentInfo {
1523 fn cancel_upload(self) -> bool {
1528 if let Some(cancel_upload) = self.cancel_upload {
1529 let _ = cancel_upload.send(());
1530 true
1531 } else {
1532 false
1533 }
1534 }
1535}
1536
1537#[derive(Clone)]
1540struct StoreLock {
1541 client: WeakClient,
1543
1544 being_sent: Arc<Mutex<Option<BeingSentInfo>>>,
1549}
1550
1551impl StoreLock {
1552 async fn lock(&self) -> StoreLockGuard {
1554 StoreLockGuard {
1555 client: self.client.clone(),
1556 being_sent: self.being_sent.clone().lock_owned().await,
1557 }
1558 }
1559}
1560
1561struct StoreLockGuard {
1563 client: WeakClient,
1565
1566 being_sent: OwnedMutexGuard<Option<BeingSentInfo>>,
1569}
1570
1571impl StoreLockGuard {
1572 fn client(&self) -> Result<Client, RoomSendQueueStorageError> {
1574 self.client.get().ok_or(RoomSendQueueStorageError::ClientShuttingDown)
1575 }
1576}
1577
1578#[derive(Clone)]
1579struct QueueStorage {
1580 store: StoreLock,
1583
1584 room_id: OwnedRoomId,
1586
1587 thumbnail_file_sizes: Arc<SyncMutex<HashMap<OwnedTransactionId, Vec<Option<usize>>>>>,
1600}
1601
1602impl QueueStorage {
1603 const LOW_PRIORITY: usize = 0;
1605
1606 const HIGH_PRIORITY: usize = 10;
1608
1609 fn new(client: WeakClient, room: OwnedRoomId) -> Self {
1611 Self {
1612 room_id: room,
1613 store: StoreLock { client, being_sent: Default::default() },
1614 thumbnail_file_sizes: Default::default(),
1615 }
1616 }
1617
1618 async fn push(
1622 &self,
1623 request: QueuedRequestKind,
1624 created_at: MilliSecondsSinceUnixEpoch,
1625 ) -> Result<OwnedTransactionId, RoomSendQueueStorageError> {
1626 let transaction_id = TransactionId::new();
1627
1628 self.store
1629 .lock()
1630 .await
1631 .client()?
1632 .state_store()
1633 .save_send_queue_request(
1634 &self.room_id,
1635 transaction_id.clone(),
1636 created_at,
1637 request,
1638 Self::LOW_PRIORITY,
1639 )
1640 .await?;
1641
1642 Ok(transaction_id)
1643 }
1644
1645 async fn peek_next_to_send(
1650 &self,
1651 ) -> Result<Option<(QueuedRequest, Option<oneshot::Receiver<()>>)>, RoomSendQueueStorageError>
1652 {
1653 let mut guard = self.store.lock().await;
1654 let queued_requests =
1655 guard.client()?.state_store().load_send_queue_requests(&self.room_id).await?;
1656
1657 if let Some(request) = queued_requests.first().filter(|queued| !queued.is_wedged()) {
1664 let (cancel_upload_tx, cancel_upload_rx) =
1665 if matches!(request.kind, QueuedRequestKind::MediaUpload { .. }) {
1666 let (tx, rx) = oneshot::channel();
1667 (Some(tx), Some(rx))
1668 } else {
1669 Default::default()
1670 };
1671
1672 let prev = guard.being_sent.replace(BeingSentInfo {
1673 transaction_id: request.transaction_id.clone(),
1674 cancel_upload: cancel_upload_tx,
1675 });
1676
1677 if let Some(prev) = prev {
1678 error!(
1679 prev_txn = ?prev.transaction_id,
1680 "a previous request was still active while picking a new one"
1681 );
1682 }
1683
1684 Ok(Some((request.clone(), cancel_upload_rx)))
1685 } else {
1686 Ok(None)
1687 }
1688 }
1689
1690 async fn mark_as_not_being_sent(&self, transaction_id: &TransactionId) {
1694 let was_being_sent = self.store.lock().await.being_sent.take();
1695
1696 let prev_txn = was_being_sent.as_ref().map(|info| info.transaction_id.as_ref());
1697 if prev_txn != Some(transaction_id) {
1698 error!(prev_txn = ?prev_txn, "previous active request didn't match that we expect (after transient error)");
1699 }
1700 }
1701
1702 async fn mark_as_wedged(
1706 &self,
1707 transaction_id: &TransactionId,
1708 reason: QueueWedgeError,
1709 ) -> Result<(), RoomSendQueueStorageError> {
1710 let mut guard = self.store.lock().await;
1712 let was_being_sent = guard.being_sent.take();
1713
1714 let prev_txn = was_being_sent.as_ref().map(|info| info.transaction_id.as_ref());
1715 if prev_txn != Some(transaction_id) {
1716 error!(
1717 ?prev_txn,
1718 "previous active request didn't match that we expect (after permanent error)",
1719 );
1720 }
1721
1722 Ok(guard
1723 .client()?
1724 .state_store()
1725 .update_send_queue_request_status(&self.room_id, transaction_id, Some(reason))
1726 .await?)
1727 }
1728
1729 async fn mark_as_unwedged(
1732 &self,
1733 transaction_id: &TransactionId,
1734 ) -> Result<(), RoomSendQueueStorageError> {
1735 Ok(self
1736 .store
1737 .lock()
1738 .await
1739 .client()?
1740 .state_store()
1741 .update_send_queue_request_status(&self.room_id, transaction_id, None)
1742 .await?)
1743 }
1744
1745 async fn requests_with_expired_token(
1748 &self,
1749 ) -> Result<Vec<OwnedTransactionId>, RoomSendQueueStorageError> {
1750 Ok(self
1751 .store
1752 .lock()
1753 .await
1754 .client()?
1755 .state_store()
1756 .load_send_queue_requests(&self.room_id)
1757 .await?
1758 .into_iter()
1759 .filter(|request| matches!(request.error, Some(QueueWedgeError::ExpiredAccessToken)))
1760 .map(|request| request.transaction_id)
1761 .collect())
1762 }
1763
1764 async fn mark_as_sent(
1767 &self,
1768 transaction_id: &TransactionId,
1769 parent_key: SentRequestKey,
1770 ) -> Result<(), RoomSendQueueStorageError> {
1771 let mut guard = self.store.lock().await;
1773 let was_being_sent = guard.being_sent.take();
1774
1775 let prev_txn = was_being_sent.as_ref().map(|info| info.transaction_id.as_ref());
1776 if prev_txn != Some(transaction_id) {
1777 error!(
1778 ?prev_txn,
1779 "previous active request didn't match that we expect (after successful send)",
1780 );
1781 }
1782
1783 let client = guard.client()?;
1784 let store = client.state_store();
1785
1786 store
1788 .mark_dependent_queued_requests_as_ready(&self.room_id, transaction_id, parent_key)
1789 .await?;
1790
1791 let removed = store.remove_send_queue_request(&self.room_id, transaction_id).await?;
1792
1793 if !removed {
1794 warn!(txn_id = %transaction_id, "request marked as sent was missing from storage");
1795 }
1796
1797 self.thumbnail_file_sizes.lock().remove(transaction_id);
1798
1799 Ok(())
1800 }
1801
1802 async fn cancel_event(
1809 &self,
1810 transaction_id: &TransactionId,
1811 reason: Option<String>,
1812 ) -> Result<bool, RoomSendQueueStorageError> {
1813 let guard = self.store.lock().await;
1814
1815 if guard.being_sent.as_ref().map(|info| info.transaction_id.as_ref())
1816 == Some(transaction_id)
1817 {
1818 guard
1820 .client()?
1821 .state_store()
1822 .save_dependent_queued_request(
1823 &self.room_id,
1824 transaction_id,
1825 ChildTransactionId::new(),
1826 MilliSecondsSinceUnixEpoch::now(),
1827 DependentQueuedRequestKind::RedactEventWithReason { reason },
1828 )
1829 .await?;
1830
1831 return Ok(true);
1832 }
1833
1834 let removed = guard
1835 .client()?
1836 .state_store()
1837 .remove_send_queue_request(&self.room_id, transaction_id)
1838 .await?;
1839
1840 self.thumbnail_file_sizes.lock().remove(transaction_id);
1841
1842 Ok(removed)
1843 }
1844
1845 async fn replace_event(
1852 &self,
1853 transaction_id: &TransactionId,
1854 serializable: SerializableEventContent,
1855 ) -> Result<bool, RoomSendQueueStorageError> {
1856 let guard = self.store.lock().await;
1857 let client = guard.client()?;
1858 let store = client.state_store();
1859
1860 if !store.load_send_queue_requests(&self.room_id).await?.iter().any(|request| {
1863 request.transaction_id == transaction_id && is_own_event_request(request)
1864 }) {
1865 return Ok(false);
1866 }
1867
1868 if guard.being_sent.as_ref().map(|info| info.transaction_id.as_ref())
1869 == Some(transaction_id)
1870 {
1871 store
1873 .save_dependent_queued_request(
1874 &self.room_id,
1875 transaction_id,
1876 ChildTransactionId::new(),
1877 MilliSecondsSinceUnixEpoch::now(),
1878 DependentQueuedRequestKind::EditEvent { new_content: serializable },
1879 )
1880 .await?;
1881
1882 return Ok(true);
1883 }
1884
1885 let request = QueuedRequestKind::Event {
1886 content: serializable,
1887 #[cfg(feature = "unstable-msc4354")]
1888 sticky_duration: self.sticky_duration_of(store, transaction_id).await?,
1889 };
1890
1891 let edited =
1892 store.update_send_queue_request(&self.room_id, transaction_id, request).await?;
1893
1894 Ok(edited)
1895 }
1896
1897 #[cfg(feature = "unstable-msc4354")]
1900 async fn sticky_duration_of(
1901 &self,
1902 store: &DynStateStore,
1903 transaction_id: &TransactionId,
1904 ) -> Result<Option<StickyDurationMs>, RoomSendQueueStorageError> {
1905 let sticky_duration = store
1906 .load_send_queue_requests(&self.room_id)
1907 .await?
1908 .into_iter()
1909 .find(|request| request.transaction_id == *transaction_id)
1910 .and_then(|request| match request.kind {
1911 QueuedRequestKind::Event { sticky_duration, .. } => sticky_duration,
1912 _ => None,
1913 });
1914
1915 Ok(sticky_duration)
1916 }
1917
1918 #[allow(clippy::too_many_arguments)]
1922 async fn push_media(
1923 &self,
1924 event: RoomMessageEventContent,
1925 content_type: Mime,
1926 send_event_txn: OwnedTransactionId,
1927 created_at: MilliSecondsSinceUnixEpoch,
1928 upload_file_txn: OwnedTransactionId,
1929 file_media_request: MediaRequestParameters,
1930 thumbnail: Option<QueueThumbnailInfo>,
1931 extra_content: Option<serde_json::Map<String, serde_json::Value>>,
1932 ) -> Result<(), RoomSendQueueStorageError> {
1933 let guard = self.store.lock().await;
1934 let client = guard.client()?;
1935 let store = client.state_store();
1936
1937 let thumbnail_file_sizes = vec![thumbnail.as_ref().map(|t| t.file_size)];
1940
1941 let thumbnail_info = self
1942 .push_thumbnail_and_media_uploads(
1943 store,
1944 &content_type,
1945 send_event_txn.clone(),
1946 created_at,
1947 upload_file_txn.clone(),
1948 file_media_request,
1949 thumbnail,
1950 )
1951 .await?;
1952
1953 store
1955 .save_dependent_queued_request(
1956 &self.room_id,
1957 &upload_file_txn,
1958 send_event_txn.clone().into(),
1959 created_at,
1960 DependentQueuedRequestKind::FinishUpload {
1961 local_echo: Box::new(event),
1962 file_upload: upload_file_txn.clone(),
1963 thumbnail_info,
1964 extra_content,
1965 },
1966 )
1967 .await?;
1968
1969 self.thumbnail_file_sizes.lock().insert(send_event_txn, thumbnail_file_sizes);
1970
1971 Ok(())
1972 }
1973
1974 #[cfg(feature = "unstable-msc4274")]
1978 #[allow(clippy::too_many_arguments)]
1979 async fn push_gallery(
1980 &self,
1981 event: RoomMessageEventContent,
1982 send_event_txn: OwnedTransactionId,
1983 created_at: MilliSecondsSinceUnixEpoch,
1984 item_queue_infos: Vec<GalleryItemQueueInfo>,
1985 extra_content: Option<serde_json::Map<String, serde_json::Value>>,
1986 ) -> Result<(), RoomSendQueueStorageError> {
1987 let guard = self.store.lock().await;
1988 let client = guard.client()?;
1989 let store = client.state_store();
1990
1991 let mut finish_item_infos = Vec::with_capacity(item_queue_infos.len());
1992 let mut thumbnail_file_sizes = Vec::with_capacity(item_queue_infos.len());
1993
1994 let Some((first, rest)) = item_queue_infos.split_first() else {
1995 return Ok(());
1996 };
1997
1998 let GalleryItemQueueInfo {
1999 content_type,
2000 upload_file_txn,
2001 file_media_request,
2002 thumbnail,
2003 extra_content: item_extra_content,
2004 } = first;
2005
2006 let thumbnail_info = self
2007 .push_thumbnail_and_media_uploads(
2008 store,
2009 content_type,
2010 send_event_txn.clone(),
2011 created_at,
2012 upload_file_txn.clone(),
2013 file_media_request.clone(),
2014 thumbnail.clone(),
2015 )
2016 .await?;
2017
2018 finish_item_infos.push(FinishGalleryItemInfo {
2019 file_upload: upload_file_txn.clone(),
2020 thumbnail_info,
2021 extra_content: item_extra_content.clone(),
2022 });
2023 thumbnail_file_sizes.push(thumbnail.as_ref().map(|t| t.file_size));
2024
2025 let mut last_upload_file_txn = upload_file_txn.clone();
2026
2027 for item_queue_info in rest {
2028 let GalleryItemQueueInfo {
2029 content_type,
2030 upload_file_txn,
2031 file_media_request,
2032 thumbnail,
2033 extra_content: item_extra_content,
2034 } = item_queue_info;
2035
2036 let thumbnail_info = if let Some(QueueThumbnailInfo {
2037 finish_upload_thumbnail_info: thumbnail_info,
2038 media_request_parameters: thumbnail_media_request,
2039 content_type: thumbnail_content_type,
2040 ..
2041 }) = thumbnail
2042 {
2043 let upload_thumbnail_txn = thumbnail_info.txn.clone();
2044
2045 store
2048 .save_dependent_queued_request(
2049 &self.room_id,
2050 &last_upload_file_txn,
2051 upload_thumbnail_txn.clone().into(),
2052 created_at,
2053 DependentQueuedRequestKind::UploadFileOrThumbnail {
2054 content_type: thumbnail_content_type.to_string(),
2055 cache_key: thumbnail_media_request.clone(),
2056 related_to: send_event_txn.clone(),
2057 parent_is_thumbnail_upload: false,
2058 },
2059 )
2060 .await?;
2061
2062 last_upload_file_txn = upload_thumbnail_txn;
2063
2064 Some(thumbnail_info)
2065 } else {
2066 None
2067 };
2068
2069 store
2072 .save_dependent_queued_request(
2073 &self.room_id,
2074 &last_upload_file_txn,
2075 upload_file_txn.clone().into(),
2076 created_at,
2077 DependentQueuedRequestKind::UploadFileOrThumbnail {
2078 content_type: content_type.to_string(),
2079 cache_key: file_media_request.clone(),
2080 related_to: send_event_txn.clone(),
2081 parent_is_thumbnail_upload: thumbnail.is_some(),
2082 },
2083 )
2084 .await?;
2085
2086 finish_item_infos.push(FinishGalleryItemInfo {
2087 file_upload: upload_file_txn.clone(),
2088 thumbnail_info: thumbnail_info.cloned(),
2089 extra_content: item_extra_content.clone(),
2090 });
2091 thumbnail_file_sizes.push(thumbnail.as_ref().map(|t| t.file_size));
2092
2093 last_upload_file_txn = upload_file_txn.clone();
2094 }
2095
2096 store
2099 .save_dependent_queued_request(
2100 &self.room_id,
2101 &last_upload_file_txn,
2102 send_event_txn.clone().into(),
2103 created_at,
2104 DependentQueuedRequestKind::FinishGallery {
2105 local_echo: Box::new(event),
2106 item_infos: finish_item_infos,
2107 extra_content,
2108 },
2109 )
2110 .await?;
2111
2112 self.thumbnail_file_sizes.lock().insert(send_event_txn, thumbnail_file_sizes);
2113
2114 Ok(())
2115 }
2116
2117 #[allow(clippy::too_many_arguments)]
2122 async fn push_thumbnail_and_media_uploads(
2123 &self,
2124 store: &DynStateStore,
2125 content_type: &Mime,
2126 send_event_txn: OwnedTransactionId,
2127 created_at: MilliSecondsSinceUnixEpoch,
2128 upload_file_txn: OwnedTransactionId,
2129 file_media_request: MediaRequestParameters,
2130 thumbnail: Option<QueueThumbnailInfo>,
2131 ) -> Result<Option<FinishUploadThumbnailInfo>, RoomSendQueueStorageError> {
2132 if let Some(QueueThumbnailInfo {
2133 finish_upload_thumbnail_info: thumbnail_info,
2134 media_request_parameters: thumbnail_media_request,
2135 content_type: thumbnail_content_type,
2136 ..
2137 }) = thumbnail
2138 {
2139 let upload_thumbnail_txn = thumbnail_info.txn.clone();
2140
2141 store
2143 .save_send_queue_request(
2144 &self.room_id,
2145 upload_thumbnail_txn.clone(),
2146 created_at,
2147 QueuedRequestKind::MediaUpload {
2148 content_type: thumbnail_content_type.to_string(),
2149 cache_key: thumbnail_media_request,
2150 thumbnail_source: None, related_to: send_event_txn.clone(),
2152 #[cfg(feature = "unstable-msc4274")]
2153 accumulated: vec![],
2154 },
2155 Self::LOW_PRIORITY,
2156 )
2157 .await?;
2158
2159 store
2162 .save_dependent_queued_request(
2163 &self.room_id,
2164 &upload_thumbnail_txn,
2165 upload_file_txn.into(),
2166 created_at,
2167 DependentQueuedRequestKind::UploadFileOrThumbnail {
2168 content_type: content_type.to_string(),
2169 cache_key: file_media_request,
2170 related_to: send_event_txn,
2171 parent_is_thumbnail_upload: true,
2172 },
2173 )
2174 .await?;
2175
2176 Ok(Some(thumbnail_info))
2177 } else {
2178 store
2180 .save_send_queue_request(
2181 &self.room_id,
2182 upload_file_txn,
2183 created_at,
2184 QueuedRequestKind::MediaUpload {
2185 content_type: content_type.to_string(),
2186 cache_key: file_media_request,
2187 thumbnail_source: None,
2188 related_to: send_event_txn,
2189 #[cfg(feature = "unstable-msc4274")]
2190 accumulated: vec![],
2191 },
2192 Self::LOW_PRIORITY,
2193 )
2194 .await?;
2195
2196 Ok(None)
2197 }
2198 }
2199
2200 #[instrument(skip(self))]
2202 async fn react(
2203 &self,
2204 transaction_id: &TransactionId,
2205 key: String,
2206 created_at: MilliSecondsSinceUnixEpoch,
2207 ) -> Result<Option<ChildTransactionId>, RoomSendQueueStorageError> {
2208 let guard = self.store.lock().await;
2209 let client = guard.client()?;
2210 let store = client.state_store();
2211
2212 let requests = store.load_send_queue_requests(&self.room_id).await?;
2213
2214 if !requests
2217 .iter()
2218 .any(|item| item.transaction_id == transaction_id && is_own_event_request(item))
2219 {
2220 let dependent_requests = store.load_dependent_queued_requests(&self.room_id).await?;
2223 if !dependent_requests
2224 .into_iter()
2225 .filter_map(|item| item.is_own_event().then_some(item.own_transaction_id))
2226 .any(|child_txn| *child_txn == *transaction_id)
2227 {
2228 return Ok(None);
2231 }
2232 }
2233
2234 let reaction_txn_id = ChildTransactionId::new();
2236 store
2237 .save_dependent_queued_request(
2238 &self.room_id,
2239 transaction_id,
2240 reaction_txn_id.clone(),
2241 created_at,
2242 DependentQueuedRequestKind::ReactEvent { key },
2243 )
2244 .await?;
2245
2246 Ok(Some(reaction_txn_id))
2247 }
2248
2249 async fn local_echoes(
2252 &self,
2253 room: &RoomSendQueue,
2254 ) -> Result<Vec<LocalEcho>, RoomSendQueueStorageError> {
2255 let guard = self.store.lock().await;
2256 let client = guard.client()?;
2257 let store = client.state_store();
2258
2259 let queued_requests = store.load_send_queue_requests(&self.room_id).await?;
2260
2261 let mut media_upload_errors: HashMap<OwnedTransactionId, QueueWedgeError> = queued_requests
2266 .iter()
2267 .filter_map(|queued| match queued.kind {
2268 QueuedRequestKind::MediaUpload { .. } => {
2269 queued.error.clone().map(|error| (queued.transaction_id.clone(), error))
2270 }
2271 _ => None,
2272 })
2273 .collect();
2274
2275 let local_requests = queued_requests.into_iter().filter_map(|queued| {
2276 Some(LocalEcho {
2277 transaction_id: queued.transaction_id.clone(),
2278 content: match queued.kind {
2279 QueuedRequestKind::Event { content, .. } => LocalEchoContent::Event {
2280 serialized_event: content,
2281 send_handle: SendHandle {
2282 room: room.clone(),
2283 transaction_id: queued.transaction_id,
2284 media_handles: vec![],
2285 created_at: queued.created_at,
2286 },
2287 send_error: queued.error,
2288 },
2289
2290 QueuedRequestKind::MediaUpload { .. } => {
2291 return None;
2295 }
2296
2297 QueuedRequestKind::Redaction { redacts, reason } => {
2298 LocalEchoContent::Redaction {
2299 redacts,
2300 reason,
2301 send_handle: SendHandle {
2302 room: room.clone(),
2303 transaction_id: queued.transaction_id,
2304 media_handles: vec![],
2305 created_at: queued.created_at,
2306 },
2307 send_error: queued.error,
2308 }
2309 }
2310 },
2311 })
2312 });
2313
2314 let dependent_requests = store.load_dependent_queued_requests(&self.room_id).await?;
2315
2316 let reactions_and_medias =
2317 dependent_requests.into_iter().filter_map(|dep| match dep.kind {
2318 DependentQueuedRequestKind::EditEvent { .. }
2319 | DependentQueuedRequestKind::RedactEvent
2320 | DependentQueuedRequestKind::RedactEventWithReason { .. } => {
2321 None
2323 }
2324
2325 DependentQueuedRequestKind::ReactEvent { key } => Some(LocalEcho {
2326 transaction_id: dep.own_transaction_id.clone().into(),
2327 content: LocalEchoContent::React {
2328 key,
2329 send_handle: SendHandle {
2330 room: room.clone(),
2331 transaction_id: dep.own_transaction_id.into(),
2332 media_handles: vec![],
2333 created_at: dep.created_at,
2334 },
2335 applies_to: dep.parent_transaction_id,
2336 },
2337 }),
2338
2339 DependentQueuedRequestKind::UploadFileOrThumbnail { .. } => {
2340 None
2343 }
2344
2345 DependentQueuedRequestKind::FinishUpload {
2346 local_echo,
2347 file_upload,
2348 thumbnail_info,
2349 extra_content,
2350 } => {
2351 let upload_thumbnail_txn = thumbnail_info.map(|info| info.txn);
2352
2353 let send_error = media_upload_errors.remove(&file_upload).or_else(|| {
2356 upload_thumbnail_txn
2357 .as_ref()
2358 .and_then(|txn| media_upload_errors.remove(&**txn))
2359 });
2360
2361 Some(LocalEcho {
2363 transaction_id: dep.own_transaction_id.clone().into(),
2364 content: LocalEchoContent::Event {
2365 serialized_event: upload::merge_extra_content(
2366 SerializableEventContent::new(&(*local_echo).into()).ok()?,
2367 extra_content,
2368 )
2369 .ok()?,
2370 send_handle: SendHandle {
2371 room: room.clone(),
2372 transaction_id: dep.own_transaction_id.into(),
2373 media_handles: vec![MediaHandles {
2374 upload_thumbnail_txn,
2375 upload_file_txn: file_upload,
2376 }],
2377 created_at: dep.created_at,
2378 },
2379 send_error,
2380 },
2381 })
2382 }
2383
2384 #[cfg(feature = "unstable-msc4274")]
2385 DependentQueuedRequestKind::FinishGallery {
2386 local_echo,
2387 item_infos,
2388 extra_content,
2389 } => {
2390 self.create_gallery_local_echo(
2392 dep.own_transaction_id,
2393 room,
2394 dep.created_at,
2395 local_echo,
2396 item_infos,
2397 extra_content,
2398 &mut media_upload_errors,
2399 )
2400 }
2401 });
2402
2403 Ok(local_requests.chain(reactions_and_medias).collect())
2404 }
2405
2406 #[cfg(feature = "unstable-msc4274")]
2408 #[allow(clippy::too_many_arguments)]
2409 fn create_gallery_local_echo(
2410 &self,
2411 transaction_id: ChildTransactionId,
2412 room: &RoomSendQueue,
2413 created_at: MilliSecondsSinceUnixEpoch,
2414 local_echo: Box<RoomMessageEventContent>,
2415 item_infos: Vec<FinishGalleryItemInfo>,
2416 extra_content: Option<serde_json::Map<String, serde_json::Value>>,
2417 media_upload_errors: &mut HashMap<OwnedTransactionId, QueueWedgeError>,
2418 ) -> Option<LocalEcho> {
2419 let send_error = item_infos.iter().find_map(|i| {
2421 media_upload_errors.remove(&i.file_upload).or_else(|| {
2422 i.thumbnail_info.as_ref().and_then(|info| media_upload_errors.remove(&*info.txn))
2423 })
2424 });
2425
2426 Some(LocalEcho {
2427 transaction_id: transaction_id.clone().into(),
2428 content: LocalEchoContent::Event {
2429 serialized_event: upload::merge_gallery_extra_content(
2430 SerializableEventContent::new(&(*local_echo).into()).ok()?,
2431 extra_content,
2432 item_infos.iter().map(|item| item.extra_content.clone()),
2433 )
2434 .ok()?,
2435 send_handle: SendHandle {
2436 room: room.clone(),
2437 transaction_id: transaction_id.into(),
2438 media_handles: item_infos
2439 .into_iter()
2440 .map(|i| MediaHandles {
2441 upload_thumbnail_txn: i.thumbnail_info.map(|info| info.txn),
2442 upload_file_txn: i.file_upload,
2443 })
2444 .collect(),
2445 created_at,
2446 },
2447 send_error,
2448 },
2449 })
2450 }
2451
2452 #[instrument(skip_all)]
2460 async fn try_apply_single_dependent_request(
2461 &self,
2462 client: &Client,
2463 dependent_request: DependentQueuedRequest,
2464 new_updates: &mut Vec<RoomSendQueueUpdate>,
2465 ) -> Result<bool, RoomSendQueueError> {
2466 let store = client.state_store();
2467
2468 let parent_key = dependent_request.parent_key;
2469
2470 match dependent_request.kind {
2471 DependentQueuedRequestKind::EditEvent { new_content } => {
2472 if let Some(parent_key) = parent_key {
2473 let Some(event_id) = parent_key.into_event_id() else {
2474 return Err(RoomSendQueueError::StorageError(
2475 RoomSendQueueStorageError::InvalidParentKey,
2476 ));
2477 };
2478
2479 let room = client
2481 .get_room(&self.room_id)
2482 .ok_or(RoomSendQueueError::RoomDisappeared)?;
2483
2484 let mut unknown_fields = None;
2490
2491 let edited_content = match new_content.deserialize() {
2493 Ok(AnyMessageLikeEventContent::RoomMessage(c)) => {
2494 unknown_fields = upload::unknown_fields(&new_content, &c);
2495
2496 EditedContent::RoomMessage(c.into())
2498 }
2499
2500 Ok(AnyMessageLikeEventContent::UnstablePollStart(c)) => {
2501 let poll_start = c.poll_start().clone();
2502 EditedContent::PollStart {
2503 fallback_text: poll_start.question.text.clone(),
2504 new_content: poll_start,
2505 }
2506 }
2507
2508 Ok(c) => {
2509 warn!("Unsupported edit content type: {:?}", c.event_type());
2510 return Ok(true);
2511 }
2512
2513 Err(err) => {
2514 warn!("Unable to deserialize: {err}");
2515 return Ok(true);
2516 }
2517 };
2518
2519 let edit_event = match room.make_edit_event(&event_id, edited_content).await {
2520 Ok(e) => e,
2521 Err(err) => {
2522 warn!("couldn't create edited event: {err}");
2523 return Ok(true);
2524 }
2525 };
2526
2527 let mut edit_json = serde_json::to_value(&edit_event)
2528 .map_err(RoomSendQueueStorageError::JsonSerialization)?;
2529
2530 if let Some(serde_json::Value::Object(mut unknown)) = unknown_fields
2533 && let Some(edited) = edit_json.get_mut("m.new_content")
2534 {
2535 unknown.remove("m.relates_to");
2537 unknown.remove("m.new_content");
2538 upload::restore_unknown_fields(edited, unknown.into());
2539 }
2540
2541 let serializable = SerializableEventContent::from_raw(
2543 Raw::from_json(
2544 serde_json::value::to_raw_value(&edit_json)
2545 .map_err(RoomSendQueueStorageError::JsonSerialization)?,
2546 ),
2547 edit_event.event_type().to_string(),
2548 );
2549
2550 store
2551 .save_send_queue_request(
2552 &self.room_id,
2553 dependent_request.own_transaction_id.into(),
2554 dependent_request.created_at,
2555 serializable.into(),
2556 Self::HIGH_PRIORITY,
2557 )
2558 .await
2559 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2560 } else {
2561 let parent_transaction_id = &dependent_request.parent_transaction_id;
2563
2564 let request = QueuedRequestKind::Event {
2565 content: new_content,
2566 #[cfg(feature = "unstable-msc4354")]
2567 sticky_duration: self
2568 .sticky_duration_of(store, parent_transaction_id)
2569 .await?,
2570 };
2571
2572 let edited = store
2573 .update_send_queue_request(&self.room_id, parent_transaction_id, request)
2574 .await
2575 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2576
2577 if !edited {
2578 warn!("missing local echo upon dependent edit");
2579 }
2580 }
2581 }
2582
2583 kind @ (DependentQueuedRequestKind::RedactEvent
2584 | DependentQueuedRequestKind::RedactEventWithReason { .. }) => {
2585 let reason = match kind {
2586 DependentQueuedRequestKind::RedactEventWithReason { reason } => reason,
2587 _ => None,
2589 };
2590
2591 if let Some(parent_key) = parent_key {
2592 let Some(event_id) = parent_key.into_event_id() else {
2593 return Err(RoomSendQueueError::StorageError(
2594 RoomSendQueueStorageError::InvalidParentKey,
2595 ));
2596 };
2597
2598 let room = client
2600 .get_room(&self.room_id)
2601 .ok_or(RoomSendQueueError::RoomDisappeared)?;
2602
2603 if let Err(err) = room
2609 .redact(
2610 &event_id,
2611 reason.as_deref(),
2612 Some(dependent_request.own_transaction_id.into()),
2613 )
2614 .await
2615 {
2616 warn!("error when sending a redact for {event_id}: {err}");
2617 return Ok(false);
2618 }
2619 } else {
2620 let removed = store
2623 .remove_send_queue_request(
2624 &self.room_id,
2625 &dependent_request.parent_transaction_id,
2626 )
2627 .await
2628 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2629
2630 if !removed {
2631 warn!("missing local echo upon dependent redact");
2632 }
2633 }
2634 }
2635
2636 DependentQueuedRequestKind::ReactEvent { key } => {
2637 if let Some(parent_key) = parent_key {
2638 let Some(parent_event_id) = parent_key.into_event_id() else {
2639 return Err(RoomSendQueueError::StorageError(
2640 RoomSendQueueStorageError::InvalidParentKey,
2641 ));
2642 };
2643
2644 let react_event =
2646 ReactionEventContent::new(Annotation::new(parent_event_id, key)).into();
2647 let serializable = SerializableEventContent::from_raw(
2648 Raw::new(&react_event)
2649 .map_err(RoomSendQueueStorageError::JsonSerialization)?,
2650 react_event.event_type().to_string(),
2651 );
2652
2653 store
2654 .save_send_queue_request(
2655 &self.room_id,
2656 dependent_request.own_transaction_id.into(),
2657 dependent_request.created_at,
2658 serializable.into(),
2659 Self::HIGH_PRIORITY,
2660 )
2661 .await
2662 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2663 } else {
2664 return Ok(false);
2666 }
2667 }
2668
2669 DependentQueuedRequestKind::UploadFileOrThumbnail {
2670 content_type,
2671 cache_key,
2672 related_to,
2673 parent_is_thumbnail_upload,
2674 } => {
2675 let Some(parent_key) = parent_key else {
2676 return Ok(false);
2678 };
2679 self.handle_dependent_file_or_thumbnail_upload(
2680 client,
2681 dependent_request.own_transaction_id.into(),
2682 parent_key,
2683 content_type,
2684 cache_key,
2685 related_to,
2686 parent_is_thumbnail_upload,
2687 )
2688 .await?;
2689 }
2690
2691 DependentQueuedRequestKind::FinishUpload {
2692 local_echo,
2693 file_upload,
2694 thumbnail_info,
2695 extra_content,
2696 } => {
2697 let Some(parent_key) = parent_key else {
2698 return Ok(false);
2700 };
2701 self.handle_dependent_finish_upload(
2702 client,
2703 dependent_request.own_transaction_id.into(),
2704 parent_key,
2705 *local_echo,
2706 file_upload,
2707 thumbnail_info,
2708 extra_content,
2709 new_updates,
2710 )
2711 .await?;
2712 }
2713
2714 #[cfg(feature = "unstable-msc4274")]
2715 DependentQueuedRequestKind::FinishGallery { local_echo, item_infos, extra_content } => {
2716 let Some(parent_key) = parent_key else {
2717 return Ok(false);
2719 };
2720 self.handle_dependent_finish_gallery_upload(
2721 client,
2722 dependent_request.own_transaction_id.into(),
2723 parent_key,
2724 *local_echo,
2725 item_infos,
2726 extra_content,
2727 new_updates,
2728 )
2729 .await?;
2730 }
2731 }
2732
2733 Ok(true)
2734 }
2735
2736 #[instrument(skip(self))]
2737 async fn apply_dependent_requests(
2738 &self,
2739 new_updates: &mut Vec<RoomSendQueueUpdate>,
2740 ) -> Result<(), RoomSendQueueError> {
2741 let guard = self.store.lock().await;
2742
2743 let client = guard.client()?;
2744 let store = client.state_store();
2745
2746 let dependent_requests = store
2747 .load_dependent_queued_requests(&self.room_id)
2748 .await
2749 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2750
2751 let num_initial_dependent_requests = dependent_requests.len();
2752 if num_initial_dependent_requests == 0 {
2753 return Ok(());
2755 }
2756
2757 let canonicalized_dependent_requests = canonicalize_dependent_requests(&dependent_requests);
2758
2759 for original in &dependent_requests {
2761 if !canonicalized_dependent_requests
2762 .iter()
2763 .any(|canonical| canonical.own_transaction_id == original.own_transaction_id)
2764 {
2765 store
2766 .remove_dependent_queued_request(&self.room_id, &original.own_transaction_id)
2767 .await
2768 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2769 }
2770 }
2771
2772 let mut num_dependent_requests = canonicalized_dependent_requests.len();
2773
2774 debug!(
2775 num_dependent_requests,
2776 num_initial_dependent_requests, "starting handling of dependent requests"
2777 );
2778
2779 for dependent in canonicalized_dependent_requests {
2780 let dependent_id = dependent.own_transaction_id.clone();
2781
2782 match self.try_apply_single_dependent_request(&client, dependent, new_updates).await {
2783 Ok(should_remove) => {
2784 if should_remove {
2785 store
2788 .remove_dependent_queued_request(&self.room_id, &dependent_id)
2789 .await
2790 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2791
2792 num_dependent_requests -= 1;
2793 }
2794 }
2795
2796 Err(err) => {
2797 warn!("error when applying single dependent request: {err}");
2798 }
2799 }
2800 }
2801
2802 debug!(
2803 leftover_dependent_requests = num_dependent_requests,
2804 "stopped handling dependent request"
2805 );
2806
2807 Ok(())
2808 }
2809
2810 async fn remove_dependent_send_queue_request(
2812 &self,
2813 dependent_event_id: &ChildTransactionId,
2814 ) -> Result<bool, RoomSendQueueStorageError> {
2815 Ok(self
2816 .store
2817 .lock()
2818 .await
2819 .client()?
2820 .state_store()
2821 .remove_dependent_queued_request(&self.room_id, dependent_event_id)
2822 .await?)
2823 }
2824}
2825
2826#[cfg(feature = "unstable-msc4274")]
2827struct GalleryItemQueueInfo {
2829 content_type: Mime,
2830 upload_file_txn: OwnedTransactionId,
2831 file_media_request: MediaRequestParameters,
2832 thumbnail: Option<QueueThumbnailInfo>,
2833 extra_content: Option<serde_json::Map<String, serde_json::Value>>,
2834}
2835
2836#[derive(Clone, Debug)]
2838pub enum LocalEchoContent {
2839 Event {
2841 serialized_event: SerializableEventContent,
2844 send_handle: SendHandle,
2846 send_error: Option<QueueWedgeError>,
2849 },
2850
2851 React {
2853 key: String,
2855 send_handle: SendHandle,
2857 applies_to: OwnedTransactionId,
2859 },
2860
2861 Redaction {
2863 redacts: OwnedEventId,
2865 reason: Option<String>,
2867 send_handle: SendHandle,
2869 send_error: Option<QueueWedgeError>,
2872 },
2873}
2874
2875#[derive(Clone, Debug)]
2878pub struct LocalEcho {
2879 pub transaction_id: OwnedTransactionId,
2881 pub content: LocalEchoContent,
2883}
2884
2885#[derive(Clone, Debug)]
2888pub enum RoomSendQueueUpdate {
2889 NewLocalEvent(LocalEcho),
2894
2895 CancelledLocalEvent {
2898 transaction_id: OwnedTransactionId,
2900 },
2901
2902 ReplacedLocalEvent {
2904 transaction_id: OwnedTransactionId,
2906
2907 new_content: SerializableEventContent,
2909 },
2910
2911 SendError {
2918 transaction_id: OwnedTransactionId,
2920 error: Arc<crate::Error>,
2922 is_recoverable: bool,
2928 },
2929
2930 RetryEvent {
2932 transaction_id: OwnedTransactionId,
2934 },
2935
2936 SentEvent {
2939 transaction_id: OwnedTransactionId,
2941 event_id: OwnedEventId,
2943 },
2944
2945 MediaUpload {
2948 related_to: OwnedTransactionId,
2950
2951 file: Option<MediaSource>,
2953
2954 index: u64,
2958
2959 progress: AbstractProgress,
2963 },
2964}
2965
2966#[derive(Clone, Debug)]
2971pub struct SendQueueUpdate {
2972 pub room_id: OwnedRoomId,
2974
2975 pub update: RoomSendQueueUpdate,
2977}
2978
2979#[derive(Debug, thiserror::Error)]
2981pub enum RoomSendQueueError {
2982 #[error("the room isn't in the joined state")]
2984 RoomNotJoined,
2985
2986 #[error("the room is now missing from the client")]
2990 RoomDisappeared,
2991
2992 #[error(transparent)]
2994 StorageError(#[from] RoomSendQueueStorageError),
2995
2996 #[error("the attachment event could not be created")]
2998 FailedToCreateAttachment,
2999
3000 #[error(transparent)]
3003 Edit(#[from] crate::room::edit::EditError),
3004
3005 #[cfg(feature = "unstable-msc4274")]
3007 #[error("the gallery contains no items")]
3008 EmptyGallery,
3009
3010 #[cfg(feature = "unstable-msc4274")]
3012 #[error("the gallery event could not be created")]
3013 FailedToCreateGallery,
3014}
3015
3016#[derive(Debug, thiserror::Error)]
3018pub enum RoomSendQueueStorageError {
3019 #[error(transparent)]
3021 StateStoreError(#[from] StoreError),
3022
3023 #[error(transparent)]
3025 EventCacheStoreError(#[from] EventCacheStoreError),
3026
3027 #[error(transparent)]
3029 MediaStoreError(#[from] MediaStoreError),
3030
3031 #[error(transparent)]
3033 LockError(#[from] CrossProcessLockError),
3034
3035 #[error(transparent)]
3037 JsonSerialization(#[from] serde_json::Error),
3038
3039 #[error("a dependent event had an invalid parent key type")]
3042 InvalidParentKey,
3043
3044 #[error("The client is shutting down.")]
3046 ClientShuttingDown,
3047
3048 #[error("This operation is not implemented for media uploads")]
3050 OperationNotImplementedYet,
3051
3052 #[error("Can't edit a media caption when the underlying event isn't a media")]
3054 InvalidMediaCaptionEdit,
3055}
3056
3057#[derive(Clone, Debug)]
3059struct MediaHandles {
3060 upload_thumbnail_txn: Option<OwnedTransactionId>,
3064
3065 upload_file_txn: OwnedTransactionId,
3067}
3068
3069#[allow(missing_debug_implementations)]
3071pub struct SendEvent<'a> {
3072 queue: &'a RoomSendQueue,
3073 content: AnyMessageLikeEventContent,
3074 extra_content: Option<serde_json::Map<String, serde_json::Value>>,
3075 #[cfg(feature = "unstable-msc4354")]
3076 sticky_duration: Option<StickyDurationMs>,
3077}
3078
3079impl<'a> SendEvent<'a> {
3080 pub fn with_extra_content(
3084 mut self,
3085 extra_content: serde_json::Map<String, serde_json::Value>,
3086 ) -> Self {
3087 self.extra_content = Some(extra_content);
3088 self
3089 }
3090
3091 #[cfg(feature = "unstable-msc4354")]
3097 pub fn with_sticky_duration(mut self, duration: Duration) -> Self {
3098 self.sticky_duration = Some(sticky_duration_ms(duration));
3099 self
3100 }
3101}
3102
3103impl<'a> IntoFuture for SendEvent<'a> {
3104 type Output = Result<SendHandle, RoomSendQueueError>;
3105 boxed_into_future!(extra_bounds: 'a);
3106
3107 fn into_future(self) -> Self::IntoFuture {
3108 Box::pin(async move {
3109 let serialized = upload::merge_extra_content(
3110 SerializableEventContent::new(&self.content)
3111 .map_err(RoomSendQueueStorageError::JsonSerialization)?,
3112 self.extra_content,
3113 )?;
3114 self.queue
3115 .send_serialized(
3116 serialized,
3117 #[cfg(feature = "unstable-msc4354")]
3118 self.sticky_duration,
3119 )
3120 .await
3121 })
3122 }
3123}
3124
3125#[allow(missing_debug_implementations)]
3127pub struct SendRawEvent<'a> {
3128 queue: &'a RoomSendQueue,
3129 content: SerializableEventContent,
3130 #[cfg(feature = "unstable-msc4354")]
3131 sticky_duration: Option<StickyDurationMs>,
3132}
3133
3134impl SendRawEvent<'_> {
3135 #[cfg(feature = "unstable-msc4354")]
3141 pub fn with_sticky_duration(mut self, duration: Duration) -> Self {
3142 self.sticky_duration = Some(sticky_duration_ms(duration));
3143 self
3144 }
3145}
3146
3147impl<'a> IntoFuture for SendRawEvent<'a> {
3148 type Output = Result<SendHandle, RoomSendQueueError>;
3149 boxed_into_future!(extra_bounds: 'a);
3150
3151 fn into_future(self) -> Self::IntoFuture {
3152 Box::pin(async move {
3153 self.queue
3154 .send_serialized(
3155 self.content,
3156 #[cfg(feature = "unstable-msc4354")]
3157 self.sticky_duration,
3158 )
3159 .await
3160 })
3161 }
3162}
3163
3164#[derive(Clone, Debug)]
3170pub struct SendHandle {
3171 room: RoomSendQueue,
3173
3174 transaction_id: OwnedTransactionId,
3179
3180 media_handles: Vec<MediaHandles>,
3182
3183 pub created_at: MilliSecondsSinceUnixEpoch,
3185}
3186
3187impl SendHandle {
3188 #[cfg(test)]
3190 pub(crate) fn new(
3191 room: RoomSendQueue,
3192 transaction_id: OwnedTransactionId,
3193 created_at: MilliSecondsSinceUnixEpoch,
3194 ) -> Self {
3195 Self { room, transaction_id, media_handles: vec![], created_at }
3196 }
3197
3198 pub fn transaction_id(&self) -> &TransactionId {
3200 &self.transaction_id
3201 }
3202
3203 fn nyi_for_uploads(&self) -> Result<(), RoomSendQueueStorageError> {
3204 if !self.media_handles.is_empty() {
3205 Err(RoomSendQueueStorageError::OperationNotImplementedYet)
3206 } else {
3207 Ok(())
3208 }
3209 }
3210
3211 pub async fn abort(&self) -> Result<bool, RoomSendQueueStorageError> {
3216 self.abort_with_reason(None).await
3217 }
3218
3219 #[instrument(skip(self), fields(room_id = %self.room.inner.room.room_id(), txn_id = %self.transaction_id))]
3230 pub async fn abort_with_reason(
3231 &self,
3232 reason: Option<String>,
3233 ) -> Result<bool, RoomSendQueueStorageError> {
3234 trace!("received an abort request");
3235
3236 let queue = &self.room.inner.queue;
3237
3238 for handles in &self.media_handles {
3239 if queue.abort_upload(&self.transaction_id, handles).await? {
3240 self.room.inner.notifier.notify_one();
3243
3244 self.room.send_update(RoomSendQueueUpdate::CancelledLocalEvent {
3246 transaction_id: self.transaction_id.clone(),
3247 });
3248
3249 return Ok(true);
3250 }
3251
3252 }
3256
3257 let aborted =
3261 queue.remove_dependent_send_queue_request(&self.transaction_id.clone().into()).await?
3262 || queue.cancel_event(&self.transaction_id, reason).await?;
3263
3264 if aborted {
3265 trace!("successful abort");
3266
3267 self.room.inner.notifier.notify_one();
3270
3271 self.room.send_update(RoomSendQueueUpdate::CancelledLocalEvent {
3273 transaction_id: self.transaction_id.clone(),
3274 });
3275
3276 Ok(true)
3277 } else {
3278 debug!("local echo didn't exist anymore, can't abort");
3279 Ok(false)
3280 }
3281 }
3282
3283 #[instrument(skip(self, new_content), fields(room_id = %self.room.inner.room.room_id(), txn_id = %self.transaction_id))]
3296 pub async fn edit_raw(
3297 &self,
3298 new_content: Raw<AnyMessageLikeEventContent>,
3299 event_type: String,
3300 ) -> Result<bool, RoomSendQueueStorageError> {
3301 trace!("received an edit request");
3302 self.nyi_for_uploads()?;
3303
3304 let serializable = SerializableEventContent::from_raw(new_content, event_type);
3305
3306 if self.room.inner.queue.replace_event(&self.transaction_id, serializable.clone()).await? {
3307 trace!("successful edit");
3308
3309 self.room.inner.notifier.notify_one();
3311
3312 self.room.send_update(RoomSendQueueUpdate::ReplacedLocalEvent {
3314 transaction_id: self.transaction_id.clone(),
3315 new_content: serializable,
3316 });
3317
3318 Ok(true)
3319 } else {
3320 debug!("local echo doesn't exist anymore, can't edit");
3321 Ok(false)
3322 }
3323 }
3324
3325 pub async fn edit(
3338 &self,
3339 new_content: AnyMessageLikeEventContent,
3340 ) -> Result<bool, RoomSendQueueStorageError> {
3341 self.edit_raw(
3342 Raw::new(&new_content).map_err(RoomSendQueueStorageError::JsonSerialization)?,
3343 new_content.event_type().to_string(),
3344 )
3345 .await
3346 }
3347
3348 pub async fn edit_media_caption(
3353 &self,
3354 caption: Option<String>,
3355 formatted_caption: Option<FormattedBody>,
3356 mentions: Option<Mentions>,
3357 ) -> Result<bool, RoomSendQueueStorageError> {
3358 if let Some(new_content) = self
3359 .room
3360 .inner
3361 .queue
3362 .edit_media_caption(&self.transaction_id, caption, formatted_caption, mentions)
3363 .await?
3364 {
3365 trace!("successful edit of media caption");
3366
3367 self.room.inner.notifier.notify_one();
3369
3370 self.room.send_update(RoomSendQueueUpdate::ReplacedLocalEvent {
3372 transaction_id: self.transaction_id.clone(),
3373 new_content,
3374 });
3375
3376 Ok(true)
3377 } else {
3378 debug!("local echo doesn't exist anymore, can't edit media caption");
3379 Ok(false)
3380 }
3381 }
3382
3383 pub async fn unwedge(&self) -> Result<(), RoomSendQueueError> {
3386 let room = &self.room.inner;
3387
3388 for handles in &self.media_handles {
3396 room.queue
3397 .mark_as_unwedged(&handles.upload_file_txn)
3398 .await
3399 .map_err(RoomSendQueueError::StorageError)?;
3400
3401 if let Some(txn) = &handles.upload_thumbnail_txn {
3402 room.queue.mark_as_unwedged(txn).await.map_err(RoomSendQueueError::StorageError)?;
3403 }
3404 }
3405
3406 self.room.unwedge_request(&self.transaction_id).await
3407 }
3408
3409 #[instrument(skip(self), fields(room_id = %self.room.inner.room.room_id(), txn_id = %self.transaction_id))]
3414 pub async fn react(
3415 &self,
3416 key: String,
3417 ) -> Result<Option<SendHandle>, RoomSendQueueStorageError> {
3418 trace!("received an intent to react");
3419
3420 let created_at = MilliSecondsSinceUnixEpoch::now();
3421 if let Some(reaction_txn_id) =
3422 self.room.inner.queue.react(&self.transaction_id, key.clone(), created_at).await?
3423 {
3424 trace!("successfully queued react");
3425
3426 self.room.inner.notifier.notify_one();
3429
3430 let send_handle = SendHandle {
3432 room: self.room.clone(),
3433 transaction_id: reaction_txn_id.clone().into(),
3434 media_handles: vec![],
3435 created_at,
3436 };
3437
3438 self.room.send_update(RoomSendQueueUpdate::NewLocalEvent(LocalEcho {
3439 transaction_id: reaction_txn_id.into(),
3442 content: LocalEchoContent::React {
3443 key,
3444 send_handle: send_handle.clone(),
3445 applies_to: self.transaction_id.clone(),
3446 },
3447 }));
3448
3449 Ok(Some(send_handle))
3450 } else {
3451 debug!("local echo doesn't exist anymore, can't react");
3452 Ok(None)
3453 }
3454 }
3455}
3456
3457fn is_own_event_request(request: &QueuedRequest) -> bool {
3465 request.as_event().is_some_and(|content| {
3466 TimelineEventType::from(content.raw().1) != TimelineEventType::Reaction
3467 })
3468}
3469
3470fn canonicalize_dependent_requests(
3474 dependent: &[DependentQueuedRequest],
3475) -> Vec<DependentQueuedRequest> {
3476 let mut by_txn = HashMap::<OwnedTransactionId, Vec<&DependentQueuedRequest>>::new();
3477
3478 for d in dependent {
3479 let prevs = by_txn.entry(d.parent_transaction_id.clone()).or_default();
3480
3481 if prevs.iter().any(|prev| {
3482 matches!(
3483 prev.kind,
3484 DependentQueuedRequestKind::RedactEvent
3485 | DependentQueuedRequestKind::RedactEventWithReason { .. }
3486 )
3487 }) {
3488 continue;
3491 }
3492
3493 match &d.kind {
3494 DependentQueuedRequestKind::EditEvent { .. } => {
3495 if let Some(prev_edit) = prevs
3497 .iter_mut()
3498 .find(|prev| matches!(prev.kind, DependentQueuedRequestKind::EditEvent { .. }))
3499 {
3500 *prev_edit = d;
3501 } else {
3502 prevs.insert(0, d);
3503 }
3504 }
3505
3506 DependentQueuedRequestKind::UploadFileOrThumbnail { .. }
3507 | DependentQueuedRequestKind::FinishUpload { .. }
3508 | DependentQueuedRequestKind::ReactEvent { .. } => {
3509 prevs.push(d);
3511 }
3512
3513 #[cfg(feature = "unstable-msc4274")]
3514 DependentQueuedRequestKind::FinishGallery { .. } => {
3515 prevs.push(d);
3517 }
3518
3519 DependentQueuedRequestKind::RedactEvent
3520 | DependentQueuedRequestKind::RedactEventWithReason { .. } => {
3521 prevs.clear();
3523 prevs.push(d);
3524 }
3525 }
3526 }
3527
3528 by_txn.into_values().flat_map(|entries| entries.into_iter().cloned()).collect()
3529}
3530
3531fn build_timeline_event(
3532 event_id: &EventId,
3533 now: UInt,
3534 sender: &UserId,
3535 event_type: &str,
3536 content: Box<serde_json::value::RawValue>,
3537) -> serde_json::Value {
3538 serde_json::json!({
3539 "event_id": event_id,
3540 "origin_server_ts": now,
3541 "sender": sender,
3542 "type": event_type,
3543 "content": content
3544 })
3545}
3546
3547fn build_redaction_event(
3548 event_id: &EventId,
3549 now: UInt,
3550 sender: &UserId,
3551 redacts: &EventId,
3552 reason: Option<&str>,
3553 content_field_redacts: bool,
3554) -> serde_json::Value {
3555 let mut event = serde_json::json!({
3556 "event_id": event_id,
3557 "origin_server_ts": now,
3558 "sender": sender,
3559 "type": TimelineEventType::RoomRedaction,
3560 "content": {},
3561 });
3562
3563 if content_field_redacts {
3564 event["content"]["redacts"] = redacts.to_string().into();
3565 } else {
3566 event["redacts"] = redacts.to_string().into();
3567 }
3568
3569 if let Some(reason) = reason {
3570 event["content"]["reason"] = reason.into();
3571 }
3572
3573 event
3574}
3575
3576#[cfg(all(test, not(target_family = "wasm")))]
3577mod tests {
3578 use std::{assert_matches, sync::Arc, time::Duration};
3579
3580 use matrix_sdk_base::store::{
3581 ChildTransactionId, DependentQueuedRequest, DependentQueuedRequestKind,
3582 SerializableEventContent,
3583 };
3584 use matrix_sdk_test::{JoinedRoomBuilder, SyncResponseBuilder, async_test};
3585 use ruma::{
3586 MilliSecondsSinceUnixEpoch, TransactionId, UInt,
3587 api::error::UnknownTokenErrorData,
3588 event_id,
3589 events::{AnyMessageLikeEventContent, room::message::RoomMessageEventContent},
3590 room_id, user_id,
3591 };
3592 use strass::assert_let;
3593
3594 use super::{
3595 RoomSendQueueUpdate, build_redaction_event, build_timeline_event,
3596 canonicalize_dependent_requests,
3597 };
3598 use crate::{
3599 SessionChange,
3600 test_utils::{logged_in_client, mocks::MatrixMockServer},
3601 };
3602
3603 #[test]
3604 fn test_canonicalize_dependent_events_created_at() {
3605 let txn = TransactionId::new();
3608 let created_at = MilliSecondsSinceUnixEpoch::now();
3609
3610 let edit = DependentQueuedRequest {
3611 own_transaction_id: ChildTransactionId::new(),
3612 parent_transaction_id: txn.clone(),
3613 kind: DependentQueuedRequestKind::EditEvent {
3614 new_content: SerializableEventContent::new(
3615 &RoomMessageEventContent::text_plain("edit").into(),
3616 )
3617 .unwrap(),
3618 },
3619 parent_key: None,
3620 created_at,
3621 };
3622
3623 let res = canonicalize_dependent_requests(&[edit]);
3624
3625 assert_eq!(res.len(), 1);
3626 assert_let!(DependentQueuedRequestKind::EditEvent { new_content } = &res[0].kind);
3627 assert_let!(
3628 AnyMessageLikeEventContent::RoomMessage(msg) = new_content.deserialize().unwrap()
3629 );
3630 assert_eq!(msg.body(), "edit");
3631 assert_eq!(res[0].parent_transaction_id, txn);
3632 assert_eq!(res[0].created_at, created_at);
3633 }
3634
3635 #[async_test]
3636 async fn test_client_no_cycle_with_send_queue() {
3637 for enabled in [true, false] {
3638 let client = logged_in_client(None).await;
3639 let weak_client = client.downgrade();
3640
3641 {
3642 let mut sync_response_builder = SyncResponseBuilder::new();
3643
3644 let room_id = room_id!("!a:b.c");
3645
3646 client
3648 .base_client()
3649 .receive_sync_response(
3650 sync_response_builder
3651 .add_joined_room(JoinedRoomBuilder::new(room_id))
3652 .build_sync_response(),
3653 )
3654 .await
3655 .unwrap();
3656
3657 let room = client.get_room(room_id).unwrap();
3658 let q = room.send_queue();
3659
3660 let _watcher = q.subscribe().await;
3661
3662 client.send_queue().set_enabled(enabled).await;
3663 }
3664
3665 drop(client);
3666
3667 tokio::time::sleep(Duration::from_millis(500)).await;
3669
3670 let client = weak_client.get();
3672 assert!(
3673 client.is_none(),
3674 "too many strong references to the client: {}",
3675 Arc::strong_count(&client.unwrap().inner)
3676 );
3677 }
3678 }
3679
3680 #[test]
3681 fn test_canonicalize_dependent_events_smoke_test() {
3682 let txn = TransactionId::new();
3684
3685 let edit = DependentQueuedRequest {
3686 own_transaction_id: ChildTransactionId::new(),
3687 parent_transaction_id: txn.clone(),
3688 kind: DependentQueuedRequestKind::EditEvent {
3689 new_content: SerializableEventContent::new(
3690 &RoomMessageEventContent::text_plain("edit").into(),
3691 )
3692 .unwrap(),
3693 },
3694 parent_key: None,
3695 created_at: MilliSecondsSinceUnixEpoch::now(),
3696 };
3697 let res = canonicalize_dependent_requests(&[edit]);
3698
3699 assert_eq!(res.len(), 1);
3700 assert_matches!(&res[0].kind, DependentQueuedRequestKind::EditEvent { .. });
3701 assert_eq!(res[0].parent_transaction_id, txn);
3702 assert!(res[0].parent_key.is_none());
3703 }
3704
3705 #[test]
3706 fn test_canonicalize_dependent_events_redaction_preferred() {
3707 let txn = TransactionId::new();
3709
3710 let mut inputs = Vec::with_capacity(100);
3711 let redact = DependentQueuedRequest {
3712 own_transaction_id: ChildTransactionId::new(),
3713 parent_transaction_id: txn.clone(),
3714 kind: DependentQueuedRequestKind::RedactEventWithReason { reason: None },
3715 parent_key: None,
3716 created_at: MilliSecondsSinceUnixEpoch::now(),
3717 };
3718
3719 let edit = DependentQueuedRequest {
3720 own_transaction_id: ChildTransactionId::new(),
3721 parent_transaction_id: txn.clone(),
3722 kind: DependentQueuedRequestKind::EditEvent {
3723 new_content: SerializableEventContent::new(
3724 &RoomMessageEventContent::text_plain("edit").into(),
3725 )
3726 .unwrap(),
3727 },
3728 parent_key: None,
3729 created_at: MilliSecondsSinceUnixEpoch::now(),
3730 };
3731
3732 inputs.push({
3733 let mut edit = edit.clone();
3734 edit.own_transaction_id = ChildTransactionId::new();
3735 edit
3736 });
3737
3738 inputs.push(redact);
3739
3740 for _ in 0..98 {
3741 let mut edit = edit.clone();
3742 edit.own_transaction_id = ChildTransactionId::new();
3743 inputs.push(edit);
3744 }
3745
3746 let res = canonicalize_dependent_requests(&inputs);
3747
3748 assert_eq!(res.len(), 1);
3749 assert_matches!(&res[0].kind, DependentQueuedRequestKind::RedactEventWithReason { .. });
3750 assert_eq!(res[0].parent_transaction_id, txn);
3751 }
3752
3753 #[test]
3754 fn test_canonicalize_dependent_events_last_edit_preferred() {
3755 let parent_txn = TransactionId::new();
3756
3757 let inputs = (0..10)
3759 .map(|i| DependentQueuedRequest {
3760 own_transaction_id: ChildTransactionId::new(),
3761 parent_transaction_id: parent_txn.clone(),
3762 kind: DependentQueuedRequestKind::EditEvent {
3763 new_content: SerializableEventContent::new(
3764 &RoomMessageEventContent::text_plain(format!("edit{i}")).into(),
3765 )
3766 .unwrap(),
3767 },
3768 parent_key: None,
3769 created_at: MilliSecondsSinceUnixEpoch::now(),
3770 })
3771 .collect::<Vec<_>>();
3772
3773 let txn = inputs[9].parent_transaction_id.clone();
3774
3775 let res = canonicalize_dependent_requests(&inputs);
3776
3777 assert_eq!(res.len(), 1);
3778 assert_let!(DependentQueuedRequestKind::EditEvent { new_content } = &res[0].kind);
3779 assert_let!(
3780 AnyMessageLikeEventContent::RoomMessage(msg) = new_content.deserialize().unwrap()
3781 );
3782 assert_eq!(msg.body(), "edit9");
3783 assert_eq!(res[0].parent_transaction_id, txn);
3784 }
3785
3786 #[test]
3787 fn test_canonicalize_multiple_local_echoes() {
3788 let txn1 = TransactionId::new();
3789 let txn2 = TransactionId::new();
3790
3791 let child1 = ChildTransactionId::new();
3792 let child2 = ChildTransactionId::new();
3793
3794 let inputs = vec![
3795 DependentQueuedRequest {
3797 own_transaction_id: child1.clone(),
3798 kind: DependentQueuedRequestKind::RedactEventWithReason { reason: None },
3799 parent_transaction_id: txn1.clone(),
3800 parent_key: None,
3801 created_at: MilliSecondsSinceUnixEpoch::now(),
3802 },
3803 DependentQueuedRequest {
3805 own_transaction_id: child2,
3806 kind: DependentQueuedRequestKind::EditEvent {
3807 new_content: SerializableEventContent::new(
3808 &RoomMessageEventContent::text_plain("edit").into(),
3809 )
3810 .unwrap(),
3811 },
3812 parent_transaction_id: txn2.clone(),
3813 parent_key: None,
3814 created_at: MilliSecondsSinceUnixEpoch::now(),
3815 },
3816 ];
3817
3818 let res = canonicalize_dependent_requests(&inputs);
3819
3820 assert_eq!(res.len(), 2);
3822
3823 for dependent in res {
3824 if dependent.own_transaction_id == child1 {
3825 assert_eq!(dependent.parent_transaction_id, txn1);
3826 assert_matches!(
3827 dependent.kind,
3828 DependentQueuedRequestKind::RedactEventWithReason { .. }
3829 );
3830 } else {
3831 assert_eq!(dependent.parent_transaction_id, txn2);
3832 assert_matches!(dependent.kind, DependentQueuedRequestKind::EditEvent { .. });
3833 }
3834 }
3835 }
3836
3837 #[test]
3838 fn test_canonicalize_reactions_after_edits() {
3839 let txn = TransactionId::new();
3841
3842 let react_id = ChildTransactionId::new();
3843 let react = DependentQueuedRequest {
3844 own_transaction_id: react_id.clone(),
3845 kind: DependentQueuedRequestKind::ReactEvent { key: "🧠".to_owned() },
3846 parent_transaction_id: txn.clone(),
3847 parent_key: None,
3848 created_at: MilliSecondsSinceUnixEpoch::now(),
3849 };
3850
3851 let edit_id = ChildTransactionId::new();
3852 let edit = DependentQueuedRequest {
3853 own_transaction_id: edit_id.clone(),
3854 kind: DependentQueuedRequestKind::EditEvent {
3855 new_content: SerializableEventContent::new(
3856 &RoomMessageEventContent::text_plain("edit").into(),
3857 )
3858 .unwrap(),
3859 },
3860 parent_transaction_id: txn,
3861 parent_key: None,
3862 created_at: MilliSecondsSinceUnixEpoch::now(),
3863 };
3864
3865 let res = canonicalize_dependent_requests(&[react, edit]);
3866
3867 assert_eq!(res.len(), 2);
3868 assert_eq!(res[0].own_transaction_id, edit_id);
3869 assert_eq!(res[1].own_transaction_id, react_id);
3870 }
3871
3872 #[async_test]
3873 async fn test_request_wedged_by_a_rejected_token_is_unwedged_on_lag() {
3874 let mock = MatrixMockServer::new().await;
3875 let client = mock.client_builder().build().await;
3876 let room = mock.sync_joined_room(&client, room_id!("!a:b.c")).await;
3877
3878 let q = room.send_queue();
3879 let (_, mut watch) = q.subscribe().await.unwrap();
3880
3881 mock.mock_room_state_encryption().plain().mount().await;
3882 mock.mock_room_send().error_unknown_token(false).mock_once().mount().await;
3883 mock.mock_room_send().ok(event_id!("$42")).mock_once().mount().await;
3884
3885 q.send(RoomMessageEventContent::text_plain("hello").into()).await.unwrap();
3886
3887 assert_let!(Ok(RoomSendQueueUpdate::NewLocalEvent(_)) = watch.recv().await);
3888 assert_let!(
3889 Ok(RoomSendQueueUpdate::SendError { is_recoverable: false, .. }) = watch.recv().await
3890 );
3891
3892 let sender = &client.auth_ctx().session_change_sender;
3895 sender.send(SessionChange::TokensRefreshed).unwrap();
3896 sender.send(SessionChange::UnknownToken(UnknownTokenErrorData::new())).unwrap();
3897
3898 assert_let!(Ok(RoomSendQueueUpdate::RetryEvent { .. }) = watch.recv().await);
3900 assert_let!(Ok(RoomSendQueueUpdate::SentEvent { .. }) = watch.recv().await);
3901 }
3902
3903 #[test]
3904 fn test_build_timeline_event() {
3905 let content = serde_json::value::to_raw_value(&serde_json::json!({
3906 "msgtype": "m.text",
3907 "body": "some text",
3908 }))
3909 .unwrap();
3910
3911 let now = UInt::new(1_700_000_000_000).unwrap();
3912
3913 let event = build_timeline_event(
3914 event_id!("$123"),
3915 now,
3916 user_id!("@bob:matrix.org"),
3917 "m.room.message",
3918 content,
3919 );
3920
3921 assert_eq!(event["event_id"], "$123");
3922 assert_eq!(event["origin_server_ts"].as_u64(), Some(now.into()));
3923 assert_eq!(event["sender"], "@bob:matrix.org");
3924 assert_eq!(event["type"], "m.room.message");
3925 assert_eq!(event["content"]["msgtype"], "m.text");
3926 assert_eq!(event["content"]["body"], "some text");
3927 }
3928
3929 #[test]
3930 fn test_build_redaction_event_no_content_field_redacts() {
3931 let now = UInt::new(1_700_000_000_000).unwrap();
3932
3933 let event = build_redaction_event(
3934 event_id!("$123"),
3935 now,
3936 user_id!("@alice:matrix.org"),
3937 event_id!("$1234"),
3938 Some("reason"),
3939 false,
3940 );
3941
3942 assert_eq!(event["event_id"], "$123");
3943 assert_eq!(event["origin_server_ts"].as_u64(), Some(now.into()));
3944 assert_eq!(event["sender"], "@alice:matrix.org");
3945 assert_eq!(event["type"], "m.room.redaction");
3946 assert_eq!(event["redacts"], "$1234");
3947 assert_eq!(event["content"]["reason"], "reason");
3948 }
3949
3950 #[test]
3951 fn test_build_redaction_event_content_field_redacts() {
3952 let now = UInt::new(1_700_000_000_000).unwrap();
3953
3954 let event = build_redaction_event(
3955 event_id!("$123"),
3956 now,
3957 user_id!("@alice:matrix.org"),
3958 event_id!("$1234"),
3959 Some("reason"),
3960 true,
3961 );
3962
3963 assert_eq!(event["event_id"], "$123");
3964 assert_eq!(event["origin_server_ts"].as_u64(), Some(now.into()));
3965 assert_eq!(event["sender"], "@alice:matrix.org");
3966 assert_eq!(event["type"], "m.room.redaction");
3967 assert_eq!(event["content"]["redacts"], "$1234");
3968 assert_eq!(event["content"]["reason"], "reason");
3969 }
3970}