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;
169#[cfg(feature = "unstable-msc4354")]
170use ruma::events::sticky::StickyDurationMs;
171use ruma::{
172 MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, OwnedTransactionId, RoomId,
173 TransactionId,
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, 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 #[inline(always)]
250 fn data(&self) -> &SendQueueData {
251 &self.client.inner.send_queue_data
252 }
253
254 pub(crate) fn for_room(&self, room: Room) -> RoomSendQueue {
257 let data = self.data();
258
259 let mut map = data.rooms.write().unwrap();
260
261 let room_id = room.room_id();
262 if let Some(room_q) = map.get(room_id).cloned() {
263 return room_q;
264 }
265
266 let owned_room_id = room_id.to_owned();
267 let room_q = RoomSendQueue::new(
268 self.is_enabled(),
269 data.global_update_sender.clone(),
270 data.error_sender.clone(),
271 data.is_dropping.clone(),
272 &self.client,
273 owned_room_id.clone(),
274 data.report_media_upload_progress.clone(),
275 );
276
277 map.insert(owned_room_id, room_q.clone());
278
279 room_q
280 }
281
282 pub async fn set_enabled(&self, enabled: bool) {
292 debug!(?enabled, "setting global send queue enablement");
293
294 self.data().globally_enabled.store(enabled, Ordering::SeqCst);
295
296 for room in self.data().rooms.read().unwrap().values() {
298 room.set_enabled(enabled);
299 }
300
301 self.respawn_tasks_for_rooms_with_unsent_requests().await;
304 }
305
306 pub fn is_enabled(&self) -> bool {
308 self.data().globally_enabled.load(Ordering::SeqCst)
309 }
310
311 pub fn enable_upload_progress(&self, enabled: bool) {
313 self.data().report_media_upload_progress.store(enabled, Ordering::SeqCst);
314 }
315
316 pub fn subscribe(&self) -> broadcast::Receiver<SendQueueUpdate> {
321 self.data().global_update_sender.subscribe()
322 }
323
324 pub async fn local_echoes(
326 &self,
327 ) -> Result<BTreeMap<OwnedRoomId, Vec<LocalEcho>>, RoomSendQueueError> {
328 let room_ids =
329 self.client.state_store().load_rooms_with_unsent_requests().await.unwrap_or_else(
330 |err| {
331 warn!("error when loading rooms with unsent requests: {err}");
332 Vec::new()
333 },
334 );
335
336 let mut local_echoes: BTreeMap<OwnedRoomId, Vec<LocalEcho>> = BTreeMap::new();
337
338 for room_id in room_ids {
339 if let Some(room) = self.client.get_room(&room_id) {
340 let queue = self.for_room(room);
341 local_echoes
342 .insert(room_id.to_owned(), queue.inner.queue.local_echoes(&queue).await?);
343 }
344 }
345
346 Ok(local_echoes)
347 }
348
349 pub fn subscribe_errors(&self) -> broadcast::Receiver<SendQueueRoomError> {
352 self.data().error_sender.subscribe()
353 }
354}
355
356#[derive(Clone, Debug)]
359struct QueueThumbnailInfo {
360 finish_upload_thumbnail_info: FinishUploadThumbnailInfo,
362
363 media_request_parameters: MediaRequestParameters,
365
366 content_type: Mime,
368
369 file_size: usize,
371}
372
373#[derive(Clone, Debug)]
375pub struct SendQueueRoomError {
376 pub room_id: OwnedRoomId,
378
379 pub error: Arc<crate::Error>,
381
382 pub is_recoverable: bool,
387}
388
389impl Client {
390 pub fn send_queue(&self) -> SendQueue {
393 SendQueue::new(self.clone())
394 }
395}
396
397pub(super) struct SendQueueData {
398 rooms: RwLock<BTreeMap<OwnedRoomId, RoomSendQueue>>,
400
401 globally_enabled: AtomicBool,
406
407 global_update_sender: broadcast::Sender<SendQueueUpdate>,
411
412 error_sender: broadcast::Sender<SendQueueRoomError>,
414
415 is_dropping: Arc<AtomicBool>,
417
418 report_media_upload_progress: Arc<AtomicBool>,
420}
421
422impl SendQueueData {
423 pub fn new(globally_enabled: bool) -> Self {
425 let (global_update_sender, _) = broadcast::channel(32);
426 let (error_sender, _) = broadcast::channel(32);
427
428 Self {
429 rooms: Default::default(),
430 globally_enabled: AtomicBool::new(globally_enabled),
431 global_update_sender,
432 error_sender,
433 is_dropping: Arc::new(false.into()),
434 report_media_upload_progress: Arc::new(false.into()),
435 }
436 }
437}
438
439impl Drop for SendQueueData {
440 fn drop(&mut self) {
441 debug!("globally dropping the send queue");
444 self.is_dropping.store(true, Ordering::SeqCst);
445
446 let rooms = self.rooms.read().unwrap();
447 for room in rooms.values() {
448 room.inner.notifier.notify_one();
449 }
450 }
451}
452
453impl Room {
454 pub fn send_queue(&self) -> RoomSendQueue {
456 self.client.send_queue().for_room(self.clone())
457 }
458}
459
460#[derive(Clone)]
464pub struct RoomSendQueue {
465 inner: Arc<RoomSendQueueInner>,
466}
467
468#[cfg(not(tarpaulin_include))]
469impl std::fmt::Debug for RoomSendQueue {
470 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
471 f.debug_struct("RoomSendQueue").finish_non_exhaustive()
472 }
473}
474
475impl RoomSendQueue {
476 fn new(
477 globally_enabled: bool,
478 global_update_sender: broadcast::Sender<SendQueueUpdate>,
479 global_error_sender: broadcast::Sender<SendQueueRoomError>,
480 is_dropping: Arc<AtomicBool>,
481 client: &Client,
482 room_id: OwnedRoomId,
483 report_media_upload_progress: Arc<AtomicBool>,
484 ) -> Self {
485 let (update_sender, _) = broadcast::channel(32);
486
487 let queue = QueueStorage::new(WeakClient::from_client(client), room_id.clone());
488 let notifier = Arc::new(Notify::new());
489
490 let weak_room = WeakRoom::new(WeakClient::from_client(client), room_id);
491 let locally_enabled = Arc::new(AtomicBool::new(globally_enabled));
492
493 let task = client.task_monitor().spawn_infinite_task(
494 "send_queue",
495 Self::sending_task(
496 weak_room.clone(),
497 queue.clone(),
498 notifier.clone(),
499 global_update_sender.clone(),
500 update_sender.clone(),
501 locally_enabled.clone(),
502 global_error_sender,
503 is_dropping,
504 report_media_upload_progress,
505 ),
506 );
507
508 Self {
509 inner: Arc::new(RoomSendQueueInner {
510 room: weak_room,
511 global_update_sender,
512 update_sender,
513 _task: task,
514 queue,
515 notifier,
516 locally_enabled,
517 }),
518 }
519 }
520
521 pub fn send_raw(
536 &self,
537 content: Raw<AnyMessageLikeEventContent>,
538 event_type: String,
539 ) -> SendRawEvent<'_> {
540 SendRawEvent {
541 queue: self,
542 content: SerializableEventContent::from_raw(content, event_type),
543 #[cfg(feature = "unstable-msc4354")]
544 sticky_duration: None,
545 }
546 }
547
548 async fn send_serialized(
550 &self,
551 content: SerializableEventContent,
552 #[cfg(feature = "unstable-msc4354")] sticky_duration: Option<StickyDurationMs>,
553 ) -> Result<SendHandle, RoomSendQueueError> {
554 let Some(room) = self.inner.room.get() else {
555 return Err(RoomSendQueueError::RoomDisappeared);
556 };
557 if room.state() != RoomState::Joined {
558 return Err(RoomSendQueueError::RoomNotJoined);
559 }
560
561 let request = QueuedRequestKind::Event {
562 content: content.clone(),
563 #[cfg(feature = "unstable-msc4354")]
564 sticky_duration,
565 };
566
567 let created_at = MilliSecondsSinceUnixEpoch::now();
568 let transaction_id = self.inner.queue.push(request, created_at).await?;
569 trace!(%transaction_id, "manager sends a raw event to the background task");
570
571 self.inner.notifier.notify_one();
572
573 let send_handle = SendHandle {
574 room: self.clone(),
575 transaction_id: transaction_id.clone(),
576 media_handles: vec![],
577 created_at,
578 };
579
580 self.send_update(RoomSendQueueUpdate::NewLocalEvent(LocalEcho {
581 transaction_id,
582 content: LocalEchoContent::Event {
583 serialized_event: content,
584 send_handle: send_handle.clone(),
585 send_error: None,
586 },
587 }));
588
589 Ok(send_handle)
590 }
591
592 pub fn send(&self, content: AnyMessageLikeEventContent) -> SendEvent<'_> {
607 SendEvent {
608 queue: self,
609 content,
610 extra_content: None,
611 #[cfg(feature = "unstable-msc4354")]
612 sticky_duration: None,
613 }
614 }
615
616 pub async fn redact(
631 &self,
632 redacts: OwnedEventId,
633 reason: Option<&str>,
634 ) -> Result<SendHandle, RoomSendQueueError> {
635 let Some(room) = self.inner.room.get() else {
636 return Err(RoomSendQueueError::RoomDisappeared);
637 };
638 if room.state() != RoomState::Joined {
639 return Err(RoomSendQueueError::RoomNotJoined);
640 }
641
642 let request = QueuedRequestKind::Redaction {
643 redacts: redacts.clone(),
644 reason: reason.map(str::to_owned),
645 };
646
647 let created_at = MilliSecondsSinceUnixEpoch::now();
648 let transaction_id = self.inner.queue.push(request, created_at).await?;
649 trace!(%transaction_id, "manager sends a redaction event to the background task");
650
651 self.inner.notifier.notify_one();
652
653 let send_handle = SendHandle {
654 room: self.clone(),
655 transaction_id: transaction_id.clone(),
656 media_handles: vec![],
657 created_at,
658 };
659
660 self.send_update(RoomSendQueueUpdate::NewLocalEvent(LocalEcho {
661 transaction_id,
662 content: LocalEchoContent::Redaction {
663 redacts,
664 reason: reason.map(str::to_owned),
665 send_handle: send_handle.clone(),
666 send_error: None,
667 },
668 }));
669
670 Ok(send_handle)
671 }
672
673 pub async fn subscribe(
679 &self,
680 ) -> Result<(Vec<LocalEcho>, broadcast::Receiver<RoomSendQueueUpdate>), RoomSendQueueError>
681 {
682 let local_echoes = self.inner.queue.local_echoes(self).await?;
683
684 Ok((local_echoes, self.inner.update_sender.subscribe()))
685 }
686
687 #[allow(clippy::too_many_arguments)]
693 #[instrument(skip_all, fields(room_id = %room.room_id()))]
694 async fn sending_task(
695 room: WeakRoom,
696 queue: QueueStorage,
697 notifier: Arc<Notify>,
698 global_update_sender: broadcast::Sender<SendQueueUpdate>,
699 update_sender: broadcast::Sender<RoomSendQueueUpdate>,
700 locally_enabled: Arc<AtomicBool>,
701 global_error_sender: broadcast::Sender<SendQueueRoomError>,
702 is_dropping: Arc<AtomicBool>,
703 report_media_upload_progress: Arc<AtomicBool>,
704 ) {
705 trace!("spawned the sending task");
706
707 let room_id = room.room_id();
708
709 loop {
710 if is_dropping.load(Ordering::SeqCst) {
712 trace!("shutting down!");
713 break;
714 }
715
716 let mut new_updates = Vec::new();
719 if let Err(err) = queue.apply_dependent_requests(&mut new_updates).await {
720 warn!("errors when applying dependent requests: {err}");
721 }
722
723 for up in new_updates {
724 send_update(&global_update_sender, &update_sender, room_id, up);
725 }
726
727 if !locally_enabled.load(Ordering::SeqCst) {
728 trace!("not enabled, sleeping");
729 notifier.notified().await;
731 continue;
732 }
733
734 let (queued_request, cancel_upload_rx) = match queue.peek_next_to_send().await {
735 Ok(Some(request)) => request,
736
737 Ok(None) => {
738 trace!("queue is empty or blocked on a wedged request, sleeping");
739 notifier.notified().await;
741 continue;
742 }
743
744 Err(err) => {
745 warn!("error when loading next request to send: {err}");
746 matrix_sdk_common::sleep::sleep(STORE_ERROR_BACKOFF).await;
749 continue;
750 }
751 };
752
753 let txn_id = queued_request.transaction_id.clone();
754 trace!(txn_id = %txn_id, "received a request to send!");
755
756 let Some(room) = room.get() else {
757 if is_dropping.load(Ordering::SeqCst) {
758 break;
759 }
760 error!("the weak room couldn't be upgraded but we're not shutting down?");
761 continue;
762 };
763
764 let (related_txn_id, media_upload_progress_info, http_progress) =
770 if let QueuedRequestKind::MediaUpload {
771 cache_key,
772 thumbnail_source,
773 #[cfg(feature = "unstable-msc4274")]
774 accumulated,
775 related_to,
776 ..
777 } = &queued_request.kind
778 {
779 let (media_upload_progress_info, http_progress) =
782 if report_media_upload_progress.load(Ordering::SeqCst) {
783 let media_upload_progress_info =
784 RoomSendQueue::create_media_upload_progress_info(
785 &queued_request.transaction_id,
786 related_to,
787 cache_key,
788 thumbnail_source.as_ref(),
789 #[cfg(feature = "unstable-msc4274")]
790 accumulated,
791 &room,
792 &queue,
793 )
794 .await;
795
796 let progress = RoomSendQueue::create_media_upload_progress_observable(
797 &media_upload_progress_info,
798 related_to,
799 &update_sender,
800 );
801
802 (Some(media_upload_progress_info), Some(progress))
803 } else {
804 Default::default()
805 };
806
807 (Some(related_to.clone()), media_upload_progress_info, http_progress)
808 } else {
809 Default::default()
810 };
811
812 match Self::handle_request(&room, queued_request, cancel_upload_rx, http_progress).await
813 {
814 Ok((Some(parent_key), encryption_info)) => match queue
815 .mark_as_sent(&txn_id, parent_key.clone())
816 .await
817 {
818 Ok(()) => match parent_key {
819 SentRequestKey::Event { event_id, event, event_type } => {
820 send_update(
821 &global_update_sender,
822 &update_sender,
823 room_id,
824 RoomSendQueueUpdate::SentEvent {
825 transaction_id: txn_id,
826 event_id: event_id.clone(),
827 },
828 );
829
830 if let Ok((room_event_cache, _drop_handles)) = room.event_cache().await
857 {
858 let timeline_event = match Raw::from_json_string(
859 format!(
861 "{{\
862 \"event_id\":\"{event_id}\",\
863 \"origin_server_ts\":{ts},\
864 \"sender\":\"{sender}\",\
865 \"type\":\"{type}\",\
866 \"content\":{content}\
867 }}",
868 event_id = event_id,
869 ts = MilliSecondsSinceUnixEpoch::now().get(),
870 sender = room.client().user_id().expect("Client must be logged-in"),
871 type = event_type,
872 content = event.into_json(),
873 ),
874 ) {
875 Ok(event) => match encryption_info {
876 #[cfg(feature = "e2e-encryption")]
877 Some(encryption_info) => {
878 use matrix_sdk_base::deserialized_responses::DecryptedRoomEvent;
879 let decrypted_event = DecryptedRoomEvent {
880 event: event.cast_unchecked(),
881 encryption_info: Arc::new(encryption_info),
882 unsigned_encryption_info: None,
883 };
884 Some(TimelineEvent::from_decrypted(
885 decrypted_event,
886 None,
887 ))
888 }
889 _ => Some(TimelineEvent::from_plaintext(event)),
890 },
891 Err(err) => {
892 error!(
893 ?err,
894 "Failed to build the (sync) event before the saving in the Event Cache"
895 );
896 None
897 }
898 };
899
900 if let Some(timeline_event) = timeline_event
904 && let Err(err) = room_event_cache
905 .insert_sent_event_from_send_queue(timeline_event)
906 .await
907 {
908 error!(
909 ?err,
910 "Failed to save the sent event in the Event Cache"
911 );
912 }
913 } else {
914 info!(
915 "Cannot insert the sent event in the Event Cache because \
916 either the room no longer exists, or the Room Event Cache cannot be retrieved"
917 );
918 }
919 }
920
921 SentRequestKey::Media(sent_media_info) => {
922 let index =
925 media_upload_progress_info.as_ref().map_or(0, |info| info.index);
926 let progress = media_upload_progress_info
927 .as_ref()
928 .map(|info| {
929 AbstractProgress { current: info.bytes, total: info.bytes }
930 + info.offsets
931 })
932 .unwrap_or(AbstractProgress { current: 1, total: 1 });
933
934 let _ = update_sender.send(RoomSendQueueUpdate::MediaUpload {
938 related_to: related_txn_id.as_ref().unwrap_or(&txn_id).clone(),
939 file: Some(sent_media_info.file),
940 index,
941 progress,
942 });
943 }
944
945 SentRequestKey::Redaction { event_id, redacts, reason } => {
946 send_update(
947 &global_update_sender,
948 &update_sender,
949 room_id,
950 RoomSendQueueUpdate::SentEvent {
951 transaction_id: txn_id,
952 event_id: event_id.clone(),
953 },
954 );
955
956 if let Ok((room_event_cache, _drop_handles)) = room.event_cache().await
962 {
963 let content_field_redacts = room.version().is_some_and(|id| {
964 id.rules()
965 .is_some_and(|rules| rules.redaction.content_field_redacts)
966 });
967 let redacts = if content_field_redacts.not() {
968 format!("\"redacts\":\"{redacts}\",")
969 } else {
970 "".to_owned()
971 };
972 let reason = reason.map_or_else(
973 || "".to_owned(),
974 |r| format!("\"reason\": \"{r}\""),
975 );
976 let content = if content_field_redacts {
977 format!("\"redacts\":\"{redacts}\",{reason}")
978 } else {
979 reason
980 };
981
982 let timeline_event = match Raw::from_json_string(
983 format!(
985 "{{\
986 {redacts}\
987 \"event_id\":\"{event_id}\",\
988 \"origin_server_ts\":{ts},\
989 \"sender\":\"{sender}\",\
990 \"type\":\"{type}\",\
991 \"content\":{{{content}}}\
992 }}",
993 redacts = redacts,
994 event_id = event_id,
995 ts = MilliSecondsSinceUnixEpoch::now().get(),
996 sender = room.client().user_id().expect("Client must be logged-in"),
997 type = TimelineEventType::RoomRedaction,
998 content = content
999 ),
1000 ) {
1001 Ok(event) => Some(TimelineEvent::from_plaintext(event)),
1002 Err(err) => {
1003 error!(
1004 ?err,
1005 "Failed to build the (sync) redaction event before the saving in the Event Cache"
1006 );
1007 None
1008 }
1009 };
1010
1011 if let Some(timeline_event) = timeline_event
1015 && let Err(err) = room_event_cache
1016 .insert_sent_event_from_send_queue(timeline_event)
1017 .await
1018 {
1019 error!(
1020 ?err,
1021 "Failed to save the sent redaction event in the Event Cache"
1022 );
1023 }
1024 } else {
1025 info!(
1026 "Cannot insert the sent redaction event in the Event Cache because \
1027 either the room no longer exists, or the Room Event Cache cannot be retrieved"
1028 );
1029 }
1030 }
1031 },
1032
1033 Err(err) => {
1034 warn!("unable to mark queued request as sent: {err}");
1035 }
1036 },
1037
1038 Ok((None, _)) => {
1039 debug!("Request has been aborted while running, continuing.");
1040 }
1041
1042 Err(err) => {
1043 let is_recoverable = match err {
1044 crate::Error::Http(ref http_err) => {
1045 matches!(
1047 http_err.retry_kind(),
1048 RetryKind::Transient { .. } | RetryKind::NetworkFailure
1049 )
1050 }
1051
1052 crate::Error::ConcurrentRequestFailed => true,
1058
1059 _ => false,
1061 };
1062
1063 if is_recoverable {
1064 locally_enabled.store(false, Ordering::SeqCst);
1068
1069 warn!(txn_id = %txn_id, error = ?err, "Recoverable error when sending request: {err}, disabling send queue");
1070
1071 queue.mark_as_not_being_sent(&txn_id).await;
1074
1075 } else {
1081 warn!(txn_id = %txn_id, error = ?err, "Unrecoverable error when sending request: {err}");
1082
1083 if let Err(storage_error) =
1088 queue.mark_as_wedged(&txn_id, QueueWedgeError::from(&err)).await
1089 {
1090 error!(
1094 "unable to mark request as wedged, disabling the queue: {storage_error}"
1095 );
1096 locally_enabled.store(false, Ordering::SeqCst);
1097 }
1098 }
1099
1100 let error = Arc::new(err);
1101
1102 let _ = global_error_sender.send(SendQueueRoomError {
1103 room_id: room_id.to_owned(),
1104 error: error.clone(),
1105 is_recoverable,
1106 });
1107
1108 send_update(
1109 &global_update_sender,
1110 &update_sender,
1111 room_id,
1112 RoomSendQueueUpdate::SendError {
1113 transaction_id: related_txn_id.unwrap_or(txn_id),
1114 error,
1115 is_recoverable,
1116 },
1117 );
1118 }
1119 }
1120 }
1121
1122 info!("exited sending task");
1123 }
1124
1125 async fn handle_request(
1128 room: &Room,
1129 request: QueuedRequest,
1130 cancel_upload_rx: Option<oneshot::Receiver<()>>,
1131 progress: Option<SharedObservable<TransmissionProgress>>,
1132 ) -> Result<(Option<SentRequestKey>, Option<EncryptionInfo>), crate::Error> {
1133 match request.kind {
1134 QueuedRequestKind::Event {
1135 content,
1136 #[cfg(feature = "unstable-msc4354")]
1137 sticky_duration,
1138 } => {
1139 let (event, event_type) = content.into_raw();
1140
1141 let future = room
1142 .send_raw(&event_type, &event)
1143 .with_transaction_id(&request.transaction_id)
1144 .with_request_config(RequestConfig::short_retry());
1145
1146 #[cfg(feature = "unstable-msc4354")]
1147 let future = match sticky_duration {
1148 Some(duration) => {
1149 future.with_sticky_duration(Duration::from_millis(duration.get().into()))
1150 }
1151 None => future,
1152 };
1153
1154 let result = future.await?;
1155
1156 trace!(txn_id = %request.transaction_id, event_id = %result.response.event_id, "event successfully sent");
1157
1158 Ok((
1159 Some(SentRequestKey::Event {
1160 event_id: result.response.event_id,
1161 event,
1162 event_type,
1163 }),
1164 result.encryption_info,
1165 ))
1166 }
1167
1168 QueuedRequestKind::MediaUpload {
1169 content_type,
1170 cache_key,
1171 thumbnail_source,
1172 related_to: relates_to,
1173 #[cfg(feature = "unstable-msc4274")]
1174 accumulated,
1175 } => {
1176 trace!(%relates_to, "uploading media related to event");
1177
1178 let fut = async move {
1179 let data = room
1180 .client()
1181 .media_store()
1182 .lock()
1183 .await?
1184 .get_media_content(&cache_key)
1185 .await?
1186 .ok_or(crate::Error::SendQueueWedgeError(Box::new(
1187 QueueWedgeError::MissingMediaContent,
1188 )))?;
1189
1190 let mime = Mime::from_str(&content_type).map_err(|_| {
1191 crate::Error::SendQueueWedgeError(Box::new(
1192 QueueWedgeError::InvalidMimeType { mime_type: content_type.clone() },
1193 ))
1194 })?;
1195
1196 #[cfg(feature = "e2e-encryption")]
1197 let media_source = if room.latest_encryption_state().await?.is_encrypted() {
1198 trace!("upload will be encrypted (encrypted room)");
1199
1200 let mut cursor = std::io::Cursor::new(data);
1201 let mut req = room
1202 .client
1203 .upload_encrypted_file(&mut cursor)
1204 .with_request_config(RequestConfig::short_retry());
1205 if let Some(progress) = progress {
1206 req = req.with_send_progress_observable(progress);
1207 }
1208 let encrypted_file = req.await?;
1209
1210 MediaSource::Encrypted(Box::new(encrypted_file))
1211 } else {
1212 trace!("upload will be in clear text (room without encryption)");
1213
1214 let request_config = RequestConfig::short_retry()
1215 .timeout(Media::reasonable_upload_timeout(&data));
1216 let mut req =
1217 room.client().media().upload(&mime, data, Some(request_config));
1218 if let Some(progress) = progress {
1219 req = req.with_send_progress_observable(progress);
1220 }
1221 let res = req.await?;
1222
1223 MediaSource::Plain(res.content_uri)
1224 };
1225
1226 #[cfg(not(feature = "e2e-encryption"))]
1227 let media_source = {
1228 let request_config = RequestConfig::short_retry()
1229 .timeout(Media::reasonable_upload_timeout(&data));
1230 let mut req =
1231 room.client().media().upload(&mime, data, Some(request_config));
1232 if let Some(progress) = progress {
1233 req = req.with_send_progress_observable(progress);
1234 }
1235 let res = req.await?;
1236 MediaSource::Plain(res.content_uri)
1237 };
1238
1239 let uri = match &media_source {
1240 MediaSource::Plain(uri) => uri,
1241 MediaSource::Encrypted(encrypted_file) => &encrypted_file.url,
1242 };
1243 trace!(%relates_to, mxc_uri = %uri, "media successfully uploaded");
1244
1245 Ok((
1246 Some(SentRequestKey::Media(SentMediaInfo {
1247 file: media_source,
1248 thumbnail: thumbnail_source,
1249 #[cfg(feature = "unstable-msc4274")]
1250 accumulated,
1251 })),
1252 None,
1253 ))
1254 };
1255
1256 let wait_for_cancel = async move {
1257 if let Some(rx) = cancel_upload_rx {
1258 rx.await
1259 } else {
1260 std::future::pending().await
1261 }
1262 };
1263
1264 tokio::select! {
1265 biased;
1266
1267 _ = wait_for_cancel => {
1268 Ok((None, None))
1269 }
1270
1271 res = fut => {
1272 res
1273 }
1274 }
1275 }
1276
1277 QueuedRequestKind::Redaction { redacts, reason } => {
1278 let result = room
1279 .redact(&redacts, reason.as_deref(), Some(request.transaction_id.clone()))
1280 .await?;
1281
1282 trace!(txn_id = %request.transaction_id, event_id = %result.event_id, "redaction successfully sent");
1283
1284 Ok((
1285 Some(SentRequestKey::Redaction { event_id: result.event_id, redacts, reason }),
1286 None,
1287 ))
1288 }
1289 }
1290 }
1291
1292 pub fn is_enabled(&self) -> bool {
1294 self.inner.locally_enabled.load(Ordering::SeqCst)
1295 }
1296
1297 pub fn set_enabled(&self, enabled: bool) {
1299 self.inner.locally_enabled.store(enabled, Ordering::SeqCst);
1300
1301 if enabled {
1304 self.inner.notifier.notify_one();
1305 }
1306 }
1307
1308 fn send_update(&self, update: RoomSendQueueUpdate) {
1312 let _ = self.inner.update_sender.send(update.clone());
1313 let _ = self
1314 .inner
1315 .global_update_sender
1316 .send(SendQueueUpdate { room_id: self.inner.room.room_id().to_owned(), update });
1317 }
1318
1319 async fn unwedge_request(
1322 &self,
1323 transaction_id: &TransactionId,
1324 ) -> Result<(), RoomSendQueueError> {
1325 self.inner
1326 .queue
1327 .mark_as_unwedged(transaction_id)
1328 .await
1329 .map_err(RoomSendQueueError::StorageError)?;
1330
1331 self.inner.notifier.notify_one();
1334
1335 self.send_update(RoomSendQueueUpdate::RetryEvent {
1336 transaction_id: transaction_id.to_owned(),
1337 });
1338
1339 Ok(())
1340 }
1341}
1342
1343fn send_update(
1344 global_update_sender: &broadcast::Sender<SendQueueUpdate>,
1345 update_sender: &broadcast::Sender<RoomSendQueueUpdate>,
1346 room_id: &RoomId,
1347 update: RoomSendQueueUpdate,
1348) {
1349 let _ = update_sender.send(update.clone());
1350 let _ = global_update_sender.send(SendQueueUpdate { room_id: room_id.to_owned(), update });
1351}
1352
1353impl From<&crate::Error> for QueueWedgeError {
1354 fn from(value: &crate::Error) -> Self {
1355 match value {
1356 #[cfg(feature = "e2e-encryption")]
1357 crate::Error::OlmError(error) => match &**error {
1358 OlmError::SessionRecipientCollectionError(error) => match error {
1359 SessionRecipientCollectionError::VerifiedUserHasUnsignedDevice(user_map) => {
1360 QueueWedgeError::InsecureDevices { user_device_map: user_map.clone() }
1361 }
1362
1363 SessionRecipientCollectionError::VerifiedUserChangedIdentity(users) => {
1364 QueueWedgeError::IdentityViolations { users: users.clone() }
1365 }
1366
1367 SessionRecipientCollectionError::CrossSigningNotSetup
1368 | SessionRecipientCollectionError::SendingFromUnverifiedDevice => {
1369 QueueWedgeError::CrossVerificationRequired
1370 }
1371 },
1372 _ => QueueWedgeError::GenericApiError { msg: value.to_string() },
1373 },
1374
1375 crate::Error::SendQueueWedgeError(error) => *error.clone(),
1377
1378 _ => QueueWedgeError::GenericApiError { msg: value.to_string() },
1379 }
1380 }
1381}
1382
1383struct RoomSendQueueInner {
1384 room: WeakRoom,
1386
1387 global_update_sender: broadcast::Sender<SendQueueUpdate>,
1391
1392 update_sender: broadcast::Sender<RoomSendQueueUpdate>,
1398
1399 queue: QueueStorage,
1405
1406 notifier: Arc<Notify>,
1409
1410 locally_enabled: Arc<AtomicBool>,
1413
1414 _task: BackgroundTaskHandle,
1417}
1418
1419struct BeingSentInfo {
1421 transaction_id: OwnedTransactionId,
1423
1424 cancel_upload: Option<oneshot::Sender<()>>,
1427}
1428
1429impl BeingSentInfo {
1430 fn cancel_upload(self) -> bool {
1435 if let Some(cancel_upload) = self.cancel_upload {
1436 let _ = cancel_upload.send(());
1437 true
1438 } else {
1439 false
1440 }
1441 }
1442}
1443
1444#[derive(Clone)]
1447struct StoreLock {
1448 client: WeakClient,
1450
1451 being_sent: Arc<Mutex<Option<BeingSentInfo>>>,
1456}
1457
1458impl StoreLock {
1459 async fn lock(&self) -> StoreLockGuard {
1461 StoreLockGuard {
1462 client: self.client.clone(),
1463 being_sent: self.being_sent.clone().lock_owned().await,
1464 }
1465 }
1466}
1467
1468struct StoreLockGuard {
1470 client: WeakClient,
1472
1473 being_sent: OwnedMutexGuard<Option<BeingSentInfo>>,
1476}
1477
1478impl StoreLockGuard {
1479 fn client(&self) -> Result<Client, RoomSendQueueStorageError> {
1481 self.client.get().ok_or(RoomSendQueueStorageError::ClientShuttingDown)
1482 }
1483}
1484
1485#[derive(Clone)]
1486struct QueueStorage {
1487 store: StoreLock,
1490
1491 room_id: OwnedRoomId,
1493
1494 thumbnail_file_sizes: Arc<SyncMutex<HashMap<OwnedTransactionId, Vec<Option<usize>>>>>,
1507}
1508
1509impl QueueStorage {
1510 const LOW_PRIORITY: usize = 0;
1512
1513 const HIGH_PRIORITY: usize = 10;
1515
1516 fn new(client: WeakClient, room: OwnedRoomId) -> Self {
1518 Self {
1519 room_id: room,
1520 store: StoreLock { client, being_sent: Default::default() },
1521 thumbnail_file_sizes: Default::default(),
1522 }
1523 }
1524
1525 async fn push(
1529 &self,
1530 request: QueuedRequestKind,
1531 created_at: MilliSecondsSinceUnixEpoch,
1532 ) -> Result<OwnedTransactionId, RoomSendQueueStorageError> {
1533 let transaction_id = TransactionId::new();
1534
1535 self.store
1536 .lock()
1537 .await
1538 .client()?
1539 .state_store()
1540 .save_send_queue_request(
1541 &self.room_id,
1542 transaction_id.clone(),
1543 created_at,
1544 request,
1545 Self::LOW_PRIORITY,
1546 )
1547 .await?;
1548
1549 Ok(transaction_id)
1550 }
1551
1552 async fn peek_next_to_send(
1557 &self,
1558 ) -> Result<Option<(QueuedRequest, Option<oneshot::Receiver<()>>)>, RoomSendQueueStorageError>
1559 {
1560 let mut guard = self.store.lock().await;
1561 let queued_requests =
1562 guard.client()?.state_store().load_send_queue_requests(&self.room_id).await?;
1563
1564 if let Some(request) = queued_requests.first().filter(|queued| !queued.is_wedged()) {
1571 let (cancel_upload_tx, cancel_upload_rx) =
1572 if matches!(request.kind, QueuedRequestKind::MediaUpload { .. }) {
1573 let (tx, rx) = oneshot::channel();
1574 (Some(tx), Some(rx))
1575 } else {
1576 Default::default()
1577 };
1578
1579 let prev = guard.being_sent.replace(BeingSentInfo {
1580 transaction_id: request.transaction_id.clone(),
1581 cancel_upload: cancel_upload_tx,
1582 });
1583
1584 if let Some(prev) = prev {
1585 error!(
1586 prev_txn = ?prev.transaction_id,
1587 "a previous request was still active while picking a new one"
1588 );
1589 }
1590
1591 Ok(Some((request.clone(), cancel_upload_rx)))
1592 } else {
1593 Ok(None)
1594 }
1595 }
1596
1597 async fn mark_as_not_being_sent(&self, transaction_id: &TransactionId) {
1601 let was_being_sent = self.store.lock().await.being_sent.take();
1602
1603 let prev_txn = was_being_sent.as_ref().map(|info| info.transaction_id.as_ref());
1604 if prev_txn != Some(transaction_id) {
1605 error!(prev_txn = ?prev_txn, "previous active request didn't match that we expect (after transient error)");
1606 }
1607 }
1608
1609 async fn mark_as_wedged(
1613 &self,
1614 transaction_id: &TransactionId,
1615 reason: QueueWedgeError,
1616 ) -> Result<(), RoomSendQueueStorageError> {
1617 let mut guard = self.store.lock().await;
1619 let was_being_sent = guard.being_sent.take();
1620
1621 let prev_txn = was_being_sent.as_ref().map(|info| info.transaction_id.as_ref());
1622 if prev_txn != Some(transaction_id) {
1623 error!(
1624 ?prev_txn,
1625 "previous active request didn't match that we expect (after permanent error)",
1626 );
1627 }
1628
1629 Ok(guard
1630 .client()?
1631 .state_store()
1632 .update_send_queue_request_status(&self.room_id, transaction_id, Some(reason))
1633 .await?)
1634 }
1635
1636 async fn mark_as_unwedged(
1639 &self,
1640 transaction_id: &TransactionId,
1641 ) -> Result<(), RoomSendQueueStorageError> {
1642 Ok(self
1643 .store
1644 .lock()
1645 .await
1646 .client()?
1647 .state_store()
1648 .update_send_queue_request_status(&self.room_id, transaction_id, None)
1649 .await?)
1650 }
1651
1652 async fn mark_as_sent(
1655 &self,
1656 transaction_id: &TransactionId,
1657 parent_key: SentRequestKey,
1658 ) -> Result<(), RoomSendQueueStorageError> {
1659 let mut guard = self.store.lock().await;
1661 let was_being_sent = guard.being_sent.take();
1662
1663 let prev_txn = was_being_sent.as_ref().map(|info| info.transaction_id.as_ref());
1664 if prev_txn != Some(transaction_id) {
1665 error!(
1666 ?prev_txn,
1667 "previous active request didn't match that we expect (after successful send)",
1668 );
1669 }
1670
1671 let client = guard.client()?;
1672 let store = client.state_store();
1673
1674 store
1676 .mark_dependent_queued_requests_as_ready(&self.room_id, transaction_id, parent_key)
1677 .await?;
1678
1679 let removed = store.remove_send_queue_request(&self.room_id, transaction_id).await?;
1680
1681 if !removed {
1682 warn!(txn_id = %transaction_id, "request marked as sent was missing from storage");
1683 }
1684
1685 self.thumbnail_file_sizes.lock().remove(transaction_id);
1686
1687 Ok(())
1688 }
1689
1690 async fn cancel_event(
1697 &self,
1698 transaction_id: &TransactionId,
1699 reason: Option<String>,
1700 ) -> Result<bool, RoomSendQueueStorageError> {
1701 let guard = self.store.lock().await;
1702
1703 if guard.being_sent.as_ref().map(|info| info.transaction_id.as_ref())
1704 == Some(transaction_id)
1705 {
1706 guard
1708 .client()?
1709 .state_store()
1710 .save_dependent_queued_request(
1711 &self.room_id,
1712 transaction_id,
1713 ChildTransactionId::new(),
1714 MilliSecondsSinceUnixEpoch::now(),
1715 DependentQueuedRequestKind::RedactEventWithReason { reason },
1716 )
1717 .await?;
1718
1719 return Ok(true);
1720 }
1721
1722 let removed = guard
1723 .client()?
1724 .state_store()
1725 .remove_send_queue_request(&self.room_id, transaction_id)
1726 .await?;
1727
1728 self.thumbnail_file_sizes.lock().remove(transaction_id);
1729
1730 Ok(removed)
1731 }
1732
1733 async fn replace_event(
1740 &self,
1741 transaction_id: &TransactionId,
1742 serializable: SerializableEventContent,
1743 ) -> Result<bool, RoomSendQueueStorageError> {
1744 let guard = self.store.lock().await;
1745 let client = guard.client()?;
1746 let store = client.state_store();
1747
1748 if !store.load_send_queue_requests(&self.room_id).await?.iter().any(|request| {
1751 request.transaction_id == transaction_id && is_own_event_request(request)
1752 }) {
1753 return Ok(false);
1754 }
1755
1756 if guard.being_sent.as_ref().map(|info| info.transaction_id.as_ref())
1757 == Some(transaction_id)
1758 {
1759 store
1761 .save_dependent_queued_request(
1762 &self.room_id,
1763 transaction_id,
1764 ChildTransactionId::new(),
1765 MilliSecondsSinceUnixEpoch::now(),
1766 DependentQueuedRequestKind::EditEvent { new_content: serializable },
1767 )
1768 .await?;
1769
1770 return Ok(true);
1771 }
1772
1773 let request = QueuedRequestKind::Event {
1774 content: serializable,
1775 #[cfg(feature = "unstable-msc4354")]
1776 sticky_duration: self.sticky_duration_of(store, transaction_id).await?,
1777 };
1778
1779 let edited =
1780 store.update_send_queue_request(&self.room_id, transaction_id, request).await?;
1781
1782 Ok(edited)
1783 }
1784
1785 #[cfg(feature = "unstable-msc4354")]
1788 async fn sticky_duration_of(
1789 &self,
1790 store: &DynStateStore,
1791 transaction_id: &TransactionId,
1792 ) -> Result<Option<StickyDurationMs>, RoomSendQueueStorageError> {
1793 let sticky_duration = store
1794 .load_send_queue_requests(&self.room_id)
1795 .await?
1796 .into_iter()
1797 .find(|request| request.transaction_id == *transaction_id)
1798 .and_then(|request| match request.kind {
1799 QueuedRequestKind::Event { sticky_duration, .. } => sticky_duration,
1800 _ => None,
1801 });
1802
1803 Ok(sticky_duration)
1804 }
1805
1806 #[allow(clippy::too_many_arguments)]
1810 async fn push_media(
1811 &self,
1812 event: RoomMessageEventContent,
1813 content_type: Mime,
1814 send_event_txn: OwnedTransactionId,
1815 created_at: MilliSecondsSinceUnixEpoch,
1816 upload_file_txn: OwnedTransactionId,
1817 file_media_request: MediaRequestParameters,
1818 thumbnail: Option<QueueThumbnailInfo>,
1819 extra_content: Option<serde_json::Map<String, serde_json::Value>>,
1820 ) -> Result<(), RoomSendQueueStorageError> {
1821 let guard = self.store.lock().await;
1822 let client = guard.client()?;
1823 let store = client.state_store();
1824
1825 let thumbnail_file_sizes = vec![thumbnail.as_ref().map(|t| t.file_size)];
1828
1829 let thumbnail_info = self
1830 .push_thumbnail_and_media_uploads(
1831 store,
1832 &content_type,
1833 send_event_txn.clone(),
1834 created_at,
1835 upload_file_txn.clone(),
1836 file_media_request,
1837 thumbnail,
1838 )
1839 .await?;
1840
1841 store
1843 .save_dependent_queued_request(
1844 &self.room_id,
1845 &upload_file_txn,
1846 send_event_txn.clone().into(),
1847 created_at,
1848 DependentQueuedRequestKind::FinishUpload {
1849 local_echo: Box::new(event),
1850 file_upload: upload_file_txn.clone(),
1851 thumbnail_info,
1852 extra_content,
1853 },
1854 )
1855 .await?;
1856
1857 self.thumbnail_file_sizes.lock().insert(send_event_txn, thumbnail_file_sizes);
1858
1859 Ok(())
1860 }
1861
1862 #[cfg(feature = "unstable-msc4274")]
1866 #[allow(clippy::too_many_arguments)]
1867 async fn push_gallery(
1868 &self,
1869 event: RoomMessageEventContent,
1870 send_event_txn: OwnedTransactionId,
1871 created_at: MilliSecondsSinceUnixEpoch,
1872 item_queue_infos: Vec<GalleryItemQueueInfo>,
1873 ) -> Result<(), RoomSendQueueStorageError> {
1874 let guard = self.store.lock().await;
1875 let client = guard.client()?;
1876 let store = client.state_store();
1877
1878 let mut finish_item_infos = Vec::with_capacity(item_queue_infos.len());
1879 let mut thumbnail_file_sizes = Vec::with_capacity(item_queue_infos.len());
1880
1881 let Some((first, rest)) = item_queue_infos.split_first() else {
1882 return Ok(());
1883 };
1884
1885 let GalleryItemQueueInfo { content_type, upload_file_txn, file_media_request, thumbnail } =
1886 first;
1887
1888 let thumbnail_info = self
1889 .push_thumbnail_and_media_uploads(
1890 store,
1891 content_type,
1892 send_event_txn.clone(),
1893 created_at,
1894 upload_file_txn.clone(),
1895 file_media_request.clone(),
1896 thumbnail.clone(),
1897 )
1898 .await?;
1899
1900 finish_item_infos
1901 .push(FinishGalleryItemInfo { file_upload: upload_file_txn.clone(), thumbnail_info });
1902 thumbnail_file_sizes.push(thumbnail.as_ref().map(|t| t.file_size));
1903
1904 let mut last_upload_file_txn = upload_file_txn.clone();
1905
1906 for item_queue_info in rest {
1907 let GalleryItemQueueInfo {
1908 content_type,
1909 upload_file_txn,
1910 file_media_request,
1911 thumbnail,
1912 } = item_queue_info;
1913
1914 let thumbnail_info = if let Some(QueueThumbnailInfo {
1915 finish_upload_thumbnail_info: thumbnail_info,
1916 media_request_parameters: thumbnail_media_request,
1917 content_type: thumbnail_content_type,
1918 ..
1919 }) = thumbnail
1920 {
1921 let upload_thumbnail_txn = thumbnail_info.txn.clone();
1922
1923 store
1926 .save_dependent_queued_request(
1927 &self.room_id,
1928 &last_upload_file_txn,
1929 upload_thumbnail_txn.clone().into(),
1930 created_at,
1931 DependentQueuedRequestKind::UploadFileOrThumbnail {
1932 content_type: thumbnail_content_type.to_string(),
1933 cache_key: thumbnail_media_request.clone(),
1934 related_to: send_event_txn.clone(),
1935 parent_is_thumbnail_upload: false,
1936 },
1937 )
1938 .await?;
1939
1940 last_upload_file_txn = upload_thumbnail_txn;
1941
1942 Some(thumbnail_info)
1943 } else {
1944 None
1945 };
1946
1947 store
1950 .save_dependent_queued_request(
1951 &self.room_id,
1952 &last_upload_file_txn,
1953 upload_file_txn.clone().into(),
1954 created_at,
1955 DependentQueuedRequestKind::UploadFileOrThumbnail {
1956 content_type: content_type.to_string(),
1957 cache_key: file_media_request.clone(),
1958 related_to: send_event_txn.clone(),
1959 parent_is_thumbnail_upload: thumbnail.is_some(),
1960 },
1961 )
1962 .await?;
1963
1964 finish_item_infos.push(FinishGalleryItemInfo {
1965 file_upload: upload_file_txn.clone(),
1966 thumbnail_info: thumbnail_info.cloned(),
1967 });
1968 thumbnail_file_sizes.push(thumbnail.as_ref().map(|t| t.file_size));
1969
1970 last_upload_file_txn = upload_file_txn.clone();
1971 }
1972
1973 store
1976 .save_dependent_queued_request(
1977 &self.room_id,
1978 &last_upload_file_txn,
1979 send_event_txn.clone().into(),
1980 created_at,
1981 DependentQueuedRequestKind::FinishGallery {
1982 local_echo: Box::new(event),
1983 item_infos: finish_item_infos,
1984 },
1985 )
1986 .await?;
1987
1988 self.thumbnail_file_sizes.lock().insert(send_event_txn, thumbnail_file_sizes);
1989
1990 Ok(())
1991 }
1992
1993 #[allow(clippy::too_many_arguments)]
1998 async fn push_thumbnail_and_media_uploads(
1999 &self,
2000 store: &DynStateStore,
2001 content_type: &Mime,
2002 send_event_txn: OwnedTransactionId,
2003 created_at: MilliSecondsSinceUnixEpoch,
2004 upload_file_txn: OwnedTransactionId,
2005 file_media_request: MediaRequestParameters,
2006 thumbnail: Option<QueueThumbnailInfo>,
2007 ) -> Result<Option<FinishUploadThumbnailInfo>, RoomSendQueueStorageError> {
2008 if let Some(QueueThumbnailInfo {
2009 finish_upload_thumbnail_info: thumbnail_info,
2010 media_request_parameters: thumbnail_media_request,
2011 content_type: thumbnail_content_type,
2012 ..
2013 }) = thumbnail
2014 {
2015 let upload_thumbnail_txn = thumbnail_info.txn.clone();
2016
2017 store
2019 .save_send_queue_request(
2020 &self.room_id,
2021 upload_thumbnail_txn.clone(),
2022 created_at,
2023 QueuedRequestKind::MediaUpload {
2024 content_type: thumbnail_content_type.to_string(),
2025 cache_key: thumbnail_media_request,
2026 thumbnail_source: None, related_to: send_event_txn.clone(),
2028 #[cfg(feature = "unstable-msc4274")]
2029 accumulated: vec![],
2030 },
2031 Self::LOW_PRIORITY,
2032 )
2033 .await?;
2034
2035 store
2038 .save_dependent_queued_request(
2039 &self.room_id,
2040 &upload_thumbnail_txn,
2041 upload_file_txn.into(),
2042 created_at,
2043 DependentQueuedRequestKind::UploadFileOrThumbnail {
2044 content_type: content_type.to_string(),
2045 cache_key: file_media_request,
2046 related_to: send_event_txn,
2047 parent_is_thumbnail_upload: true,
2048 },
2049 )
2050 .await?;
2051
2052 Ok(Some(thumbnail_info))
2053 } else {
2054 store
2056 .save_send_queue_request(
2057 &self.room_id,
2058 upload_file_txn,
2059 created_at,
2060 QueuedRequestKind::MediaUpload {
2061 content_type: content_type.to_string(),
2062 cache_key: file_media_request,
2063 thumbnail_source: None,
2064 related_to: send_event_txn,
2065 #[cfg(feature = "unstable-msc4274")]
2066 accumulated: vec![],
2067 },
2068 Self::LOW_PRIORITY,
2069 )
2070 .await?;
2071
2072 Ok(None)
2073 }
2074 }
2075
2076 #[instrument(skip(self))]
2078 async fn react(
2079 &self,
2080 transaction_id: &TransactionId,
2081 key: String,
2082 created_at: MilliSecondsSinceUnixEpoch,
2083 ) -> Result<Option<ChildTransactionId>, RoomSendQueueStorageError> {
2084 let guard = self.store.lock().await;
2085 let client = guard.client()?;
2086 let store = client.state_store();
2087
2088 let requests = store.load_send_queue_requests(&self.room_id).await?;
2089
2090 if !requests
2093 .iter()
2094 .any(|item| item.transaction_id == transaction_id && is_own_event_request(item))
2095 {
2096 let dependent_requests = store.load_dependent_queued_requests(&self.room_id).await?;
2099 if !dependent_requests
2100 .into_iter()
2101 .filter_map(|item| item.is_own_event().then_some(item.own_transaction_id))
2102 .any(|child_txn| *child_txn == *transaction_id)
2103 {
2104 return Ok(None);
2107 }
2108 }
2109
2110 let reaction_txn_id = ChildTransactionId::new();
2112 store
2113 .save_dependent_queued_request(
2114 &self.room_id,
2115 transaction_id,
2116 reaction_txn_id.clone(),
2117 created_at,
2118 DependentQueuedRequestKind::ReactEvent { key },
2119 )
2120 .await?;
2121
2122 Ok(Some(reaction_txn_id))
2123 }
2124
2125 async fn local_echoes(
2128 &self,
2129 room: &RoomSendQueue,
2130 ) -> Result<Vec<LocalEcho>, RoomSendQueueStorageError> {
2131 let guard = self.store.lock().await;
2132 let client = guard.client()?;
2133 let store = client.state_store();
2134
2135 let queued_requests = store.load_send_queue_requests(&self.room_id).await?;
2136
2137 let mut media_upload_errors: HashMap<OwnedTransactionId, QueueWedgeError> = queued_requests
2142 .iter()
2143 .filter_map(|queued| match queued.kind {
2144 QueuedRequestKind::MediaUpload { .. } => {
2145 queued.error.clone().map(|error| (queued.transaction_id.clone(), error))
2146 }
2147 _ => None,
2148 })
2149 .collect();
2150
2151 let local_requests = queued_requests.into_iter().filter_map(|queued| {
2152 Some(LocalEcho {
2153 transaction_id: queued.transaction_id.clone(),
2154 content: match queued.kind {
2155 QueuedRequestKind::Event { content, .. } => LocalEchoContent::Event {
2156 serialized_event: content,
2157 send_handle: SendHandle {
2158 room: room.clone(),
2159 transaction_id: queued.transaction_id,
2160 media_handles: vec![],
2161 created_at: queued.created_at,
2162 },
2163 send_error: queued.error,
2164 },
2165
2166 QueuedRequestKind::MediaUpload { .. } => {
2167 return None;
2171 }
2172
2173 QueuedRequestKind::Redaction { redacts, reason } => {
2174 LocalEchoContent::Redaction {
2175 redacts,
2176 reason,
2177 send_handle: SendHandle {
2178 room: room.clone(),
2179 transaction_id: queued.transaction_id,
2180 media_handles: vec![],
2181 created_at: queued.created_at,
2182 },
2183 send_error: queued.error,
2184 }
2185 }
2186 },
2187 })
2188 });
2189
2190 let dependent_requests = store.load_dependent_queued_requests(&self.room_id).await?;
2191
2192 let reactions_and_medias =
2193 dependent_requests.into_iter().filter_map(|dep| match dep.kind {
2194 DependentQueuedRequestKind::EditEvent { .. }
2195 | DependentQueuedRequestKind::RedactEvent
2196 | DependentQueuedRequestKind::RedactEventWithReason { .. } => {
2197 None
2199 }
2200
2201 DependentQueuedRequestKind::ReactEvent { key } => Some(LocalEcho {
2202 transaction_id: dep.own_transaction_id.clone().into(),
2203 content: LocalEchoContent::React {
2204 key,
2205 send_handle: SendHandle {
2206 room: room.clone(),
2207 transaction_id: dep.own_transaction_id.into(),
2208 media_handles: vec![],
2209 created_at: dep.created_at,
2210 },
2211 applies_to: dep.parent_transaction_id,
2212 },
2213 }),
2214
2215 DependentQueuedRequestKind::UploadFileOrThumbnail { .. } => {
2216 None
2219 }
2220
2221 DependentQueuedRequestKind::FinishUpload {
2222 local_echo,
2223 file_upload,
2224 thumbnail_info,
2225 extra_content,
2226 } => {
2227 let upload_thumbnail_txn = thumbnail_info.map(|info| info.txn);
2228
2229 let send_error = media_upload_errors.remove(&file_upload).or_else(|| {
2232 upload_thumbnail_txn
2233 .as_ref()
2234 .and_then(|txn| media_upload_errors.remove(&**txn))
2235 });
2236
2237 Some(LocalEcho {
2239 transaction_id: dep.own_transaction_id.clone().into(),
2240 content: LocalEchoContent::Event {
2241 serialized_event: upload::merge_extra_content(
2242 SerializableEventContent::new(&(*local_echo).into()).ok()?,
2243 extra_content,
2244 )
2245 .ok()?,
2246 send_handle: SendHandle {
2247 room: room.clone(),
2248 transaction_id: dep.own_transaction_id.into(),
2249 media_handles: vec![MediaHandles {
2250 upload_thumbnail_txn,
2251 upload_file_txn: file_upload,
2252 }],
2253 created_at: dep.created_at,
2254 },
2255 send_error,
2256 },
2257 })
2258 }
2259
2260 #[cfg(feature = "unstable-msc4274")]
2261 DependentQueuedRequestKind::FinishGallery { local_echo, item_infos } => {
2262 self.create_gallery_local_echo(
2264 dep.own_transaction_id,
2265 room,
2266 dep.created_at,
2267 local_echo,
2268 item_infos,
2269 &mut media_upload_errors,
2270 )
2271 }
2272 });
2273
2274 Ok(local_requests.chain(reactions_and_medias).collect())
2275 }
2276
2277 #[cfg(feature = "unstable-msc4274")]
2279 fn create_gallery_local_echo(
2280 &self,
2281 transaction_id: ChildTransactionId,
2282 room: &RoomSendQueue,
2283 created_at: MilliSecondsSinceUnixEpoch,
2284 local_echo: Box<RoomMessageEventContent>,
2285 item_infos: Vec<FinishGalleryItemInfo>,
2286 media_upload_errors: &mut HashMap<OwnedTransactionId, QueueWedgeError>,
2287 ) -> Option<LocalEcho> {
2288 let send_error = item_infos.iter().find_map(|i| {
2290 media_upload_errors.remove(&i.file_upload).or_else(|| {
2291 i.thumbnail_info.as_ref().and_then(|info| media_upload_errors.remove(&*info.txn))
2292 })
2293 });
2294
2295 Some(LocalEcho {
2296 transaction_id: transaction_id.clone().into(),
2297 content: LocalEchoContent::Event {
2298 serialized_event: SerializableEventContent::new(&(*local_echo).into()).ok()?,
2299 send_handle: SendHandle {
2300 room: room.clone(),
2301 transaction_id: transaction_id.into(),
2302 media_handles: item_infos
2303 .into_iter()
2304 .map(|i| MediaHandles {
2305 upload_thumbnail_txn: i.thumbnail_info.map(|info| info.txn),
2306 upload_file_txn: i.file_upload,
2307 })
2308 .collect(),
2309 created_at,
2310 },
2311 send_error,
2312 },
2313 })
2314 }
2315
2316 #[instrument(skip_all)]
2324 async fn try_apply_single_dependent_request(
2325 &self,
2326 client: &Client,
2327 dependent_request: DependentQueuedRequest,
2328 new_updates: &mut Vec<RoomSendQueueUpdate>,
2329 ) -> Result<bool, RoomSendQueueError> {
2330 let store = client.state_store();
2331
2332 let parent_key = dependent_request.parent_key;
2333
2334 match dependent_request.kind {
2335 DependentQueuedRequestKind::EditEvent { new_content } => {
2336 if let Some(parent_key) = parent_key {
2337 let Some(event_id) = parent_key.into_event_id() else {
2338 return Err(RoomSendQueueError::StorageError(
2339 RoomSendQueueStorageError::InvalidParentKey,
2340 ));
2341 };
2342
2343 let room = client
2345 .get_room(&self.room_id)
2346 .ok_or(RoomSendQueueError::RoomDisappeared)?;
2347
2348 let edited_content = match new_content.deserialize() {
2353 Ok(AnyMessageLikeEventContent::RoomMessage(c)) => {
2354 EditedContent::RoomMessage(c.into())
2356 }
2357
2358 Ok(AnyMessageLikeEventContent::UnstablePollStart(c)) => {
2359 let poll_start = c.poll_start().clone();
2360 EditedContent::PollStart {
2361 fallback_text: poll_start.question.text.clone(),
2362 new_content: poll_start,
2363 }
2364 }
2365
2366 Ok(c) => {
2367 warn!("Unsupported edit content type: {:?}", c.event_type());
2368 return Ok(true);
2369 }
2370
2371 Err(err) => {
2372 warn!("Unable to deserialize: {err}");
2373 return Ok(true);
2374 }
2375 };
2376
2377 let edit_event = match room.make_edit_event(&event_id, edited_content).await {
2378 Ok(e) => e,
2379 Err(err) => {
2380 warn!("couldn't create edited event: {err}");
2381 return Ok(true);
2382 }
2383 };
2384
2385 let serializable = SerializableEventContent::from_raw(
2387 Raw::new(&edit_event)
2388 .map_err(RoomSendQueueStorageError::JsonSerialization)?,
2389 edit_event.event_type().to_string(),
2390 );
2391
2392 store
2393 .save_send_queue_request(
2394 &self.room_id,
2395 dependent_request.own_transaction_id.into(),
2396 dependent_request.created_at,
2397 serializable.into(),
2398 Self::HIGH_PRIORITY,
2399 )
2400 .await
2401 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2402 } else {
2403 let parent_transaction_id = &dependent_request.parent_transaction_id;
2405
2406 let request = QueuedRequestKind::Event {
2407 content: new_content,
2408 #[cfg(feature = "unstable-msc4354")]
2409 sticky_duration: self
2410 .sticky_duration_of(store, parent_transaction_id)
2411 .await?,
2412 };
2413
2414 let edited = store
2415 .update_send_queue_request(&self.room_id, parent_transaction_id, request)
2416 .await
2417 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2418
2419 if !edited {
2420 warn!("missing local echo upon dependent edit");
2421 }
2422 }
2423 }
2424
2425 kind @ (DependentQueuedRequestKind::RedactEvent
2426 | DependentQueuedRequestKind::RedactEventWithReason { .. }) => {
2427 let reason = match kind {
2428 DependentQueuedRequestKind::RedactEventWithReason { reason } => reason,
2429 _ => None,
2431 };
2432
2433 if let Some(parent_key) = parent_key {
2434 let Some(event_id) = parent_key.into_event_id() else {
2435 return Err(RoomSendQueueError::StorageError(
2436 RoomSendQueueStorageError::InvalidParentKey,
2437 ));
2438 };
2439
2440 let room = client
2442 .get_room(&self.room_id)
2443 .ok_or(RoomSendQueueError::RoomDisappeared)?;
2444
2445 if let Err(err) = room
2451 .redact(
2452 &event_id,
2453 reason.as_deref(),
2454 Some(dependent_request.own_transaction_id.into()),
2455 )
2456 .await
2457 {
2458 warn!("error when sending a redact for {event_id}: {err}");
2459 return Ok(false);
2460 }
2461 } else {
2462 let removed = store
2465 .remove_send_queue_request(
2466 &self.room_id,
2467 &dependent_request.parent_transaction_id,
2468 )
2469 .await
2470 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2471
2472 if !removed {
2473 warn!("missing local echo upon dependent redact");
2474 }
2475 }
2476 }
2477
2478 DependentQueuedRequestKind::ReactEvent { key } => {
2479 if let Some(parent_key) = parent_key {
2480 let Some(parent_event_id) = parent_key.into_event_id() else {
2481 return Err(RoomSendQueueError::StorageError(
2482 RoomSendQueueStorageError::InvalidParentKey,
2483 ));
2484 };
2485
2486 let react_event =
2488 ReactionEventContent::new(Annotation::new(parent_event_id, key)).into();
2489 let serializable = SerializableEventContent::from_raw(
2490 Raw::new(&react_event)
2491 .map_err(RoomSendQueueStorageError::JsonSerialization)?,
2492 react_event.event_type().to_string(),
2493 );
2494
2495 store
2496 .save_send_queue_request(
2497 &self.room_id,
2498 dependent_request.own_transaction_id.into(),
2499 dependent_request.created_at,
2500 serializable.into(),
2501 Self::HIGH_PRIORITY,
2502 )
2503 .await
2504 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2505 } else {
2506 return Ok(false);
2508 }
2509 }
2510
2511 DependentQueuedRequestKind::UploadFileOrThumbnail {
2512 content_type,
2513 cache_key,
2514 related_to,
2515 parent_is_thumbnail_upload,
2516 } => {
2517 let Some(parent_key) = parent_key else {
2518 return Ok(false);
2520 };
2521 self.handle_dependent_file_or_thumbnail_upload(
2522 client,
2523 dependent_request.own_transaction_id.into(),
2524 parent_key,
2525 content_type,
2526 cache_key,
2527 related_to,
2528 parent_is_thumbnail_upload,
2529 )
2530 .await?;
2531 }
2532
2533 DependentQueuedRequestKind::FinishUpload {
2534 local_echo,
2535 file_upload,
2536 thumbnail_info,
2537 extra_content,
2538 } => {
2539 let Some(parent_key) = parent_key else {
2540 return Ok(false);
2542 };
2543 self.handle_dependent_finish_upload(
2544 client,
2545 dependent_request.own_transaction_id.into(),
2546 parent_key,
2547 *local_echo,
2548 file_upload,
2549 thumbnail_info,
2550 extra_content,
2551 new_updates,
2552 )
2553 .await?;
2554 }
2555
2556 #[cfg(feature = "unstable-msc4274")]
2557 DependentQueuedRequestKind::FinishGallery { local_echo, item_infos } => {
2558 let Some(parent_key) = parent_key else {
2559 return Ok(false);
2561 };
2562 self.handle_dependent_finish_gallery_upload(
2563 client,
2564 dependent_request.own_transaction_id.into(),
2565 parent_key,
2566 *local_echo,
2567 item_infos,
2568 new_updates,
2569 )
2570 .await?;
2571 }
2572 }
2573
2574 Ok(true)
2575 }
2576
2577 #[instrument(skip(self))]
2578 async fn apply_dependent_requests(
2579 &self,
2580 new_updates: &mut Vec<RoomSendQueueUpdate>,
2581 ) -> Result<(), RoomSendQueueError> {
2582 let guard = self.store.lock().await;
2583
2584 let client = guard.client()?;
2585 let store = client.state_store();
2586
2587 let dependent_requests = store
2588 .load_dependent_queued_requests(&self.room_id)
2589 .await
2590 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2591
2592 let num_initial_dependent_requests = dependent_requests.len();
2593 if num_initial_dependent_requests == 0 {
2594 return Ok(());
2596 }
2597
2598 let canonicalized_dependent_requests = canonicalize_dependent_requests(&dependent_requests);
2599
2600 for original in &dependent_requests {
2602 if !canonicalized_dependent_requests
2603 .iter()
2604 .any(|canonical| canonical.own_transaction_id == original.own_transaction_id)
2605 {
2606 store
2607 .remove_dependent_queued_request(&self.room_id, &original.own_transaction_id)
2608 .await
2609 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2610 }
2611 }
2612
2613 let mut num_dependent_requests = canonicalized_dependent_requests.len();
2614
2615 debug!(
2616 num_dependent_requests,
2617 num_initial_dependent_requests, "starting handling of dependent requests"
2618 );
2619
2620 for dependent in canonicalized_dependent_requests {
2621 let dependent_id = dependent.own_transaction_id.clone();
2622
2623 match self.try_apply_single_dependent_request(&client, dependent, new_updates).await {
2624 Ok(should_remove) => {
2625 if should_remove {
2626 store
2629 .remove_dependent_queued_request(&self.room_id, &dependent_id)
2630 .await
2631 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2632
2633 num_dependent_requests -= 1;
2634 }
2635 }
2636
2637 Err(err) => {
2638 warn!("error when applying single dependent request: {err}");
2639 }
2640 }
2641 }
2642
2643 debug!(
2644 leftover_dependent_requests = num_dependent_requests,
2645 "stopped handling dependent request"
2646 );
2647
2648 Ok(())
2649 }
2650
2651 async fn remove_dependent_send_queue_request(
2653 &self,
2654 dependent_event_id: &ChildTransactionId,
2655 ) -> Result<bool, RoomSendQueueStorageError> {
2656 Ok(self
2657 .store
2658 .lock()
2659 .await
2660 .client()?
2661 .state_store()
2662 .remove_dependent_queued_request(&self.room_id, dependent_event_id)
2663 .await?)
2664 }
2665}
2666
2667#[cfg(feature = "unstable-msc4274")]
2668struct GalleryItemQueueInfo {
2670 content_type: Mime,
2671 upload_file_txn: OwnedTransactionId,
2672 file_media_request: MediaRequestParameters,
2673 thumbnail: Option<QueueThumbnailInfo>,
2674}
2675
2676#[derive(Clone, Debug)]
2678pub enum LocalEchoContent {
2679 Event {
2681 serialized_event: SerializableEventContent,
2684 send_handle: SendHandle,
2686 send_error: Option<QueueWedgeError>,
2689 },
2690
2691 React {
2693 key: String,
2695 send_handle: SendHandle,
2697 applies_to: OwnedTransactionId,
2699 },
2700
2701 Redaction {
2703 redacts: OwnedEventId,
2705 reason: Option<String>,
2707 send_handle: SendHandle,
2709 send_error: Option<QueueWedgeError>,
2712 },
2713}
2714
2715#[derive(Clone, Debug)]
2718pub struct LocalEcho {
2719 pub transaction_id: OwnedTransactionId,
2721 pub content: LocalEchoContent,
2723}
2724
2725#[derive(Clone, Debug)]
2728pub enum RoomSendQueueUpdate {
2729 NewLocalEvent(LocalEcho),
2734
2735 CancelledLocalEvent {
2738 transaction_id: OwnedTransactionId,
2740 },
2741
2742 ReplacedLocalEvent {
2744 transaction_id: OwnedTransactionId,
2746
2747 new_content: SerializableEventContent,
2749 },
2750
2751 SendError {
2758 transaction_id: OwnedTransactionId,
2760 error: Arc<crate::Error>,
2762 is_recoverable: bool,
2768 },
2769
2770 RetryEvent {
2772 transaction_id: OwnedTransactionId,
2774 },
2775
2776 SentEvent {
2779 transaction_id: OwnedTransactionId,
2781 event_id: OwnedEventId,
2783 },
2784
2785 MediaUpload {
2788 related_to: OwnedTransactionId,
2790
2791 file: Option<MediaSource>,
2793
2794 index: u64,
2798
2799 progress: AbstractProgress,
2803 },
2804}
2805
2806#[derive(Clone, Debug)]
2811pub struct SendQueueUpdate {
2812 pub room_id: OwnedRoomId,
2814
2815 pub update: RoomSendQueueUpdate,
2817}
2818
2819#[derive(Debug, thiserror::Error)]
2821pub enum RoomSendQueueError {
2822 #[error("the room isn't in the joined state")]
2824 RoomNotJoined,
2825
2826 #[error("the room is now missing from the client")]
2830 RoomDisappeared,
2831
2832 #[error(transparent)]
2834 StorageError(#[from] RoomSendQueueStorageError),
2835
2836 #[error("the attachment event could not be created")]
2838 FailedToCreateAttachment,
2839
2840 #[error(transparent)]
2843 Edit(#[from] crate::room::edit::EditError),
2844
2845 #[cfg(feature = "unstable-msc4274")]
2847 #[error("the gallery contains no items")]
2848 EmptyGallery,
2849
2850 #[cfg(feature = "unstable-msc4274")]
2852 #[error("the gallery event could not be created")]
2853 FailedToCreateGallery,
2854}
2855
2856#[derive(Debug, thiserror::Error)]
2858pub enum RoomSendQueueStorageError {
2859 #[error(transparent)]
2861 StateStoreError(#[from] StoreError),
2862
2863 #[error(transparent)]
2865 EventCacheStoreError(#[from] EventCacheStoreError),
2866
2867 #[error(transparent)]
2869 MediaStoreError(#[from] MediaStoreError),
2870
2871 #[error(transparent)]
2873 LockError(#[from] CrossProcessLockError),
2874
2875 #[error(transparent)]
2877 JsonSerialization(#[from] serde_json::Error),
2878
2879 #[error("a dependent event had an invalid parent key type")]
2882 InvalidParentKey,
2883
2884 #[error("The client is shutting down.")]
2886 ClientShuttingDown,
2887
2888 #[error("This operation is not implemented for media uploads")]
2890 OperationNotImplementedYet,
2891
2892 #[error("Can't edit a media caption when the underlying event isn't a media")]
2894 InvalidMediaCaptionEdit,
2895}
2896
2897#[derive(Clone, Debug)]
2899struct MediaHandles {
2900 upload_thumbnail_txn: Option<OwnedTransactionId>,
2904
2905 upload_file_txn: OwnedTransactionId,
2907}
2908
2909#[allow(missing_debug_implementations)]
2911pub struct SendEvent<'a> {
2912 queue: &'a RoomSendQueue,
2913 content: AnyMessageLikeEventContent,
2914 extra_content: Option<serde_json::Map<String, serde_json::Value>>,
2915 #[cfg(feature = "unstable-msc4354")]
2916 sticky_duration: Option<StickyDurationMs>,
2917}
2918
2919impl<'a> SendEvent<'a> {
2920 pub fn with_extra_content(
2924 mut self,
2925 extra_content: serde_json::Map<String, serde_json::Value>,
2926 ) -> Self {
2927 self.extra_content = Some(extra_content);
2928 self
2929 }
2930
2931 #[cfg(feature = "unstable-msc4354")]
2937 pub fn with_sticky_duration(mut self, duration: Duration) -> Self {
2938 self.sticky_duration = Some(sticky_duration_ms(duration));
2939 self
2940 }
2941}
2942
2943impl<'a> IntoFuture for SendEvent<'a> {
2944 type Output = Result<SendHandle, RoomSendQueueError>;
2945 boxed_into_future!(extra_bounds: 'a);
2946
2947 fn into_future(self) -> Self::IntoFuture {
2948 Box::pin(async move {
2949 let serialized = upload::merge_extra_content(
2950 SerializableEventContent::new(&self.content)
2951 .map_err(RoomSendQueueStorageError::JsonSerialization)?,
2952 self.extra_content,
2953 )?;
2954 self.queue
2955 .send_serialized(
2956 serialized,
2957 #[cfg(feature = "unstable-msc4354")]
2958 self.sticky_duration,
2959 )
2960 .await
2961 })
2962 }
2963}
2964
2965#[allow(missing_debug_implementations)]
2967pub struct SendRawEvent<'a> {
2968 queue: &'a RoomSendQueue,
2969 content: SerializableEventContent,
2970 #[cfg(feature = "unstable-msc4354")]
2971 sticky_duration: Option<StickyDurationMs>,
2972}
2973
2974impl SendRawEvent<'_> {
2975 #[cfg(feature = "unstable-msc4354")]
2981 pub fn with_sticky_duration(mut self, duration: Duration) -> Self {
2982 self.sticky_duration = Some(sticky_duration_ms(duration));
2983 self
2984 }
2985}
2986
2987impl<'a> IntoFuture for SendRawEvent<'a> {
2988 type Output = Result<SendHandle, RoomSendQueueError>;
2989 boxed_into_future!(extra_bounds: 'a);
2990
2991 fn into_future(self) -> Self::IntoFuture {
2992 Box::pin(async move {
2993 self.queue
2994 .send_serialized(
2995 self.content,
2996 #[cfg(feature = "unstable-msc4354")]
2997 self.sticky_duration,
2998 )
2999 .await
3000 })
3001 }
3002}
3003
3004#[derive(Clone, Debug)]
3010pub struct SendHandle {
3011 room: RoomSendQueue,
3013
3014 transaction_id: OwnedTransactionId,
3019
3020 media_handles: Vec<MediaHandles>,
3022
3023 pub created_at: MilliSecondsSinceUnixEpoch,
3025}
3026
3027impl SendHandle {
3028 #[cfg(test)]
3030 pub(crate) fn new(
3031 room: RoomSendQueue,
3032 transaction_id: OwnedTransactionId,
3033 created_at: MilliSecondsSinceUnixEpoch,
3034 ) -> Self {
3035 Self { room, transaction_id, media_handles: vec![], created_at }
3036 }
3037
3038 pub fn transaction_id(&self) -> &TransactionId {
3040 &self.transaction_id
3041 }
3042
3043 fn nyi_for_uploads(&self) -> Result<(), RoomSendQueueStorageError> {
3044 if !self.media_handles.is_empty() {
3045 Err(RoomSendQueueStorageError::OperationNotImplementedYet)
3046 } else {
3047 Ok(())
3048 }
3049 }
3050
3051 pub async fn abort(&self) -> Result<bool, RoomSendQueueStorageError> {
3056 self.abort_with_reason(None).await
3057 }
3058
3059 #[instrument(skip(self), fields(room_id = %self.room.inner.room.room_id(), txn_id = %self.transaction_id))]
3070 pub async fn abort_with_reason(
3071 &self,
3072 reason: Option<String>,
3073 ) -> Result<bool, RoomSendQueueStorageError> {
3074 trace!("received an abort request");
3075
3076 let queue = &self.room.inner.queue;
3077
3078 for handles in &self.media_handles {
3079 if queue.abort_upload(&self.transaction_id, handles).await? {
3080 self.room.inner.notifier.notify_one();
3083
3084 self.room.send_update(RoomSendQueueUpdate::CancelledLocalEvent {
3086 transaction_id: self.transaction_id.clone(),
3087 });
3088
3089 return Ok(true);
3090 }
3091
3092 }
3096
3097 let aborted =
3100 queue.remove_dependent_send_queue_request(&self.transaction_id.clone().into()).await?
3101 || queue.cancel_event(&self.transaction_id, reason).await?;
3102
3103 if aborted {
3104 trace!("successful abort");
3105
3106 self.room.inner.notifier.notify_one();
3109
3110 self.room.send_update(RoomSendQueueUpdate::CancelledLocalEvent {
3112 transaction_id: self.transaction_id.clone(),
3113 });
3114
3115 Ok(true)
3116 } else {
3117 debug!("local echo didn't exist anymore, can't abort");
3118 Ok(false)
3119 }
3120 }
3121
3122 #[instrument(skip(self, new_content), fields(room_id = %self.room.inner.room.room_id(), txn_id = %self.transaction_id))]
3135 pub async fn edit_raw(
3136 &self,
3137 new_content: Raw<AnyMessageLikeEventContent>,
3138 event_type: String,
3139 ) -> Result<bool, RoomSendQueueStorageError> {
3140 trace!("received an edit request");
3141 self.nyi_for_uploads()?;
3142
3143 let serializable = SerializableEventContent::from_raw(new_content, event_type);
3144
3145 if self.room.inner.queue.replace_event(&self.transaction_id, serializable.clone()).await? {
3146 trace!("successful edit");
3147
3148 self.room.inner.notifier.notify_one();
3150
3151 self.room.send_update(RoomSendQueueUpdate::ReplacedLocalEvent {
3153 transaction_id: self.transaction_id.clone(),
3154 new_content: serializable,
3155 });
3156
3157 Ok(true)
3158 } else {
3159 debug!("local echo doesn't exist anymore, can't edit");
3160 Ok(false)
3161 }
3162 }
3163
3164 pub async fn edit(
3177 &self,
3178 new_content: AnyMessageLikeEventContent,
3179 ) -> Result<bool, RoomSendQueueStorageError> {
3180 self.edit_raw(
3181 Raw::new(&new_content).map_err(RoomSendQueueStorageError::JsonSerialization)?,
3182 new_content.event_type().to_string(),
3183 )
3184 .await
3185 }
3186
3187 pub async fn edit_media_caption(
3192 &self,
3193 caption: Option<String>,
3194 formatted_caption: Option<FormattedBody>,
3195 mentions: Option<Mentions>,
3196 ) -> Result<bool, RoomSendQueueStorageError> {
3197 if let Some(new_content) = self
3198 .room
3199 .inner
3200 .queue
3201 .edit_media_caption(&self.transaction_id, caption, formatted_caption, mentions)
3202 .await?
3203 {
3204 trace!("successful edit of media caption");
3205
3206 self.room.inner.notifier.notify_one();
3208
3209 let new_content = SerializableEventContent::new(&new_content)
3210 .map_err(RoomSendQueueStorageError::JsonSerialization)?;
3211
3212 self.room.send_update(RoomSendQueueUpdate::ReplacedLocalEvent {
3214 transaction_id: self.transaction_id.clone(),
3215 new_content,
3216 });
3217
3218 Ok(true)
3219 } else {
3220 debug!("local echo doesn't exist anymore, can't edit media caption");
3221 Ok(false)
3222 }
3223 }
3224
3225 pub async fn unwedge(&self) -> Result<(), RoomSendQueueError> {
3228 let room = &self.room.inner;
3229
3230 for handles in &self.media_handles {
3238 room.queue
3239 .mark_as_unwedged(&handles.upload_file_txn)
3240 .await
3241 .map_err(RoomSendQueueError::StorageError)?;
3242
3243 if let Some(txn) = &handles.upload_thumbnail_txn {
3244 room.queue.mark_as_unwedged(txn).await.map_err(RoomSendQueueError::StorageError)?;
3245 }
3246 }
3247
3248 self.room.unwedge_request(&self.transaction_id).await
3249 }
3250
3251 #[instrument(skip(self), fields(room_id = %self.room.inner.room.room_id(), txn_id = %self.transaction_id))]
3256 pub async fn react(
3257 &self,
3258 key: String,
3259 ) -> Result<Option<SendHandle>, RoomSendQueueStorageError> {
3260 trace!("received an intent to react");
3261
3262 let created_at = MilliSecondsSinceUnixEpoch::now();
3263 if let Some(reaction_txn_id) =
3264 self.room.inner.queue.react(&self.transaction_id, key.clone(), created_at).await?
3265 {
3266 trace!("successfully queued react");
3267
3268 self.room.inner.notifier.notify_one();
3271
3272 let send_handle = SendHandle {
3274 room: self.room.clone(),
3275 transaction_id: reaction_txn_id.clone().into(),
3276 media_handles: vec![],
3277 created_at,
3278 };
3279
3280 self.room.send_update(RoomSendQueueUpdate::NewLocalEvent(LocalEcho {
3281 transaction_id: reaction_txn_id.into(),
3284 content: LocalEchoContent::React {
3285 key,
3286 send_handle: send_handle.clone(),
3287 applies_to: self.transaction_id.clone(),
3288 },
3289 }));
3290
3291 Ok(Some(send_handle))
3292 } else {
3293 debug!("local echo doesn't exist anymore, can't react");
3294 Ok(None)
3295 }
3296 }
3297}
3298
3299fn is_own_event_request(request: &QueuedRequest) -> bool {
3307 request.as_event().is_some_and(|content| {
3308 TimelineEventType::from(content.raw().1) != TimelineEventType::Reaction
3309 })
3310}
3311
3312fn canonicalize_dependent_requests(
3316 dependent: &[DependentQueuedRequest],
3317) -> Vec<DependentQueuedRequest> {
3318 let mut by_txn = HashMap::<OwnedTransactionId, Vec<&DependentQueuedRequest>>::new();
3319
3320 for d in dependent {
3321 let prevs = by_txn.entry(d.parent_transaction_id.clone()).or_default();
3322
3323 if prevs.iter().any(|prev| {
3324 matches!(
3325 prev.kind,
3326 DependentQueuedRequestKind::RedactEvent
3327 | DependentQueuedRequestKind::RedactEventWithReason { .. }
3328 )
3329 }) {
3330 continue;
3333 }
3334
3335 match &d.kind {
3336 DependentQueuedRequestKind::EditEvent { .. } => {
3337 if let Some(prev_edit) = prevs
3339 .iter_mut()
3340 .find(|prev| matches!(prev.kind, DependentQueuedRequestKind::EditEvent { .. }))
3341 {
3342 *prev_edit = d;
3343 } else {
3344 prevs.insert(0, d);
3345 }
3346 }
3347
3348 DependentQueuedRequestKind::UploadFileOrThumbnail { .. }
3349 | DependentQueuedRequestKind::FinishUpload { .. }
3350 | DependentQueuedRequestKind::ReactEvent { .. } => {
3351 prevs.push(d);
3353 }
3354
3355 #[cfg(feature = "unstable-msc4274")]
3356 DependentQueuedRequestKind::FinishGallery { .. } => {
3357 prevs.push(d);
3359 }
3360
3361 DependentQueuedRequestKind::RedactEvent
3362 | DependentQueuedRequestKind::RedactEventWithReason { .. } => {
3363 prevs.clear();
3365 prevs.push(d);
3366 }
3367 }
3368 }
3369
3370 by_txn.into_values().flat_map(|entries| entries.into_iter().cloned()).collect()
3371}
3372
3373#[cfg(all(test, not(target_family = "wasm")))]
3374mod tests {
3375 use std::{assert_matches, sync::Arc, time::Duration};
3376
3377 use matrix_sdk_base::store::{
3378 ChildTransactionId, DependentQueuedRequest, DependentQueuedRequestKind,
3379 SerializableEventContent,
3380 };
3381 use matrix_sdk_test::{JoinedRoomBuilder, SyncResponseBuilder, async_test};
3382 use ruma::{
3383 MilliSecondsSinceUnixEpoch, TransactionId,
3384 events::{AnyMessageLikeEventContent, room::message::RoomMessageEventContent},
3385 room_id,
3386 };
3387 use strass::assert_let;
3388
3389 use super::canonicalize_dependent_requests;
3390 use crate::{client::WeakClient, test_utils::logged_in_client};
3391
3392 #[test]
3393 fn test_canonicalize_dependent_events_created_at() {
3394 let txn = TransactionId::new();
3397 let created_at = MilliSecondsSinceUnixEpoch::now();
3398
3399 let edit = DependentQueuedRequest {
3400 own_transaction_id: ChildTransactionId::new(),
3401 parent_transaction_id: txn.clone(),
3402 kind: DependentQueuedRequestKind::EditEvent {
3403 new_content: SerializableEventContent::new(
3404 &RoomMessageEventContent::text_plain("edit").into(),
3405 )
3406 .unwrap(),
3407 },
3408 parent_key: None,
3409 created_at,
3410 };
3411
3412 let res = canonicalize_dependent_requests(&[edit]);
3413
3414 assert_eq!(res.len(), 1);
3415 assert_let!(DependentQueuedRequestKind::EditEvent { new_content } = &res[0].kind);
3416 assert_let!(
3417 AnyMessageLikeEventContent::RoomMessage(msg) = new_content.deserialize().unwrap()
3418 );
3419 assert_eq!(msg.body(), "edit");
3420 assert_eq!(res[0].parent_transaction_id, txn);
3421 assert_eq!(res[0].created_at, created_at);
3422 }
3423
3424 #[async_test]
3425 async fn test_client_no_cycle_with_send_queue() {
3426 for enabled in [true, false] {
3427 let client = logged_in_client(None).await;
3428 let weak_client = WeakClient::from_client(&client);
3429
3430 {
3431 let mut sync_response_builder = SyncResponseBuilder::new();
3432
3433 let room_id = room_id!("!a:b.c");
3434
3435 client
3437 .base_client()
3438 .receive_sync_response(
3439 sync_response_builder
3440 .add_joined_room(JoinedRoomBuilder::new(room_id))
3441 .build_sync_response(),
3442 )
3443 .await
3444 .unwrap();
3445
3446 let room = client.get_room(room_id).unwrap();
3447 let q = room.send_queue();
3448
3449 let _watcher = q.subscribe().await;
3450
3451 client.send_queue().set_enabled(enabled).await;
3452 }
3453
3454 drop(client);
3455
3456 tokio::time::sleep(Duration::from_millis(500)).await;
3458
3459 let client = weak_client.get();
3461 assert!(
3462 client.is_none(),
3463 "too many strong references to the client: {}",
3464 Arc::strong_count(&client.unwrap().inner)
3465 );
3466 }
3467 }
3468
3469 #[test]
3470 fn test_canonicalize_dependent_events_smoke_test() {
3471 let txn = TransactionId::new();
3473
3474 let edit = DependentQueuedRequest {
3475 own_transaction_id: ChildTransactionId::new(),
3476 parent_transaction_id: txn.clone(),
3477 kind: DependentQueuedRequestKind::EditEvent {
3478 new_content: SerializableEventContent::new(
3479 &RoomMessageEventContent::text_plain("edit").into(),
3480 )
3481 .unwrap(),
3482 },
3483 parent_key: None,
3484 created_at: MilliSecondsSinceUnixEpoch::now(),
3485 };
3486 let res = canonicalize_dependent_requests(&[edit]);
3487
3488 assert_eq!(res.len(), 1);
3489 assert_matches!(&res[0].kind, DependentQueuedRequestKind::EditEvent { .. });
3490 assert_eq!(res[0].parent_transaction_id, txn);
3491 assert!(res[0].parent_key.is_none());
3492 }
3493
3494 #[test]
3495 fn test_canonicalize_dependent_events_redaction_preferred() {
3496 let txn = TransactionId::new();
3498
3499 let mut inputs = Vec::with_capacity(100);
3500 let redact = DependentQueuedRequest {
3501 own_transaction_id: ChildTransactionId::new(),
3502 parent_transaction_id: txn.clone(),
3503 kind: DependentQueuedRequestKind::RedactEventWithReason { reason: None },
3504 parent_key: None,
3505 created_at: MilliSecondsSinceUnixEpoch::now(),
3506 };
3507
3508 let edit = DependentQueuedRequest {
3509 own_transaction_id: ChildTransactionId::new(),
3510 parent_transaction_id: txn.clone(),
3511 kind: DependentQueuedRequestKind::EditEvent {
3512 new_content: SerializableEventContent::new(
3513 &RoomMessageEventContent::text_plain("edit").into(),
3514 )
3515 .unwrap(),
3516 },
3517 parent_key: None,
3518 created_at: MilliSecondsSinceUnixEpoch::now(),
3519 };
3520
3521 inputs.push({
3522 let mut edit = edit.clone();
3523 edit.own_transaction_id = ChildTransactionId::new();
3524 edit
3525 });
3526
3527 inputs.push(redact);
3528
3529 for _ in 0..98 {
3530 let mut edit = edit.clone();
3531 edit.own_transaction_id = ChildTransactionId::new();
3532 inputs.push(edit);
3533 }
3534
3535 let res = canonicalize_dependent_requests(&inputs);
3536
3537 assert_eq!(res.len(), 1);
3538 assert_matches!(&res[0].kind, DependentQueuedRequestKind::RedactEventWithReason { .. });
3539 assert_eq!(res[0].parent_transaction_id, txn);
3540 }
3541
3542 #[test]
3543 fn test_canonicalize_dependent_events_last_edit_preferred() {
3544 let parent_txn = TransactionId::new();
3545
3546 let inputs = (0..10)
3548 .map(|i| DependentQueuedRequest {
3549 own_transaction_id: ChildTransactionId::new(),
3550 parent_transaction_id: parent_txn.clone(),
3551 kind: DependentQueuedRequestKind::EditEvent {
3552 new_content: SerializableEventContent::new(
3553 &RoomMessageEventContent::text_plain(format!("edit{i}")).into(),
3554 )
3555 .unwrap(),
3556 },
3557 parent_key: None,
3558 created_at: MilliSecondsSinceUnixEpoch::now(),
3559 })
3560 .collect::<Vec<_>>();
3561
3562 let txn = inputs[9].parent_transaction_id.clone();
3563
3564 let res = canonicalize_dependent_requests(&inputs);
3565
3566 assert_eq!(res.len(), 1);
3567 assert_let!(DependentQueuedRequestKind::EditEvent { new_content } = &res[0].kind);
3568 assert_let!(
3569 AnyMessageLikeEventContent::RoomMessage(msg) = new_content.deserialize().unwrap()
3570 );
3571 assert_eq!(msg.body(), "edit9");
3572 assert_eq!(res[0].parent_transaction_id, txn);
3573 }
3574
3575 #[test]
3576 fn test_canonicalize_multiple_local_echoes() {
3577 let txn1 = TransactionId::new();
3578 let txn2 = TransactionId::new();
3579
3580 let child1 = ChildTransactionId::new();
3581 let child2 = ChildTransactionId::new();
3582
3583 let inputs = vec![
3584 DependentQueuedRequest {
3586 own_transaction_id: child1.clone(),
3587 kind: DependentQueuedRequestKind::RedactEventWithReason { reason: None },
3588 parent_transaction_id: txn1.clone(),
3589 parent_key: None,
3590 created_at: MilliSecondsSinceUnixEpoch::now(),
3591 },
3592 DependentQueuedRequest {
3594 own_transaction_id: child2,
3595 kind: DependentQueuedRequestKind::EditEvent {
3596 new_content: SerializableEventContent::new(
3597 &RoomMessageEventContent::text_plain("edit").into(),
3598 )
3599 .unwrap(),
3600 },
3601 parent_transaction_id: txn2.clone(),
3602 parent_key: None,
3603 created_at: MilliSecondsSinceUnixEpoch::now(),
3604 },
3605 ];
3606
3607 let res = canonicalize_dependent_requests(&inputs);
3608
3609 assert_eq!(res.len(), 2);
3611
3612 for dependent in res {
3613 if dependent.own_transaction_id == child1 {
3614 assert_eq!(dependent.parent_transaction_id, txn1);
3615 assert_matches!(
3616 dependent.kind,
3617 DependentQueuedRequestKind::RedactEventWithReason { .. }
3618 );
3619 } else {
3620 assert_eq!(dependent.parent_transaction_id, txn2);
3621 assert_matches!(dependent.kind, DependentQueuedRequestKind::EditEvent { .. });
3622 }
3623 }
3624 }
3625
3626 #[test]
3627 fn test_canonicalize_reactions_after_edits() {
3628 let txn = TransactionId::new();
3630
3631 let react_id = ChildTransactionId::new();
3632 let react = DependentQueuedRequest {
3633 own_transaction_id: react_id.clone(),
3634 kind: DependentQueuedRequestKind::ReactEvent { key: "🧠".to_owned() },
3635 parent_transaction_id: txn.clone(),
3636 parent_key: None,
3637 created_at: MilliSecondsSinceUnixEpoch::now(),
3638 };
3639
3640 let edit_id = ChildTransactionId::new();
3641 let edit = DependentQueuedRequest {
3642 own_transaction_id: edit_id.clone(),
3643 kind: DependentQueuedRequestKind::EditEvent {
3644 new_content: SerializableEventContent::new(
3645 &RoomMessageEventContent::text_plain("edit").into(),
3646 )
3647 .unwrap(),
3648 },
3649 parent_transaction_id: txn,
3650 parent_key: None,
3651 created_at: MilliSecondsSinceUnixEpoch::now(),
3652 };
3653
3654 let res = canonicalize_dependent_requests(&[react, edit]);
3655
3656 assert_eq!(res.len(), 2);
3657 assert_eq!(res[0].own_transaction_id, edit_id);
3658 assert_eq!(res[1].own_transaction_id, react_id);
3659 }
3660}