Skip to main content

matrix_sdk/send_queue/
mod.rs

1// Copyright 2024 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! A send queue facility to serializing queuing and sending of messages.
16//!
17//! # [`Room`] send queue
18//!
19//! Each room gets its own [`RoomSendQueue`], that's available by calling
20//! [`Room::send_queue()`]. The first time this method is called, it will spawn
21//! a background task that's used to actually send events, in the order they
22//! were passed from calls to [`RoomSendQueue::send()`].
23//!
24//! This queue tries to simplify error management around sending events, using
25//! [`RoomSendQueue::send`] or [`RoomSendQueue::send_raw`]: by default, it will retry to send the
26//! same event a few times, before automatically disabling itself, and emitting
27//! a notification that can be listened to with the global send queue (see
28//! paragraph below) or using [`RoomSendQueue::subscribe()`].
29//!
30//! Requests are sent in the order they were queued. A request that failed with
31//! an unrecoverable error is marked as "wedged", and blocks all the requests
32//! queued after it (in the same room) from being sent, so events are never sent
33//! out of order; the queue resumes when the wedged request is retried (with
34//! [`SendHandle::unwedge`]) or removed (with [`SendHandle::abort`]).
35//!
36//! It is possible to control whether a single room is enabled using
37//! [`RoomSendQueue::set_enabled()`].
38//!
39//! # Global [`SendQueue`] object
40//!
41//! The [`Client::send_queue()`] method returns an API object allowing to
42//! control all the room send queues:
43//!
44//! - enable/disable them all at once with [`SendQueue::set_enabled()`].
45//! - get notifications about send errors with [`SendQueue::subscribe_errors`].
46//! - reload all unsent events that had been persisted in storage using
47//!   [`SendQueue::respawn_tasks_for_rooms_with_unsent_requests()`]. It is
48//!   recommended to call this method during initialization of a client,
49//!   otherwise persisted unsent events will only be re-sent after the send
50//!   queue for the given room has been reopened for the first time.
51//!
52//! # Send handle
53//!
54//! Just after queuing a request to send something, a [`SendHandle`] is
55//! returned, allowing manipulating the inflight request.
56//!
57//! For a send handle for an event, it's possible to edit the event / abort
58//! sending it. If it was still in the queue (i.e. not sent yet, or not being
59//! sent), then such an action would happen locally (i.e. in the database).
60//! Otherwise, it is "too late": the background task may be sending
61//! the event already, or has sent it; in that case, the edit/aborting must
62//! happen as an actual event materializing this, on the server. To accomplish
63//! this, the send queue may send such an event, using the dependency system
64//! described below.
65//!
66//! # Dependency system
67//!
68//! The send queue includes a simple dependency system, where a
69//! [`QueuedRequest`] can have zero or more dependents in the form of
70//! [`DependentQueuedRequest`]. A dependent queued request can have at most one
71//! depended-upon (parent) queued request.
72//!
73//! This allows implementing deferred edits/redacts, as hinted to in the
74//! previous section.
75//!
76//! ## Media upload
77//!
78//! This dependency system also allows uploading medias, since the media's
79//! *content* must be uploaded before we send the media *event* that describes
80//! it.
81//!
82//! In the simplest case, that is, a media file and its event must be sent (i.e.
83//! no thumbnails):
84//!
85//! - The file's content is immediately cached in the
86//!   [`matrix_sdk_base::event_cache::store::EventCacheStore`], using an MXC ID
87//!   that is temporary and designates a local URI without any possible doubt.
88//! - An initial media event is created and uses this temporary MXC ID, and
89//!   propagated as a local echo for an event.
90//! - A [`QueuedRequest`] is pushed to upload the file's media
91//!   ([`QueuedRequestKind::MediaUpload`]).
92//! - A [`DependentQueuedRequest`] is pushed to finish the upload
93//!   ([`DependentQueuedRequestKind::FinishUpload`]).
94//!
95//! What is expected to happen, if all goes well, is the following:
96//!
97//! - the media is uploaded to the media homeserver, which returns the final MXC
98//!   ID.
99//! - when marking the upload request as sent, the MXC ID is injected (as a
100//!   [`matrix_sdk_base::store::SentRequestKey`]) into the dependent request
101//!   [`DependentQueuedRequestKind::FinishUpload`] created in the last step
102//!   above.
103//! - next time the send queue handles dependent queries, it'll see this one is
104//!   ready to be sent, and it will transform it into an event queued request
105//!   ([`QueuedRequestKind::Event`]), with the event created in the local echo
106//!   before, updated with the MXC ID returned from the server.
107//! - this updated local echo is also propagated as an edit of the local echo to
108//!   observers, who get the final version with the final MXC IDs at this point
109//!   too.
110//! - then the event is sent normally, as any event sent with the send queue.
111//!
112//! When there is a thumbnail, things behave similarly, with some tweaks:
113//!
114//! - the thumbnail's content is also stored into the cache store immediately,
115//! - the thumbnail is sent first as an [`QueuedRequestKind::MediaUpload`]
116//!   request,
117//! - the file upload is pushed as a dependent request of kind
118//!   [`DependentQueuedRequestKind::UploadFileOrThumbnail`] (this variant keeps
119//!   the file's key used to look it up in the cache store).
120//! - the media event is then sent as a dependent request as described in the
121//!   previous section.
122//!
123//! What's expected to happen is thus the following:
124//!
125//! - After the thumbnail has been uploaded, the dependent query will retrieve
126//!   the final MXC ID returned by the homeserver for the thumbnail, and store
127//!   it into the [`QueuedRequestKind::MediaUpload`]'s `thumbnail_source` field,
128//!   allowing to remember the thumbnail MXC ID when it's time to finish the
129//!   upload later.
130//! - The dependent request is morphed into another
131//!   [`QueuedRequestKind::MediaUpload`], for the file itself.
132//!
133//! The rest of the process is then similar to that of uploading a file without
134//! a thumbnail. The only difference is that there's a thumbnail source (MXC ID)
135//! remembered and fixed up into the media event, just before sending it.
136
137use std::{
138    collections::{BTreeMap, HashMap},
139    future::IntoFuture,
140    ops::Not,
141    str::FromStr as _,
142    sync::{
143        Arc, RwLock,
144        atomic::{AtomicBool, Ordering},
145    },
146    time::Duration,
147};
148
149use eyeball::SharedObservable;
150#[cfg(feature = "e2e-encryption")]
151use matrix_sdk_base::crypto::{OlmError, SessionRecipientCollectionError};
152#[cfg(feature = "unstable-msc4274")]
153use matrix_sdk_base::store::FinishGalleryItemInfo;
154use matrix_sdk_base::{
155    RoomState, StoreError,
156    cross_process_lock::CrossProcessLockError,
157    deserialized_responses::{EncryptionInfo, TimelineEvent},
158    event_cache::store::EventCacheStoreError,
159    media::{MediaRequestParameters, store::MediaStoreError},
160    store::{
161        ChildTransactionId, DependentQueuedRequest, DependentQueuedRequestKind, DynStateStore,
162        FinishUploadThumbnailInfo, QueueWedgeError, QueuedRequest, QueuedRequestKind,
163        SentMediaInfo, SentRequestKey, SerializableEventContent,
164    },
165    task_monitor::BackgroundTaskHandle,
166};
167use matrix_sdk_common::{boxed_into_future, locks::Mutex as SyncMutex};
168use mime::Mime;
169use ruma::{
170    MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, OwnedTransactionId, RoomId,
171    TransactionId,
172    events::{
173        AnyMessageLikeEventContent, Mentions, MessageLikeEventContent as _, TimelineEventType,
174        reaction::ReactionEventContent,
175        relation::Annotation,
176        room::{
177            MediaSource,
178            message::{FormattedBody, RoomMessageEventContent},
179        },
180    },
181    serde::Raw,
182};
183use tokio::sync::{Mutex, Notify, OwnedMutexGuard, broadcast, oneshot};
184use tracing::{debug, error, info, instrument, trace, warn};
185
186use crate::{
187    Client, Media, Room, TransmissionProgress,
188    client::WeakClient,
189    config::RequestConfig,
190    error::RetryKind,
191    room::{WeakRoom, edit::EditedContent},
192};
193
194mod progress;
195mod upload;
196
197pub use progress::AbstractProgress;
198
199/// How long to wait before retrying, when loading the next request to send
200/// failed against the state store.
201const STORE_ERROR_BACKOFF: Duration = Duration::from_millis(250);
202
203/// A client-wide send queue, for all the rooms known by a client.
204pub struct SendQueue {
205    client: Client,
206}
207
208#[cfg(not(tarpaulin_include))]
209impl std::fmt::Debug for SendQueue {
210    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211        f.debug_struct("SendQueue").finish_non_exhaustive()
212    }
213}
214
215impl SendQueue {
216    pub(super) fn new(client: Client) -> Self {
217        Self { client }
218    }
219
220    /// Reload all the rooms which had unsent requests, and respawn tasks for
221    /// those rooms.
222    pub async fn respawn_tasks_for_rooms_with_unsent_requests(&self) {
223        if !self.is_enabled() {
224            return;
225        }
226
227        let room_ids =
228            self.client.state_store().load_rooms_with_unsent_requests().await.unwrap_or_else(
229                |err| {
230                    warn!("error when loading rooms with unsent requests: {err}");
231                    Vec::new()
232                },
233            );
234
235        // Getting the [`RoomSendQueue`] is sufficient to spawn the task if needs be.
236        for room_id in room_ids {
237            if let Some(room) = self.client.get_room(&room_id) {
238                let _ = self.for_room(room);
239            }
240        }
241    }
242
243    /// Tiny helper to get the send queue's global context from the [`Client`].
244    #[inline(always)]
245    fn data(&self) -> &SendQueueData {
246        &self.client.inner.send_queue_data
247    }
248
249    /// Get or create a new send queue for a given room, and insert it into our
250    /// memoized rooms mapping.
251    pub(crate) fn for_room(&self, room: Room) -> RoomSendQueue {
252        let data = self.data();
253
254        let mut map = data.rooms.write().unwrap();
255
256        let room_id = room.room_id();
257        if let Some(room_q) = map.get(room_id).cloned() {
258            return room_q;
259        }
260
261        let owned_room_id = room_id.to_owned();
262        let room_q = RoomSendQueue::new(
263            self.is_enabled(),
264            data.global_update_sender.clone(),
265            data.error_sender.clone(),
266            data.is_dropping.clone(),
267            &self.client,
268            owned_room_id.clone(),
269            data.report_media_upload_progress.clone(),
270        );
271
272        map.insert(owned_room_id, room_q.clone());
273
274        room_q
275    }
276
277    /// Enable or disable the send queue for the entire client, i.e. all rooms.
278    ///
279    /// If we're disabling the queue, and requests were being sent, they're not
280    /// aborted, and will continue until a status resolves (error responses
281    /// will keep the events in the buffer of events to send later). The
282    /// disablement will happen before the next request is sent.
283    ///
284    /// This may wake up background tasks and resume sending of requests in the
285    /// background.
286    pub async fn set_enabled(&self, enabled: bool) {
287        debug!(?enabled, "setting global send queue enablement");
288
289        self.data().globally_enabled.store(enabled, Ordering::SeqCst);
290
291        // Wake up individual rooms we already know about.
292        for room in self.data().rooms.read().unwrap().values() {
293            room.set_enabled(enabled);
294        }
295
296        // Reload some extra rooms that might not have been awaken yet, but could have
297        // requests from previous sessions.
298        self.respawn_tasks_for_rooms_with_unsent_requests().await;
299    }
300
301    /// Returns whether the send queue is enabled, at a client-wide
302    /// granularity.
303    pub fn is_enabled(&self) -> bool {
304        self.data().globally_enabled.load(Ordering::SeqCst)
305    }
306
307    /// Enable or disable progress reporting for media uploads.
308    pub fn enable_upload_progress(&self, enabled: bool) {
309        self.data().report_media_upload_progress.store(enabled, Ordering::SeqCst);
310    }
311
312    /// Subscribe to all updates for all rooms.
313    ///
314    /// Use [`RoomSendQueue::subscribe`] to subscribe to update for a _specific
315    /// room_.
316    pub fn subscribe(&self) -> broadcast::Receiver<SendQueueUpdate> {
317        self.data().global_update_sender.subscribe()
318    }
319
320    /// Get local echoes from all room send queues.
321    pub async fn local_echoes(
322        &self,
323    ) -> Result<BTreeMap<OwnedRoomId, Vec<LocalEcho>>, RoomSendQueueError> {
324        let room_ids =
325            self.client.state_store().load_rooms_with_unsent_requests().await.unwrap_or_else(
326                |err| {
327                    warn!("error when loading rooms with unsent requests: {err}");
328                    Vec::new()
329                },
330            );
331
332        let mut local_echoes: BTreeMap<OwnedRoomId, Vec<LocalEcho>> = BTreeMap::new();
333
334        for room_id in room_ids {
335            if let Some(room) = self.client.get_room(&room_id) {
336                let queue = self.for_room(room);
337                local_echoes
338                    .insert(room_id.to_owned(), queue.inner.queue.local_echoes(&queue).await?);
339            }
340        }
341
342        Ok(local_echoes)
343    }
344
345    /// A subscriber to the enablement status (enabled or disabled) of the
346    /// send queue, along with useful errors.
347    pub fn subscribe_errors(&self) -> broadcast::Receiver<SendQueueRoomError> {
348        self.data().error_sender.subscribe()
349    }
350}
351
352/// Metadata about a thumbnail needed when pushing media uploads to the send
353/// queue.
354#[derive(Clone, Debug)]
355struct QueueThumbnailInfo {
356    /// Metadata about the thumbnail needed when finishing a media upload.
357    finish_upload_thumbnail_info: FinishUploadThumbnailInfo,
358
359    /// The parameters for the request to retrieve the thumbnail data.
360    media_request_parameters: MediaRequestParameters,
361
362    /// The thumbnail's mime type.
363    content_type: Mime,
364
365    /// The thumbnail's file size in bytes.
366    file_size: usize,
367}
368
369/// A specific room's send queue ran into an error, and it has disabled itself.
370#[derive(Clone, Debug)]
371pub struct SendQueueRoomError {
372    /// For which room is the send queue failing?
373    pub room_id: OwnedRoomId,
374
375    /// The error the room has ran into, when trying to send a request.
376    pub error: Arc<crate::Error>,
377
378    /// Whether the error is considered recoverable or not.
379    ///
380    /// An error that's recoverable will disable the room's send queue, while an
381    /// unrecoverable error will be parked, until the user decides to do
382    /// something about it.
383    pub is_recoverable: bool,
384}
385
386impl Client {
387    /// Returns a [`SendQueue`] that handles sending, retrying and not
388    /// forgetting about requests that are to be sent.
389    pub fn send_queue(&self) -> SendQueue {
390        SendQueue::new(self.clone())
391    }
392}
393
394pub(super) struct SendQueueData {
395    /// Mapping of room to their unique send queue.
396    rooms: RwLock<BTreeMap<OwnedRoomId, RoomSendQueue>>,
397
398    /// Is the whole mechanism enabled or disabled?
399    ///
400    /// This is only kept in memory to initialize new room queues with an
401    /// initial enablement state.
402    globally_enabled: AtomicBool,
403
404    /// Global sender to send [`SendQueueUpdate`].
405    ///
406    /// See [`SendQueue::subscribe`].
407    global_update_sender: broadcast::Sender<SendQueueUpdate>,
408
409    /// Global error updates for the send queue.
410    error_sender: broadcast::Sender<SendQueueRoomError>,
411
412    /// Are we currently dropping the Client?
413    is_dropping: Arc<AtomicBool>,
414
415    /// Will media upload progress be reported via send queue updates?
416    report_media_upload_progress: Arc<AtomicBool>,
417}
418
419impl SendQueueData {
420    /// Create the data for a send queue, in the given enabled state.
421    pub fn new(globally_enabled: bool) -> Self {
422        let (global_update_sender, _) = broadcast::channel(32);
423        let (error_sender, _) = broadcast::channel(32);
424
425        Self {
426            rooms: Default::default(),
427            globally_enabled: AtomicBool::new(globally_enabled),
428            global_update_sender,
429            error_sender,
430            is_dropping: Arc::new(false.into()),
431            report_media_upload_progress: Arc::new(false.into()),
432        }
433    }
434}
435
436impl Drop for SendQueueData {
437    fn drop(&mut self) {
438        // Mark the whole send queue as shutting down, then wake up all the room
439        // queues so they're stopped too.
440        debug!("globally dropping the send queue");
441        self.is_dropping.store(true, Ordering::SeqCst);
442
443        let rooms = self.rooms.read().unwrap();
444        for room in rooms.values() {
445            room.inner.notifier.notify_one();
446        }
447    }
448}
449
450impl Room {
451    /// Returns the [`RoomSendQueue`] for this specific room.
452    pub fn send_queue(&self) -> RoomSendQueue {
453        self.client.send_queue().for_room(self.clone())
454    }
455}
456
457/// A per-room send queue.
458///
459/// This is cheap to clone.
460#[derive(Clone)]
461pub struct RoomSendQueue {
462    inner: Arc<RoomSendQueueInner>,
463}
464
465#[cfg(not(tarpaulin_include))]
466impl std::fmt::Debug for RoomSendQueue {
467    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
468        f.debug_struct("RoomSendQueue").finish_non_exhaustive()
469    }
470}
471
472impl RoomSendQueue {
473    fn new(
474        globally_enabled: bool,
475        global_update_sender: broadcast::Sender<SendQueueUpdate>,
476        global_error_sender: broadcast::Sender<SendQueueRoomError>,
477        is_dropping: Arc<AtomicBool>,
478        client: &Client,
479        room_id: OwnedRoomId,
480        report_media_upload_progress: Arc<AtomicBool>,
481    ) -> Self {
482        let (update_sender, _) = broadcast::channel(32);
483
484        let queue = QueueStorage::new(WeakClient::from_client(client), room_id.clone());
485        let notifier = Arc::new(Notify::new());
486
487        let weak_room = WeakRoom::new(WeakClient::from_client(client), room_id);
488        let locally_enabled = Arc::new(AtomicBool::new(globally_enabled));
489
490        let task = client.task_monitor().spawn_infinite_task(
491            "send_queue",
492            Self::sending_task(
493                weak_room.clone(),
494                queue.clone(),
495                notifier.clone(),
496                global_update_sender.clone(),
497                update_sender.clone(),
498                locally_enabled.clone(),
499                global_error_sender,
500                is_dropping,
501                report_media_upload_progress,
502            ),
503        );
504
505        Self {
506            inner: Arc::new(RoomSendQueueInner {
507                room: weak_room,
508                global_update_sender,
509                update_sender,
510                _task: task,
511                queue,
512                notifier,
513                locally_enabled,
514            }),
515        }
516    }
517
518    /// Queues a raw event for sending it to this room.
519    ///
520    /// This immediately returns, and will push the event to be sent into a
521    /// queue, handled in the background.
522    ///
523    /// Callers are expected to consume [`RoomSendQueueUpdate`] via calling
524    /// the [`Self::subscribe()`] method to get updates about the sending of
525    /// that event.
526    ///
527    /// By default, if sending failed on the first attempt, it will be retried a
528    /// few times. If sending failed after those retries, the entire
529    /// client's sending queue will be disabled, and it will need to be
530    /// manually re-enabled by the caller (e.g. after network is back, or when
531    /// something has been done about the faulty requests).
532    pub async fn send_raw(
533        &self,
534        content: Raw<AnyMessageLikeEventContent>,
535        event_type: String,
536    ) -> Result<SendHandle, RoomSendQueueError> {
537        let Some(room) = self.inner.room.get() else {
538            return Err(RoomSendQueueError::RoomDisappeared);
539        };
540        if room.state() != RoomState::Joined {
541            return Err(RoomSendQueueError::RoomNotJoined);
542        }
543
544        let content = SerializableEventContent::from_raw(content, event_type);
545
546        let created_at = MilliSecondsSinceUnixEpoch::now();
547        let transaction_id = self.inner.queue.push(content.clone().into(), created_at).await?;
548        trace!(%transaction_id, "manager sends a raw event to the background task");
549
550        self.inner.notifier.notify_one();
551
552        let send_handle = SendHandle {
553            room: self.clone(),
554            transaction_id: transaction_id.clone(),
555            media_handles: vec![],
556            created_at,
557        };
558
559        self.send_update(RoomSendQueueUpdate::NewLocalEvent(LocalEcho {
560            transaction_id,
561            content: LocalEchoContent::Event {
562                serialized_event: content,
563                send_handle: send_handle.clone(),
564                send_error: None,
565            },
566        }));
567
568        Ok(send_handle)
569    }
570
571    /// Queues an event for sending it to this room.
572    ///
573    /// This immediately returns, and will push the event to be sent into a
574    /// queue, handled in the background.
575    ///
576    /// Callers are expected to consume [`RoomSendQueueUpdate`] via calling
577    /// the [`Self::subscribe()`] method to get updates about the sending of
578    /// that event.
579    ///
580    /// By default, if sending failed on the first attempt, it will be retried a
581    /// few times. If sending failed after those retries, the entire
582    /// client's sending queue will be disabled, and it will need to be
583    /// manually re-enabled by the caller (e.g. after network is back, or when
584    /// something has been done about the faulty requests).
585    pub fn send(&self, content: AnyMessageLikeEventContent) -> SendEvent<'_> {
586        SendEvent { queue: self, content, extra_content: None }
587    }
588
589    /// Queues a redaction of another event for sending it to this room.
590    ///
591    /// This immediately returns, and will push the redaction to be sent into a
592    /// queue, handled in the background.
593    ///
594    /// Callers are expected to consume [`RoomSendQueueUpdate`] via calling
595    /// the [`Self::subscribe()`] method to get updates about the sending of
596    /// that redaction.
597    ///
598    /// By default, if sending failed on the first attempt, it will be retried a
599    /// few times. If sending failed after those retries, the entire
600    /// client's sending queue will be disabled, and it will need to be
601    /// manually re-enabled by the caller (e.g. after network is back, or when
602    /// something has been done about the faulty requests).
603    pub async fn redact(
604        &self,
605        redacts: OwnedEventId,
606        reason: Option<&str>,
607    ) -> Result<SendRedactionHandle, RoomSendQueueError> {
608        let Some(room) = self.inner.room.get() else {
609            return Err(RoomSendQueueError::RoomDisappeared);
610        };
611        if room.state() != RoomState::Joined {
612            return Err(RoomSendQueueError::RoomNotJoined);
613        }
614
615        let request = QueuedRequestKind::Redaction {
616            redacts: redacts.clone(),
617            reason: reason.map(str::to_owned),
618        };
619
620        let created_at = MilliSecondsSinceUnixEpoch::now();
621        let transaction_id = self.inner.queue.push(request, created_at).await?;
622        trace!(%transaction_id, "manager sends a redaction event to the background task");
623
624        self.inner.notifier.notify_one();
625
626        let send_handle =
627            SendRedactionHandle { room: self.clone(), transaction_id: transaction_id.clone() };
628
629        self.send_update(RoomSendQueueUpdate::NewLocalEvent(LocalEcho {
630            transaction_id,
631            content: LocalEchoContent::Redaction {
632                redacts,
633                reason: reason.map(str::to_owned),
634                send_handle: send_handle.clone(),
635                send_error: None,
636            },
637        }));
638
639        Ok(send_handle)
640    }
641
642    /// Returns the current local requests as well as a receiver to listen to
643    /// the send queue updates, as defined in [`RoomSendQueueUpdate`].
644    ///
645    /// Use [`SendQueue::subscribe`] to subscribe to update for _all rooms_ with
646    /// a single receiver.
647    pub async fn subscribe(
648        &self,
649    ) -> Result<(Vec<LocalEcho>, broadcast::Receiver<RoomSendQueueUpdate>), RoomSendQueueError>
650    {
651        let local_echoes = self.inner.queue.local_echoes(self).await?;
652
653        Ok((local_echoes, self.inner.update_sender.subscribe()))
654    }
655
656    /// A task that must be spawned in the async runtime, running in the
657    /// background for each room that has a send queue.
658    ///
659    /// It only progresses forward: nothing can be cancelled at any point, which
660    /// makes the implementation not overly complicated to follow.
661    #[allow(clippy::too_many_arguments)]
662    #[instrument(skip_all, fields(room_id = %room.room_id()))]
663    async fn sending_task(
664        room: WeakRoom,
665        queue: QueueStorage,
666        notifier: Arc<Notify>,
667        global_update_sender: broadcast::Sender<SendQueueUpdate>,
668        update_sender: broadcast::Sender<RoomSendQueueUpdate>,
669        locally_enabled: Arc<AtomicBool>,
670        global_error_sender: broadcast::Sender<SendQueueRoomError>,
671        is_dropping: Arc<AtomicBool>,
672        report_media_upload_progress: Arc<AtomicBool>,
673    ) {
674        trace!("spawned the sending task");
675
676        let room_id = room.room_id();
677
678        loop {
679            // A request to shut down should be preferred above everything else.
680            if is_dropping.load(Ordering::SeqCst) {
681                trace!("shutting down!");
682                break;
683            }
684
685            // Try to apply dependent requests now; those applying to previously failed
686            // attempts (local echoes) would succeed now.
687            let mut new_updates = Vec::new();
688            if let Err(err) = queue.apply_dependent_requests(&mut new_updates).await {
689                warn!("errors when applying dependent requests: {err}");
690            }
691
692            for up in new_updates {
693                send_update(&global_update_sender, &update_sender, room_id, up);
694            }
695
696            if !locally_enabled.load(Ordering::SeqCst) {
697                trace!("not enabled, sleeping");
698                // Wait for an explicit wakeup.
699                notifier.notified().await;
700                continue;
701            }
702
703            let (queued_request, cancel_upload_rx) = match queue.peek_next_to_send().await {
704                Ok(Some(request)) => request,
705
706                Ok(None) => {
707                    trace!("queue is empty or blocked on a wedged request, sleeping");
708                    // Wait for an explicit wakeup.
709                    notifier.notified().await;
710                    continue;
711                }
712
713                Err(err) => {
714                    warn!("error when loading next request to send: {err}");
715                    // Don't hammer a failing store; back off a bit before retrying.
716                    matrix_sdk_common::sleep::sleep(STORE_ERROR_BACKOFF).await;
717                    continue;
718                }
719            };
720
721            let txn_id = queued_request.transaction_id.clone();
722            trace!(txn_id = %txn_id, "received a request to send!");
723
724            let Some(room) = room.get() else {
725                if is_dropping.load(Ordering::SeqCst) {
726                    break;
727                }
728                error!("the weak room couldn't be upgraded but we're not shutting down?");
729                continue;
730            };
731
732            // If this is a media/gallery upload, prepare the following:
733            // - transaction id for the related media event request,
734            // - progress metadata to feed the final media upload progress
735            // - an observable to watch the media upload progress.
736            let (related_txn_id, media_upload_progress_info, http_progress) =
737                if let QueuedRequestKind::MediaUpload {
738                    cache_key,
739                    thumbnail_source,
740                    #[cfg(feature = "unstable-msc4274")]
741                    accumulated,
742                    related_to,
743                    ..
744                } = &queued_request.kind
745                {
746                    // Prepare to watch and communicate the request's progress for media uploads, if
747                    // it has been requested.
748                    let (media_upload_progress_info, http_progress) =
749                        if report_media_upload_progress.load(Ordering::SeqCst) {
750                            let media_upload_progress_info =
751                                RoomSendQueue::create_media_upload_progress_info(
752                                    &queued_request.transaction_id,
753                                    related_to,
754                                    cache_key,
755                                    thumbnail_source.as_ref(),
756                                    #[cfg(feature = "unstable-msc4274")]
757                                    accumulated,
758                                    &room,
759                                    &queue,
760                                )
761                                .await;
762
763                            let progress = RoomSendQueue::create_media_upload_progress_observable(
764                                &media_upload_progress_info,
765                                related_to,
766                                &update_sender,
767                            );
768
769                            (Some(media_upload_progress_info), Some(progress))
770                        } else {
771                            Default::default()
772                        };
773
774                    (Some(related_to.clone()), media_upload_progress_info, http_progress)
775                } else {
776                    Default::default()
777                };
778
779            match Self::handle_request(&room, queued_request, cancel_upload_rx, http_progress).await
780            {
781                Ok((Some(parent_key), encryption_info)) => match queue
782                    .mark_as_sent(&txn_id, parent_key.clone())
783                    .await
784                {
785                    Ok(()) => match parent_key {
786                        SentRequestKey::Event { event_id, event, event_type } => {
787                            send_update(
788                                &global_update_sender,
789                                &update_sender,
790                                room_id,
791                                RoomSendQueueUpdate::SentEvent {
792                                    transaction_id: txn_id,
793                                    event_id: event_id.clone(),
794                                },
795                            );
796
797                            // The event has been sent to the server and the server has received it.
798                            // Yepee! Now, we usually wait on the server to give us back the event
799                            // via the sync.
800                            //
801                            // Problem: sometimes the network lags, can be down, or the server may
802                            // be slow; well, anything can happen.
803                            //
804                            // It results in a weird situation where the user sees its event being
805                            // sent, then disappears before it's received again from the server.
806                            //
807                            // To avoid this situation, we eagerly save the event in the Event
808                            // Cache. It's similar to what would happen if the event was echoed back
809                            // from the server via the sync, but we avoid any network issues. The
810                            // Event Cache is smart enough to deduplicate events based on the event
811                            // ID, so it's safe to do that.
812                            //
813                            // If this little feature fails, it MUST NOT stop the Send Queue. Any
814                            // errors are logged, but the Send Queue will continue as if everything
815                            // happened successfully. This feature is not considered “crucial”.
816                            if let Ok((room_event_cache, _drop_handles)) = room.event_cache().await
817                            {
818                                let timeline_event = match Raw::from_json_string(
819                                    // Create a compact string: remove all useless spaces.
820                                    format!(
821                                        "{{\
822                                            \"event_id\":\"{event_id}\",\
823                                            \"origin_server_ts\":{ts},\
824                                            \"sender\":\"{sender}\",\
825                                            \"type\":\"{type}\",\
826                                            \"content\":{content}\
827                                        }}",
828                                        event_id = event_id,
829                                        ts = MilliSecondsSinceUnixEpoch::now().get(),
830                                        sender = room.client().user_id().expect("Client must be logged-in"),
831                                        type = event_type,
832                                        content = event.into_json(),
833                                    ),
834                                ) {
835                                    Ok(event) => match encryption_info {
836                                        #[cfg(feature = "e2e-encryption")]
837                                        Some(encryption_info) => {
838                                            use matrix_sdk_base::deserialized_responses::DecryptedRoomEvent;
839                                            let decrypted_event = DecryptedRoomEvent {
840                                                event: event.cast_unchecked(),
841                                                encryption_info: Arc::new(encryption_info),
842                                                unsigned_encryption_info: None,
843                                            };
844                                            Some(TimelineEvent::from_decrypted(
845                                                decrypted_event,
846                                                None,
847                                            ))
848                                        }
849                                        _ => Some(TimelineEvent::from_plaintext(event)),
850                                    },
851                                    Err(err) => {
852                                        error!(
853                                            ?err,
854                                            "Failed to build the (sync) event before the saving in the Event Cache"
855                                        );
856                                        None
857                                    }
858                                };
859
860                                // In case of an error, just log the error but don't stop the Send
861                                // Queue. This feature is not crucial.
862                                if let Some(timeline_event) = timeline_event
863                                    && let Err(err) = room_event_cache
864                                        .insert_sent_event_from_send_queue(timeline_event)
865                                        .await
866                                {
867                                    error!(
868                                        ?err,
869                                        "Failed to save the sent event in the Event Cache"
870                                    );
871                                }
872                            } else {
873                                info!(
874                                    "Cannot insert the sent event in the Event Cache because \
875                                    either the room no longer exists, or the Room Event Cache cannot be retrieved"
876                                );
877                            }
878                        }
879
880                        SentRequestKey::Media(sent_media_info) => {
881                            // Generate some final progress information, even if incremental
882                            // progress wasn't requested.
883                            let index =
884                                media_upload_progress_info.as_ref().map_or(0, |info| info.index);
885                            let progress = media_upload_progress_info
886                                .as_ref()
887                                .map(|info| {
888                                    AbstractProgress { current: info.bytes, total: info.bytes }
889                                        + info.offsets
890                                })
891                                .unwrap_or(AbstractProgress { current: 1, total: 1 });
892
893                            // Purposefully don't use `send_update` here, because we don't want to
894                            // notify the global listeners about an upload progress update.
895                            let _ = update_sender.send(RoomSendQueueUpdate::MediaUpload {
896                                related_to: related_txn_id.as_ref().unwrap_or(&txn_id).clone(),
897                                file: Some(sent_media_info.file),
898                                index,
899                                progress,
900                            });
901                        }
902
903                        SentRequestKey::Redaction { event_id, redacts, reason } => {
904                            send_update(
905                                &global_update_sender,
906                                &update_sender,
907                                room_id,
908                                RoomSendQueueUpdate::SentEvent {
909                                    transaction_id: txn_id,
910                                    event_id: event_id.clone(),
911                                },
912                            );
913
914                            // The redaction event has been sent to the server and the server has
915                            // received it. It's safe to cache the event
916                            // now to avoid any inconsistencies until the server
917                            // sends down the remote echo via the sync.
918                            if let Ok((room_event_cache, _drop_handles)) = room.event_cache().await
919                            {
920                                let content_field_redacts = room.version().is_some_and(|id| {
921                                    id.rules()
922                                        .is_some_and(|rules| rules.redaction.content_field_redacts)
923                                });
924                                let redacts = if content_field_redacts.not() {
925                                    format!("\"redacts\":\"{redacts}\",")
926                                } else {
927                                    "".to_owned()
928                                };
929                                let reason = reason.map_or_else(
930                                    || "".to_owned(),
931                                    |r| format!("\"reason\": \"{r}\""),
932                                );
933                                let content = if content_field_redacts {
934                                    format!("\"redacts\":\"{redacts}\",{reason}")
935                                } else {
936                                    reason
937                                };
938
939                                let timeline_event = match Raw::from_json_string(
940                                    // Create a compact string: remove all useless spaces.
941                                    format!(
942                                        "{{\
943                                            {redacts}\
944                                            \"event_id\":\"{event_id}\",\
945                                            \"origin_server_ts\":{ts},\
946                                            \"sender\":\"{sender}\",\
947                                            \"type\":\"{type}\",\
948                                            \"content\":{{{content}}}\
949                                        }}",
950                                        redacts = redacts,
951                                        event_id = event_id,
952                                        ts = MilliSecondsSinceUnixEpoch::now().get(),
953                                        sender = room.client().user_id().expect("Client must be logged-in"),
954                                        type = TimelineEventType::RoomRedaction,
955                                        content = content
956                                    ),
957                                ) {
958                                    Ok(event) => Some(TimelineEvent::from_plaintext(event)),
959                                    Err(err) => {
960                                        error!(
961                                            ?err,
962                                            "Failed to build the (sync) redaction event before the saving in the Event Cache"
963                                        );
964                                        None
965                                    }
966                                };
967
968                                // In case of an error, just log the error but don't stop the Send
969                                // Queue. This feature is not crucial.
970                                if let Some(timeline_event) = timeline_event
971                                    && let Err(err) = room_event_cache
972                                        .insert_sent_event_from_send_queue(timeline_event)
973                                        .await
974                                {
975                                    error!(
976                                        ?err,
977                                        "Failed to save the sent redaction event in the Event Cache"
978                                    );
979                                }
980                            } else {
981                                info!(
982                                    "Cannot insert the sent redaction event in the Event Cache because \
983                                    either the room no longer exists, or the Room Event Cache cannot be retrieved"
984                                );
985                            }
986                        }
987                    },
988
989                    Err(err) => {
990                        warn!("unable to mark queued request as sent: {err}");
991                    }
992                },
993
994                Ok((None, _)) => {
995                    debug!("Request has been aborted while running, continuing.");
996                }
997
998                Err(err) => {
999                    let is_recoverable = match err {
1000                        crate::Error::Http(ref http_err) => {
1001                            // All transient errors are recoverable.
1002                            matches!(
1003                                http_err.retry_kind(),
1004                                RetryKind::Transient { .. } | RetryKind::NetworkFailure
1005                            )
1006                        }
1007
1008                        // `ConcurrentRequestFailed` typically happens because of an HTTP failure;
1009                        // since we don't get the underlying error, be lax and consider it
1010                        // recoverable, and let observers decide to retry it or not. At some point
1011                        // we'll get the actual underlying error.
1012                        crate::Error::ConcurrentRequestFailed => true,
1013
1014                        // As of 2024-06-27, all other error types are considered unrecoverable.
1015                        _ => false,
1016                    };
1017
1018                    // Disable the queue for this room after any kind of error happened.
1019                    locally_enabled.store(false, Ordering::SeqCst);
1020
1021                    if is_recoverable {
1022                        warn!(txn_id = %txn_id, error = ?err, "Recoverable error when sending request: {err}, disabling send queue");
1023
1024                        // In this case, we intentionally keep the request in the queue, but mark it
1025                        // as not being sent anymore.
1026                        queue.mark_as_not_being_sent(&txn_id).await;
1027
1028                        // Let observers know about a failure *after* we've
1029                        // marked the item as not being sent anymore. Otherwise,
1030                        // there's a possible race where a caller might try to
1031                        // remove an item, while it's still marked as being
1032                        // sent, resulting in a cancellation failure.
1033                    } else {
1034                        warn!(txn_id = %txn_id, error = ?err, "Unrecoverable error when sending request: {err}");
1035
1036                        // Mark the request as wedged, so it's not picked at any future point;
1037                        // it will also block subsequent requests in the same room from being
1038                        // sent, until it's unwedged or removed, so as to preserve ordering.
1039                        if let Err(storage_error) =
1040                            queue.mark_as_wedged(&txn_id, QueueWedgeError::from(&err)).await
1041                        {
1042                            warn!("unable to mark request as wedged: {storage_error}");
1043                        }
1044                    }
1045
1046                    let error = Arc::new(err);
1047
1048                    let _ = global_error_sender.send(SendQueueRoomError {
1049                        room_id: room_id.to_owned(),
1050                        error: error.clone(),
1051                        is_recoverable,
1052                    });
1053
1054                    send_update(
1055                        &global_update_sender,
1056                        &update_sender,
1057                        room_id,
1058                        RoomSendQueueUpdate::SendError {
1059                            transaction_id: related_txn_id.unwrap_or(txn_id),
1060                            error,
1061                            is_recoverable,
1062                        },
1063                    );
1064                }
1065            }
1066        }
1067
1068        info!("exited sending task");
1069    }
1070
1071    /// Handles a single request and returns the [`SentRequestKey`] on success
1072    /// (unless the request was cancelled, in which case it'll return
1073    /// `None`).
1074    async fn handle_request(
1075        room: &Room,
1076        request: QueuedRequest,
1077        cancel_upload_rx: Option<oneshot::Receiver<()>>,
1078        progress: Option<SharedObservable<TransmissionProgress>>,
1079    ) -> Result<(Option<SentRequestKey>, Option<EncryptionInfo>), crate::Error> {
1080        match request.kind {
1081            QueuedRequestKind::Event { content } => {
1082                let (event, event_type) = content.into_raw();
1083
1084                let result = room
1085                    .send_raw(&event_type, &event)
1086                    .with_transaction_id(&request.transaction_id)
1087                    .with_request_config(RequestConfig::short_retry())
1088                    .await?;
1089
1090                trace!(txn_id = %request.transaction_id, event_id = %result.response.event_id, "event successfully sent");
1091
1092                Ok((
1093                    Some(SentRequestKey::Event {
1094                        event_id: result.response.event_id,
1095                        event,
1096                        event_type,
1097                    }),
1098                    result.encryption_info,
1099                ))
1100            }
1101
1102            QueuedRequestKind::MediaUpload {
1103                content_type,
1104                cache_key,
1105                thumbnail_source,
1106                related_to: relates_to,
1107                #[cfg(feature = "unstable-msc4274")]
1108                accumulated,
1109            } => {
1110                trace!(%relates_to, "uploading media related to event");
1111
1112                let fut = async move {
1113                    let data = room
1114                        .client()
1115                        .media_store()
1116                        .lock()
1117                        .await?
1118                        .get_media_content(&cache_key)
1119                        .await?
1120                        .ok_or(crate::Error::SendQueueWedgeError(Box::new(
1121                            QueueWedgeError::MissingMediaContent,
1122                        )))?;
1123
1124                    let mime = Mime::from_str(&content_type).map_err(|_| {
1125                        crate::Error::SendQueueWedgeError(Box::new(
1126                            QueueWedgeError::InvalidMimeType { mime_type: content_type.clone() },
1127                        ))
1128                    })?;
1129
1130                    #[cfg(feature = "e2e-encryption")]
1131                    let media_source = if room.latest_encryption_state().await?.is_encrypted() {
1132                        trace!("upload will be encrypted (encrypted room)");
1133
1134                        let mut cursor = std::io::Cursor::new(data);
1135                        let mut req = room
1136                            .client
1137                            .upload_encrypted_file(&mut cursor)
1138                            .with_request_config(RequestConfig::short_retry());
1139                        if let Some(progress) = progress {
1140                            req = req.with_send_progress_observable(progress);
1141                        }
1142                        let encrypted_file = req.await?;
1143
1144                        MediaSource::Encrypted(Box::new(encrypted_file))
1145                    } else {
1146                        trace!("upload will be in clear text (room without encryption)");
1147
1148                        let request_config = RequestConfig::short_retry()
1149                            .timeout(Media::reasonable_upload_timeout(&data));
1150                        let mut req =
1151                            room.client().media().upload(&mime, data, Some(request_config));
1152                        if let Some(progress) = progress {
1153                            req = req.with_send_progress_observable(progress);
1154                        }
1155                        let res = req.await?;
1156
1157                        MediaSource::Plain(res.content_uri)
1158                    };
1159
1160                    #[cfg(not(feature = "e2e-encryption"))]
1161                    let media_source = {
1162                        let request_config = RequestConfig::short_retry()
1163                            .timeout(Media::reasonable_upload_timeout(&data));
1164                        let mut req =
1165                            room.client().media().upload(&mime, data, Some(request_config));
1166                        if let Some(progress) = progress {
1167                            req = req.with_send_progress_observable(progress);
1168                        }
1169                        let res = req.await?;
1170                        MediaSource::Plain(res.content_uri)
1171                    };
1172
1173                    let uri = match &media_source {
1174                        MediaSource::Plain(uri) => uri,
1175                        MediaSource::Encrypted(encrypted_file) => &encrypted_file.url,
1176                    };
1177                    trace!(%relates_to, mxc_uri = %uri, "media successfully uploaded");
1178
1179                    Ok((
1180                        Some(SentRequestKey::Media(SentMediaInfo {
1181                            file: media_source,
1182                            thumbnail: thumbnail_source,
1183                            #[cfg(feature = "unstable-msc4274")]
1184                            accumulated,
1185                        })),
1186                        None,
1187                    ))
1188                };
1189
1190                let wait_for_cancel = async move {
1191                    if let Some(rx) = cancel_upload_rx {
1192                        rx.await
1193                    } else {
1194                        std::future::pending().await
1195                    }
1196                };
1197
1198                tokio::select! {
1199                    biased;
1200
1201                    _ = wait_for_cancel => {
1202                        Ok((None, None))
1203                    }
1204
1205                    res = fut => {
1206                        res
1207                    }
1208                }
1209            }
1210
1211            QueuedRequestKind::Redaction { redacts, reason } => {
1212                let result = room
1213                    .redact(&redacts, reason.as_deref(), Some(request.transaction_id.clone()))
1214                    .await?;
1215
1216                trace!(txn_id = %request.transaction_id, event_id = %result.event_id, "redaction successfully sent");
1217
1218                Ok((
1219                    Some(SentRequestKey::Redaction { event_id: result.event_id, redacts, reason }),
1220                    None,
1221                ))
1222            }
1223        }
1224    }
1225
1226    /// Returns whether the room is enabled, at the room level.
1227    pub fn is_enabled(&self) -> bool {
1228        self.inner.locally_enabled.load(Ordering::SeqCst)
1229    }
1230
1231    /// Set the locally enabled flag for this room queue.
1232    pub fn set_enabled(&self, enabled: bool) {
1233        self.inner.locally_enabled.store(enabled, Ordering::SeqCst);
1234
1235        // No need to wake a task to tell it it's been disabled, so only notify if we're
1236        // re-enabling the queue.
1237        if enabled {
1238            self.inner.notifier.notify_one();
1239        }
1240    }
1241
1242    /// Send an update on the room send queue channel, and on the global send
1243    /// queue channel, i.e. it sends a [`RoomSendQueueUpdate`] and a
1244    /// [`SendQueueUpdate`].
1245    fn send_update(&self, update: RoomSendQueueUpdate) {
1246        let _ = self.inner.update_sender.send(update.clone());
1247        let _ = self
1248            .inner
1249            .global_update_sender
1250            .send(SendQueueUpdate { room_id: self.inner.room.room_id().to_owned(), update });
1251    }
1252}
1253
1254fn send_update(
1255    global_update_sender: &broadcast::Sender<SendQueueUpdate>,
1256    update_sender: &broadcast::Sender<RoomSendQueueUpdate>,
1257    room_id: &RoomId,
1258    update: RoomSendQueueUpdate,
1259) {
1260    let _ = update_sender.send(update.clone());
1261    let _ = global_update_sender.send(SendQueueUpdate { room_id: room_id.to_owned(), update });
1262}
1263
1264impl From<&crate::Error> for QueueWedgeError {
1265    fn from(value: &crate::Error) -> Self {
1266        match value {
1267            #[cfg(feature = "e2e-encryption")]
1268            crate::Error::OlmError(error) => match &**error {
1269                OlmError::SessionRecipientCollectionError(error) => match error {
1270                    SessionRecipientCollectionError::VerifiedUserHasUnsignedDevice(user_map) => {
1271                        QueueWedgeError::InsecureDevices { user_device_map: user_map.clone() }
1272                    }
1273
1274                    SessionRecipientCollectionError::VerifiedUserChangedIdentity(users) => {
1275                        QueueWedgeError::IdentityViolations { users: users.clone() }
1276                    }
1277
1278                    SessionRecipientCollectionError::CrossSigningNotSetup
1279                    | SessionRecipientCollectionError::SendingFromUnverifiedDevice => {
1280                        QueueWedgeError::CrossVerificationRequired
1281                    }
1282                },
1283                _ => QueueWedgeError::GenericApiError { msg: value.to_string() },
1284            },
1285
1286            // Flatten errors of `Self` type.
1287            crate::Error::SendQueueWedgeError(error) => *error.clone(),
1288
1289            _ => QueueWedgeError::GenericApiError { msg: value.to_string() },
1290        }
1291    }
1292}
1293
1294struct RoomSendQueueInner {
1295    /// The room which this send queue relates to.
1296    room: WeakRoom,
1297
1298    /// Global sender to send [`SendQueueUpdate`].
1299    ///
1300    /// See [`SendQueue::subscribe`].
1301    global_update_sender: broadcast::Sender<SendQueueUpdate>,
1302
1303    /// Broadcaster for notifications about the statuses of requests to be sent.
1304    ///
1305    /// Can be subscribed to from the outside.
1306    ///
1307    /// See [`RoomSendQueue::subscribe`].
1308    update_sender: broadcast::Sender<RoomSendQueueUpdate>,
1309
1310    /// Queue of requests that are either to be sent, or being sent.
1311    ///
1312    /// When a request has been sent to the server, it is removed from that
1313    /// queue *after* being sent. That way, we will retry sending upon
1314    /// failure, in the same order requests have been inserted in the first
1315    /// place.
1316    queue: QueueStorage,
1317
1318    /// A notifier that's updated any time common data is touched (stopped or
1319    /// enabled statuses), or the associated room [`QueueStorage`].
1320    notifier: Arc<Notify>,
1321
1322    /// Should the room process new requests or not (because e.g. it might be
1323    /// running off the network)?
1324    locally_enabled: Arc<AtomicBool>,
1325
1326    /// Handle to the actual sending task. Unused, but kept alive along this
1327    /// data structure.
1328    _task: BackgroundTaskHandle,
1329}
1330
1331/// Information about a request being sent right this moment.
1332struct BeingSentInfo {
1333    /// Transaction id of the thing being sent.
1334    transaction_id: OwnedTransactionId,
1335
1336    /// For an upload request, a trigger to cancel the upload before it
1337    /// completes.
1338    cancel_upload: Option<oneshot::Sender<()>>,
1339}
1340
1341impl BeingSentInfo {
1342    /// Aborts the upload, if a trigger is available.
1343    ///
1344    /// Consumes the object because the sender is a oneshot and will be consumed
1345    /// upon sending.
1346    fn cancel_upload(self) -> bool {
1347        if let Some(cancel_upload) = self.cancel_upload {
1348            let _ = cancel_upload.send(());
1349            true
1350        } else {
1351            false
1352        }
1353    }
1354}
1355
1356/// A specialized lock that guards both against the state store and the
1357/// [`Self::being_sent`] data.
1358#[derive(Clone)]
1359struct StoreLock {
1360    /// Reference to the client, to get access to the underlying store.
1361    client: WeakClient,
1362
1363    /// The one queued request that is being sent at the moment, along with
1364    /// associated data that can be useful to act upon it.
1365    ///
1366    /// Also used as the lock to access the state store.
1367    being_sent: Arc<Mutex<Option<BeingSentInfo>>>,
1368}
1369
1370impl StoreLock {
1371    /// Gets a hold of the locked store and [`Self::being_sent`] pair.
1372    async fn lock(&self) -> StoreLockGuard {
1373        StoreLockGuard {
1374            client: self.client.clone(),
1375            being_sent: self.being_sent.clone().lock_owned().await,
1376        }
1377    }
1378}
1379
1380/// A lock guard obtained through locking with [`StoreLock`].
1381/// `being_sent` data.
1382struct StoreLockGuard {
1383    /// Reference to the client, to get access to the underlying store.
1384    client: WeakClient,
1385
1386    /// The one queued request that is being sent at the moment, along with
1387    /// associated data that can be useful to act upon it.
1388    being_sent: OwnedMutexGuard<Option<BeingSentInfo>>,
1389}
1390
1391impl StoreLockGuard {
1392    /// Get a client from the locked state, useful to get a handle on a store.
1393    fn client(&self) -> Result<Client, RoomSendQueueStorageError> {
1394        self.client.get().ok_or(RoomSendQueueStorageError::ClientShuttingDown)
1395    }
1396}
1397
1398#[derive(Clone)]
1399struct QueueStorage {
1400    /// A lock to make sure the state store is only accessed once at a time, to
1401    /// make some store operations atomic.
1402    store: StoreLock,
1403
1404    /// To which room is this storage related.
1405    room_id: OwnedRoomId,
1406
1407    /// In-memory mapping of media transaction IDs to thumbnail sizes for the
1408    /// purpose of progress reporting.
1409    ///
1410    /// The keys are the transaction IDs for sending the media or gallery event
1411    /// after all uploads have finished. This allows us to easily clean up the
1412    /// cache after the event was sent.
1413    ///
1414    /// For media uploads, the value vector will always have a single element.
1415    ///
1416    /// For galleries, some gallery items might not have a thumbnail while
1417    /// others do. Since we access the thumbnails by their index within the
1418    /// gallery, the vector needs to hold optional usize's.
1419    thumbnail_file_sizes: Arc<SyncMutex<HashMap<OwnedTransactionId, Vec<Option<usize>>>>>,
1420}
1421
1422impl QueueStorage {
1423    /// Default priority for a queued request.
1424    const LOW_PRIORITY: usize = 0;
1425
1426    /// High priority for a queued request that must be handled before others.
1427    const HIGH_PRIORITY: usize = 10;
1428
1429    /// Create a new queue for queuing requests to be sent later.
1430    fn new(client: WeakClient, room: OwnedRoomId) -> Self {
1431        Self {
1432            room_id: room,
1433            store: StoreLock { client, being_sent: Default::default() },
1434            thumbnail_file_sizes: Default::default(),
1435        }
1436    }
1437
1438    /// Push a new event to be sent in the queue, with a default priority of 0.
1439    ///
1440    /// Returns the transaction id chosen to identify the request.
1441    async fn push(
1442        &self,
1443        request: QueuedRequestKind,
1444        created_at: MilliSecondsSinceUnixEpoch,
1445    ) -> Result<OwnedTransactionId, RoomSendQueueStorageError> {
1446        let transaction_id = TransactionId::new();
1447
1448        self.store
1449            .lock()
1450            .await
1451            .client()?
1452            .state_store()
1453            .save_send_queue_request(
1454                &self.room_id,
1455                transaction_id.clone(),
1456                created_at,
1457                request,
1458                Self::LOW_PRIORITY,
1459            )
1460            .await?;
1461
1462        Ok(transaction_id)
1463    }
1464
1465    /// Peeks the next request to be sent, marking it as being sent.
1466    ///
1467    /// It is required to call [`Self::mark_as_sent`] after it's been
1468    /// effectively sent.
1469    async fn peek_next_to_send(
1470        &self,
1471    ) -> Result<Option<(QueuedRequest, Option<oneshot::Receiver<()>>)>, RoomSendQueueStorageError>
1472    {
1473        let mut guard = self.store.lock().await;
1474        let queued_requests =
1475            guard.client()?.state_store().load_send_queue_requests(&self.room_id).await?;
1476
1477        // Only ever consider the head of the queue: requests must be sent in the
1478        // order they were queued, so a wedged request (which failed to be sent with an
1479        // unrecoverable error) blocks all the requests queued after it. Otherwise,
1480        // messages would be sent out of order, until the wedged request is either
1481        // manually unwedged or removed (both of which will wake up the sending task).
1482        if let Some(request) = queued_requests.first().filter(|queued| !queued.is_wedged()) {
1483            let (cancel_upload_tx, cancel_upload_rx) =
1484                if matches!(request.kind, QueuedRequestKind::MediaUpload { .. }) {
1485                    let (tx, rx) = oneshot::channel();
1486                    (Some(tx), Some(rx))
1487                } else {
1488                    Default::default()
1489                };
1490
1491            let prev = guard.being_sent.replace(BeingSentInfo {
1492                transaction_id: request.transaction_id.clone(),
1493                cancel_upload: cancel_upload_tx,
1494            });
1495
1496            if let Some(prev) = prev {
1497                error!(
1498                    prev_txn = ?prev.transaction_id,
1499                    "a previous request was still active while picking a new one"
1500                );
1501            }
1502
1503            Ok(Some((request.clone(), cancel_upload_rx)))
1504        } else {
1505            Ok(None)
1506        }
1507    }
1508
1509    /// Marks a request popped with [`Self::peek_next_to_send`] and identified
1510    /// with the given transaction id as not being sent anymore, so it can
1511    /// be removed from the queue later.
1512    async fn mark_as_not_being_sent(&self, transaction_id: &TransactionId) {
1513        let was_being_sent = self.store.lock().await.being_sent.take();
1514
1515        let prev_txn = was_being_sent.as_ref().map(|info| info.transaction_id.as_ref());
1516        if prev_txn != Some(transaction_id) {
1517            error!(prev_txn = ?prev_txn, "previous active request didn't match that we expect (after transient error)");
1518        }
1519    }
1520
1521    /// Marks a request popped with [`Self::peek_next_to_send`] and identified
1522    /// with the given transaction id as being wedged (and not being sent
1523    /// anymore), so it can be removed from the queue later.
1524    async fn mark_as_wedged(
1525        &self,
1526        transaction_id: &TransactionId,
1527        reason: QueueWedgeError,
1528    ) -> Result<(), RoomSendQueueStorageError> {
1529        // Keep the lock until we're done touching the storage.
1530        let mut guard = self.store.lock().await;
1531        let was_being_sent = guard.being_sent.take();
1532
1533        let prev_txn = was_being_sent.as_ref().map(|info| info.transaction_id.as_ref());
1534        if prev_txn != Some(transaction_id) {
1535            error!(
1536                ?prev_txn,
1537                "previous active request didn't match that we expect (after permanent error)",
1538            );
1539        }
1540
1541        Ok(guard
1542            .client()?
1543            .state_store()
1544            .update_send_queue_request_status(&self.room_id, transaction_id, Some(reason))
1545            .await?)
1546    }
1547
1548    /// Marks a request identified with the given transaction id as being now
1549    /// unwedged and adds it back to the queue.
1550    async fn mark_as_unwedged(
1551        &self,
1552        transaction_id: &TransactionId,
1553    ) -> Result<(), RoomSendQueueStorageError> {
1554        Ok(self
1555            .store
1556            .lock()
1557            .await
1558            .client()?
1559            .state_store()
1560            .update_send_queue_request_status(&self.room_id, transaction_id, None)
1561            .await?)
1562    }
1563
1564    /// Marks a request pushed with [`Self::push`] and identified with the given
1565    /// transaction id as sent, by removing it from the local queue.
1566    async fn mark_as_sent(
1567        &self,
1568        transaction_id: &TransactionId,
1569        parent_key: SentRequestKey,
1570    ) -> Result<(), RoomSendQueueStorageError> {
1571        // Keep the lock until we're done touching the storage.
1572        let mut guard = self.store.lock().await;
1573        let was_being_sent = guard.being_sent.take();
1574
1575        let prev_txn = was_being_sent.as_ref().map(|info| info.transaction_id.as_ref());
1576        if prev_txn != Some(transaction_id) {
1577            error!(
1578                ?prev_txn,
1579                "previous active request didn't match that we expect (after successful send)",
1580            );
1581        }
1582
1583        let client = guard.client()?;
1584        let store = client.state_store();
1585
1586        // Update all dependent requests.
1587        store
1588            .mark_dependent_queued_requests_as_ready(&self.room_id, transaction_id, parent_key)
1589            .await?;
1590
1591        let removed = store.remove_send_queue_request(&self.room_id, transaction_id).await?;
1592
1593        if !removed {
1594            warn!(txn_id = %transaction_id, "request marked as sent was missing from storage");
1595        }
1596
1597        self.thumbnail_file_sizes.lock().remove(transaction_id);
1598
1599        Ok(())
1600    }
1601
1602    /// Cancel a sending command for an event that has been sent with
1603    /// [`Self::push`] with the given transaction id.
1604    ///
1605    /// Returns whether the given transaction has been effectively removed. If
1606    /// false, this either means that the transaction id was unrelated to
1607    /// this queue, or that the request was sent before we cancelled it.
1608    async fn cancel_event(
1609        &self,
1610        transaction_id: &TransactionId,
1611        reason: Option<String>,
1612    ) -> Result<bool, RoomSendQueueStorageError> {
1613        let guard = self.store.lock().await;
1614
1615        if guard.being_sent.as_ref().map(|info| info.transaction_id.as_ref())
1616            == Some(transaction_id)
1617        {
1618            // Save the intent to redact the event.
1619            guard
1620                .client()?
1621                .state_store()
1622                .save_dependent_queued_request(
1623                    &self.room_id,
1624                    transaction_id,
1625                    ChildTransactionId::new(),
1626                    MilliSecondsSinceUnixEpoch::now(),
1627                    DependentQueuedRequestKind::RedactEventWithReason { reason },
1628                )
1629                .await?;
1630
1631            return Ok(true);
1632        }
1633
1634        let removed = guard
1635            .client()?
1636            .state_store()
1637            .remove_send_queue_request(&self.room_id, transaction_id)
1638            .await?;
1639
1640        self.thumbnail_file_sizes.lock().remove(transaction_id);
1641
1642        Ok(removed)
1643    }
1644
1645    /// Replace an event that has been sent with [`Self::push`] with the given
1646    /// transaction id, before it's been actually sent.
1647    ///
1648    /// Returns whether the given transaction has been effectively edited. If
1649    /// false, this either means that the transaction id was unrelated to
1650    /// this queue, or that the request was sent before we edited it.
1651    async fn replace_event(
1652        &self,
1653        transaction_id: &TransactionId,
1654        serializable: SerializableEventContent,
1655    ) -> Result<bool, RoomSendQueueStorageError> {
1656        let guard = self.store.lock().await;
1657
1658        if guard.being_sent.as_ref().map(|info| info.transaction_id.as_ref())
1659            == Some(transaction_id)
1660        {
1661            // Save the intent to edit the associated event.
1662            guard
1663                .client()?
1664                .state_store()
1665                .save_dependent_queued_request(
1666                    &self.room_id,
1667                    transaction_id,
1668                    ChildTransactionId::new(),
1669                    MilliSecondsSinceUnixEpoch::now(),
1670                    DependentQueuedRequestKind::EditEvent { new_content: serializable },
1671                )
1672                .await?;
1673
1674            return Ok(true);
1675        }
1676
1677        let edited = guard
1678            .client()?
1679            .state_store()
1680            .update_send_queue_request(&self.room_id, transaction_id, serializable.into())
1681            .await?;
1682
1683        Ok(edited)
1684    }
1685
1686    /// Push requests (and dependents) to upload a media.
1687    ///
1688    /// See the module-level description for details of the whole processus.
1689    #[allow(clippy::too_many_arguments)]
1690    async fn push_media(
1691        &self,
1692        event: RoomMessageEventContent,
1693        content_type: Mime,
1694        send_event_txn: OwnedTransactionId,
1695        created_at: MilliSecondsSinceUnixEpoch,
1696        upload_file_txn: OwnedTransactionId,
1697        file_media_request: MediaRequestParameters,
1698        thumbnail: Option<QueueThumbnailInfo>,
1699        extra_content: Option<serde_json::Map<String, serde_json::Value>>,
1700    ) -> Result<(), RoomSendQueueStorageError> {
1701        let guard = self.store.lock().await;
1702        let client = guard.client()?;
1703        let store = client.state_store();
1704
1705        // There's only a single media to be sent, so it has at most one thumbnail.
1706        let thumbnail_file_sizes = vec![thumbnail.as_ref().map(|t| t.file_size)];
1707
1708        let thumbnail_info = self
1709            .push_thumbnail_and_media_uploads(
1710                store,
1711                &content_type,
1712                send_event_txn.clone(),
1713                created_at,
1714                upload_file_txn.clone(),
1715                file_media_request,
1716                thumbnail,
1717            )
1718            .await?;
1719
1720        // Push the dependent request for the event itself.
1721        store
1722            .save_dependent_queued_request(
1723                &self.room_id,
1724                &upload_file_txn,
1725                send_event_txn.clone().into(),
1726                created_at,
1727                DependentQueuedRequestKind::FinishUpload {
1728                    local_echo: Box::new(event),
1729                    file_upload: upload_file_txn.clone(),
1730                    thumbnail_info,
1731                    extra_content,
1732                },
1733            )
1734            .await?;
1735
1736        self.thumbnail_file_sizes.lock().insert(send_event_txn, thumbnail_file_sizes);
1737
1738        Ok(())
1739    }
1740
1741    /// Push requests (and dependents) to upload a gallery.
1742    ///
1743    /// See the module-level description for details of the whole processus.
1744    #[cfg(feature = "unstable-msc4274")]
1745    #[allow(clippy::too_many_arguments)]
1746    async fn push_gallery(
1747        &self,
1748        event: RoomMessageEventContent,
1749        send_event_txn: OwnedTransactionId,
1750        created_at: MilliSecondsSinceUnixEpoch,
1751        item_queue_infos: Vec<GalleryItemQueueInfo>,
1752    ) -> Result<(), RoomSendQueueStorageError> {
1753        let guard = self.store.lock().await;
1754        let client = guard.client()?;
1755        let store = client.state_store();
1756
1757        let mut finish_item_infos = Vec::with_capacity(item_queue_infos.len());
1758        let mut thumbnail_file_sizes = Vec::with_capacity(item_queue_infos.len());
1759
1760        let Some((first, rest)) = item_queue_infos.split_first() else {
1761            return Ok(());
1762        };
1763
1764        let GalleryItemQueueInfo { content_type, upload_file_txn, file_media_request, thumbnail } =
1765            first;
1766
1767        let thumbnail_info = self
1768            .push_thumbnail_and_media_uploads(
1769                store,
1770                content_type,
1771                send_event_txn.clone(),
1772                created_at,
1773                upload_file_txn.clone(),
1774                file_media_request.clone(),
1775                thumbnail.clone(),
1776            )
1777            .await?;
1778
1779        finish_item_infos
1780            .push(FinishGalleryItemInfo { file_upload: upload_file_txn.clone(), thumbnail_info });
1781        thumbnail_file_sizes.push(thumbnail.as_ref().map(|t| t.file_size));
1782
1783        let mut last_upload_file_txn = upload_file_txn.clone();
1784
1785        for item_queue_info in rest {
1786            let GalleryItemQueueInfo {
1787                content_type,
1788                upload_file_txn,
1789                file_media_request,
1790                thumbnail,
1791            } = item_queue_info;
1792
1793            let thumbnail_info = if let Some(QueueThumbnailInfo {
1794                finish_upload_thumbnail_info: thumbnail_info,
1795                media_request_parameters: thumbnail_media_request,
1796                content_type: thumbnail_content_type,
1797                ..
1798            }) = thumbnail
1799            {
1800                let upload_thumbnail_txn = thumbnail_info.txn.clone();
1801
1802                // Save the thumbnail upload request as a dependent request of the last file
1803                // upload.
1804                store
1805                    .save_dependent_queued_request(
1806                        &self.room_id,
1807                        &last_upload_file_txn,
1808                        upload_thumbnail_txn.clone().into(),
1809                        created_at,
1810                        DependentQueuedRequestKind::UploadFileOrThumbnail {
1811                            content_type: thumbnail_content_type.to_string(),
1812                            cache_key: thumbnail_media_request.clone(),
1813                            related_to: send_event_txn.clone(),
1814                            parent_is_thumbnail_upload: false,
1815                        },
1816                    )
1817                    .await?;
1818
1819                last_upload_file_txn = upload_thumbnail_txn;
1820
1821                Some(thumbnail_info)
1822            } else {
1823                None
1824            };
1825
1826            // Save the file upload as a dependent request of the previous upload.
1827            store
1828                .save_dependent_queued_request(
1829                    &self.room_id,
1830                    &last_upload_file_txn,
1831                    upload_file_txn.clone().into(),
1832                    created_at,
1833                    DependentQueuedRequestKind::UploadFileOrThumbnail {
1834                        content_type: content_type.to_string(),
1835                        cache_key: file_media_request.clone(),
1836                        related_to: send_event_txn.clone(),
1837                        parent_is_thumbnail_upload: thumbnail.is_some(),
1838                    },
1839                )
1840                .await?;
1841
1842            finish_item_infos.push(FinishGalleryItemInfo {
1843                file_upload: upload_file_txn.clone(),
1844                thumbnail_info: thumbnail_info.cloned(),
1845            });
1846            thumbnail_file_sizes.push(thumbnail.as_ref().map(|t| t.file_size));
1847
1848            last_upload_file_txn = upload_file_txn.clone();
1849        }
1850
1851        // Push the request for the event itself as a dependent request of the last file
1852        // upload.
1853        store
1854            .save_dependent_queued_request(
1855                &self.room_id,
1856                &last_upload_file_txn,
1857                send_event_txn.clone().into(),
1858                created_at,
1859                DependentQueuedRequestKind::FinishGallery {
1860                    local_echo: Box::new(event),
1861                    item_infos: finish_item_infos,
1862                },
1863            )
1864            .await?;
1865
1866        self.thumbnail_file_sizes.lock().insert(send_event_txn, thumbnail_file_sizes);
1867
1868        Ok(())
1869    }
1870
1871    /// If a thumbnail exists, pushes a [`QueuedRequestKind::MediaUpload`] to
1872    /// upload it
1873    /// and a [`DependentQueuedRequestKind::UploadFileOrThumbnail`] to upload
1874    /// the media itself. Otherwise, pushes a
1875    /// [`QueuedRequestKind::MediaUpload`] to upload the media directly.
1876    #[allow(clippy::too_many_arguments)]
1877    async fn push_thumbnail_and_media_uploads(
1878        &self,
1879        store: &DynStateStore,
1880        content_type: &Mime,
1881        send_event_txn: OwnedTransactionId,
1882        created_at: MilliSecondsSinceUnixEpoch,
1883        upload_file_txn: OwnedTransactionId,
1884        file_media_request: MediaRequestParameters,
1885        thumbnail: Option<QueueThumbnailInfo>,
1886    ) -> Result<Option<FinishUploadThumbnailInfo>, RoomSendQueueStorageError> {
1887        if let Some(QueueThumbnailInfo {
1888            finish_upload_thumbnail_info: thumbnail_info,
1889            media_request_parameters: thumbnail_media_request,
1890            content_type: thumbnail_content_type,
1891            ..
1892        }) = thumbnail
1893        {
1894            let upload_thumbnail_txn = thumbnail_info.txn.clone();
1895
1896            // Save the thumbnail upload request.
1897            store
1898                .save_send_queue_request(
1899                    &self.room_id,
1900                    upload_thumbnail_txn.clone(),
1901                    created_at,
1902                    QueuedRequestKind::MediaUpload {
1903                        content_type: thumbnail_content_type.to_string(),
1904                        cache_key: thumbnail_media_request,
1905                        thumbnail_source: None, // the thumbnail has no thumbnails :)
1906                        related_to: send_event_txn.clone(),
1907                        #[cfg(feature = "unstable-msc4274")]
1908                        accumulated: vec![],
1909                    },
1910                    Self::LOW_PRIORITY,
1911                )
1912                .await?;
1913
1914            // Save the file upload request as a dependent request of the thumbnail upload.
1915            store
1916                .save_dependent_queued_request(
1917                    &self.room_id,
1918                    &upload_thumbnail_txn,
1919                    upload_file_txn.into(),
1920                    created_at,
1921                    DependentQueuedRequestKind::UploadFileOrThumbnail {
1922                        content_type: content_type.to_string(),
1923                        cache_key: file_media_request,
1924                        related_to: send_event_txn,
1925                        parent_is_thumbnail_upload: true,
1926                    },
1927                )
1928                .await?;
1929
1930            Ok(Some(thumbnail_info))
1931        } else {
1932            // Save the file upload as its own request, not a dependent one.
1933            store
1934                .save_send_queue_request(
1935                    &self.room_id,
1936                    upload_file_txn,
1937                    created_at,
1938                    QueuedRequestKind::MediaUpload {
1939                        content_type: content_type.to_string(),
1940                        cache_key: file_media_request,
1941                        thumbnail_source: None,
1942                        related_to: send_event_txn,
1943                        #[cfg(feature = "unstable-msc4274")]
1944                        accumulated: vec![],
1945                    },
1946                    Self::LOW_PRIORITY,
1947                )
1948                .await?;
1949
1950            Ok(None)
1951        }
1952    }
1953
1954    /// Reacts to the given local echo of an event.
1955    #[instrument(skip(self))]
1956    async fn react(
1957        &self,
1958        transaction_id: &TransactionId,
1959        key: String,
1960        created_at: MilliSecondsSinceUnixEpoch,
1961    ) -> Result<Option<ChildTransactionId>, RoomSendQueueStorageError> {
1962        let guard = self.store.lock().await;
1963        let client = guard.client()?;
1964        let store = client.state_store();
1965
1966        let requests = store.load_send_queue_requests(&self.room_id).await?;
1967
1968        // If the target event has been already sent, abort immediately.
1969        if !requests.iter().any(|item| item.transaction_id == transaction_id) {
1970            // We didn't find it as a queued request; try to find it as a dependent queued
1971            // request.
1972            let dependent_requests = store.load_dependent_queued_requests(&self.room_id).await?;
1973            if !dependent_requests
1974                .into_iter()
1975                .filter_map(|item| item.is_own_event().then_some(item.own_transaction_id))
1976                .any(|child_txn| *child_txn == *transaction_id)
1977            {
1978                // We didn't find it as either a request or a dependent request, abort.
1979                return Ok(None);
1980            }
1981        }
1982
1983        // Record the dependent request.
1984        let reaction_txn_id = ChildTransactionId::new();
1985        store
1986            .save_dependent_queued_request(
1987                &self.room_id,
1988                transaction_id,
1989                reaction_txn_id.clone(),
1990                created_at,
1991                DependentQueuedRequestKind::ReactEvent { key },
1992            )
1993            .await?;
1994
1995        Ok(Some(reaction_txn_id))
1996    }
1997
1998    /// Returns a list of the local echoes, that is, all the requests that we're
1999    /// about to send but that haven't been sent yet (or are being sent).
2000    async fn local_echoes(
2001        &self,
2002        room: &RoomSendQueue,
2003    ) -> Result<Vec<LocalEcho>, RoomSendQueueStorageError> {
2004        let guard = self.store.lock().await;
2005        let client = guard.client()?;
2006        let store = client.state_store();
2007
2008        let queued_requests = store.load_send_queue_requests(&self.room_id).await?;
2009
2010        // Media upload requests aren't returned as echoes themselves (the media event,
2011        // represented as a dependent request, is), so carry their send errors over to
2012        // the dependent request's echo: a wedged upload wedges the media event.
2013        let mut media_upload_errors: HashMap<OwnedTransactionId, QueueWedgeError> = queued_requests
2014            .iter()
2015            .filter_map(|queued| match queued.kind {
2016                QueuedRequestKind::MediaUpload { .. } => {
2017                    queued.error.clone().map(|error| (queued.transaction_id.clone(), error))
2018                }
2019                _ => None,
2020            })
2021            .collect();
2022
2023        let local_requests = queued_requests.into_iter().filter_map(|queued| {
2024            Some(LocalEcho {
2025                transaction_id: queued.transaction_id.clone(),
2026                content: match queued.kind {
2027                    QueuedRequestKind::Event { content } => LocalEchoContent::Event {
2028                        serialized_event: content,
2029                        send_handle: SendHandle {
2030                            room: room.clone(),
2031                            transaction_id: queued.transaction_id,
2032                            media_handles: vec![],
2033                            created_at: queued.created_at,
2034                        },
2035                        send_error: queued.error,
2036                    },
2037
2038                    QueuedRequestKind::MediaUpload { .. } => {
2039                        // Don't return uploaded medias as their own things; the accompanying
2040                        // event represented as a dependent request should be sufficient.
2041                        return None;
2042                    }
2043
2044                    QueuedRequestKind::Redaction { redacts, reason } => {
2045                        LocalEchoContent::Redaction {
2046                            redacts,
2047                            reason,
2048                            send_handle: SendRedactionHandle {
2049                                room: room.clone(),
2050                                transaction_id: queued.transaction_id,
2051                            },
2052                            send_error: queued.error,
2053                        }
2054                    }
2055                },
2056            })
2057        });
2058
2059        let dependent_requests = store.load_dependent_queued_requests(&self.room_id).await?;
2060
2061        let reactions_and_medias =
2062            dependent_requests.into_iter().filter_map(|dep| match dep.kind {
2063                DependentQueuedRequestKind::EditEvent { .. }
2064                | DependentQueuedRequestKind::RedactEvent
2065                | DependentQueuedRequestKind::RedactEventWithReason { .. } => {
2066                    // TODO: reflect local edits/redacts too?
2067                    None
2068                }
2069
2070                DependentQueuedRequestKind::ReactEvent { key } => Some(LocalEcho {
2071                    transaction_id: dep.own_transaction_id.clone().into(),
2072                    content: LocalEchoContent::React {
2073                        key,
2074                        send_handle: SendReactionHandle {
2075                            room: room.clone(),
2076                            transaction_id: dep.own_transaction_id,
2077                        },
2078                        applies_to: dep.parent_transaction_id,
2079                    },
2080                }),
2081
2082                DependentQueuedRequestKind::UploadFileOrThumbnail { .. } => {
2083                    // Don't reflect these: only the associated event is interesting to observers.
2084                    None
2085                }
2086
2087                DependentQueuedRequestKind::FinishUpload {
2088                    local_echo,
2089                    file_upload,
2090                    thumbnail_info,
2091                    extra_content,
2092                } => {
2093                    let upload_thumbnail_txn = thumbnail_info.map(|info| info.txn);
2094
2095                    // If one of the uploads wedged, the media event is wedged too.
2096                    let send_error = media_upload_errors.remove(&file_upload).or_else(|| {
2097                        upload_thumbnail_txn
2098                            .as_ref()
2099                            .and_then(|txn| media_upload_errors.remove(&**txn))
2100                    });
2101
2102                    // Materialize as an event local echo.
2103                    Some(LocalEcho {
2104                        transaction_id: dep.own_transaction_id.clone().into(),
2105                        content: LocalEchoContent::Event {
2106                            serialized_event: upload::merge_extra_content(
2107                                SerializableEventContent::new(&(*local_echo).into()).ok()?,
2108                                extra_content,
2109                            )
2110                            .ok()?,
2111                            send_handle: SendHandle {
2112                                room: room.clone(),
2113                                transaction_id: dep.own_transaction_id.into(),
2114                                media_handles: vec![MediaHandles {
2115                                    upload_thumbnail_txn,
2116                                    upload_file_txn: file_upload,
2117                                }],
2118                                created_at: dep.created_at,
2119                            },
2120                            send_error,
2121                        },
2122                    })
2123                }
2124
2125                #[cfg(feature = "unstable-msc4274")]
2126                DependentQueuedRequestKind::FinishGallery { local_echo, item_infos } => {
2127                    // Materialize as an event local echo.
2128                    self.create_gallery_local_echo(
2129                        dep.own_transaction_id,
2130                        room,
2131                        dep.created_at,
2132                        local_echo,
2133                        item_infos,
2134                        &mut media_upload_errors,
2135                    )
2136                }
2137            });
2138
2139        Ok(local_requests.chain(reactions_and_medias).collect())
2140    }
2141
2142    /// Create a local echo for a gallery event.
2143    #[cfg(feature = "unstable-msc4274")]
2144    fn create_gallery_local_echo(
2145        &self,
2146        transaction_id: ChildTransactionId,
2147        room: &RoomSendQueue,
2148        created_at: MilliSecondsSinceUnixEpoch,
2149        local_echo: Box<RoomMessageEventContent>,
2150        item_infos: Vec<FinishGalleryItemInfo>,
2151        media_upload_errors: &mut HashMap<OwnedTransactionId, QueueWedgeError>,
2152    ) -> Option<LocalEcho> {
2153        // If any of the uploads wedged, the gallery event is wedged too.
2154        let send_error = item_infos.iter().find_map(|i| {
2155            media_upload_errors.remove(&i.file_upload).or_else(|| {
2156                i.thumbnail_info.as_ref().and_then(|info| media_upload_errors.remove(&*info.txn))
2157            })
2158        });
2159
2160        Some(LocalEcho {
2161            transaction_id: transaction_id.clone().into(),
2162            content: LocalEchoContent::Event {
2163                serialized_event: SerializableEventContent::new(&(*local_echo).into()).ok()?,
2164                send_handle: SendHandle {
2165                    room: room.clone(),
2166                    transaction_id: transaction_id.into(),
2167                    media_handles: item_infos
2168                        .into_iter()
2169                        .map(|i| MediaHandles {
2170                            upload_thumbnail_txn: i.thumbnail_info.map(|info| info.txn),
2171                            upload_file_txn: i.file_upload,
2172                        })
2173                        .collect(),
2174                    created_at,
2175                },
2176                send_error,
2177            },
2178        })
2179    }
2180
2181    /// Try to apply a single dependent request, whether it's local or remote.
2182    ///
2183    /// This swallows errors that would retrigger every time if we retried
2184    /// applying the dependent request: invalid edit content, etc.
2185    ///
2186    /// Returns true if the dependent request has been sent (or should not be
2187    /// retried later).
2188    #[instrument(skip_all)]
2189    async fn try_apply_single_dependent_request(
2190        &self,
2191        client: &Client,
2192        dependent_request: DependentQueuedRequest,
2193        new_updates: &mut Vec<RoomSendQueueUpdate>,
2194    ) -> Result<bool, RoomSendQueueError> {
2195        let store = client.state_store();
2196
2197        let parent_key = dependent_request.parent_key;
2198
2199        match dependent_request.kind {
2200            DependentQueuedRequestKind::EditEvent { new_content } => {
2201                if let Some(parent_key) = parent_key {
2202                    let Some(event_id) = parent_key.into_event_id() else {
2203                        return Err(RoomSendQueueError::StorageError(
2204                            RoomSendQueueStorageError::InvalidParentKey,
2205                        ));
2206                    };
2207
2208                    // The parent event has been sent, so send an edit event.
2209                    let room = client
2210                        .get_room(&self.room_id)
2211                        .ok_or(RoomSendQueueError::RoomDisappeared)?;
2212
2213                    // Check the event is one we know how to edit with an edit event.
2214
2215                    // It must be deserializable…
2216                    let edited_content = match new_content.deserialize() {
2217                        Ok(AnyMessageLikeEventContent::RoomMessage(c)) => {
2218                            // Assume no relationships.
2219                            EditedContent::RoomMessage(c.into())
2220                        }
2221
2222                        Ok(AnyMessageLikeEventContent::UnstablePollStart(c)) => {
2223                            let poll_start = c.poll_start().clone();
2224                            EditedContent::PollStart {
2225                                fallback_text: poll_start.question.text.clone(),
2226                                new_content: poll_start,
2227                            }
2228                        }
2229
2230                        Ok(c) => {
2231                            warn!("Unsupported edit content type: {:?}", c.event_type());
2232                            return Ok(true);
2233                        }
2234
2235                        Err(err) => {
2236                            warn!("Unable to deserialize: {err}");
2237                            return Ok(true);
2238                        }
2239                    };
2240
2241                    let edit_event = match room.make_edit_event(&event_id, edited_content).await {
2242                        Ok(e) => e,
2243                        Err(err) => {
2244                            warn!("couldn't create edited event: {err}");
2245                            return Ok(true);
2246                        }
2247                    };
2248
2249                    // Queue the edit event in the send queue 🧠.
2250                    let serializable = SerializableEventContent::from_raw(
2251                        Raw::new(&edit_event)
2252                            .map_err(RoomSendQueueStorageError::JsonSerialization)?,
2253                        edit_event.event_type().to_string(),
2254                    );
2255
2256                    store
2257                        .save_send_queue_request(
2258                            &self.room_id,
2259                            dependent_request.own_transaction_id.into(),
2260                            dependent_request.created_at,
2261                            serializable.into(),
2262                            Self::HIGH_PRIORITY,
2263                        )
2264                        .await
2265                        .map_err(RoomSendQueueStorageError::StateStoreError)?;
2266                } else {
2267                    // The parent event is still local; update the local echo.
2268                    let edited = store
2269                        .update_send_queue_request(
2270                            &self.room_id,
2271                            &dependent_request.parent_transaction_id,
2272                            new_content.into(),
2273                        )
2274                        .await
2275                        .map_err(RoomSendQueueStorageError::StateStoreError)?;
2276
2277                    if !edited {
2278                        warn!("missing local echo upon dependent edit");
2279                    }
2280                }
2281            }
2282
2283            kind @ (DependentQueuedRequestKind::RedactEvent
2284            | DependentQueuedRequestKind::RedactEventWithReason { .. }) => {
2285                let reason = match kind {
2286                    DependentQueuedRequestKind::RedactEventWithReason { reason } => reason,
2287                    // The legacy variant carries no reason.
2288                    _ => None,
2289                };
2290
2291                if let Some(parent_key) = parent_key {
2292                    let Some(event_id) = parent_key.into_event_id() else {
2293                        return Err(RoomSendQueueError::StorageError(
2294                            RoomSendQueueStorageError::InvalidParentKey,
2295                        ));
2296                    };
2297
2298                    // The parent event has been sent; send a redaction.
2299                    let room = client
2300                        .get_room(&self.room_id)
2301                        .ok_or(RoomSendQueueError::RoomDisappeared)?;
2302
2303                    // Ideally we'd use the send queue to send the redaction, but the protocol has
2304                    // changed the shape of a room.redaction after v11, so keep it simple and try
2305                    // once here.
2306
2307                    if let Err(err) = room
2308                        .redact(
2309                            &event_id,
2310                            reason.as_deref(),
2311                            Some(dependent_request.own_transaction_id.into()),
2312                        )
2313                        .await
2314                    {
2315                        warn!("error when sending a redact for {event_id}: {err}");
2316                        return Ok(false);
2317                    }
2318                } else {
2319                    // The parent event is still local (sending must have failed); redact the local
2320                    // echo.
2321                    let removed = store
2322                        .remove_send_queue_request(
2323                            &self.room_id,
2324                            &dependent_request.parent_transaction_id,
2325                        )
2326                        .await
2327                        .map_err(RoomSendQueueStorageError::StateStoreError)?;
2328
2329                    if !removed {
2330                        warn!("missing local echo upon dependent redact");
2331                    }
2332                }
2333            }
2334
2335            DependentQueuedRequestKind::ReactEvent { key } => {
2336                if let Some(parent_key) = parent_key {
2337                    let Some(parent_event_id) = parent_key.into_event_id() else {
2338                        return Err(RoomSendQueueError::StorageError(
2339                            RoomSendQueueStorageError::InvalidParentKey,
2340                        ));
2341                    };
2342
2343                    // Queue the reaction event in the send queue 🧠.
2344                    let react_event =
2345                        ReactionEventContent::new(Annotation::new(parent_event_id, key)).into();
2346                    let serializable = SerializableEventContent::from_raw(
2347                        Raw::new(&react_event)
2348                            .map_err(RoomSendQueueStorageError::JsonSerialization)?,
2349                        react_event.event_type().to_string(),
2350                    );
2351
2352                    store
2353                        .save_send_queue_request(
2354                            &self.room_id,
2355                            dependent_request.own_transaction_id.into(),
2356                            dependent_request.created_at,
2357                            serializable.into(),
2358                            Self::HIGH_PRIORITY,
2359                        )
2360                        .await
2361                        .map_err(RoomSendQueueStorageError::StateStoreError)?;
2362                } else {
2363                    // Not applied yet, we should retry later => false.
2364                    return Ok(false);
2365                }
2366            }
2367
2368            DependentQueuedRequestKind::UploadFileOrThumbnail {
2369                content_type,
2370                cache_key,
2371                related_to,
2372                parent_is_thumbnail_upload,
2373            } => {
2374                let Some(parent_key) = parent_key else {
2375                    // Not finished yet, we should retry later => false.
2376                    return Ok(false);
2377                };
2378                self.handle_dependent_file_or_thumbnail_upload(
2379                    client,
2380                    dependent_request.own_transaction_id.into(),
2381                    parent_key,
2382                    content_type,
2383                    cache_key,
2384                    related_to,
2385                    parent_is_thumbnail_upload,
2386                )
2387                .await?;
2388            }
2389
2390            DependentQueuedRequestKind::FinishUpload {
2391                local_echo,
2392                file_upload,
2393                thumbnail_info,
2394                extra_content,
2395            } => {
2396                let Some(parent_key) = parent_key else {
2397                    // Not finished yet, we should retry later => false.
2398                    return Ok(false);
2399                };
2400                self.handle_dependent_finish_upload(
2401                    client,
2402                    dependent_request.own_transaction_id.into(),
2403                    parent_key,
2404                    *local_echo,
2405                    file_upload,
2406                    thumbnail_info,
2407                    extra_content,
2408                    new_updates,
2409                )
2410                .await?;
2411            }
2412
2413            #[cfg(feature = "unstable-msc4274")]
2414            DependentQueuedRequestKind::FinishGallery { local_echo, item_infos } => {
2415                let Some(parent_key) = parent_key else {
2416                    // Not finished yet, we should retry later => false.
2417                    return Ok(false);
2418                };
2419                self.handle_dependent_finish_gallery_upload(
2420                    client,
2421                    dependent_request.own_transaction_id.into(),
2422                    parent_key,
2423                    *local_echo,
2424                    item_infos,
2425                    new_updates,
2426                )
2427                .await?;
2428            }
2429        }
2430
2431        Ok(true)
2432    }
2433
2434    #[instrument(skip(self))]
2435    async fn apply_dependent_requests(
2436        &self,
2437        new_updates: &mut Vec<RoomSendQueueUpdate>,
2438    ) -> Result<(), RoomSendQueueError> {
2439        let guard = self.store.lock().await;
2440
2441        let client = guard.client()?;
2442        let store = client.state_store();
2443
2444        let dependent_requests = store
2445            .load_dependent_queued_requests(&self.room_id)
2446            .await
2447            .map_err(RoomSendQueueStorageError::StateStoreError)?;
2448
2449        let num_initial_dependent_requests = dependent_requests.len();
2450        if num_initial_dependent_requests == 0 {
2451            // Returning early here avoids a bit of useless logging.
2452            return Ok(());
2453        }
2454
2455        let canonicalized_dependent_requests = canonicalize_dependent_requests(&dependent_requests);
2456
2457        // Get rid of the all non-canonical dependent events.
2458        for original in &dependent_requests {
2459            if !canonicalized_dependent_requests
2460                .iter()
2461                .any(|canonical| canonical.own_transaction_id == original.own_transaction_id)
2462            {
2463                store
2464                    .remove_dependent_queued_request(&self.room_id, &original.own_transaction_id)
2465                    .await
2466                    .map_err(RoomSendQueueStorageError::StateStoreError)?;
2467            }
2468        }
2469
2470        let mut num_dependent_requests = canonicalized_dependent_requests.len();
2471
2472        debug!(
2473            num_dependent_requests,
2474            num_initial_dependent_requests, "starting handling of dependent requests"
2475        );
2476
2477        for dependent in canonicalized_dependent_requests {
2478            let dependent_id = dependent.own_transaction_id.clone();
2479
2480            match self.try_apply_single_dependent_request(&client, dependent, new_updates).await {
2481                Ok(should_remove) => {
2482                    if should_remove {
2483                        // The dependent request has been successfully applied, forget about it.
2484                        store
2485                            .remove_dependent_queued_request(&self.room_id, &dependent_id)
2486                            .await
2487                            .map_err(RoomSendQueueStorageError::StateStoreError)?;
2488
2489                        num_dependent_requests -= 1;
2490                    }
2491                }
2492
2493                Err(err) => {
2494                    warn!("error when applying single dependent request: {err}");
2495                }
2496            }
2497        }
2498
2499        debug!(
2500            leftover_dependent_requests = num_dependent_requests,
2501            "stopped handling dependent request"
2502        );
2503
2504        Ok(())
2505    }
2506
2507    /// Remove a single dependent request from storage.
2508    async fn remove_dependent_send_queue_request(
2509        &self,
2510        dependent_event_id: &ChildTransactionId,
2511    ) -> Result<bool, RoomSendQueueStorageError> {
2512        Ok(self
2513            .store
2514            .lock()
2515            .await
2516            .client()?
2517            .state_store()
2518            .remove_dependent_queued_request(&self.room_id, dependent_event_id)
2519            .await?)
2520    }
2521}
2522
2523#[cfg(feature = "unstable-msc4274")]
2524/// Metadata needed for pushing gallery item uploads onto the send queue.
2525struct GalleryItemQueueInfo {
2526    content_type: Mime,
2527    upload_file_txn: OwnedTransactionId,
2528    file_media_request: MediaRequestParameters,
2529    thumbnail: Option<QueueThumbnailInfo>,
2530}
2531
2532/// The content of a local echo.
2533#[derive(Clone, Debug)]
2534pub enum LocalEchoContent {
2535    /// The local echo contains an actual event ready to display.
2536    Event {
2537        /// Content of the event itself (along with its type) that we are about
2538        /// to send.
2539        serialized_event: SerializableEventContent,
2540        /// A handle to manipulate the sending of the associated event.
2541        send_handle: SendHandle,
2542        /// Whether trying to send this local echo failed in the past with an
2543        /// unrecoverable error (see [`SendQueueRoomError::is_recoverable`]).
2544        send_error: Option<QueueWedgeError>,
2545    },
2546
2547    /// A local echo has been reacted to.
2548    React {
2549        /// The key with which the local echo has been reacted to.
2550        key: String,
2551        /// A handle to manipulate the sending of the reaction.
2552        send_handle: SendReactionHandle,
2553        /// The local echo which has been reacted to.
2554        applies_to: OwnedTransactionId,
2555    },
2556
2557    /// A local echo of a redaction event.
2558    Redaction {
2559        /// The ID of the redacted event.
2560        redacts: OwnedEventId,
2561        /// The reason for the event being redacted.
2562        reason: Option<String>,
2563        /// A handle to manipulate the sending of the associated event.
2564        send_handle: SendRedactionHandle,
2565        /// Whether trying to send this local echo failed in the past with an
2566        /// unrecoverable error (see [`SendQueueRoomError::is_recoverable`]).
2567        send_error: Option<QueueWedgeError>,
2568    },
2569}
2570
2571/// A local representation for a request that hasn't been sent yet to the user's
2572/// homeserver.
2573#[derive(Clone, Debug)]
2574pub struct LocalEcho {
2575    /// Transaction id used to identify the associated request.
2576    pub transaction_id: OwnedTransactionId,
2577    /// The content for the local echo.
2578    pub content: LocalEchoContent,
2579}
2580
2581/// An update to a room send queue, observable with
2582/// [`RoomSendQueue::subscribe`].
2583#[derive(Clone, Debug)]
2584pub enum RoomSendQueueUpdate {
2585    /// A new local event is being sent.
2586    ///
2587    /// There's been a user query to create this event. It is being sent to the
2588    /// server.
2589    NewLocalEvent(LocalEcho),
2590
2591    /// A local event that hadn't been sent to the server yet has been cancelled
2592    /// before sending.
2593    CancelledLocalEvent {
2594        /// Transaction id used to identify this event.
2595        transaction_id: OwnedTransactionId,
2596    },
2597
2598    /// A local event's content has been replaced with something else.
2599    ReplacedLocalEvent {
2600        /// Transaction id used to identify this event.
2601        transaction_id: OwnedTransactionId,
2602
2603        /// The new content replacing the previous one.
2604        new_content: SerializableEventContent,
2605    },
2606
2607    /// An error happened when an event was being sent.
2608    ///
2609    /// The event has not been removed from the queue. All the send queues
2610    /// will be disabled after this happens, and must be manually re-enabled.
2611    SendError {
2612        /// Transaction id used to identify this event.
2613        transaction_id: OwnedTransactionId,
2614        /// Error received while sending the event.
2615        error: Arc<crate::Error>,
2616        /// Whether the error is considered recoverable or not.
2617        ///
2618        /// An error that's recoverable will disable the room's send queue,
2619        /// while an unrecoverable error will be parked, until the user
2620        /// decides to cancel sending it.
2621        is_recoverable: bool,
2622    },
2623
2624    /// The event has been unwedged and sending is now being retried.
2625    RetryEvent {
2626        /// Transaction id used to identify this event.
2627        transaction_id: OwnedTransactionId,
2628    },
2629
2630    /// The event has been sent to the server, and the query returned
2631    /// successfully.
2632    SentEvent {
2633        /// Transaction id used to identify this event.
2634        transaction_id: OwnedTransactionId,
2635        /// Received event id from the send response.
2636        event_id: OwnedEventId,
2637    },
2638
2639    /// A media upload (consisting of a file and possibly a thumbnail) has made
2640    /// progress.
2641    MediaUpload {
2642        /// The media event this uploaded media relates to.
2643        related_to: OwnedTransactionId,
2644
2645        /// The final media source for the file if it has finished uploading.
2646        file: Option<MediaSource>,
2647
2648        /// The index of the media within the transaction. A file and its
2649        /// thumbnail share the same index. Will always be 0 for non-gallery
2650        /// media uploads.
2651        index: u64,
2652
2653        /// The combined upload progress across the file and, if existing, its
2654        /// thumbnail. For gallery uploads, the progress is reported per indexed
2655        /// gallery item.
2656        progress: AbstractProgress,
2657    },
2658}
2659
2660/// A [`RoomSendQueueUpdate`] with an associated [`OwnedRoomId`].
2661///
2662/// This is used by [`SendQueue::subscribe`] to get a single channel to receive
2663/// updates for all [`RoomSendQueue`]s.
2664#[derive(Clone, Debug)]
2665pub struct SendQueueUpdate {
2666    /// The room where the update happened.
2667    pub room_id: OwnedRoomId,
2668
2669    /// The update for this room.
2670    pub update: RoomSendQueueUpdate,
2671}
2672
2673/// An error triggered by the send queue module.
2674#[derive(Debug, thiserror::Error)]
2675pub enum RoomSendQueueError {
2676    /// The room isn't in the joined state.
2677    #[error("the room isn't in the joined state")]
2678    RoomNotJoined,
2679
2680    /// The room is missing from the client.
2681    ///
2682    /// This happens only whenever the client is shutting down.
2683    #[error("the room is now missing from the client")]
2684    RoomDisappeared,
2685
2686    /// Error coming from storage.
2687    #[error(transparent)]
2688    StorageError(#[from] RoomSendQueueStorageError),
2689
2690    /// The attachment event failed to be created.
2691    #[error("the attachment event could not be created")]
2692    FailedToCreateAttachment,
2693
2694    /// The gallery contains no items.
2695    #[cfg(feature = "unstable-msc4274")]
2696    #[error("the gallery contains no items")]
2697    EmptyGallery,
2698
2699    /// The gallery event failed to be created.
2700    #[cfg(feature = "unstable-msc4274")]
2701    #[error("the gallery event could not be created")]
2702    FailedToCreateGallery,
2703}
2704
2705/// An error triggered by the send queue storage.
2706#[derive(Debug, thiserror::Error)]
2707pub enum RoomSendQueueStorageError {
2708    /// Error caused by the state store.
2709    #[error(transparent)]
2710    StateStoreError(#[from] StoreError),
2711
2712    /// Error caused by the event cache store.
2713    #[error(transparent)]
2714    EventCacheStoreError(#[from] EventCacheStoreError),
2715
2716    /// Error caused by the event cache store.
2717    #[error(transparent)]
2718    MediaStoreError(#[from] MediaStoreError),
2719
2720    /// Error caused when attempting to get a handle on the event cache store.
2721    #[error(transparent)]
2722    LockError(#[from] CrossProcessLockError),
2723
2724    /// Error caused when (de)serializing into/from json.
2725    #[error(transparent)]
2726    JsonSerialization(#[from] serde_json::Error),
2727
2728    /// A parent key was expected to be of a certain type, and it was another
2729    /// type instead.
2730    #[error("a dependent event had an invalid parent key type")]
2731    InvalidParentKey,
2732
2733    /// The client is shutting down.
2734    #[error("The client is shutting down.")]
2735    ClientShuttingDown,
2736
2737    /// An operation not implemented on a send handle.
2738    #[error("This operation is not implemented for media uploads")]
2739    OperationNotImplementedYet,
2740
2741    /// Trying to edit a media caption for something that's not a media.
2742    #[error("Can't edit a media caption when the underlying event isn't a media")]
2743    InvalidMediaCaptionEdit,
2744}
2745
2746/// Extra transaction IDs useful during an upload.
2747#[derive(Clone, Debug)]
2748struct MediaHandles {
2749    /// Transaction id used when uploading the thumbnail.
2750    ///
2751    /// Optional because a media can be uploaded without a thumbnail.
2752    upload_thumbnail_txn: Option<OwnedTransactionId>,
2753
2754    /// Transaction id used when uploading the media itself.
2755    upload_file_txn: OwnedTransactionId,
2756}
2757
2758/// Future returned by [`RoomSendQueue::send`].
2759#[allow(missing_debug_implementations)]
2760pub struct SendEvent<'a> {
2761    queue: &'a RoomSendQueue,
2762    content: AnyMessageLikeEventContent,
2763    extra_content: Option<serde_json::Map<String, serde_json::Value>>,
2764}
2765
2766impl<'a> SendEvent<'a> {
2767    /// Merge additional top-level fields into the outgoing event's content.
2768    ///
2769    /// The event's own fields take precedence on conflicts.
2770    pub fn with_extra_content(
2771        mut self,
2772        extra_content: serde_json::Map<String, serde_json::Value>,
2773    ) -> Self {
2774        self.extra_content = Some(extra_content);
2775        self
2776    }
2777}
2778
2779impl<'a> IntoFuture for SendEvent<'a> {
2780    type Output = Result<SendHandle, RoomSendQueueError>;
2781    boxed_into_future!(extra_bounds: 'a);
2782
2783    fn into_future(self) -> Self::IntoFuture {
2784        Box::pin(async move {
2785            let serialized = upload::merge_extra_content(
2786                SerializableEventContent::new(&self.content)
2787                    .map_err(RoomSendQueueStorageError::JsonSerialization)?,
2788                self.extra_content,
2789            )?;
2790            let (raw, event_type) = serialized.into_raw();
2791            self.queue.send_raw(raw, event_type).await
2792        })
2793    }
2794}
2795
2796/// A handle to manipulate an event that was scheduled to be sent to a room.
2797#[derive(Clone, Debug)]
2798pub struct SendHandle {
2799    /// Link to the send queue used to send this request.
2800    room: RoomSendQueue,
2801
2802    /// Transaction id used for the sent request.
2803    ///
2804    /// If this is a media upload, this is the "main" transaction id, i.e. the
2805    /// one used to send the event, and that will be seen by observers.
2806    transaction_id: OwnedTransactionId,
2807
2808    /// Additional handles for a media upload.
2809    media_handles: Vec<MediaHandles>,
2810
2811    /// The time at which the event to be sent has been created.
2812    pub created_at: MilliSecondsSinceUnixEpoch,
2813}
2814
2815impl SendHandle {
2816    /// Creates a new [`SendHandle`].
2817    #[cfg(test)]
2818    pub(crate) fn new(
2819        room: RoomSendQueue,
2820        transaction_id: OwnedTransactionId,
2821        created_at: MilliSecondsSinceUnixEpoch,
2822    ) -> Self {
2823        Self { room, transaction_id, media_handles: vec![], created_at }
2824    }
2825
2826    fn nyi_for_uploads(&self) -> Result<(), RoomSendQueueStorageError> {
2827        if !self.media_handles.is_empty() {
2828            Err(RoomSendQueueStorageError::OperationNotImplementedYet)
2829        } else {
2830            Ok(())
2831        }
2832    }
2833
2834    /// Aborts the sending of the event, if it wasn't sent yet.
2835    ///
2836    /// Returns true if the sending could be aborted, false if not (i.e. the
2837    /// event had already been sent).
2838    pub async fn abort(&self) -> Result<bool, RoomSendQueueStorageError> {
2839        self.abort_with_reason(None).await
2840    }
2841
2842    /// Aborts the sending of the event, if it wasn't sent yet, with an
2843    /// optional reason.
2844    ///
2845    /// If the event was being sent when the abort was requested and the send
2846    /// succeeds, the event is redacted server-side; the given reason is
2847    /// applied to that redaction. It is unused in every other case (the local
2848    /// echo is simply dropped).
2849    ///
2850    /// Returns true if the sending could be aborted, false if not (i.e. the
2851    /// event had already been sent).
2852    #[instrument(skip(self), fields(room_id = %self.room.inner.room.room_id(), txn_id = %self.transaction_id))]
2853    pub async fn abort_with_reason(
2854        &self,
2855        reason: Option<String>,
2856    ) -> Result<bool, RoomSendQueueStorageError> {
2857        trace!("received an abort request");
2858
2859        let queue = &self.room.inner.queue;
2860
2861        for handles in &self.media_handles {
2862            if queue.abort_upload(&self.transaction_id, handles).await? {
2863                // Wake up the queue, in case it was blocked on this request being wedged.
2864                self.room.inner.notifier.notify_one();
2865
2866                // Propagate a cancelled update.
2867                self.room.send_update(RoomSendQueueUpdate::CancelledLocalEvent {
2868                    transaction_id: self.transaction_id.clone(),
2869                });
2870
2871                return Ok(true);
2872            }
2873
2874            // If it failed, it means the sending of the event is not a
2875            // dependent request anymore. Fall back to the regular
2876            // code path below, that handles aborting sending of an event.
2877        }
2878
2879        if queue.cancel_event(&self.transaction_id, reason).await? {
2880            trace!("successful abort");
2881
2882            // Wake up the queue, in case it was blocked on this request being wedged.
2883            self.room.inner.notifier.notify_one();
2884
2885            // Propagate a cancelled update too.
2886            self.room.send_update(RoomSendQueueUpdate::CancelledLocalEvent {
2887                transaction_id: self.transaction_id.clone(),
2888            });
2889
2890            Ok(true)
2891        } else {
2892            debug!("local echo didn't exist anymore, can't abort");
2893            Ok(false)
2894        }
2895    }
2896
2897    /// Edits the content of a local echo with a raw event content.
2898    ///
2899    /// Returns true if the event to be sent was replaced, false if not (i.e.
2900    /// the event had already been sent).
2901    #[instrument(skip(self, new_content), fields(room_id = %self.room.inner.room.room_id(), txn_id = %self.transaction_id))]
2902    pub async fn edit_raw(
2903        &self,
2904        new_content: Raw<AnyMessageLikeEventContent>,
2905        event_type: String,
2906    ) -> Result<bool, RoomSendQueueStorageError> {
2907        trace!("received an edit request");
2908        self.nyi_for_uploads()?;
2909
2910        let serializable = SerializableEventContent::from_raw(new_content, event_type);
2911
2912        if self.room.inner.queue.replace_event(&self.transaction_id, serializable.clone()).await? {
2913            trace!("successful edit");
2914
2915            // Wake up the queue, in case the room was asleep before the edit.
2916            self.room.inner.notifier.notify_one();
2917
2918            // Propagate a replaced update too.
2919            self.room.send_update(RoomSendQueueUpdate::ReplacedLocalEvent {
2920                transaction_id: self.transaction_id.clone(),
2921                new_content: serializable,
2922            });
2923
2924            Ok(true)
2925        } else {
2926            debug!("local echo doesn't exist anymore, can't edit");
2927            Ok(false)
2928        }
2929    }
2930
2931    /// Edits the content of a local echo with an event content.
2932    ///
2933    /// Returns true if the event to be sent was replaced, false if not (i.e.
2934    /// the event had already been sent).
2935    pub async fn edit(
2936        &self,
2937        new_content: AnyMessageLikeEventContent,
2938    ) -> Result<bool, RoomSendQueueStorageError> {
2939        self.edit_raw(
2940            Raw::new(&new_content).map_err(RoomSendQueueStorageError::JsonSerialization)?,
2941            new_content.event_type().to_string(),
2942        )
2943        .await
2944    }
2945
2946    /// Edits the content of a local echo with a media caption.
2947    ///
2948    /// Will fail if the event to be sent, represented by this send handle,
2949    /// wasn't a media.
2950    pub async fn edit_media_caption(
2951        &self,
2952        caption: Option<String>,
2953        formatted_caption: Option<FormattedBody>,
2954        mentions: Option<Mentions>,
2955    ) -> Result<bool, RoomSendQueueStorageError> {
2956        if let Some(new_content) = self
2957            .room
2958            .inner
2959            .queue
2960            .edit_media_caption(&self.transaction_id, caption, formatted_caption, mentions)
2961            .await?
2962        {
2963            trace!("successful edit of media caption");
2964
2965            // Wake up the queue, in case the room was asleep before the edit.
2966            self.room.inner.notifier.notify_one();
2967
2968            let new_content = SerializableEventContent::new(&new_content)
2969                .map_err(RoomSendQueueStorageError::JsonSerialization)?;
2970
2971            // Propagate a replaced update too.
2972            self.room.send_update(RoomSendQueueUpdate::ReplacedLocalEvent {
2973                transaction_id: self.transaction_id.clone(),
2974                new_content,
2975            });
2976
2977            Ok(true)
2978        } else {
2979            debug!("local echo doesn't exist anymore, can't edit media caption");
2980            Ok(false)
2981        }
2982    }
2983
2984    /// Unwedge a local echo identified by its transaction identifier and try to
2985    /// resend it.
2986    pub async fn unwedge(&self) -> Result<(), RoomSendQueueError> {
2987        let room = &self.room.inner;
2988        room.queue
2989            .mark_as_unwedged(&self.transaction_id)
2990            .await
2991            .map_err(RoomSendQueueError::StorageError)?;
2992
2993        // If we have media handles, also try to unwedge them.
2994        //
2995        // It's fine to always do it to *all* the transaction IDs at once, because only
2996        // one of the three requests will be active at the same time, i.e. only
2997        // one entry will be updated in the store. The other two are either
2998        // done, or dependent requests.
2999
3000        for handles in &self.media_handles {
3001            room.queue
3002                .mark_as_unwedged(&handles.upload_file_txn)
3003                .await
3004                .map_err(RoomSendQueueError::StorageError)?;
3005
3006            if let Some(txn) = &handles.upload_thumbnail_txn {
3007                room.queue.mark_as_unwedged(txn).await.map_err(RoomSendQueueError::StorageError)?;
3008            }
3009        }
3010
3011        // Wake up the queue, in case the room was asleep before unwedging the request.
3012        room.notifier.notify_one();
3013
3014        self.room.send_update(RoomSendQueueUpdate::RetryEvent {
3015            transaction_id: self.transaction_id.clone(),
3016        });
3017
3018        Ok(())
3019    }
3020
3021    /// Send a reaction to the event as soon as it's sent.
3022    ///
3023    /// If returning `Ok(None)`; this means the reaction couldn't be sent
3024    /// because the event is already a remote one.
3025    #[instrument(skip(self), fields(room_id = %self.room.inner.room.room_id(), txn_id = %self.transaction_id))]
3026    pub async fn react(
3027        &self,
3028        key: String,
3029    ) -> Result<Option<SendReactionHandle>, RoomSendQueueStorageError> {
3030        trace!("received an intent to react");
3031
3032        let created_at = MilliSecondsSinceUnixEpoch::now();
3033        if let Some(reaction_txn_id) =
3034            self.room.inner.queue.react(&self.transaction_id, key.clone(), created_at).await?
3035        {
3036            trace!("successfully queued react");
3037
3038            // Wake up the queue, in case the room was asleep before the sending.
3039            self.room.inner.notifier.notify_one();
3040
3041            // Propagate a new local event.
3042            let send_handle = SendReactionHandle {
3043                room: self.room.clone(),
3044                transaction_id: reaction_txn_id.clone(),
3045            };
3046
3047            self.room.send_update(RoomSendQueueUpdate::NewLocalEvent(LocalEcho {
3048                // Note: we do want to use the `txn_id` we're going to use for the reaction, not
3049                // the one for the event we're reacting to.
3050                transaction_id: reaction_txn_id.into(),
3051                content: LocalEchoContent::React {
3052                    key,
3053                    send_handle: send_handle.clone(),
3054                    applies_to: self.transaction_id.clone(),
3055                },
3056            }));
3057
3058            Ok(Some(send_handle))
3059        } else {
3060            debug!("local echo doesn't exist anymore, can't react");
3061            Ok(None)
3062        }
3063    }
3064}
3065
3066/// A handle to execute actions on the sending of a reaction.
3067#[derive(Clone, Debug)]
3068pub struct SendReactionHandle {
3069    /// Reference to the send queue for the room where this reaction was sent.
3070    room: RoomSendQueue,
3071    /// The own transaction id for the reaction.
3072    transaction_id: ChildTransactionId,
3073}
3074
3075impl SendReactionHandle {
3076    /// Creates a new [`SendReactionHandle`].
3077    #[cfg(test)]
3078    pub(crate) fn new(room: RoomSendQueue, transaction_id: ChildTransactionId) -> Self {
3079        Self { room, transaction_id }
3080    }
3081
3082    /// Abort the sending of the reaction.
3083    ///
3084    /// Will return true if the reaction could be aborted, false if it's been
3085    /// sent (and there's no matching local echo anymore).
3086    pub async fn abort(&self) -> Result<bool, RoomSendQueueStorageError> {
3087        if self.room.inner.queue.remove_dependent_send_queue_request(&self.transaction_id).await? {
3088            // Simple case: the reaction was found in the dependent event list.
3089
3090            // Propagate a cancelled update too.
3091            self.room.send_update(RoomSendQueueUpdate::CancelledLocalEvent {
3092                transaction_id: self.transaction_id.clone().into(),
3093            });
3094
3095            return Ok(true);
3096        }
3097
3098        // The reaction has already been queued for sending, try to abort it using a
3099        // regular abort.
3100        let handle = SendHandle {
3101            room: self.room.clone(),
3102            transaction_id: self.transaction_id.clone().into(),
3103            media_handles: vec![],
3104            created_at: MilliSecondsSinceUnixEpoch::now(),
3105        };
3106
3107        handle.abort().await
3108    }
3109
3110    /// The transaction id that will be used to send this reaction later.
3111    pub fn transaction_id(&self) -> &TransactionId {
3112        &self.transaction_id
3113    }
3114}
3115
3116/// A handle to manipulate a redaction event that was scheduled to be sent to a
3117/// room.
3118#[derive(Clone, Debug)]
3119pub struct SendRedactionHandle {
3120    /// Link to the send queue used to send this request.
3121    room: RoomSendQueue,
3122
3123    /// Transaction id used for the sent request.
3124    transaction_id: OwnedTransactionId,
3125}
3126
3127impl SendRedactionHandle {
3128    /// Creates a new [`SendRedactionHandle`].
3129    #[cfg(test)]
3130    pub(crate) fn new(room: RoomSendQueue, transaction_id: OwnedTransactionId) -> Self {
3131        Self { room, transaction_id }
3132    }
3133
3134    /// Abort the sending of the redaction.
3135    ///
3136    /// Will return true if the redaction could be aborted, false if it's been
3137    /// sent (and there's no matching local echo anymore).
3138    pub async fn abort(&self) -> Result<bool, RoomSendQueueStorageError> {
3139        trace!("received a redaction abort request");
3140
3141        let queue = &self.room.inner.queue;
3142
3143        if queue.cancel_event(&self.transaction_id, None).await? {
3144            trace!("successful redaction abort");
3145
3146            // Wake up the queue, in case it was blocked on this request being wedged.
3147            self.room.inner.notifier.notify_one();
3148
3149            // Propagate a cancelled update too.
3150            self.room.send_update(RoomSendQueueUpdate::CancelledLocalEvent {
3151                transaction_id: self.transaction_id.clone(),
3152            });
3153
3154            Ok(true)
3155        } else {
3156            debug!("local echo of redaction didn't exist anymore, can't abort");
3157            Ok(false)
3158        }
3159    }
3160}
3161
3162/// From a given source of [`DependentQueuedRequest`], return only the most
3163/// meaningful, i.e. the ones that wouldn't be overridden after applying the
3164/// others.
3165fn canonicalize_dependent_requests(
3166    dependent: &[DependentQueuedRequest],
3167) -> Vec<DependentQueuedRequest> {
3168    let mut by_txn = HashMap::<OwnedTransactionId, Vec<&DependentQueuedRequest>>::new();
3169
3170    for d in dependent {
3171        let prevs = by_txn.entry(d.parent_transaction_id.clone()).or_default();
3172
3173        if prevs.iter().any(|prev| {
3174            matches!(
3175                prev.kind,
3176                DependentQueuedRequestKind::RedactEvent
3177                    | DependentQueuedRequestKind::RedactEventWithReason { .. }
3178            )
3179        }) {
3180            // The parent event has already been flagged for redaction, don't consider the
3181            // other dependent events.
3182            continue;
3183        }
3184
3185        match &d.kind {
3186            DependentQueuedRequestKind::EditEvent { .. } => {
3187                // Replace any previous edit with this one.
3188                if let Some(prev_edit) = prevs
3189                    .iter_mut()
3190                    .find(|prev| matches!(prev.kind, DependentQueuedRequestKind::EditEvent { .. }))
3191                {
3192                    *prev_edit = d;
3193                } else {
3194                    prevs.insert(0, d);
3195                }
3196            }
3197
3198            DependentQueuedRequestKind::UploadFileOrThumbnail { .. }
3199            | DependentQueuedRequestKind::FinishUpload { .. }
3200            | DependentQueuedRequestKind::ReactEvent { .. } => {
3201                // These requests can't be canonicalized, push them as is.
3202                prevs.push(d);
3203            }
3204
3205            #[cfg(feature = "unstable-msc4274")]
3206            DependentQueuedRequestKind::FinishGallery { .. } => {
3207                // This request can't be canonicalized, push it as is.
3208                prevs.push(d);
3209            }
3210
3211            DependentQueuedRequestKind::RedactEvent
3212            | DependentQueuedRequestKind::RedactEventWithReason { .. } => {
3213                // Remove every other dependent action.
3214                prevs.clear();
3215                prevs.push(d);
3216            }
3217        }
3218    }
3219
3220    by_txn.into_values().flat_map(|entries| entries.into_iter().cloned()).collect()
3221}
3222
3223#[cfg(all(test, not(target_family = "wasm")))]
3224mod tests {
3225    use std::{sync::Arc, time::Duration};
3226
3227    use assert_matches2::{assert_let, assert_matches};
3228    use matrix_sdk_base::store::{
3229        ChildTransactionId, DependentQueuedRequest, DependentQueuedRequestKind,
3230        SerializableEventContent,
3231    };
3232    use matrix_sdk_test::{JoinedRoomBuilder, SyncResponseBuilder, async_test};
3233    use ruma::{
3234        MilliSecondsSinceUnixEpoch, TransactionId,
3235        events::{AnyMessageLikeEventContent, room::message::RoomMessageEventContent},
3236        room_id,
3237    };
3238
3239    use super::canonicalize_dependent_requests;
3240    use crate::{client::WeakClient, test_utils::logged_in_client};
3241
3242    #[test]
3243    fn test_canonicalize_dependent_events_created_at() {
3244        // Test to ensure the created_at field is being serialized and retrieved
3245        // correctly.
3246        let txn = TransactionId::new();
3247        let created_at = MilliSecondsSinceUnixEpoch::now();
3248
3249        let edit = DependentQueuedRequest {
3250            own_transaction_id: ChildTransactionId::new(),
3251            parent_transaction_id: txn.clone(),
3252            kind: DependentQueuedRequestKind::EditEvent {
3253                new_content: SerializableEventContent::new(
3254                    &RoomMessageEventContent::text_plain("edit").into(),
3255                )
3256                .unwrap(),
3257            },
3258            parent_key: None,
3259            created_at,
3260        };
3261
3262        let res = canonicalize_dependent_requests(&[edit]);
3263
3264        assert_eq!(res.len(), 1);
3265        assert_let!(DependentQueuedRequestKind::EditEvent { new_content } = &res[0].kind);
3266        assert_let!(
3267            AnyMessageLikeEventContent::RoomMessage(msg) = new_content.deserialize().unwrap()
3268        );
3269        assert_eq!(msg.body(), "edit");
3270        assert_eq!(res[0].parent_transaction_id, txn);
3271        assert_eq!(res[0].created_at, created_at);
3272    }
3273
3274    #[async_test]
3275    async fn test_client_no_cycle_with_send_queue() {
3276        for enabled in [true, false] {
3277            let client = logged_in_client(None).await;
3278            let weak_client = WeakClient::from_client(&client);
3279
3280            {
3281                let mut sync_response_builder = SyncResponseBuilder::new();
3282
3283                let room_id = room_id!("!a:b.c");
3284
3285                // Make sure the client knows about the room.
3286                client
3287                    .base_client()
3288                    .receive_sync_response(
3289                        sync_response_builder
3290                            .add_joined_room(JoinedRoomBuilder::new(room_id))
3291                            .build_sync_response(),
3292                    )
3293                    .await
3294                    .unwrap();
3295
3296                let room = client.get_room(room_id).unwrap();
3297                let q = room.send_queue();
3298
3299                let _watcher = q.subscribe().await;
3300
3301                client.send_queue().set_enabled(enabled).await;
3302            }
3303
3304            drop(client);
3305
3306            // Give a bit of time for background tasks to die.
3307            tokio::time::sleep(Duration::from_millis(500)).await;
3308
3309            // The weak client must be the last reference to the client now.
3310            let client = weak_client.get();
3311            assert!(
3312                client.is_none(),
3313                "too many strong references to the client: {}",
3314                Arc::strong_count(&client.unwrap().inner)
3315            );
3316        }
3317    }
3318
3319    #[test]
3320    fn test_canonicalize_dependent_events_smoke_test() {
3321        // Smoke test: canonicalizing a single dependent event returns it.
3322        let txn = TransactionId::new();
3323
3324        let edit = DependentQueuedRequest {
3325            own_transaction_id: ChildTransactionId::new(),
3326            parent_transaction_id: txn.clone(),
3327            kind: DependentQueuedRequestKind::EditEvent {
3328                new_content: SerializableEventContent::new(
3329                    &RoomMessageEventContent::text_plain("edit").into(),
3330                )
3331                .unwrap(),
3332            },
3333            parent_key: None,
3334            created_at: MilliSecondsSinceUnixEpoch::now(),
3335        };
3336        let res = canonicalize_dependent_requests(&[edit]);
3337
3338        assert_eq!(res.len(), 1);
3339        assert_matches!(&res[0].kind, DependentQueuedRequestKind::EditEvent { .. });
3340        assert_eq!(res[0].parent_transaction_id, txn);
3341        assert!(res[0].parent_key.is_none());
3342    }
3343
3344    #[test]
3345    fn test_canonicalize_dependent_events_redaction_preferred() {
3346        // A redaction is preferred over any other kind of dependent event.
3347        let txn = TransactionId::new();
3348
3349        let mut inputs = Vec::with_capacity(100);
3350        let redact = DependentQueuedRequest {
3351            own_transaction_id: ChildTransactionId::new(),
3352            parent_transaction_id: txn.clone(),
3353            kind: DependentQueuedRequestKind::RedactEventWithReason { reason: None },
3354            parent_key: None,
3355            created_at: MilliSecondsSinceUnixEpoch::now(),
3356        };
3357
3358        let edit = DependentQueuedRequest {
3359            own_transaction_id: ChildTransactionId::new(),
3360            parent_transaction_id: txn.clone(),
3361            kind: DependentQueuedRequestKind::EditEvent {
3362                new_content: SerializableEventContent::new(
3363                    &RoomMessageEventContent::text_plain("edit").into(),
3364                )
3365                .unwrap(),
3366            },
3367            parent_key: None,
3368            created_at: MilliSecondsSinceUnixEpoch::now(),
3369        };
3370
3371        inputs.push({
3372            let mut edit = edit.clone();
3373            edit.own_transaction_id = ChildTransactionId::new();
3374            edit
3375        });
3376
3377        inputs.push(redact);
3378
3379        for _ in 0..98 {
3380            let mut edit = edit.clone();
3381            edit.own_transaction_id = ChildTransactionId::new();
3382            inputs.push(edit);
3383        }
3384
3385        let res = canonicalize_dependent_requests(&inputs);
3386
3387        assert_eq!(res.len(), 1);
3388        assert_matches!(&res[0].kind, DependentQueuedRequestKind::RedactEventWithReason { .. });
3389        assert_eq!(res[0].parent_transaction_id, txn);
3390    }
3391
3392    #[test]
3393    fn test_canonicalize_dependent_events_last_edit_preferred() {
3394        let parent_txn = TransactionId::new();
3395
3396        // The latest edit of a list is always preferred.
3397        let inputs = (0..10)
3398            .map(|i| DependentQueuedRequest {
3399                own_transaction_id: ChildTransactionId::new(),
3400                parent_transaction_id: parent_txn.clone(),
3401                kind: DependentQueuedRequestKind::EditEvent {
3402                    new_content: SerializableEventContent::new(
3403                        &RoomMessageEventContent::text_plain(format!("edit{i}")).into(),
3404                    )
3405                    .unwrap(),
3406                },
3407                parent_key: None,
3408                created_at: MilliSecondsSinceUnixEpoch::now(),
3409            })
3410            .collect::<Vec<_>>();
3411
3412        let txn = inputs[9].parent_transaction_id.clone();
3413
3414        let res = canonicalize_dependent_requests(&inputs);
3415
3416        assert_eq!(res.len(), 1);
3417        assert_let!(DependentQueuedRequestKind::EditEvent { new_content } = &res[0].kind);
3418        assert_let!(
3419            AnyMessageLikeEventContent::RoomMessage(msg) = new_content.deserialize().unwrap()
3420        );
3421        assert_eq!(msg.body(), "edit9");
3422        assert_eq!(res[0].parent_transaction_id, txn);
3423    }
3424
3425    #[test]
3426    fn test_canonicalize_multiple_local_echoes() {
3427        let txn1 = TransactionId::new();
3428        let txn2 = TransactionId::new();
3429
3430        let child1 = ChildTransactionId::new();
3431        let child2 = ChildTransactionId::new();
3432
3433        let inputs = vec![
3434            // This one pertains to txn1.
3435            DependentQueuedRequest {
3436                own_transaction_id: child1.clone(),
3437                kind: DependentQueuedRequestKind::RedactEventWithReason { reason: None },
3438                parent_transaction_id: txn1.clone(),
3439                parent_key: None,
3440                created_at: MilliSecondsSinceUnixEpoch::now(),
3441            },
3442            // This one pertains to txn2.
3443            DependentQueuedRequest {
3444                own_transaction_id: child2,
3445                kind: DependentQueuedRequestKind::EditEvent {
3446                    new_content: SerializableEventContent::new(
3447                        &RoomMessageEventContent::text_plain("edit").into(),
3448                    )
3449                    .unwrap(),
3450                },
3451                parent_transaction_id: txn2.clone(),
3452                parent_key: None,
3453                created_at: MilliSecondsSinceUnixEpoch::now(),
3454            },
3455        ];
3456
3457        let res = canonicalize_dependent_requests(&inputs);
3458
3459        // The canonicalization shouldn't depend per event id.
3460        assert_eq!(res.len(), 2);
3461
3462        for dependent in res {
3463            if dependent.own_transaction_id == child1 {
3464                assert_eq!(dependent.parent_transaction_id, txn1);
3465                assert_matches!(
3466                    dependent.kind,
3467                    DependentQueuedRequestKind::RedactEventWithReason { .. }
3468                );
3469            } else {
3470                assert_eq!(dependent.parent_transaction_id, txn2);
3471                assert_matches!(dependent.kind, DependentQueuedRequestKind::EditEvent { .. });
3472            }
3473        }
3474    }
3475
3476    #[test]
3477    fn test_canonicalize_reactions_after_edits() {
3478        // Sending reactions should happen after edits to a given event.
3479        let txn = TransactionId::new();
3480
3481        let react_id = ChildTransactionId::new();
3482        let react = DependentQueuedRequest {
3483            own_transaction_id: react_id.clone(),
3484            kind: DependentQueuedRequestKind::ReactEvent { key: "🧠".to_owned() },
3485            parent_transaction_id: txn.clone(),
3486            parent_key: None,
3487            created_at: MilliSecondsSinceUnixEpoch::now(),
3488        };
3489
3490        let edit_id = ChildTransactionId::new();
3491        let edit = DependentQueuedRequest {
3492            own_transaction_id: edit_id.clone(),
3493            kind: DependentQueuedRequestKind::EditEvent {
3494                new_content: SerializableEventContent::new(
3495                    &RoomMessageEventContent::text_plain("edit").into(),
3496                )
3497                .unwrap(),
3498            },
3499            parent_transaction_id: txn,
3500            parent_key: None,
3501            created_at: MilliSecondsSinceUnixEpoch::now(),
3502        };
3503
3504        let res = canonicalize_dependent_requests(&[react, edit]);
3505
3506        assert_eq!(res.len(), 2);
3507        assert_eq!(res[0].own_transaction_id, edit_id);
3508        assert_eq!(res[1].own_transaction_id, react_id);
3509    }
3510}