Skip to main content

matrix_sdk/send_queue/
mod.rs

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