1use std::{
132 collections::{BTreeMap, HashMap},
133 future::IntoFuture,
134 ops::Not,
135 str::FromStr as _,
136 sync::{
137 Arc, RwLock,
138 atomic::{AtomicBool, Ordering},
139 },
140};
141
142use eyeball::SharedObservable;
143#[cfg(feature = "e2e-encryption")]
144use matrix_sdk_base::crypto::{OlmError, SessionRecipientCollectionError};
145#[cfg(feature = "unstable-msc4274")]
146use matrix_sdk_base::store::FinishGalleryItemInfo;
147use matrix_sdk_base::{
148 RoomState, StoreError,
149 cross_process_lock::CrossProcessLockError,
150 deserialized_responses::{EncryptionInfo, TimelineEvent},
151 event_cache::store::EventCacheStoreError,
152 media::{MediaRequestParameters, store::MediaStoreError},
153 store::{
154 ChildTransactionId, DependentQueuedRequest, DependentQueuedRequestKind, DynStateStore,
155 FinishUploadThumbnailInfo, QueueWedgeError, QueuedRequest, QueuedRequestKind,
156 SentMediaInfo, SentRequestKey, SerializableEventContent,
157 },
158 task_monitor::BackgroundTaskHandle,
159};
160use matrix_sdk_common::{boxed_into_future, locks::Mutex as SyncMutex};
161use mime::Mime;
162use ruma::{
163 MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, OwnedTransactionId, RoomId,
164 TransactionId,
165 events::{
166 AnyMessageLikeEventContent, Mentions, MessageLikeEventContent as _, TimelineEventType,
167 reaction::ReactionEventContent,
168 relation::Annotation,
169 room::{
170 MediaSource,
171 message::{FormattedBody, RoomMessageEventContent},
172 },
173 },
174 serde::Raw,
175};
176use tokio::sync::{Mutex, Notify, OwnedMutexGuard, broadcast, oneshot};
177use tracing::{debug, error, info, instrument, trace, warn};
178
179use crate::{
180 Client, Media, Room, TransmissionProgress,
181 client::WeakClient,
182 config::RequestConfig,
183 error::RetryKind,
184 room::{WeakRoom, edit::EditedContent},
185};
186
187mod progress;
188mod upload;
189
190pub use progress::AbstractProgress;
191
192pub struct SendQueue {
194 client: Client,
195}
196
197#[cfg(not(tarpaulin_include))]
198impl std::fmt::Debug for SendQueue {
199 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
200 f.debug_struct("SendQueue").finish_non_exhaustive()
201 }
202}
203
204impl SendQueue {
205 pub(super) fn new(client: Client) -> Self {
206 Self { client }
207 }
208
209 pub async fn respawn_tasks_for_rooms_with_unsent_requests(&self) {
212 if !self.is_enabled() {
213 return;
214 }
215
216 let room_ids =
217 self.client.state_store().load_rooms_with_unsent_requests().await.unwrap_or_else(
218 |err| {
219 warn!("error when loading rooms with unsent requests: {err}");
220 Vec::new()
221 },
222 );
223
224 for room_id in room_ids {
226 if let Some(room) = self.client.get_room(&room_id) {
227 let _ = self.for_room(room);
228 }
229 }
230 }
231
232 #[inline(always)]
234 fn data(&self) -> &SendQueueData {
235 &self.client.inner.send_queue_data
236 }
237
238 pub(crate) fn for_room(&self, room: Room) -> RoomSendQueue {
241 let data = self.data();
242
243 let mut map = data.rooms.write().unwrap();
244
245 let room_id = room.room_id();
246 if let Some(room_q) = map.get(room_id).cloned() {
247 return room_q;
248 }
249
250 let owned_room_id = room_id.to_owned();
251 let room_q = RoomSendQueue::new(
252 self.is_enabled(),
253 data.global_update_sender.clone(),
254 data.error_sender.clone(),
255 data.is_dropping.clone(),
256 &self.client,
257 owned_room_id.clone(),
258 data.report_media_upload_progress.clone(),
259 );
260
261 map.insert(owned_room_id, room_q.clone());
262
263 room_q
264 }
265
266 pub async fn set_enabled(&self, enabled: bool) {
276 debug!(?enabled, "setting global send queue enablement");
277
278 self.data().globally_enabled.store(enabled, Ordering::SeqCst);
279
280 for room in self.data().rooms.read().unwrap().values() {
282 room.set_enabled(enabled);
283 }
284
285 self.respawn_tasks_for_rooms_with_unsent_requests().await;
288 }
289
290 pub fn is_enabled(&self) -> bool {
293 self.data().globally_enabled.load(Ordering::SeqCst)
294 }
295
296 pub fn enable_upload_progress(&self, enabled: bool) {
298 self.data().report_media_upload_progress.store(enabled, Ordering::SeqCst);
299 }
300
301 pub fn subscribe(&self) -> broadcast::Receiver<SendQueueUpdate> {
306 self.data().global_update_sender.subscribe()
307 }
308
309 pub async fn local_echoes(
311 &self,
312 ) -> Result<BTreeMap<OwnedRoomId, Vec<LocalEcho>>, RoomSendQueueError> {
313 let room_ids =
314 self.client.state_store().load_rooms_with_unsent_requests().await.unwrap_or_else(
315 |err| {
316 warn!("error when loading rooms with unsent requests: {err}");
317 Vec::new()
318 },
319 );
320
321 let mut local_echoes: BTreeMap<OwnedRoomId, Vec<LocalEcho>> = BTreeMap::new();
322
323 for room_id in room_ids {
324 if let Some(room) = self.client.get_room(&room_id) {
325 let queue = self.for_room(room);
326 local_echoes
327 .insert(room_id.to_owned(), queue.inner.queue.local_echoes(&queue).await?);
328 }
329 }
330
331 Ok(local_echoes)
332 }
333
334 pub fn subscribe_errors(&self) -> broadcast::Receiver<SendQueueRoomError> {
337 self.data().error_sender.subscribe()
338 }
339}
340
341#[derive(Clone, Debug)]
344struct QueueThumbnailInfo {
345 finish_upload_thumbnail_info: FinishUploadThumbnailInfo,
347
348 media_request_parameters: MediaRequestParameters,
350
351 content_type: Mime,
353
354 file_size: usize,
356}
357
358#[derive(Clone, Debug)]
360pub struct SendQueueRoomError {
361 pub room_id: OwnedRoomId,
363
364 pub error: Arc<crate::Error>,
366
367 pub is_recoverable: bool,
373}
374
375impl Client {
376 pub fn send_queue(&self) -> SendQueue {
379 SendQueue::new(self.clone())
380 }
381}
382
383pub(super) struct SendQueueData {
384 rooms: RwLock<BTreeMap<OwnedRoomId, RoomSendQueue>>,
386
387 globally_enabled: AtomicBool,
392
393 global_update_sender: broadcast::Sender<SendQueueUpdate>,
397
398 error_sender: broadcast::Sender<SendQueueRoomError>,
400
401 is_dropping: Arc<AtomicBool>,
403
404 report_media_upload_progress: Arc<AtomicBool>,
406}
407
408impl SendQueueData {
409 pub fn new(globally_enabled: bool) -> Self {
411 let (global_update_sender, _) = broadcast::channel(32);
412 let (error_sender, _) = broadcast::channel(32);
413
414 Self {
415 rooms: Default::default(),
416 globally_enabled: AtomicBool::new(globally_enabled),
417 global_update_sender,
418 error_sender,
419 is_dropping: Arc::new(false.into()),
420 report_media_upload_progress: Arc::new(false.into()),
421 }
422 }
423}
424
425impl Drop for SendQueueData {
426 fn drop(&mut self) {
427 debug!("globally dropping the send queue");
430 self.is_dropping.store(true, Ordering::SeqCst);
431
432 let rooms = self.rooms.read().unwrap();
433 for room in rooms.values() {
434 room.inner.notifier.notify_one();
435 }
436 }
437}
438
439impl Room {
440 pub fn send_queue(&self) -> RoomSendQueue {
442 self.client.send_queue().for_room(self.clone())
443 }
444}
445
446#[derive(Clone)]
450pub struct RoomSendQueue {
451 inner: Arc<RoomSendQueueInner>,
452}
453
454#[cfg(not(tarpaulin_include))]
455impl std::fmt::Debug for RoomSendQueue {
456 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
457 f.debug_struct("RoomSendQueue").finish_non_exhaustive()
458 }
459}
460
461impl RoomSendQueue {
462 fn new(
463 globally_enabled: bool,
464 global_update_sender: broadcast::Sender<SendQueueUpdate>,
465 global_error_sender: broadcast::Sender<SendQueueRoomError>,
466 is_dropping: Arc<AtomicBool>,
467 client: &Client,
468 room_id: OwnedRoomId,
469 report_media_upload_progress: Arc<AtomicBool>,
470 ) -> Self {
471 let (update_sender, _) = broadcast::channel(32);
472
473 let queue = QueueStorage::new(WeakClient::from_client(client), room_id.clone());
474 let notifier = Arc::new(Notify::new());
475
476 let weak_room = WeakRoom::new(WeakClient::from_client(client), room_id);
477 let locally_enabled = Arc::new(AtomicBool::new(globally_enabled));
478
479 let task = client.task_monitor().spawn_infinite_task(
480 "send_queue",
481 Self::sending_task(
482 weak_room.clone(),
483 queue.clone(),
484 notifier.clone(),
485 global_update_sender.clone(),
486 update_sender.clone(),
487 locally_enabled.clone(),
488 global_error_sender,
489 is_dropping,
490 report_media_upload_progress,
491 ),
492 );
493
494 Self {
495 inner: Arc::new(RoomSendQueueInner {
496 room: weak_room,
497 global_update_sender,
498 update_sender,
499 _task: task,
500 queue,
501 notifier,
502 locally_enabled,
503 }),
504 }
505 }
506
507 pub async fn send_raw(
522 &self,
523 content: Raw<AnyMessageLikeEventContent>,
524 event_type: String,
525 ) -> Result<SendHandle, RoomSendQueueError> {
526 let Some(room) = self.inner.room.get() else {
527 return Err(RoomSendQueueError::RoomDisappeared);
528 };
529 if room.state() != RoomState::Joined {
530 return Err(RoomSendQueueError::RoomNotJoined);
531 }
532
533 let content = SerializableEventContent::from_raw(content, event_type);
534
535 let created_at = MilliSecondsSinceUnixEpoch::now();
536 let transaction_id = self.inner.queue.push(content.clone().into(), created_at).await?;
537 trace!(%transaction_id, "manager sends a raw event to the background task");
538
539 self.inner.notifier.notify_one();
540
541 let send_handle = SendHandle {
542 room: self.clone(),
543 transaction_id: transaction_id.clone(),
544 media_handles: vec![],
545 created_at,
546 };
547
548 self.send_update(RoomSendQueueUpdate::NewLocalEvent(LocalEcho {
549 transaction_id,
550 content: LocalEchoContent::Event {
551 serialized_event: content,
552 send_handle: send_handle.clone(),
553 send_error: None,
554 },
555 }));
556
557 Ok(send_handle)
558 }
559
560 pub fn send(&self, content: AnyMessageLikeEventContent) -> SendEvent<'_> {
575 SendEvent { queue: self, content, extra_content: None }
576 }
577
578 pub async fn redact(
593 &self,
594 redacts: OwnedEventId,
595 reason: Option<&str>,
596 ) -> Result<SendRedactionHandle, RoomSendQueueError> {
597 let Some(room) = self.inner.room.get() else {
598 return Err(RoomSendQueueError::RoomDisappeared);
599 };
600 if room.state() != RoomState::Joined {
601 return Err(RoomSendQueueError::RoomNotJoined);
602 }
603
604 let request = QueuedRequestKind::Redaction {
605 redacts: redacts.clone(),
606 reason: reason.map(str::to_owned),
607 };
608
609 let created_at = MilliSecondsSinceUnixEpoch::now();
610 let transaction_id = self.inner.queue.push(request, created_at).await?;
611 trace!(%transaction_id, "manager sends a redaction event to the background task");
612
613 self.inner.notifier.notify_one();
614
615 let send_handle =
616 SendRedactionHandle { room: self.clone(), transaction_id: transaction_id.clone() };
617
618 self.send_update(RoomSendQueueUpdate::NewLocalEvent(LocalEcho {
619 transaction_id,
620 content: LocalEchoContent::Redaction {
621 redacts,
622 reason: reason.map(str::to_owned),
623 send_handle: send_handle.clone(),
624 send_error: None,
625 },
626 }));
627
628 Ok(send_handle)
629 }
630
631 pub async fn subscribe(
637 &self,
638 ) -> Result<(Vec<LocalEcho>, broadcast::Receiver<RoomSendQueueUpdate>), RoomSendQueueError>
639 {
640 let local_echoes = self.inner.queue.local_echoes(self).await?;
641
642 Ok((local_echoes, self.inner.update_sender.subscribe()))
643 }
644
645 #[allow(clippy::too_many_arguments)]
651 #[instrument(skip_all, fields(room_id = %room.room_id()))]
652 async fn sending_task(
653 room: WeakRoom,
654 queue: QueueStorage,
655 notifier: Arc<Notify>,
656 global_update_sender: broadcast::Sender<SendQueueUpdate>,
657 update_sender: broadcast::Sender<RoomSendQueueUpdate>,
658 locally_enabled: Arc<AtomicBool>,
659 global_error_sender: broadcast::Sender<SendQueueRoomError>,
660 is_dropping: Arc<AtomicBool>,
661 report_media_upload_progress: Arc<AtomicBool>,
662 ) {
663 trace!("spawned the sending task");
664
665 let room_id = room.room_id();
666
667 loop {
668 if is_dropping.load(Ordering::SeqCst) {
670 trace!("shutting down!");
671 break;
672 }
673
674 let mut new_updates = Vec::new();
677 if let Err(err) = queue.apply_dependent_requests(&mut new_updates).await {
678 warn!("errors when applying dependent requests: {err}");
679 }
680
681 for up in new_updates {
682 send_update(&global_update_sender, &update_sender, room_id, up);
683 }
684
685 if !locally_enabled.load(Ordering::SeqCst) {
686 trace!("not enabled, sleeping");
687 notifier.notified().await;
689 continue;
690 }
691
692 let (queued_request, cancel_upload_rx) = match queue.peek_next_to_send().await {
693 Ok(Some(request)) => request,
694
695 Ok(None) => {
696 trace!("queue is empty, sleeping");
697 notifier.notified().await;
699 continue;
700 }
701
702 Err(err) => {
703 warn!("error when loading next request to send: {err}");
704 continue;
705 }
706 };
707
708 let txn_id = queued_request.transaction_id.clone();
709 trace!(txn_id = %txn_id, "received a request to send!");
710
711 let Some(room) = room.get() else {
712 if is_dropping.load(Ordering::SeqCst) {
713 break;
714 }
715 error!("the weak room couldn't be upgraded but we're not shutting down?");
716 continue;
717 };
718
719 let (related_txn_id, media_upload_progress_info, http_progress) =
724 if let QueuedRequestKind::MediaUpload {
725 cache_key,
726 thumbnail_source,
727 #[cfg(feature = "unstable-msc4274")]
728 accumulated,
729 related_to,
730 ..
731 } = &queued_request.kind
732 {
733 let (media_upload_progress_info, http_progress) =
736 if report_media_upload_progress.load(Ordering::SeqCst) {
737 let media_upload_progress_info =
738 RoomSendQueue::create_media_upload_progress_info(
739 &queued_request.transaction_id,
740 related_to,
741 cache_key,
742 thumbnail_source.as_ref(),
743 #[cfg(feature = "unstable-msc4274")]
744 accumulated,
745 &room,
746 &queue,
747 )
748 .await;
749
750 let progress = RoomSendQueue::create_media_upload_progress_observable(
751 &media_upload_progress_info,
752 related_to,
753 &update_sender,
754 );
755
756 (Some(media_upload_progress_info), Some(progress))
757 } else {
758 Default::default()
759 };
760
761 (Some(related_to.clone()), media_upload_progress_info, http_progress)
762 } else {
763 Default::default()
764 };
765
766 match Self::handle_request(&room, queued_request, cancel_upload_rx, http_progress).await
767 {
768 Ok((Some(parent_key), encryption_info)) => match queue
769 .mark_as_sent(&txn_id, parent_key.clone())
770 .await
771 {
772 Ok(()) => match parent_key {
773 SentRequestKey::Event { event_id, event, event_type } => {
774 send_update(
775 &global_update_sender,
776 &update_sender,
777 room_id,
778 RoomSendQueueUpdate::SentEvent {
779 transaction_id: txn_id,
780 event_id: event_id.clone(),
781 },
782 );
783
784 if let Ok((room_event_cache, _drop_handles)) = room.event_cache().await
804 {
805 let timeline_event = match Raw::from_json_string(
806 format!(
808 "{{\
809 \"event_id\":\"{event_id}\",\
810 \"origin_server_ts\":{ts},\
811 \"sender\":\"{sender}\",\
812 \"type\":\"{type}\",\
813 \"content\":{content}\
814 }}",
815 event_id = event_id,
816 ts = MilliSecondsSinceUnixEpoch::now().get(),
817 sender = room.client().user_id().expect("Client must be logged-in"),
818 type = event_type,
819 content = event.into_json(),
820 ),
821 ) {
822 Ok(event) => match encryption_info {
823 #[cfg(feature = "e2e-encryption")]
824 Some(encryption_info) => {
825 use matrix_sdk_base::deserialized_responses::DecryptedRoomEvent;
826 let decrypted_event = DecryptedRoomEvent {
827 event: event.cast_unchecked(),
828 encryption_info: Arc::new(encryption_info),
829 unsigned_encryption_info: None,
830 };
831 Some(TimelineEvent::from_decrypted(
832 decrypted_event,
833 None,
834 ))
835 }
836 _ => Some(TimelineEvent::from_plaintext(event)),
837 },
838 Err(err) => {
839 error!(
840 ?err,
841 "Failed to build the (sync) event before the saving in the Event Cache"
842 );
843 None
844 }
845 };
846
847 if let Some(timeline_event) = timeline_event
850 && let Err(err) = room_event_cache
851 .insert_sent_event_from_send_queue(timeline_event)
852 .await
853 {
854 error!(
855 ?err,
856 "Failed to save the sent event in the Event Cache"
857 );
858 }
859 } else {
860 info!(
861 "Cannot insert the sent event in the Event Cache because \
862 either the room no longer exists, or the Room Event Cache cannot be retrieved"
863 );
864 }
865 }
866
867 SentRequestKey::Media(sent_media_info) => {
868 let index =
871 media_upload_progress_info.as_ref().map_or(0, |info| info.index);
872 let progress = media_upload_progress_info
873 .as_ref()
874 .map(|info| {
875 AbstractProgress { current: info.bytes, total: info.bytes }
876 + info.offsets
877 })
878 .unwrap_or(AbstractProgress { current: 1, total: 1 });
879
880 let _ = update_sender.send(RoomSendQueueUpdate::MediaUpload {
883 related_to: related_txn_id.as_ref().unwrap_or(&txn_id).clone(),
884 file: Some(sent_media_info.file),
885 index,
886 progress,
887 });
888 }
889
890 SentRequestKey::Redaction { event_id, redacts, reason } => {
891 send_update(
892 &global_update_sender,
893 &update_sender,
894 room_id,
895 RoomSendQueueUpdate::SentEvent {
896 transaction_id: txn_id,
897 event_id: event_id.clone(),
898 },
899 );
900
901 if let Ok((room_event_cache, _drop_handles)) = room.event_cache().await
906 {
907 let content_field_redacts = room.version().is_some_and(|id| {
908 id.rules()
909 .is_some_and(|rules| rules.redaction.content_field_redacts)
910 });
911 let redacts = if content_field_redacts.not() {
912 format!("\"redacts\":\"{redacts}\",")
913 } else {
914 "".to_owned()
915 };
916 let reason = reason.map_or_else(
917 || "".to_owned(),
918 |r| format!("\"reason\": \"{r}\""),
919 );
920 let content = if content_field_redacts {
921 format!("\"redacts\":\"{redacts}\",{reason}")
922 } else {
923 reason
924 };
925
926 let timeline_event = match Raw::from_json_string(
927 format!(
929 "{{\
930 {redacts}\
931 \"event_id\":\"{event_id}\",\
932 \"origin_server_ts\":{ts},\
933 \"sender\":\"{sender}\",\
934 \"type\":\"{type}\",\
935 \"content\":{{{content}}}\
936 }}",
937 redacts = redacts,
938 event_id = event_id,
939 ts = MilliSecondsSinceUnixEpoch::now().get(),
940 sender = room.client().user_id().expect("Client must be logged-in"),
941 type = TimelineEventType::RoomRedaction,
942 content = content
943 ),
944 ) {
945 Ok(event) => Some(TimelineEvent::from_plaintext(event)),
946 Err(err) => {
947 error!(
948 ?err,
949 "Failed to build the (sync) redaction event before the saving in the Event Cache"
950 );
951 None
952 }
953 };
954
955 if let Some(timeline_event) = timeline_event
958 && let Err(err) = room_event_cache
959 .insert_sent_event_from_send_queue(timeline_event)
960 .await
961 {
962 error!(
963 ?err,
964 "Failed to save the sent redaction event in the Event Cache"
965 );
966 }
967 } else {
968 info!(
969 "Cannot insert the sent redaction event in the Event Cache because \
970 either the room no longer exists, or the Room Event Cache cannot be retrieved"
971 );
972 }
973 }
974 },
975
976 Err(err) => {
977 warn!("unable to mark queued request as sent: {err}");
978 }
979 },
980
981 Ok((None, _)) => {
982 debug!("Request has been aborted while running, continuing.");
983 }
984
985 Err(err) => {
986 let is_recoverable = match err {
987 crate::Error::Http(ref http_err) => {
988 matches!(
990 http_err.retry_kind(),
991 RetryKind::Transient { .. } | RetryKind::NetworkFailure
992 )
993 }
994
995 crate::Error::ConcurrentRequestFailed => true,
1000
1001 _ => false,
1003 };
1004
1005 locally_enabled.store(false, Ordering::SeqCst);
1007
1008 if is_recoverable {
1009 warn!(txn_id = %txn_id, error = ?err, "Recoverable error when sending request: {err}, disabling send queue");
1010
1011 queue.mark_as_not_being_sent(&txn_id).await;
1014
1015 } else {
1021 warn!(txn_id = %txn_id, error = ?err, "Unrecoverable error when sending request: {err}");
1022
1023 if let Err(storage_error) =
1025 queue.mark_as_wedged(&txn_id, QueueWedgeError::from(&err)).await
1026 {
1027 warn!("unable to mark request as wedged: {storage_error}");
1028 }
1029 }
1030
1031 let error = Arc::new(err);
1032
1033 let _ = global_error_sender.send(SendQueueRoomError {
1034 room_id: room_id.to_owned(),
1035 error: error.clone(),
1036 is_recoverable,
1037 });
1038
1039 send_update(
1040 &global_update_sender,
1041 &update_sender,
1042 room_id,
1043 RoomSendQueueUpdate::SendError {
1044 transaction_id: related_txn_id.unwrap_or(txn_id),
1045 error,
1046 is_recoverable,
1047 },
1048 );
1049 }
1050 }
1051 }
1052
1053 info!("exited sending task");
1054 }
1055
1056 async fn handle_request(
1060 room: &Room,
1061 request: QueuedRequest,
1062 cancel_upload_rx: Option<oneshot::Receiver<()>>,
1063 progress: Option<SharedObservable<TransmissionProgress>>,
1064 ) -> Result<(Option<SentRequestKey>, Option<EncryptionInfo>), crate::Error> {
1065 match request.kind {
1066 QueuedRequestKind::Event { content } => {
1067 let (event, event_type) = content.into_raw();
1068
1069 let result = room
1070 .send_raw(&event_type, &event)
1071 .with_transaction_id(&request.transaction_id)
1072 .with_request_config(RequestConfig::short_retry())
1073 .await?;
1074
1075 trace!(txn_id = %request.transaction_id, event_id = %result.response.event_id, "event successfully sent");
1076
1077 Ok((
1078 Some(SentRequestKey::Event {
1079 event_id: result.response.event_id,
1080 event,
1081 event_type,
1082 }),
1083 result.encryption_info,
1084 ))
1085 }
1086
1087 QueuedRequestKind::MediaUpload {
1088 content_type,
1089 cache_key,
1090 thumbnail_source,
1091 related_to: relates_to,
1092 #[cfg(feature = "unstable-msc4274")]
1093 accumulated,
1094 } => {
1095 trace!(%relates_to, "uploading media related to event");
1096
1097 let fut = async move {
1098 let data = room
1099 .client()
1100 .media_store()
1101 .lock()
1102 .await?
1103 .get_media_content(&cache_key)
1104 .await?
1105 .ok_or(crate::Error::SendQueueWedgeError(Box::new(
1106 QueueWedgeError::MissingMediaContent,
1107 )))?;
1108
1109 let mime = Mime::from_str(&content_type).map_err(|_| {
1110 crate::Error::SendQueueWedgeError(Box::new(
1111 QueueWedgeError::InvalidMimeType { mime_type: content_type.clone() },
1112 ))
1113 })?;
1114
1115 #[cfg(feature = "e2e-encryption")]
1116 let media_source = if room.latest_encryption_state().await?.is_encrypted() {
1117 trace!("upload will be encrypted (encrypted room)");
1118
1119 let mut cursor = std::io::Cursor::new(data);
1120 let mut req = room
1121 .client
1122 .upload_encrypted_file(&mut cursor)
1123 .with_request_config(RequestConfig::short_retry());
1124 if let Some(progress) = progress {
1125 req = req.with_send_progress_observable(progress);
1126 }
1127 let encrypted_file = req.await?;
1128
1129 MediaSource::Encrypted(Box::new(encrypted_file))
1130 } else {
1131 trace!("upload will be in clear text (room without encryption)");
1132
1133 let request_config = RequestConfig::short_retry()
1134 .timeout(Media::reasonable_upload_timeout(&data));
1135 let mut req =
1136 room.client().media().upload(&mime, data, Some(request_config));
1137 if let Some(progress) = progress {
1138 req = req.with_send_progress_observable(progress);
1139 }
1140 let res = req.await?;
1141
1142 MediaSource::Plain(res.content_uri)
1143 };
1144
1145 #[cfg(not(feature = "e2e-encryption"))]
1146 let media_source = {
1147 let request_config = RequestConfig::short_retry()
1148 .timeout(Media::reasonable_upload_timeout(&data));
1149 let mut req =
1150 room.client().media().upload(&mime, data, Some(request_config));
1151 if let Some(progress) = progress {
1152 req = req.with_send_progress_observable(progress);
1153 }
1154 let res = req.await?;
1155 MediaSource::Plain(res.content_uri)
1156 };
1157
1158 let uri = match &media_source {
1159 MediaSource::Plain(uri) => uri,
1160 MediaSource::Encrypted(encrypted_file) => &encrypted_file.url,
1161 };
1162 trace!(%relates_to, mxc_uri = %uri, "media successfully uploaded");
1163
1164 Ok((
1165 Some(SentRequestKey::Media(SentMediaInfo {
1166 file: media_source,
1167 thumbnail: thumbnail_source,
1168 #[cfg(feature = "unstable-msc4274")]
1169 accumulated,
1170 })),
1171 None,
1172 ))
1173 };
1174
1175 let wait_for_cancel = async move {
1176 if let Some(rx) = cancel_upload_rx {
1177 rx.await
1178 } else {
1179 std::future::pending().await
1180 }
1181 };
1182
1183 tokio::select! {
1184 biased;
1185
1186 _ = wait_for_cancel => {
1187 Ok((None, None))
1188 }
1189
1190 res = fut => {
1191 res
1192 }
1193 }
1194 }
1195
1196 QueuedRequestKind::Redaction { redacts, reason } => {
1197 let result = room
1198 .redact(&redacts, reason.as_deref(), Some(request.transaction_id.clone()))
1199 .await?;
1200
1201 trace!(txn_id = %request.transaction_id, event_id = %result.event_id, "redaction successfully sent");
1202
1203 Ok((
1204 Some(SentRequestKey::Redaction { event_id: result.event_id, redacts, reason }),
1205 None,
1206 ))
1207 }
1208 }
1209 }
1210
1211 pub fn is_enabled(&self) -> bool {
1213 self.inner.locally_enabled.load(Ordering::SeqCst)
1214 }
1215
1216 pub fn set_enabled(&self, enabled: bool) {
1218 self.inner.locally_enabled.store(enabled, Ordering::SeqCst);
1219
1220 if enabled {
1223 self.inner.notifier.notify_one();
1224 }
1225 }
1226
1227 fn send_update(&self, update: RoomSendQueueUpdate) {
1231 let _ = self.inner.update_sender.send(update.clone());
1232 let _ = self
1233 .inner
1234 .global_update_sender
1235 .send(SendQueueUpdate { room_id: self.inner.room.room_id().to_owned(), update });
1236 }
1237}
1238
1239fn send_update(
1240 global_update_sender: &broadcast::Sender<SendQueueUpdate>,
1241 update_sender: &broadcast::Sender<RoomSendQueueUpdate>,
1242 room_id: &RoomId,
1243 update: RoomSendQueueUpdate,
1244) {
1245 let _ = update_sender.send(update.clone());
1246 let _ = global_update_sender.send(SendQueueUpdate { room_id: room_id.to_owned(), update });
1247}
1248
1249impl From<&crate::Error> for QueueWedgeError {
1250 fn from(value: &crate::Error) -> Self {
1251 match value {
1252 #[cfg(feature = "e2e-encryption")]
1253 crate::Error::OlmError(error) => match &**error {
1254 OlmError::SessionRecipientCollectionError(error) => match error {
1255 SessionRecipientCollectionError::VerifiedUserHasUnsignedDevice(user_map) => {
1256 QueueWedgeError::InsecureDevices { user_device_map: user_map.clone() }
1257 }
1258
1259 SessionRecipientCollectionError::VerifiedUserChangedIdentity(users) => {
1260 QueueWedgeError::IdentityViolations { users: users.clone() }
1261 }
1262
1263 SessionRecipientCollectionError::CrossSigningNotSetup
1264 | SessionRecipientCollectionError::SendingFromUnverifiedDevice => {
1265 QueueWedgeError::CrossVerificationRequired
1266 }
1267 },
1268 _ => QueueWedgeError::GenericApiError { msg: value.to_string() },
1269 },
1270
1271 crate::Error::SendQueueWedgeError(error) => *error.clone(),
1273
1274 _ => QueueWedgeError::GenericApiError { msg: value.to_string() },
1275 }
1276 }
1277}
1278
1279struct RoomSendQueueInner {
1280 room: WeakRoom,
1282
1283 global_update_sender: broadcast::Sender<SendQueueUpdate>,
1287
1288 update_sender: broadcast::Sender<RoomSendQueueUpdate>,
1294
1295 queue: QueueStorage,
1302
1303 notifier: Arc<Notify>,
1306
1307 locally_enabled: Arc<AtomicBool>,
1310
1311 _task: BackgroundTaskHandle,
1314}
1315
1316struct BeingSentInfo {
1318 transaction_id: OwnedTransactionId,
1320
1321 cancel_upload: Option<oneshot::Sender<()>>,
1324}
1325
1326impl BeingSentInfo {
1327 fn cancel_upload(self) -> bool {
1332 if let Some(cancel_upload) = self.cancel_upload {
1333 let _ = cancel_upload.send(());
1334 true
1335 } else {
1336 false
1337 }
1338 }
1339}
1340
1341#[derive(Clone)]
1344struct StoreLock {
1345 client: WeakClient,
1347
1348 being_sent: Arc<Mutex<Option<BeingSentInfo>>>,
1353}
1354
1355impl StoreLock {
1356 async fn lock(&self) -> StoreLockGuard {
1358 StoreLockGuard {
1359 client: self.client.clone(),
1360 being_sent: self.being_sent.clone().lock_owned().await,
1361 }
1362 }
1363}
1364
1365struct StoreLockGuard {
1368 client: WeakClient,
1370
1371 being_sent: OwnedMutexGuard<Option<BeingSentInfo>>,
1374}
1375
1376impl StoreLockGuard {
1377 fn client(&self) -> Result<Client, RoomSendQueueStorageError> {
1379 self.client.get().ok_or(RoomSendQueueStorageError::ClientShuttingDown)
1380 }
1381}
1382
1383#[derive(Clone)]
1384struct QueueStorage {
1385 store: StoreLock,
1388
1389 room_id: OwnedRoomId,
1391
1392 thumbnail_file_sizes: Arc<SyncMutex<HashMap<OwnedTransactionId, Vec<Option<usize>>>>>,
1405}
1406
1407impl QueueStorage {
1408 const LOW_PRIORITY: usize = 0;
1410
1411 const HIGH_PRIORITY: usize = 10;
1413
1414 fn new(client: WeakClient, room: OwnedRoomId) -> Self {
1416 Self {
1417 room_id: room,
1418 store: StoreLock { client, being_sent: Default::default() },
1419 thumbnail_file_sizes: Default::default(),
1420 }
1421 }
1422
1423 async fn push(
1427 &self,
1428 request: QueuedRequestKind,
1429 created_at: MilliSecondsSinceUnixEpoch,
1430 ) -> Result<OwnedTransactionId, RoomSendQueueStorageError> {
1431 let transaction_id = TransactionId::new();
1432
1433 self.store
1434 .lock()
1435 .await
1436 .client()?
1437 .state_store()
1438 .save_send_queue_request(
1439 &self.room_id,
1440 transaction_id.clone(),
1441 created_at,
1442 request,
1443 Self::LOW_PRIORITY,
1444 )
1445 .await?;
1446
1447 Ok(transaction_id)
1448 }
1449
1450 async fn peek_next_to_send(
1455 &self,
1456 ) -> Result<Option<(QueuedRequest, Option<oneshot::Receiver<()>>)>, RoomSendQueueStorageError>
1457 {
1458 let mut guard = self.store.lock().await;
1459 let queued_requests =
1460 guard.client()?.state_store().load_send_queue_requests(&self.room_id).await?;
1461
1462 if let Some(request) = queued_requests.iter().find(|queued| !queued.is_wedged()) {
1463 let (cancel_upload_tx, cancel_upload_rx) =
1464 if matches!(request.kind, QueuedRequestKind::MediaUpload { .. }) {
1465 let (tx, rx) = oneshot::channel();
1466 (Some(tx), Some(rx))
1467 } else {
1468 Default::default()
1469 };
1470
1471 let prev = guard.being_sent.replace(BeingSentInfo {
1472 transaction_id: request.transaction_id.clone(),
1473 cancel_upload: cancel_upload_tx,
1474 });
1475
1476 if let Some(prev) = prev {
1477 error!(
1478 prev_txn = ?prev.transaction_id,
1479 "a previous request was still active while picking a new one"
1480 );
1481 }
1482
1483 Ok(Some((request.clone(), cancel_upload_rx)))
1484 } else {
1485 Ok(None)
1486 }
1487 }
1488
1489 async fn mark_as_not_being_sent(&self, transaction_id: &TransactionId) {
1493 let was_being_sent = self.store.lock().await.being_sent.take();
1494
1495 let prev_txn = was_being_sent.as_ref().map(|info| info.transaction_id.as_ref());
1496 if prev_txn != Some(transaction_id) {
1497 error!(prev_txn = ?prev_txn, "previous active request didn't match that we expect (after transient error)");
1498 }
1499 }
1500
1501 async fn mark_as_wedged(
1505 &self,
1506 transaction_id: &TransactionId,
1507 reason: QueueWedgeError,
1508 ) -> Result<(), RoomSendQueueStorageError> {
1509 let mut guard = self.store.lock().await;
1511 let was_being_sent = guard.being_sent.take();
1512
1513 let prev_txn = was_being_sent.as_ref().map(|info| info.transaction_id.as_ref());
1514 if prev_txn != Some(transaction_id) {
1515 error!(
1516 ?prev_txn,
1517 "previous active request didn't match that we expect (after permanent error)",
1518 );
1519 }
1520
1521 Ok(guard
1522 .client()?
1523 .state_store()
1524 .update_send_queue_request_status(&self.room_id, transaction_id, Some(reason))
1525 .await?)
1526 }
1527
1528 async fn mark_as_unwedged(
1531 &self,
1532 transaction_id: &TransactionId,
1533 ) -> Result<(), RoomSendQueueStorageError> {
1534 Ok(self
1535 .store
1536 .lock()
1537 .await
1538 .client()?
1539 .state_store()
1540 .update_send_queue_request_status(&self.room_id, transaction_id, None)
1541 .await?)
1542 }
1543
1544 async fn mark_as_sent(
1547 &self,
1548 transaction_id: &TransactionId,
1549 parent_key: SentRequestKey,
1550 ) -> Result<(), RoomSendQueueStorageError> {
1551 let mut guard = self.store.lock().await;
1553 let was_being_sent = guard.being_sent.take();
1554
1555 let prev_txn = was_being_sent.as_ref().map(|info| info.transaction_id.as_ref());
1556 if prev_txn != Some(transaction_id) {
1557 error!(
1558 ?prev_txn,
1559 "previous active request didn't match that we expect (after successful send)",
1560 );
1561 }
1562
1563 let client = guard.client()?;
1564 let store = client.state_store();
1565
1566 store
1568 .mark_dependent_queued_requests_as_ready(&self.room_id, transaction_id, parent_key)
1569 .await?;
1570
1571 let removed = store.remove_send_queue_request(&self.room_id, transaction_id).await?;
1572
1573 if !removed {
1574 warn!(txn_id = %transaction_id, "request marked as sent was missing from storage");
1575 }
1576
1577 self.thumbnail_file_sizes.lock().remove(transaction_id);
1578
1579 Ok(())
1580 }
1581
1582 async fn cancel_event(
1589 &self,
1590 transaction_id: &TransactionId,
1591 ) -> Result<bool, RoomSendQueueStorageError> {
1592 let guard = self.store.lock().await;
1593
1594 if guard.being_sent.as_ref().map(|info| info.transaction_id.as_ref())
1595 == Some(transaction_id)
1596 {
1597 guard
1599 .client()?
1600 .state_store()
1601 .save_dependent_queued_request(
1602 &self.room_id,
1603 transaction_id,
1604 ChildTransactionId::new(),
1605 MilliSecondsSinceUnixEpoch::now(),
1606 DependentQueuedRequestKind::RedactEvent,
1607 )
1608 .await?;
1609
1610 return Ok(true);
1611 }
1612
1613 let removed = guard
1614 .client()?
1615 .state_store()
1616 .remove_send_queue_request(&self.room_id, transaction_id)
1617 .await?;
1618
1619 self.thumbnail_file_sizes.lock().remove(transaction_id);
1620
1621 Ok(removed)
1622 }
1623
1624 async fn replace_event(
1631 &self,
1632 transaction_id: &TransactionId,
1633 serializable: SerializableEventContent,
1634 ) -> Result<bool, RoomSendQueueStorageError> {
1635 let guard = self.store.lock().await;
1636
1637 if guard.being_sent.as_ref().map(|info| info.transaction_id.as_ref())
1638 == Some(transaction_id)
1639 {
1640 guard
1642 .client()?
1643 .state_store()
1644 .save_dependent_queued_request(
1645 &self.room_id,
1646 transaction_id,
1647 ChildTransactionId::new(),
1648 MilliSecondsSinceUnixEpoch::now(),
1649 DependentQueuedRequestKind::EditEvent { new_content: serializable },
1650 )
1651 .await?;
1652
1653 return Ok(true);
1654 }
1655
1656 let edited = guard
1657 .client()?
1658 .state_store()
1659 .update_send_queue_request(&self.room_id, transaction_id, serializable.into())
1660 .await?;
1661
1662 Ok(edited)
1663 }
1664
1665 #[allow(clippy::too_many_arguments)]
1669 async fn push_media(
1670 &self,
1671 event: RoomMessageEventContent,
1672 content_type: Mime,
1673 send_event_txn: OwnedTransactionId,
1674 created_at: MilliSecondsSinceUnixEpoch,
1675 upload_file_txn: OwnedTransactionId,
1676 file_media_request: MediaRequestParameters,
1677 thumbnail: Option<QueueThumbnailInfo>,
1678 extra_content: Option<serde_json::Map<String, serde_json::Value>>,
1679 ) -> Result<(), RoomSendQueueStorageError> {
1680 let guard = self.store.lock().await;
1681 let client = guard.client()?;
1682 let store = client.state_store();
1683
1684 let thumbnail_file_sizes = vec![thumbnail.as_ref().map(|t| t.file_size)];
1686
1687 let thumbnail_info = self
1688 .push_thumbnail_and_media_uploads(
1689 store,
1690 &content_type,
1691 send_event_txn.clone(),
1692 created_at,
1693 upload_file_txn.clone(),
1694 file_media_request,
1695 thumbnail,
1696 )
1697 .await?;
1698
1699 store
1701 .save_dependent_queued_request(
1702 &self.room_id,
1703 &upload_file_txn,
1704 send_event_txn.clone().into(),
1705 created_at,
1706 DependentQueuedRequestKind::FinishUpload {
1707 local_echo: Box::new(event),
1708 file_upload: upload_file_txn.clone(),
1709 thumbnail_info,
1710 extra_content,
1711 },
1712 )
1713 .await?;
1714
1715 self.thumbnail_file_sizes.lock().insert(send_event_txn, thumbnail_file_sizes);
1716
1717 Ok(())
1718 }
1719
1720 #[cfg(feature = "unstable-msc4274")]
1724 #[allow(clippy::too_many_arguments)]
1725 async fn push_gallery(
1726 &self,
1727 event: RoomMessageEventContent,
1728 send_event_txn: OwnedTransactionId,
1729 created_at: MilliSecondsSinceUnixEpoch,
1730 item_queue_infos: Vec<GalleryItemQueueInfo>,
1731 ) -> Result<(), RoomSendQueueStorageError> {
1732 let guard = self.store.lock().await;
1733 let client = guard.client()?;
1734 let store = client.state_store();
1735
1736 let mut finish_item_infos = Vec::with_capacity(item_queue_infos.len());
1737 let mut thumbnail_file_sizes = Vec::with_capacity(item_queue_infos.len());
1738
1739 let Some((first, rest)) = item_queue_infos.split_first() else {
1740 return Ok(());
1741 };
1742
1743 let GalleryItemQueueInfo { content_type, upload_file_txn, file_media_request, thumbnail } =
1744 first;
1745
1746 let thumbnail_info = self
1747 .push_thumbnail_and_media_uploads(
1748 store,
1749 content_type,
1750 send_event_txn.clone(),
1751 created_at,
1752 upload_file_txn.clone(),
1753 file_media_request.clone(),
1754 thumbnail.clone(),
1755 )
1756 .await?;
1757
1758 finish_item_infos
1759 .push(FinishGalleryItemInfo { file_upload: upload_file_txn.clone(), thumbnail_info });
1760 thumbnail_file_sizes.push(thumbnail.as_ref().map(|t| t.file_size));
1761
1762 let mut last_upload_file_txn = upload_file_txn.clone();
1763
1764 for item_queue_info in rest {
1765 let GalleryItemQueueInfo {
1766 content_type,
1767 upload_file_txn,
1768 file_media_request,
1769 thumbnail,
1770 } = item_queue_info;
1771
1772 let thumbnail_info = if let Some(QueueThumbnailInfo {
1773 finish_upload_thumbnail_info: thumbnail_info,
1774 media_request_parameters: thumbnail_media_request,
1775 content_type: thumbnail_content_type,
1776 ..
1777 }) = thumbnail
1778 {
1779 let upload_thumbnail_txn = thumbnail_info.txn.clone();
1780
1781 store
1784 .save_dependent_queued_request(
1785 &self.room_id,
1786 &last_upload_file_txn,
1787 upload_thumbnail_txn.clone().into(),
1788 created_at,
1789 DependentQueuedRequestKind::UploadFileOrThumbnail {
1790 content_type: thumbnail_content_type.to_string(),
1791 cache_key: thumbnail_media_request.clone(),
1792 related_to: send_event_txn.clone(),
1793 parent_is_thumbnail_upload: false,
1794 },
1795 )
1796 .await?;
1797
1798 last_upload_file_txn = upload_thumbnail_txn;
1799
1800 Some(thumbnail_info)
1801 } else {
1802 None
1803 };
1804
1805 store
1807 .save_dependent_queued_request(
1808 &self.room_id,
1809 &last_upload_file_txn,
1810 upload_file_txn.clone().into(),
1811 created_at,
1812 DependentQueuedRequestKind::UploadFileOrThumbnail {
1813 content_type: content_type.to_string(),
1814 cache_key: file_media_request.clone(),
1815 related_to: send_event_txn.clone(),
1816 parent_is_thumbnail_upload: thumbnail.is_some(),
1817 },
1818 )
1819 .await?;
1820
1821 finish_item_infos.push(FinishGalleryItemInfo {
1822 file_upload: upload_file_txn.clone(),
1823 thumbnail_info: thumbnail_info.cloned(),
1824 });
1825 thumbnail_file_sizes.push(thumbnail.as_ref().map(|t| t.file_size));
1826
1827 last_upload_file_txn = upload_file_txn.clone();
1828 }
1829
1830 store
1833 .save_dependent_queued_request(
1834 &self.room_id,
1835 &last_upload_file_txn,
1836 send_event_txn.clone().into(),
1837 created_at,
1838 DependentQueuedRequestKind::FinishGallery {
1839 local_echo: Box::new(event),
1840 item_infos: finish_item_infos,
1841 },
1842 )
1843 .await?;
1844
1845 self.thumbnail_file_sizes.lock().insert(send_event_txn, thumbnail_file_sizes);
1846
1847 Ok(())
1848 }
1849
1850 #[allow(clippy::too_many_arguments)]
1856 async fn push_thumbnail_and_media_uploads(
1857 &self,
1858 store: &DynStateStore,
1859 content_type: &Mime,
1860 send_event_txn: OwnedTransactionId,
1861 created_at: MilliSecondsSinceUnixEpoch,
1862 upload_file_txn: OwnedTransactionId,
1863 file_media_request: MediaRequestParameters,
1864 thumbnail: Option<QueueThumbnailInfo>,
1865 ) -> Result<Option<FinishUploadThumbnailInfo>, RoomSendQueueStorageError> {
1866 if let Some(QueueThumbnailInfo {
1867 finish_upload_thumbnail_info: thumbnail_info,
1868 media_request_parameters: thumbnail_media_request,
1869 content_type: thumbnail_content_type,
1870 ..
1871 }) = thumbnail
1872 {
1873 let upload_thumbnail_txn = thumbnail_info.txn.clone();
1874
1875 store
1877 .save_send_queue_request(
1878 &self.room_id,
1879 upload_thumbnail_txn.clone(),
1880 created_at,
1881 QueuedRequestKind::MediaUpload {
1882 content_type: thumbnail_content_type.to_string(),
1883 cache_key: thumbnail_media_request,
1884 thumbnail_source: None, related_to: send_event_txn.clone(),
1886 #[cfg(feature = "unstable-msc4274")]
1887 accumulated: vec![],
1888 },
1889 Self::LOW_PRIORITY,
1890 )
1891 .await?;
1892
1893 store
1895 .save_dependent_queued_request(
1896 &self.room_id,
1897 &upload_thumbnail_txn,
1898 upload_file_txn.into(),
1899 created_at,
1900 DependentQueuedRequestKind::UploadFileOrThumbnail {
1901 content_type: content_type.to_string(),
1902 cache_key: file_media_request,
1903 related_to: send_event_txn,
1904 parent_is_thumbnail_upload: true,
1905 },
1906 )
1907 .await?;
1908
1909 Ok(Some(thumbnail_info))
1910 } else {
1911 store
1913 .save_send_queue_request(
1914 &self.room_id,
1915 upload_file_txn,
1916 created_at,
1917 QueuedRequestKind::MediaUpload {
1918 content_type: content_type.to_string(),
1919 cache_key: file_media_request,
1920 thumbnail_source: None,
1921 related_to: send_event_txn,
1922 #[cfg(feature = "unstable-msc4274")]
1923 accumulated: vec![],
1924 },
1925 Self::LOW_PRIORITY,
1926 )
1927 .await?;
1928
1929 Ok(None)
1930 }
1931 }
1932
1933 #[instrument(skip(self))]
1935 async fn react(
1936 &self,
1937 transaction_id: &TransactionId,
1938 key: String,
1939 created_at: MilliSecondsSinceUnixEpoch,
1940 ) -> Result<Option<ChildTransactionId>, RoomSendQueueStorageError> {
1941 let guard = self.store.lock().await;
1942 let client = guard.client()?;
1943 let store = client.state_store();
1944
1945 let requests = store.load_send_queue_requests(&self.room_id).await?;
1946
1947 if !requests.iter().any(|item| item.transaction_id == transaction_id) {
1949 let dependent_requests = store.load_dependent_queued_requests(&self.room_id).await?;
1952 if !dependent_requests
1953 .into_iter()
1954 .filter_map(|item| item.is_own_event().then_some(item.own_transaction_id))
1955 .any(|child_txn| *child_txn == *transaction_id)
1956 {
1957 return Ok(None);
1959 }
1960 }
1961
1962 let reaction_txn_id = ChildTransactionId::new();
1964 store
1965 .save_dependent_queued_request(
1966 &self.room_id,
1967 transaction_id,
1968 reaction_txn_id.clone(),
1969 created_at,
1970 DependentQueuedRequestKind::ReactEvent { key },
1971 )
1972 .await?;
1973
1974 Ok(Some(reaction_txn_id))
1975 }
1976
1977 async fn local_echoes(
1980 &self,
1981 room: &RoomSendQueue,
1982 ) -> Result<Vec<LocalEcho>, RoomSendQueueStorageError> {
1983 let guard = self.store.lock().await;
1984 let client = guard.client()?;
1985 let store = client.state_store();
1986
1987 let local_requests =
1988 store.load_send_queue_requests(&self.room_id).await?.into_iter().filter_map(|queued| {
1989 Some(LocalEcho {
1990 transaction_id: queued.transaction_id.clone(),
1991 content: match queued.kind {
1992 QueuedRequestKind::Event { content } => LocalEchoContent::Event {
1993 serialized_event: content,
1994 send_handle: SendHandle {
1995 room: room.clone(),
1996 transaction_id: queued.transaction_id,
1997 media_handles: vec![],
1998 created_at: queued.created_at,
1999 },
2000 send_error: queued.error,
2001 },
2002
2003 QueuedRequestKind::MediaUpload { .. } => {
2004 return None;
2007 }
2008
2009 QueuedRequestKind::Redaction { redacts, reason } => {
2010 LocalEchoContent::Redaction {
2011 redacts,
2012 reason,
2013 send_handle: SendRedactionHandle {
2014 room: room.clone(),
2015 transaction_id: queued.transaction_id,
2016 },
2017 send_error: queued.error,
2018 }
2019 }
2020 },
2021 })
2022 });
2023
2024 let reactions_and_medias = store
2025 .load_dependent_queued_requests(&self.room_id)
2026 .await?
2027 .into_iter()
2028 .filter_map(|dep| match dep.kind {
2029 DependentQueuedRequestKind::EditEvent { .. }
2030 | DependentQueuedRequestKind::RedactEvent => {
2031 None
2033 }
2034
2035 DependentQueuedRequestKind::ReactEvent { key } => Some(LocalEcho {
2036 transaction_id: dep.own_transaction_id.clone().into(),
2037 content: LocalEchoContent::React {
2038 key,
2039 send_handle: SendReactionHandle {
2040 room: room.clone(),
2041 transaction_id: dep.own_transaction_id,
2042 },
2043 applies_to: dep.parent_transaction_id,
2044 },
2045 }),
2046
2047 DependentQueuedRequestKind::UploadFileOrThumbnail { .. } => {
2048 None
2050 }
2051
2052 DependentQueuedRequestKind::FinishUpload {
2053 local_echo,
2054 file_upload,
2055 thumbnail_info,
2056 extra_content,
2057 } => {
2058 Some(LocalEcho {
2060 transaction_id: dep.own_transaction_id.clone().into(),
2061 content: LocalEchoContent::Event {
2062 serialized_event: upload::merge_extra_content(
2063 SerializableEventContent::new(&(*local_echo).into()).ok()?,
2064 extra_content,
2065 )
2066 .ok()?,
2067 send_handle: SendHandle {
2068 room: room.clone(),
2069 transaction_id: dep.own_transaction_id.into(),
2070 media_handles: vec![MediaHandles {
2071 upload_thumbnail_txn: thumbnail_info.map(|info| info.txn),
2072 upload_file_txn: file_upload,
2073 }],
2074 created_at: dep.created_at,
2075 },
2076 send_error: None,
2077 },
2078 })
2079 }
2080
2081 #[cfg(feature = "unstable-msc4274")]
2082 DependentQueuedRequestKind::FinishGallery { local_echo, item_infos } => {
2083 self.create_gallery_local_echo(
2085 dep.own_transaction_id,
2086 room,
2087 dep.created_at,
2088 local_echo,
2089 item_infos,
2090 )
2091 }
2092 });
2093
2094 Ok(local_requests.chain(reactions_and_medias).collect())
2095 }
2096
2097 #[cfg(feature = "unstable-msc4274")]
2099 fn create_gallery_local_echo(
2100 &self,
2101 transaction_id: ChildTransactionId,
2102 room: &RoomSendQueue,
2103 created_at: MilliSecondsSinceUnixEpoch,
2104 local_echo: Box<RoomMessageEventContent>,
2105 item_infos: Vec<FinishGalleryItemInfo>,
2106 ) -> Option<LocalEcho> {
2107 Some(LocalEcho {
2108 transaction_id: transaction_id.clone().into(),
2109 content: LocalEchoContent::Event {
2110 serialized_event: SerializableEventContent::new(&(*local_echo).into()).ok()?,
2111 send_handle: SendHandle {
2112 room: room.clone(),
2113 transaction_id: transaction_id.into(),
2114 media_handles: item_infos
2115 .into_iter()
2116 .map(|i| MediaHandles {
2117 upload_thumbnail_txn: i.thumbnail_info.map(|info| info.txn),
2118 upload_file_txn: i.file_upload,
2119 })
2120 .collect(),
2121 created_at,
2122 },
2123 send_error: None,
2124 },
2125 })
2126 }
2127
2128 #[instrument(skip_all)]
2136 async fn try_apply_single_dependent_request(
2137 &self,
2138 client: &Client,
2139 dependent_request: DependentQueuedRequest,
2140 new_updates: &mut Vec<RoomSendQueueUpdate>,
2141 ) -> Result<bool, RoomSendQueueError> {
2142 let store = client.state_store();
2143
2144 let parent_key = dependent_request.parent_key;
2145
2146 match dependent_request.kind {
2147 DependentQueuedRequestKind::EditEvent { new_content } => {
2148 if let Some(parent_key) = parent_key {
2149 let Some(event_id) = parent_key.into_event_id() else {
2150 return Err(RoomSendQueueError::StorageError(
2151 RoomSendQueueStorageError::InvalidParentKey,
2152 ));
2153 };
2154
2155 let room = client
2157 .get_room(&self.room_id)
2158 .ok_or(RoomSendQueueError::RoomDisappeared)?;
2159
2160 let edited_content = match new_content.deserialize() {
2164 Ok(AnyMessageLikeEventContent::RoomMessage(c)) => {
2165 EditedContent::RoomMessage(c.into())
2167 }
2168
2169 Ok(AnyMessageLikeEventContent::UnstablePollStart(c)) => {
2170 let poll_start = c.poll_start().clone();
2171 EditedContent::PollStart {
2172 fallback_text: poll_start.question.text.clone(),
2173 new_content: poll_start,
2174 }
2175 }
2176
2177 Ok(c) => {
2178 warn!("Unsupported edit content type: {:?}", c.event_type());
2179 return Ok(true);
2180 }
2181
2182 Err(err) => {
2183 warn!("Unable to deserialize: {err}");
2184 return Ok(true);
2185 }
2186 };
2187
2188 let edit_event = match room.make_edit_event(&event_id, edited_content).await {
2189 Ok(e) => e,
2190 Err(err) => {
2191 warn!("couldn't create edited event: {err}");
2192 return Ok(true);
2193 }
2194 };
2195
2196 let serializable = SerializableEventContent::from_raw(
2198 Raw::new(&edit_event)
2199 .map_err(RoomSendQueueStorageError::JsonSerialization)?,
2200 edit_event.event_type().to_string(),
2201 );
2202
2203 store
2204 .save_send_queue_request(
2205 &self.room_id,
2206 dependent_request.own_transaction_id.into(),
2207 dependent_request.created_at,
2208 serializable.into(),
2209 Self::HIGH_PRIORITY,
2210 )
2211 .await
2212 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2213 } else {
2214 let edited = store
2216 .update_send_queue_request(
2217 &self.room_id,
2218 &dependent_request.parent_transaction_id,
2219 new_content.into(),
2220 )
2221 .await
2222 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2223
2224 if !edited {
2225 warn!("missing local echo upon dependent edit");
2226 }
2227 }
2228 }
2229
2230 DependentQueuedRequestKind::RedactEvent => {
2231 if let Some(parent_key) = parent_key {
2232 let Some(event_id) = parent_key.into_event_id() else {
2233 return Err(RoomSendQueueError::StorageError(
2234 RoomSendQueueStorageError::InvalidParentKey,
2235 ));
2236 };
2237
2238 let room = client
2240 .get_room(&self.room_id)
2241 .ok_or(RoomSendQueueError::RoomDisappeared)?;
2242
2243 if let Err(err) = room
2251 .redact(&event_id, None, Some(dependent_request.own_transaction_id.into()))
2252 .await
2253 {
2254 warn!("error when sending a redact for {event_id}: {err}");
2255 return Ok(false);
2256 }
2257 } else {
2258 let removed = store
2261 .remove_send_queue_request(
2262 &self.room_id,
2263 &dependent_request.parent_transaction_id,
2264 )
2265 .await
2266 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2267
2268 if !removed {
2269 warn!("missing local echo upon dependent redact");
2270 }
2271 }
2272 }
2273
2274 DependentQueuedRequestKind::ReactEvent { key } => {
2275 if let Some(parent_key) = parent_key {
2276 let Some(parent_event_id) = parent_key.into_event_id() else {
2277 return Err(RoomSendQueueError::StorageError(
2278 RoomSendQueueStorageError::InvalidParentKey,
2279 ));
2280 };
2281
2282 let react_event =
2284 ReactionEventContent::new(Annotation::new(parent_event_id, key)).into();
2285 let serializable = SerializableEventContent::from_raw(
2286 Raw::new(&react_event)
2287 .map_err(RoomSendQueueStorageError::JsonSerialization)?,
2288 react_event.event_type().to_string(),
2289 );
2290
2291 store
2292 .save_send_queue_request(
2293 &self.room_id,
2294 dependent_request.own_transaction_id.into(),
2295 dependent_request.created_at,
2296 serializable.into(),
2297 Self::HIGH_PRIORITY,
2298 )
2299 .await
2300 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2301 } else {
2302 return Ok(false);
2304 }
2305 }
2306
2307 DependentQueuedRequestKind::UploadFileOrThumbnail {
2308 content_type,
2309 cache_key,
2310 related_to,
2311 parent_is_thumbnail_upload,
2312 } => {
2313 let Some(parent_key) = parent_key else {
2314 return Ok(false);
2316 };
2317 self.handle_dependent_file_or_thumbnail_upload(
2318 client,
2319 dependent_request.own_transaction_id.into(),
2320 parent_key,
2321 content_type,
2322 cache_key,
2323 related_to,
2324 parent_is_thumbnail_upload,
2325 )
2326 .await?;
2327 }
2328
2329 DependentQueuedRequestKind::FinishUpload {
2330 local_echo,
2331 file_upload,
2332 thumbnail_info,
2333 extra_content,
2334 } => {
2335 let Some(parent_key) = parent_key else {
2336 return Ok(false);
2338 };
2339 self.handle_dependent_finish_upload(
2340 client,
2341 dependent_request.own_transaction_id.into(),
2342 parent_key,
2343 *local_echo,
2344 file_upload,
2345 thumbnail_info,
2346 extra_content,
2347 new_updates,
2348 )
2349 .await?;
2350 }
2351
2352 #[cfg(feature = "unstable-msc4274")]
2353 DependentQueuedRequestKind::FinishGallery { local_echo, item_infos } => {
2354 let Some(parent_key) = parent_key else {
2355 return Ok(false);
2357 };
2358 self.handle_dependent_finish_gallery_upload(
2359 client,
2360 dependent_request.own_transaction_id.into(),
2361 parent_key,
2362 *local_echo,
2363 item_infos,
2364 new_updates,
2365 )
2366 .await?;
2367 }
2368 }
2369
2370 Ok(true)
2371 }
2372
2373 #[instrument(skip(self))]
2374 async fn apply_dependent_requests(
2375 &self,
2376 new_updates: &mut Vec<RoomSendQueueUpdate>,
2377 ) -> Result<(), RoomSendQueueError> {
2378 let guard = self.store.lock().await;
2379
2380 let client = guard.client()?;
2381 let store = client.state_store();
2382
2383 let dependent_requests = store
2384 .load_dependent_queued_requests(&self.room_id)
2385 .await
2386 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2387
2388 let num_initial_dependent_requests = dependent_requests.len();
2389 if num_initial_dependent_requests == 0 {
2390 return Ok(());
2392 }
2393
2394 let canonicalized_dependent_requests = canonicalize_dependent_requests(&dependent_requests);
2395
2396 for original in &dependent_requests {
2398 if !canonicalized_dependent_requests
2399 .iter()
2400 .any(|canonical| canonical.own_transaction_id == original.own_transaction_id)
2401 {
2402 store
2403 .remove_dependent_queued_request(&self.room_id, &original.own_transaction_id)
2404 .await
2405 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2406 }
2407 }
2408
2409 let mut num_dependent_requests = canonicalized_dependent_requests.len();
2410
2411 debug!(
2412 num_dependent_requests,
2413 num_initial_dependent_requests, "starting handling of dependent requests"
2414 );
2415
2416 for dependent in canonicalized_dependent_requests {
2417 let dependent_id = dependent.own_transaction_id.clone();
2418
2419 match self.try_apply_single_dependent_request(&client, dependent, new_updates).await {
2420 Ok(should_remove) => {
2421 if should_remove {
2422 store
2424 .remove_dependent_queued_request(&self.room_id, &dependent_id)
2425 .await
2426 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2427
2428 num_dependent_requests -= 1;
2429 }
2430 }
2431
2432 Err(err) => {
2433 warn!("error when applying single dependent request: {err}");
2434 }
2435 }
2436 }
2437
2438 debug!(
2439 leftover_dependent_requests = num_dependent_requests,
2440 "stopped handling dependent request"
2441 );
2442
2443 Ok(())
2444 }
2445
2446 async fn remove_dependent_send_queue_request(
2448 &self,
2449 dependent_event_id: &ChildTransactionId,
2450 ) -> Result<bool, RoomSendQueueStorageError> {
2451 Ok(self
2452 .store
2453 .lock()
2454 .await
2455 .client()?
2456 .state_store()
2457 .remove_dependent_queued_request(&self.room_id, dependent_event_id)
2458 .await?)
2459 }
2460}
2461
2462#[cfg(feature = "unstable-msc4274")]
2463struct GalleryItemQueueInfo {
2465 content_type: Mime,
2466 upload_file_txn: OwnedTransactionId,
2467 file_media_request: MediaRequestParameters,
2468 thumbnail: Option<QueueThumbnailInfo>,
2469}
2470
2471#[derive(Clone, Debug)]
2473pub enum LocalEchoContent {
2474 Event {
2476 serialized_event: SerializableEventContent,
2479 send_handle: SendHandle,
2481 send_error: Option<QueueWedgeError>,
2484 },
2485
2486 React {
2488 key: String,
2490 send_handle: SendReactionHandle,
2492 applies_to: OwnedTransactionId,
2494 },
2495
2496 Redaction {
2498 redacts: OwnedEventId,
2500 reason: Option<String>,
2502 send_handle: SendRedactionHandle,
2504 send_error: Option<QueueWedgeError>,
2507 },
2508}
2509
2510#[derive(Clone, Debug)]
2513pub struct LocalEcho {
2514 pub transaction_id: OwnedTransactionId,
2516 pub content: LocalEchoContent,
2518}
2519
2520#[derive(Clone, Debug)]
2523pub enum RoomSendQueueUpdate {
2524 NewLocalEvent(LocalEcho),
2529
2530 CancelledLocalEvent {
2533 transaction_id: OwnedTransactionId,
2535 },
2536
2537 ReplacedLocalEvent {
2539 transaction_id: OwnedTransactionId,
2541
2542 new_content: SerializableEventContent,
2544 },
2545
2546 SendError {
2551 transaction_id: OwnedTransactionId,
2553 error: Arc<crate::Error>,
2555 is_recoverable: bool,
2561 },
2562
2563 RetryEvent {
2565 transaction_id: OwnedTransactionId,
2567 },
2568
2569 SentEvent {
2572 transaction_id: OwnedTransactionId,
2574 event_id: OwnedEventId,
2576 },
2577
2578 MediaUpload {
2581 related_to: OwnedTransactionId,
2583
2584 file: Option<MediaSource>,
2586
2587 index: u64,
2591
2592 progress: AbstractProgress,
2596 },
2597}
2598
2599#[derive(Clone, Debug)]
2604pub struct SendQueueUpdate {
2605 pub room_id: OwnedRoomId,
2607
2608 pub update: RoomSendQueueUpdate,
2610}
2611
2612#[derive(Debug, thiserror::Error)]
2614pub enum RoomSendQueueError {
2615 #[error("the room isn't in the joined state")]
2617 RoomNotJoined,
2618
2619 #[error("the room is now missing from the client")]
2623 RoomDisappeared,
2624
2625 #[error(transparent)]
2627 StorageError(#[from] RoomSendQueueStorageError),
2628
2629 #[error("the attachment event could not be created")]
2631 FailedToCreateAttachment,
2632
2633 #[cfg(feature = "unstable-msc4274")]
2635 #[error("the gallery contains no items")]
2636 EmptyGallery,
2637
2638 #[cfg(feature = "unstable-msc4274")]
2640 #[error("the gallery event could not be created")]
2641 FailedToCreateGallery,
2642}
2643
2644#[derive(Debug, thiserror::Error)]
2646pub enum RoomSendQueueStorageError {
2647 #[error(transparent)]
2649 StateStoreError(#[from] StoreError),
2650
2651 #[error(transparent)]
2653 EventCacheStoreError(#[from] EventCacheStoreError),
2654
2655 #[error(transparent)]
2657 MediaStoreError(#[from] MediaStoreError),
2658
2659 #[error(transparent)]
2661 LockError(#[from] CrossProcessLockError),
2662
2663 #[error(transparent)]
2665 JsonSerialization(#[from] serde_json::Error),
2666
2667 #[error("a dependent event had an invalid parent key type")]
2670 InvalidParentKey,
2671
2672 #[error("The client is shutting down.")]
2674 ClientShuttingDown,
2675
2676 #[error("This operation is not implemented for media uploads")]
2678 OperationNotImplementedYet,
2679
2680 #[error("Can't edit a media caption when the underlying event isn't a media")]
2682 InvalidMediaCaptionEdit,
2683}
2684
2685#[derive(Clone, Debug)]
2687struct MediaHandles {
2688 upload_thumbnail_txn: Option<OwnedTransactionId>,
2692
2693 upload_file_txn: OwnedTransactionId,
2695}
2696
2697#[allow(missing_debug_implementations)]
2699pub struct SendEvent<'a> {
2700 queue: &'a RoomSendQueue,
2701 content: AnyMessageLikeEventContent,
2702 extra_content: Option<serde_json::Map<String, serde_json::Value>>,
2703}
2704
2705impl<'a> SendEvent<'a> {
2706 pub fn with_extra_content(
2710 mut self,
2711 extra_content: serde_json::Map<String, serde_json::Value>,
2712 ) -> Self {
2713 self.extra_content = Some(extra_content);
2714 self
2715 }
2716}
2717
2718impl<'a> IntoFuture for SendEvent<'a> {
2719 type Output = Result<SendHandle, RoomSendQueueError>;
2720 boxed_into_future!(extra_bounds: 'a);
2721
2722 fn into_future(self) -> Self::IntoFuture {
2723 Box::pin(async move {
2724 let serialized = upload::merge_extra_content(
2725 SerializableEventContent::new(&self.content)
2726 .map_err(RoomSendQueueStorageError::JsonSerialization)?,
2727 self.extra_content,
2728 )?;
2729 let (raw, event_type) = serialized.into_raw();
2730 self.queue.send_raw(raw, event_type).await
2731 })
2732 }
2733}
2734
2735#[derive(Clone, Debug)]
2737pub struct SendHandle {
2738 room: RoomSendQueue,
2740
2741 transaction_id: OwnedTransactionId,
2746
2747 media_handles: Vec<MediaHandles>,
2749
2750 pub created_at: MilliSecondsSinceUnixEpoch,
2752}
2753
2754impl SendHandle {
2755 #[cfg(test)]
2757 pub(crate) fn new(
2758 room: RoomSendQueue,
2759 transaction_id: OwnedTransactionId,
2760 created_at: MilliSecondsSinceUnixEpoch,
2761 ) -> Self {
2762 Self { room, transaction_id, media_handles: vec![], created_at }
2763 }
2764
2765 fn nyi_for_uploads(&self) -> Result<(), RoomSendQueueStorageError> {
2766 if !self.media_handles.is_empty() {
2767 Err(RoomSendQueueStorageError::OperationNotImplementedYet)
2768 } else {
2769 Ok(())
2770 }
2771 }
2772
2773 #[instrument(skip(self), fields(room_id = %self.room.inner.room.room_id(), txn_id = %self.transaction_id))]
2778 pub async fn abort(&self) -> Result<bool, RoomSendQueueStorageError> {
2779 trace!("received an abort request");
2780
2781 let queue = &self.room.inner.queue;
2782
2783 for handles in &self.media_handles {
2784 if queue.abort_upload(&self.transaction_id, handles).await? {
2785 self.room.send_update(RoomSendQueueUpdate::CancelledLocalEvent {
2787 transaction_id: self.transaction_id.clone(),
2788 });
2789
2790 return Ok(true);
2791 }
2792
2793 }
2797
2798 if queue.cancel_event(&self.transaction_id).await? {
2799 trace!("successful abort");
2800
2801 self.room.send_update(RoomSendQueueUpdate::CancelledLocalEvent {
2803 transaction_id: self.transaction_id.clone(),
2804 });
2805
2806 Ok(true)
2807 } else {
2808 debug!("local echo didn't exist anymore, can't abort");
2809 Ok(false)
2810 }
2811 }
2812
2813 #[instrument(skip(self, new_content), fields(room_id = %self.room.inner.room.room_id(), txn_id = %self.transaction_id))]
2818 pub async fn edit_raw(
2819 &self,
2820 new_content: Raw<AnyMessageLikeEventContent>,
2821 event_type: String,
2822 ) -> Result<bool, RoomSendQueueStorageError> {
2823 trace!("received an edit request");
2824 self.nyi_for_uploads()?;
2825
2826 let serializable = SerializableEventContent::from_raw(new_content, event_type);
2827
2828 if self.room.inner.queue.replace_event(&self.transaction_id, serializable.clone()).await? {
2829 trace!("successful edit");
2830
2831 self.room.inner.notifier.notify_one();
2833
2834 self.room.send_update(RoomSendQueueUpdate::ReplacedLocalEvent {
2836 transaction_id: self.transaction_id.clone(),
2837 new_content: serializable,
2838 });
2839
2840 Ok(true)
2841 } else {
2842 debug!("local echo doesn't exist anymore, can't edit");
2843 Ok(false)
2844 }
2845 }
2846
2847 pub async fn edit(
2852 &self,
2853 new_content: AnyMessageLikeEventContent,
2854 ) -> Result<bool, RoomSendQueueStorageError> {
2855 self.edit_raw(
2856 Raw::new(&new_content).map_err(RoomSendQueueStorageError::JsonSerialization)?,
2857 new_content.event_type().to_string(),
2858 )
2859 .await
2860 }
2861
2862 pub async fn edit_media_caption(
2867 &self,
2868 caption: Option<String>,
2869 formatted_caption: Option<FormattedBody>,
2870 mentions: Option<Mentions>,
2871 ) -> Result<bool, RoomSendQueueStorageError> {
2872 if let Some(new_content) = self
2873 .room
2874 .inner
2875 .queue
2876 .edit_media_caption(&self.transaction_id, caption, formatted_caption, mentions)
2877 .await?
2878 {
2879 trace!("successful edit of media caption");
2880
2881 self.room.inner.notifier.notify_one();
2883
2884 let new_content = SerializableEventContent::new(&new_content)
2885 .map_err(RoomSendQueueStorageError::JsonSerialization)?;
2886
2887 self.room.send_update(RoomSendQueueUpdate::ReplacedLocalEvent {
2889 transaction_id: self.transaction_id.clone(),
2890 new_content,
2891 });
2892
2893 Ok(true)
2894 } else {
2895 debug!("local echo doesn't exist anymore, can't edit media caption");
2896 Ok(false)
2897 }
2898 }
2899
2900 pub async fn unwedge(&self) -> Result<(), RoomSendQueueError> {
2903 let room = &self.room.inner;
2904 room.queue
2905 .mark_as_unwedged(&self.transaction_id)
2906 .await
2907 .map_err(RoomSendQueueError::StorageError)?;
2908
2909 for handles in &self.media_handles {
2917 room.queue
2918 .mark_as_unwedged(&handles.upload_file_txn)
2919 .await
2920 .map_err(RoomSendQueueError::StorageError)?;
2921
2922 if let Some(txn) = &handles.upload_thumbnail_txn {
2923 room.queue.mark_as_unwedged(txn).await.map_err(RoomSendQueueError::StorageError)?;
2924 }
2925 }
2926
2927 room.notifier.notify_one();
2929
2930 self.room.send_update(RoomSendQueueUpdate::RetryEvent {
2931 transaction_id: self.transaction_id.clone(),
2932 });
2933
2934 Ok(())
2935 }
2936
2937 #[instrument(skip(self), fields(room_id = %self.room.inner.room.room_id(), txn_id = %self.transaction_id))]
2942 pub async fn react(
2943 &self,
2944 key: String,
2945 ) -> Result<Option<SendReactionHandle>, RoomSendQueueStorageError> {
2946 trace!("received an intent to react");
2947
2948 let created_at = MilliSecondsSinceUnixEpoch::now();
2949 if let Some(reaction_txn_id) =
2950 self.room.inner.queue.react(&self.transaction_id, key.clone(), created_at).await?
2951 {
2952 trace!("successfully queued react");
2953
2954 self.room.inner.notifier.notify_one();
2956
2957 let send_handle = SendReactionHandle {
2959 room: self.room.clone(),
2960 transaction_id: reaction_txn_id.clone(),
2961 };
2962
2963 self.room.send_update(RoomSendQueueUpdate::NewLocalEvent(LocalEcho {
2964 transaction_id: reaction_txn_id.into(),
2967 content: LocalEchoContent::React {
2968 key,
2969 send_handle: send_handle.clone(),
2970 applies_to: self.transaction_id.clone(),
2971 },
2972 }));
2973
2974 Ok(Some(send_handle))
2975 } else {
2976 debug!("local echo doesn't exist anymore, can't react");
2977 Ok(None)
2978 }
2979 }
2980}
2981
2982#[derive(Clone, Debug)]
2984pub struct SendReactionHandle {
2985 room: RoomSendQueue,
2987 transaction_id: ChildTransactionId,
2989}
2990
2991impl SendReactionHandle {
2992 #[cfg(test)]
2994 pub(crate) fn new(room: RoomSendQueue, transaction_id: ChildTransactionId) -> Self {
2995 Self { room, transaction_id }
2996 }
2997
2998 pub async fn abort(&self) -> Result<bool, RoomSendQueueStorageError> {
3003 if self.room.inner.queue.remove_dependent_send_queue_request(&self.transaction_id).await? {
3004 self.room.send_update(RoomSendQueueUpdate::CancelledLocalEvent {
3008 transaction_id: self.transaction_id.clone().into(),
3009 });
3010
3011 return Ok(true);
3012 }
3013
3014 let handle = SendHandle {
3017 room: self.room.clone(),
3018 transaction_id: self.transaction_id.clone().into(),
3019 media_handles: vec![],
3020 created_at: MilliSecondsSinceUnixEpoch::now(),
3021 };
3022
3023 handle.abort().await
3024 }
3025
3026 pub fn transaction_id(&self) -> &TransactionId {
3028 &self.transaction_id
3029 }
3030}
3031
3032#[derive(Clone, Debug)]
3035pub struct SendRedactionHandle {
3036 room: RoomSendQueue,
3038
3039 transaction_id: OwnedTransactionId,
3041}
3042
3043impl SendRedactionHandle {
3044 #[cfg(test)]
3046 pub(crate) fn new(room: RoomSendQueue, transaction_id: OwnedTransactionId) -> Self {
3047 Self { room, transaction_id }
3048 }
3049
3050 pub async fn abort(&self) -> Result<bool, RoomSendQueueStorageError> {
3055 trace!("received a redaction abort request");
3056
3057 let queue = &self.room.inner.queue;
3058
3059 if queue.cancel_event(&self.transaction_id).await? {
3060 trace!("successful redaction abort");
3061
3062 self.room.send_update(RoomSendQueueUpdate::CancelledLocalEvent {
3064 transaction_id: self.transaction_id.clone(),
3065 });
3066
3067 Ok(true)
3068 } else {
3069 debug!("local echo of redaction didn't exist anymore, can't abort");
3070 Ok(false)
3071 }
3072 }
3073}
3074
3075fn canonicalize_dependent_requests(
3079 dependent: &[DependentQueuedRequest],
3080) -> Vec<DependentQueuedRequest> {
3081 let mut by_txn = HashMap::<OwnedTransactionId, Vec<&DependentQueuedRequest>>::new();
3082
3083 for d in dependent {
3084 let prevs = by_txn.entry(d.parent_transaction_id.clone()).or_default();
3085
3086 if prevs.iter().any(|prev| matches!(prev.kind, DependentQueuedRequestKind::RedactEvent)) {
3087 continue;
3090 }
3091
3092 match &d.kind {
3093 DependentQueuedRequestKind::EditEvent { .. } => {
3094 if let Some(prev_edit) = prevs
3096 .iter_mut()
3097 .find(|prev| matches!(prev.kind, DependentQueuedRequestKind::EditEvent { .. }))
3098 {
3099 *prev_edit = d;
3100 } else {
3101 prevs.insert(0, d);
3102 }
3103 }
3104
3105 DependentQueuedRequestKind::UploadFileOrThumbnail { .. }
3106 | DependentQueuedRequestKind::FinishUpload { .. }
3107 | DependentQueuedRequestKind::ReactEvent { .. } => {
3108 prevs.push(d);
3110 }
3111
3112 #[cfg(feature = "unstable-msc4274")]
3113 DependentQueuedRequestKind::FinishGallery { .. } => {
3114 prevs.push(d);
3116 }
3117
3118 DependentQueuedRequestKind::RedactEvent => {
3119 prevs.clear();
3121 prevs.push(d);
3122 }
3123 }
3124 }
3125
3126 by_txn.into_values().flat_map(|entries| entries.into_iter().cloned()).collect()
3127}
3128
3129#[cfg(all(test, not(target_family = "wasm")))]
3130mod tests {
3131 use std::{sync::Arc, time::Duration};
3132
3133 use assert_matches2::{assert_let, assert_matches};
3134 use matrix_sdk_base::store::{
3135 ChildTransactionId, DependentQueuedRequest, DependentQueuedRequestKind,
3136 SerializableEventContent,
3137 };
3138 use matrix_sdk_test::{JoinedRoomBuilder, SyncResponseBuilder, async_test};
3139 use ruma::{
3140 MilliSecondsSinceUnixEpoch, TransactionId,
3141 events::{AnyMessageLikeEventContent, room::message::RoomMessageEventContent},
3142 room_id,
3143 };
3144
3145 use super::canonicalize_dependent_requests;
3146 use crate::{client::WeakClient, test_utils::logged_in_client};
3147
3148 #[test]
3149 fn test_canonicalize_dependent_events_created_at() {
3150 let txn = TransactionId::new();
3153 let created_at = MilliSecondsSinceUnixEpoch::now();
3154
3155 let edit = DependentQueuedRequest {
3156 own_transaction_id: ChildTransactionId::new(),
3157 parent_transaction_id: txn.clone(),
3158 kind: DependentQueuedRequestKind::EditEvent {
3159 new_content: SerializableEventContent::new(
3160 &RoomMessageEventContent::text_plain("edit").into(),
3161 )
3162 .unwrap(),
3163 },
3164 parent_key: None,
3165 created_at,
3166 };
3167
3168 let res = canonicalize_dependent_requests(&[edit]);
3169
3170 assert_eq!(res.len(), 1);
3171 assert_let!(DependentQueuedRequestKind::EditEvent { new_content } = &res[0].kind);
3172 assert_let!(
3173 AnyMessageLikeEventContent::RoomMessage(msg) = new_content.deserialize().unwrap()
3174 );
3175 assert_eq!(msg.body(), "edit");
3176 assert_eq!(res[0].parent_transaction_id, txn);
3177 assert_eq!(res[0].created_at, created_at);
3178 }
3179
3180 #[async_test]
3181 async fn test_client_no_cycle_with_send_queue() {
3182 for enabled in [true, false] {
3183 let client = logged_in_client(None).await;
3184 let weak_client = WeakClient::from_client(&client);
3185
3186 {
3187 let mut sync_response_builder = SyncResponseBuilder::new();
3188
3189 let room_id = room_id!("!a:b.c");
3190
3191 client
3193 .base_client()
3194 .receive_sync_response(
3195 sync_response_builder
3196 .add_joined_room(JoinedRoomBuilder::new(room_id))
3197 .build_sync_response(),
3198 )
3199 .await
3200 .unwrap();
3201
3202 let room = client.get_room(room_id).unwrap();
3203 let q = room.send_queue();
3204
3205 let _watcher = q.subscribe().await;
3206
3207 client.send_queue().set_enabled(enabled).await;
3208 }
3209
3210 drop(client);
3211
3212 tokio::time::sleep(Duration::from_millis(500)).await;
3214
3215 let client = weak_client.get();
3217 assert!(
3218 client.is_none(),
3219 "too many strong references to the client: {}",
3220 Arc::strong_count(&client.unwrap().inner)
3221 );
3222 }
3223 }
3224
3225 #[test]
3226 fn test_canonicalize_dependent_events_smoke_test() {
3227 let txn = TransactionId::new();
3229
3230 let edit = DependentQueuedRequest {
3231 own_transaction_id: ChildTransactionId::new(),
3232 parent_transaction_id: txn.clone(),
3233 kind: DependentQueuedRequestKind::EditEvent {
3234 new_content: SerializableEventContent::new(
3235 &RoomMessageEventContent::text_plain("edit").into(),
3236 )
3237 .unwrap(),
3238 },
3239 parent_key: None,
3240 created_at: MilliSecondsSinceUnixEpoch::now(),
3241 };
3242 let res = canonicalize_dependent_requests(&[edit]);
3243
3244 assert_eq!(res.len(), 1);
3245 assert_matches!(&res[0].kind, DependentQueuedRequestKind::EditEvent { .. });
3246 assert_eq!(res[0].parent_transaction_id, txn);
3247 assert!(res[0].parent_key.is_none());
3248 }
3249
3250 #[test]
3251 fn test_canonicalize_dependent_events_redaction_preferred() {
3252 let txn = TransactionId::new();
3254
3255 let mut inputs = Vec::with_capacity(100);
3256 let redact = DependentQueuedRequest {
3257 own_transaction_id: ChildTransactionId::new(),
3258 parent_transaction_id: txn.clone(),
3259 kind: DependentQueuedRequestKind::RedactEvent,
3260 parent_key: None,
3261 created_at: MilliSecondsSinceUnixEpoch::now(),
3262 };
3263
3264 let edit = DependentQueuedRequest {
3265 own_transaction_id: ChildTransactionId::new(),
3266 parent_transaction_id: txn.clone(),
3267 kind: DependentQueuedRequestKind::EditEvent {
3268 new_content: SerializableEventContent::new(
3269 &RoomMessageEventContent::text_plain("edit").into(),
3270 )
3271 .unwrap(),
3272 },
3273 parent_key: None,
3274 created_at: MilliSecondsSinceUnixEpoch::now(),
3275 };
3276
3277 inputs.push({
3278 let mut edit = edit.clone();
3279 edit.own_transaction_id = ChildTransactionId::new();
3280 edit
3281 });
3282
3283 inputs.push(redact);
3284
3285 for _ in 0..98 {
3286 let mut edit = edit.clone();
3287 edit.own_transaction_id = ChildTransactionId::new();
3288 inputs.push(edit);
3289 }
3290
3291 let res = canonicalize_dependent_requests(&inputs);
3292
3293 assert_eq!(res.len(), 1);
3294 assert_matches!(&res[0].kind, DependentQueuedRequestKind::RedactEvent);
3295 assert_eq!(res[0].parent_transaction_id, txn);
3296 }
3297
3298 #[test]
3299 fn test_canonicalize_dependent_events_last_edit_preferred() {
3300 let parent_txn = TransactionId::new();
3301
3302 let inputs = (0..10)
3304 .map(|i| DependentQueuedRequest {
3305 own_transaction_id: ChildTransactionId::new(),
3306 parent_transaction_id: parent_txn.clone(),
3307 kind: DependentQueuedRequestKind::EditEvent {
3308 new_content: SerializableEventContent::new(
3309 &RoomMessageEventContent::text_plain(format!("edit{i}")).into(),
3310 )
3311 .unwrap(),
3312 },
3313 parent_key: None,
3314 created_at: MilliSecondsSinceUnixEpoch::now(),
3315 })
3316 .collect::<Vec<_>>();
3317
3318 let txn = inputs[9].parent_transaction_id.clone();
3319
3320 let res = canonicalize_dependent_requests(&inputs);
3321
3322 assert_eq!(res.len(), 1);
3323 assert_let!(DependentQueuedRequestKind::EditEvent { new_content } = &res[0].kind);
3324 assert_let!(
3325 AnyMessageLikeEventContent::RoomMessage(msg) = new_content.deserialize().unwrap()
3326 );
3327 assert_eq!(msg.body(), "edit9");
3328 assert_eq!(res[0].parent_transaction_id, txn);
3329 }
3330
3331 #[test]
3332 fn test_canonicalize_multiple_local_echoes() {
3333 let txn1 = TransactionId::new();
3334 let txn2 = TransactionId::new();
3335
3336 let child1 = ChildTransactionId::new();
3337 let child2 = ChildTransactionId::new();
3338
3339 let inputs = vec![
3340 DependentQueuedRequest {
3342 own_transaction_id: child1.clone(),
3343 kind: DependentQueuedRequestKind::RedactEvent,
3344 parent_transaction_id: txn1.clone(),
3345 parent_key: None,
3346 created_at: MilliSecondsSinceUnixEpoch::now(),
3347 },
3348 DependentQueuedRequest {
3350 own_transaction_id: child2,
3351 kind: DependentQueuedRequestKind::EditEvent {
3352 new_content: SerializableEventContent::new(
3353 &RoomMessageEventContent::text_plain("edit").into(),
3354 )
3355 .unwrap(),
3356 },
3357 parent_transaction_id: txn2.clone(),
3358 parent_key: None,
3359 created_at: MilliSecondsSinceUnixEpoch::now(),
3360 },
3361 ];
3362
3363 let res = canonicalize_dependent_requests(&inputs);
3364
3365 assert_eq!(res.len(), 2);
3367
3368 for dependent in res {
3369 if dependent.own_transaction_id == child1 {
3370 assert_eq!(dependent.parent_transaction_id, txn1);
3371 assert_matches!(dependent.kind, DependentQueuedRequestKind::RedactEvent);
3372 } else {
3373 assert_eq!(dependent.parent_transaction_id, txn2);
3374 assert_matches!(dependent.kind, DependentQueuedRequestKind::EditEvent { .. });
3375 }
3376 }
3377 }
3378
3379 #[test]
3380 fn test_canonicalize_reactions_after_edits() {
3381 let txn = TransactionId::new();
3383
3384 let react_id = ChildTransactionId::new();
3385 let react = DependentQueuedRequest {
3386 own_transaction_id: react_id.clone(),
3387 kind: DependentQueuedRequestKind::ReactEvent { key: "🧠".to_owned() },
3388 parent_transaction_id: txn.clone(),
3389 parent_key: None,
3390 created_at: MilliSecondsSinceUnixEpoch::now(),
3391 };
3392
3393 let edit_id = ChildTransactionId::new();
3394 let edit = DependentQueuedRequest {
3395 own_transaction_id: edit_id.clone(),
3396 kind: DependentQueuedRequestKind::EditEvent {
3397 new_content: SerializableEventContent::new(
3398 &RoomMessageEventContent::text_plain("edit").into(),
3399 )
3400 .unwrap(),
3401 },
3402 parent_transaction_id: txn,
3403 parent_key: None,
3404 created_at: MilliSecondsSinceUnixEpoch::now(),
3405 };
3406
3407 let res = canonicalize_dependent_requests(&[react, edit]);
3408
3409 assert_eq!(res.len(), 2);
3410 assert_eq!(res[0].own_transaction_id, edit_id);
3411 assert_eq!(res[1].own_transaction_id, react_id);
3412 }
3413}