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