Skip to main content

matrix_sdk_ui/timeline/controller/
mod.rs

1// Copyright 2023 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
15use std::{
16    collections::BTreeSet,
17    fmt,
18    ops::Deref,
19    sync::{Arc, OnceLock},
20};
21
22use as_variant::as_variant;
23use eyeball_im::{VectorDiff, VectorSubscriberStream};
24use eyeball_im_util::vector::{FilterMap, VectorObserverExt};
25use futures_core::Stream;
26use futures_util::future::try_join_all;
27use imbl::{HashSet, Vector};
28use matrix_sdk::{
29    deserialized_responses::TimelineEvent,
30    event_cache::{
31        DecryptionRetryRequest, EventCache, EventFocusedCache, PaginationStatus, PinnedEventsCache,
32        RoomEventCache, Subscriber as EventCacheSubscriber, ThreadEventCache,
33        ThreadEventCacheUpdate,
34    },
35    send_queue::{
36        LocalEcho, LocalEchoContent, RoomSendQueueUpdate, SendHandle, SendReactionHandle,
37        SendRedactionHandle,
38    },
39    task_monitor::BackgroundTaskHandle,
40};
41use ruma::{
42    EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, OwnedUserId,
43    TransactionId, UserId,
44    api::client::receipt::create_receipt::v3::ReceiptType as SendReceiptType,
45    events::{
46        AnyMessageLikeEventContent, AnySyncMessageLikeEvent, AnySyncTimelineEvent,
47        MessageLikeEventType,
48        poll::unstable_start::UnstablePollStartEventContent,
49        reaction::ReactionEventContent,
50        receipt::{Receipt, ReceiptEventContent, ReceiptThread, ReceiptType},
51        relation::{Annotation, RelationType},
52        room::message::{MessageType, Relation},
53    },
54    room_version_rules::RoomVersionRules,
55};
56use tokio::sync::{RwLock, RwLockWriteGuard};
57use tracing::{
58    Instrument as _, Span, debug, error, field::debug, info, info_span, instrument, trace, warn,
59};
60
61pub(super) use self::{
62    metadata::{RelativePosition, TimelineMetadata},
63    observable_items::{
64        AllRemoteEvents, ObservableItems, ObservableItemsEntry, ObservableItemsTransaction,
65        ObservableItemsTransactionEntry,
66    },
67    state::TimelineState,
68    state_transaction::TimelineStateTransaction,
69};
70use super::{
71    DateDividerMode, EmbeddedEvent, Error, EventSendState, EventTimelineItem, InReplyToDetails,
72    MediaUploadProgress, Profile, TimelineDetails, TimelineEventItemId, TimelineFocus,
73    TimelineItem, TimelineItemContent, TimelineItemKind, TimelineReadReceiptTracking,
74    VirtualTimelineItem,
75    algorithms::{rfind_event_by_id, rfind_event_item},
76    event_item::RemoteEventOrigin,
77    item::TimelineUniqueId,
78    subscriber::TimelineSubscriber,
79    traits::RoomDataProvider,
80};
81use crate::{
82    timeline::{
83        MsgLikeContent, MsgLikeKind, Room, TimelineEventFilterFn, TimelineEventFocusThreadMode,
84        algorithms::rfind_event_by_item_id,
85        controller::decryption_retry_task::compute_redecryption_candidates,
86        date_dividers::DateDividerAdjuster,
87        event_item::TimelineItemHandle,
88        tasks::{event_focused_task, pinned_events_task, thread_updates_task},
89    },
90    unable_to_decrypt_hook::UtdHookManager,
91};
92
93pub(in crate::timeline) mod aggregations;
94mod decryption_retry_task;
95mod metadata;
96mod observable_items;
97mod read_receipts;
98mod state;
99mod state_transaction;
100
101pub(super) use aggregations::*;
102pub(super) use decryption_retry_task::{CryptoDropHandles, spawn_crypto_tasks};
103use matrix_sdk_base::{CallIntentConsensus, RoomInfo};
104
105/// The outcome of [`TimelineController::should_send_receipt`].
106pub(super) enum SendReceiptDecision {
107    /// No read receipt should be sent.
108    DoNotSend,
109
110    /// A read receipt should be sent, targeting this event.
111    ///
112    /// This may differ from the event the caller asked about, since a read
113    /// receipt should not point at one of the user's own events.
114    SendTo(OwnedEventId),
115}
116
117/// Data associated to the current timeline focus.
118///
119/// This is the private counterpart of [`TimelineFocus`], and it is an augmented
120/// version of it, including extra state that makes it useful over the lifetime
121/// of a timeline.
122#[derive(Debug)]
123pub(in crate::timeline) enum TimelineFocusKind {
124    /// The timeline receives live events from the sync.
125    Live {
126        /// Whether to hide in-thread events from the timeline.
127        hide_threaded_events: bool,
128
129        /// The cache holding all the events for this focus.
130        event_cache: RoomEventCache,
131    },
132
133    /// The timeline is focused on a single event, and it can expand in one
134    /// direction or another.
135    Event {
136        /// The focused event ID.
137        focused_event_id: OwnedEventId,
138
139        /// If the focused event is part or the root of a thread, what's the
140        /// thread root?
141        ///
142        /// This is determined once when initializing the event-focused cache,
143        /// and then it won't change for the duration of this timeline.
144        thread_root: OnceLock<OwnedEventId>,
145
146        /// The thread mode to use for this event-focused timeline, which is
147        /// part of the key for the memoized event-focused cache.
148        thread_mode: TimelineEventFocusThreadMode,
149
150        /// The cache holding all the events for this focus.
151        event_cache: EventFocusedCache,
152    },
153
154    /// A live timeline for a thread.
155    Thread {
156        /// The root event for the current thread.
157        root_event_id: OwnedEventId,
158
159        /// The cache holding all the events for this focus.
160        event_cache: ThreadEventCache,
161    },
162
163    PinnedEvents {
164        /// The cache holding all the events for this focus.
165        event_cache: PinnedEventsCache,
166    },
167}
168
169impl TimelineFocusKind {
170    /// Returns the [`ReceiptThread`] that should be used for the current
171    /// timeline focus.
172    ///
173    /// Live and event timelines will use the unthreaded read receipt type in
174    /// general, unless they hide in-thread events, in which case they will
175    /// use the main thread.
176    pub(super) fn receipt_thread(&self) -> ReceiptThread {
177        if let Some(thread_root) = self.thread_root() {
178            ReceiptThread::Thread(thread_root.to_owned())
179        } else if self.hide_threaded_events() {
180            ReceiptThread::Main
181        } else {
182            ReceiptThread::Unthreaded
183        }
184    }
185
186    /// Whether to hide in-thread events from the timeline.
187    fn hide_threaded_events(&self) -> bool {
188        match self {
189            TimelineFocusKind::Live { hide_threaded_events, .. } => *hide_threaded_events,
190            TimelineFocusKind::Event { thread_mode, .. } => {
191                matches!(
192                    thread_mode,
193                    TimelineEventFocusThreadMode::Automatic { hide_threaded_events: true }
194                )
195            }
196            TimelineFocusKind::Thread { .. } | TimelineFocusKind::PinnedEvents { .. } => false,
197        }
198    }
199
200    /// Whether the focus is on a thread (from a live thread or a thread
201    /// permalink).
202    fn is_thread(&self) -> bool {
203        self.thread_root().is_some()
204    }
205
206    /// If the focus is a thread, returns its root event ID.
207    fn thread_root(&self) -> Option<&EventId> {
208        match self {
209            TimelineFocusKind::Event { thread_root, .. } => thread_root.get().map(|v| &**v),
210            TimelineFocusKind::Live { .. } | TimelineFocusKind::PinnedEvents { .. } => None,
211            TimelineFocusKind::Thread { root_event_id, .. } => Some(root_event_id),
212        }
213    }
214}
215
216#[derive(Clone, Debug)]
217pub(super) struct TimelineController<P: RoomDataProvider = Room> {
218    /// Inner mutable state.
219    state: Arc<RwLock<TimelineState<P>>>,
220
221    /// Focus data.
222    focus: Arc<TimelineFocusKind>,
223
224    /// A [`RoomDataProvider`] implementation, providing data.
225    ///
226    /// The type is a `RoomDataProvider` to allow testing. In the real world,
227    /// this would normally be a [`Room`].
228    pub(crate) room_data_provider: P,
229
230    /// Settings applied to this timeline.
231    pub(super) settings: TimelineSettings,
232}
233
234#[derive(Clone)]
235pub(super) struct TimelineSettings {
236    /// Should the read receipts and read markers be handled and on which event
237    /// types?
238    pub(super) track_read_receipts: TimelineReadReceiptTracking,
239
240    /// Event filter that controls what's rendered as a timeline item (and thus
241    /// what can carry read receipts).
242    pub(super) event_filter: Arc<TimelineEventFilterFn>,
243
244    /// Are unparsable events added as timeline items of their own kind?
245    pub(super) add_failed_to_parse: bool,
246
247    /// Should the timeline items be grouped by day or month?
248    pub(super) date_divider_mode: DateDividerMode,
249}
250
251#[cfg(not(tarpaulin_include))]
252impl fmt::Debug for TimelineSettings {
253    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
254        f.debug_struct("TimelineSettings")
255            .field("track_read_receipts", &self.track_read_receipts)
256            .field("add_failed_to_parse", &self.add_failed_to_parse)
257            .finish_non_exhaustive()
258    }
259}
260
261impl Default for TimelineSettings {
262    fn default() -> Self {
263        Self {
264            track_read_receipts: TimelineReadReceiptTracking::Disabled,
265            event_filter: Arc::new(default_event_filter),
266            add_failed_to_parse: true,
267            date_divider_mode: DateDividerMode::Daily,
268        }
269    }
270}
271
272/// The default event filter for
273/// [`crate::timeline::TimelineBuilder::event_filter`].
274///
275/// It filters out events that are not rendered by the timeline, including but
276/// not limited to: reactions, edits, redactions on existing messages.
277///
278/// If you have a custom filter, it may be best to chain yours with this one if
279/// you do not want to run into situations where a read receipt is not visible
280/// because it's living on an event that doesn't have a matching timeline item.
281pub fn default_event_filter(event: &AnySyncTimelineEvent, rules: &RoomVersionRules) -> bool {
282    match event {
283        AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomRedaction(ev)) => {
284            if ev.redacts(&rules.redaction).is_some() {
285                // This is a redaction of an existing message, we'll only update the previous
286                // message and not render a new entry.
287                false
288            } else {
289                // This is a redacted entry, that we'll show only if the redacted entity wasn't
290                // a reaction.
291                ev.event_type() != MessageLikeEventType::Reaction
292            }
293        }
294
295        AnySyncTimelineEvent::MessageLike(msg) => {
296            match msg.original_content() {
297                None => {
298                    // This is a redacted entry, that we'll show only if the redacted entity wasn't
299                    // a reaction.
300                    msg.event_type() != MessageLikeEventType::Reaction
301                }
302
303                Some(original_content) => {
304                    match original_content {
305                        AnyMessageLikeEventContent::RoomMessage(content) => {
306                            if content
307                                .relates_to
308                                .as_ref()
309                                .is_some_and(|rel| matches!(rel, Relation::Replacement(_)))
310                            {
311                                // Edits aren't visible by default.
312                                return false;
313                            }
314
315                            match content.msgtype {
316                                MessageType::Audio(_)
317                                | MessageType::Emote(_)
318                                | MessageType::File(_)
319                                | MessageType::Image(_)
320                                | MessageType::Location(_)
321                                | MessageType::Notice(_)
322                                | MessageType::ServerNotice(_)
323                                | MessageType::Text(_)
324                                | MessageType::Video(_)
325                                | MessageType::VerificationRequest(_) => true,
326                                #[cfg(feature = "unstable-msc4274")]
327                                MessageType::Gallery(_) => true,
328                                _ => false,
329                            }
330                        }
331
332                        AnyMessageLikeEventContent::Sticker(_)
333                        | AnyMessageLikeEventContent::UnstablePollStart(
334                            UnstablePollStartEventContent::New(_),
335                        )
336                        | AnyMessageLikeEventContent::CallInvite(_)
337                        | AnyMessageLikeEventContent::RtcNotification(_)
338                        | AnyMessageLikeEventContent::RoomEncrypted(_) => true,
339
340                        // Beacon location-update events are aggregated onto
341                        // their parent `beacon_info` state event's timeline
342                        // item. They are never rendered as standalone items.
343                        AnyMessageLikeEventContent::Beacon(_) => false,
344                        // Ignore decline events, the matching RtcNotification event will be updated
345                        // to reflect the decline.
346                        AnyMessageLikeEventContent::RtcDecline(_) => false,
347
348                        _ => false,
349                    }
350                }
351            }
352        }
353
354        AnySyncTimelineEvent::State(_) => {
355            // All the state events may get displayed by default.
356            true
357        }
358    }
359}
360
361/// Result of calling [`TimelineController::init_focus`].
362pub(super) struct InitFocusResult {
363    /// Did the initialization result in having some events in the timeline?
364    pub has_events: bool,
365    /// If the timeline is a non-live timeline, an extra task that subscribes to
366    /// changes to the focus source.
367    pub focus_task: Option<BackgroundTaskHandle>,
368}
369
370/// Holds the various info about the current call
371#[derive(Clone, Debug, PartialEq)]
372pub struct ActiveCallInfo {
373    /// The list of users in the call
374    pub active_members: HashSet<OwnedUserId>,
375    /// The consensus intent of the call, audio/video
376    pub call_intent: CallIntentConsensus,
377    /// True if the user (with any device) is currently in the call, meaning
378    /// they have joined and haven't left yet.
379    pub is_joined: bool,
380    /// The timestamp of when the call started, in milliseconds since the unix
381    /// epoch. Currently, this is the origin_server_ts of the rtc.notification
382    /// event.
383    pub call_started_ts_millis: Option<MilliSecondsSinceUnixEpoch>,
384}
385
386impl ActiveCallInfo {
387    pub(crate) fn from_info(room_info: RoomInfo, owned_user_id: OwnedUserId) -> Option<Self> {
388        if room_info.has_active_room_call() {
389            Some(ActiveCallInfo {
390                active_members: HashSet::from(room_info.active_room_call_participants()),
391                call_intent: room_info.active_room_call_consensus_intent(),
392                is_joined: room_info.active_room_call_participants().contains(&owned_user_id),
393                call_started_ts_millis: None,
394            })
395        } else {
396            None
397        }
398    }
399
400    pub(crate) fn with_start_time(self, timestamp: Option<MilliSecondsSinceUnixEpoch>) -> Self {
401        Self { call_started_ts_millis: timestamp, ..self }
402    }
403}
404
405impl<P: RoomDataProvider> TimelineController<P> {
406    pub(super) async fn new(
407        room_data_provider: P,
408        focus: &TimelineFocus,
409        event_cache: &EventCache,
410        internal_id_prefix: Option<String>,
411        unable_to_decrypt_hook: Option<Arc<UtdHookManager>>,
412        is_room_encrypted: bool,
413        settings: TimelineSettings,
414    ) -> Result<Self, Error> {
415        let room_id = room_data_provider.room_id();
416
417        let focus = match focus {
418            TimelineFocus::Live { hide_threaded_events } => TimelineFocusKind::Live {
419                hide_threaded_events: *hide_threaded_events,
420                event_cache: event_cache.room(room_id).await?.0,
421            },
422
423            TimelineFocus::Event { target, thread_mode, num_context_events, .. } => {
424                TimelineFocusKind::Event {
425                    event_cache: event_cache
426                        .event_focused(room_id, target, (*thread_mode).into(), *num_context_events)
427                        .await?
428                        .0,
429                    focused_event_id: target.clone(),
430                    // This will be initialized in `Self::init_focus`.
431                    thread_root: OnceLock::new(),
432                    thread_mode: *thread_mode,
433                }
434            }
435
436            TimelineFocus::Thread { root_event_id, .. } => TimelineFocusKind::Thread {
437                event_cache: event_cache.thread(room_id, root_event_id).await?.0,
438                root_event_id: root_event_id.clone(),
439            },
440
441            TimelineFocus::PinnedEvents => TimelineFocusKind::PinnedEvents {
442                event_cache: event_cache.pinned_events(room_id).await?.0,
443            },
444        };
445
446        let focus = Arc::new(focus);
447        let state = Arc::new(RwLock::new(TimelineState::new(
448            focus.clone(),
449            room_data_provider.own_user_id().to_owned(),
450            room_data_provider.room_version_rules(),
451            internal_id_prefix,
452            unable_to_decrypt_hook,
453            is_room_encrypted,
454            None,
455        )));
456
457        Ok(Self { state, focus, room_data_provider, settings })
458    }
459
460    /// Listens to encryption state changes for the room in
461    /// [`matrix_sdk_base::RoomInfo`] and applies the new value to the
462    /// existing timeline items. This will then cause a refresh of those
463    /// timeline items.
464    pub async fn handle_encryption_state_changes(&self) {
465        let mut room_info = self.room_data_provider.room_info();
466
467        // Small function helper to help mark as encrypted.
468        let mark_encrypted = || async {
469            let mut state = self.state.write().await;
470            state.meta.is_room_encrypted = true;
471            state.mark_all_events_as_encrypted();
472        };
473
474        if room_info.get().encryption_state().is_encrypted() {
475            // If the room was already encrypted, it won't toggle to unencrypted, so we can
476            // shut down this task early.
477            mark_encrypted().await;
478            return;
479        }
480
481        while let Some(info) = room_info.next().await {
482            if info.encryption_state().is_encrypted() {
483                mark_encrypted().await;
484                // Once the room is encrypted, it cannot switch back to unencrypted, so our work
485                // here is done.
486                break;
487            }
488        }
489    }
490
491    /// Run a lazy backwards pagination (in live mode).
492    ///
493    /// It adjusts the `count` value of the `Skip` higher-order stream so that
494    /// more items are pushed front in the timeline.
495    ///
496    /// If no more items are available (i.e. if the `count` is zero), this
497    /// method returns `Some(needs)` where `needs` is the number of events that
498    /// must be unlazily backwards paginated.
499    pub(super) async fn live_lazy_paginate_backwards(&self, num_events: u16) -> Option<usize> {
500        let state = self.state.read().await;
501
502        let (count, needs) = state
503            .meta
504            .subscriber_skip_count
505            .compute_next_when_paginating_backwards(num_events.into());
506
507        // This always happens on a live timeline.
508        let is_live_timeline = true;
509        state.meta.subscriber_skip_count.update(count, is_live_timeline);
510
511        needs
512    }
513
514    /// Is this timeline receiving events from sync (aka has a live focus)?
515    pub(super) fn is_live(&self) -> bool {
516        matches!(&*self.focus, TimelineFocusKind::Live { .. })
517    }
518
519    /// Is this timeline focused on a thread?
520    pub(super) fn is_threaded(&self) -> bool {
521        self.focus.is_thread()
522    }
523
524    /// The root of the current thread, for a live thread timeline or a
525    /// permalink to a thread message.
526    pub(super) fn thread_root(&self) -> Option<OwnedEventId> {
527        self.focus.thread_root().map(ToOwned::to_owned)
528    }
529
530    /// Get a copy of the current items in the list.
531    ///
532    /// Cheap because `im::Vector` is cheap to clone.
533    pub(super) async fn items(&self) -> Vector<Arc<TimelineItem>> {
534        self.state.read().await.items.clone_items()
535    }
536
537    #[cfg(test)]
538    pub(super) async fn subscribe_raw(
539        &self,
540    ) -> (Vector<Arc<TimelineItem>>, VectorSubscriberStream<Arc<TimelineItem>>) {
541        self.state.read().await.items.subscribe().into_values_and_stream()
542    }
543
544    pub(super) async fn subscribe(&self) -> (Vector<Arc<TimelineItem>>, TimelineSubscriber) {
545        let state = self.state.read().await;
546
547        TimelineSubscriber::new(&state.items, &state.meta.subscriber_skip_count)
548    }
549
550    pub(super) async fn subscribe_filter_map<U, F>(
551        &self,
552        f: F,
553    ) -> (Vector<U>, FilterMap<VectorSubscriberStream<Arc<TimelineItem>>, F>)
554    where
555        U: Clone,
556        F: Fn(Arc<TimelineItem>) -> Option<U>,
557    {
558        self.state.read().await.items.subscribe().filter_map(f)
559    }
560
561    /// Toggle a reaction locally.
562    ///
563    /// Returns true if the reaction was added, false if it was removed.
564    #[instrument(skip_all)]
565    pub(super) async fn toggle_reaction_local(
566        &self,
567        item_id: &TimelineEventItemId,
568        key: &str,
569        extra_content: Option<serde_json::Map<String, serde_json::Value>>,
570    ) -> Result<bool, Error> {
571        let mut state = self.state.write().await;
572
573        let Some((item_pos, item)) = rfind_event_by_item_id(&state.items, item_id) else {
574            warn!("Timeline item not found, can't add reaction");
575            return Err(Error::FailedToToggleReaction);
576        };
577
578        let user_id = self.room_data_provider.own_user_id();
579        let target = item.identifier();
580
581        // The item says whether we reacted; the registry has the handle and event id.
582        let has_reaction = item.content().reactions().is_some_and(|reactions| {
583            reactions.get(key).is_some_and(|by_user| by_user.contains_key(user_id))
584        });
585        let previous = has_reaction
586            .then(|| state.meta.aggregations.find_reaction(&target, key, user_id).cloned())
587            .flatten();
588
589        if has_reaction && previous.is_none() {
590            warn!("reaction is on the item but unknown to the aggregations");
591            return Ok(false);
592        }
593
594        let Some(previous) = previous else {
595            // Adding the new reaction.
596            match item.handle() {
597                TimelineItemHandle::Local(send_handle) => {
598                    if send_handle
599                        .react(key.to_owned())
600                        .await
601                        .map_err(|err| Error::SendQueueError(err.into()))?
602                        .is_some()
603                    {
604                        trace!("adding a reaction to a local echo");
605                        return Ok(true);
606                    }
607
608                    warn!("couldn't toggle reaction for local echo");
609                    return Ok(false);
610                }
611
612                TimelineItemHandle::Remote(event_id) => {
613                    // Add a reaction through the room data provider.
614                    // No need to reflect the effect locally, since the local echo handling will
615                    // take care of it.
616                    trace!("adding a reaction to a remote echo");
617                    let annotation = Annotation::new(event_id.to_owned(), key.to_owned());
618                    self.room_data_provider
619                        .send(ReactionEventContent::from(annotation).into(), extra_content)
620                        .await?;
621                    return Ok(true);
622                }
623            }
624        };
625
626        trace!("removing a previous reaction");
627
628        if previous.is_local() {
629            // Aborting the local echo is enough: its discard removes the reaction.
630            if let Some(handle) = &previous.send_handle {
631                if !handle.abort().await.map_err(|err| Error::SendQueueError(err.into()))? {
632                    // Impossible state: the reaction has moved from local to echo under our
633                    // feet, but the timeline was supposed to be locked!
634                    warn!("unexpectedly unable to abort sending of local reaction");
635                }
636            } else {
637                warn!("no send handle (this should only happen in testing contexts)");
638            }
639            return Ok(false);
640        }
641
642        let TimelineEventItemId::EventId(event_id) = previous.own_id else {
643            warn!("sent reaction without an event id");
644            return Ok(false);
645        };
646
647        // Assume the redaction will work; we'll re-add the reaction if it didn't.
648        let Some(annotated_event_id) =
649            item.as_remote().map(|event_item| event_item.event_id.clone())
650        else {
651            warn!("remote reaction to remote event, but the associated item isn't remote");
652            return Ok(false);
653        };
654
655        let mut reactions = item.content().reactions().cloned().unwrap_or_default();
656        let reaction_info = reactions.remove_reaction(user_id, key);
657
658        if reaction_info.is_some() {
659            let new_item = item.with_reactions(reactions);
660            state.items.replace(item_pos, new_item);
661        } else {
662            warn!(
663                "reaction is missing on the item, not removing it locally, \
664                 but sending redaction."
665            );
666        }
667
668        // Release the lock before running the request.
669        drop(state);
670
671        trace!("sending redact for a previous reaction");
672        if let Err(err) = self.room_data_provider.redact(&event_id, None, None).await {
673            if let Some(reaction_info) = reaction_info {
674                debug!("sending redact failed, adding the reaction back to the list");
675
676                let mut state = self.state.write().await;
677                if let Some((item_pos, item)) = rfind_event_by_id(&state.items, &annotated_event_id)
678                {
679                    // Re-add the reaction to the mapping.
680                    let mut reactions = item.content().reactions().cloned().unwrap_or_default();
681                    reactions
682                        .entry(key.to_owned())
683                        .or_default()
684                        .insert(user_id.to_owned(), reaction_info);
685                    let new_item = item.with_reactions(reactions);
686                    state.items.replace(item_pos, new_item);
687                } else {
688                    warn!(
689                        "couldn't find item to re-add reaction anymore; \
690                         maybe it's been redacted?"
691                    );
692                }
693            }
694
695            return Err(err);
696        }
697
698        Ok(false)
699    }
700
701    /// Handle updates on events as [`VectorDiff`]s.
702    pub(super) async fn handle_remote_events_with_diffs(
703        &self,
704        diffs: Vec<VectorDiff<TimelineEvent>>,
705        origin: RemoteEventOrigin,
706    ) {
707        if diffs.is_empty() {
708            return;
709        }
710
711        let mut state = self.state.write().await;
712        state
713            .handle_remote_events_with_diffs(
714                diffs,
715                origin,
716                &self.room_data_provider,
717                &self.settings,
718            )
719            .await
720    }
721
722    /// Only handle aggregations received as [`VectorDiff`]s.
723    pub(super) async fn handle_remote_aggregations(
724        &self,
725        diffs: Vec<VectorDiff<TimelineEvent>>,
726        origin: RemoteEventOrigin,
727    ) {
728        if diffs.is_empty() {
729            return;
730        }
731
732        let mut state = self.state.write().await;
733        state
734            .handle_remote_aggregations(diffs, origin, &self.room_data_provider, &self.settings)
735            .await
736    }
737
738    pub(super) async fn clear(&self) {
739        self.state.write().await.clear();
740    }
741
742    /// Replaces the content of the current timeline with initial events.
743    ///
744    /// Also sets up read receipts and the read marker for a live timeline of a
745    /// room.
746    ///
747    /// This is all done with a single lock guard, since we don't want the state
748    /// to be modified between the clear and re-insertion of new events.
749    pub(super) async fn replace_with_initial_remote_events<Events>(
750        &self,
751        events: Events,
752        origin: RemoteEventOrigin,
753    ) where
754        Events: IntoIterator,
755        <Events as IntoIterator>::Item: Into<TimelineEvent>,
756    {
757        let mut state = self.state.write().await;
758
759        let track_read_markers = &self.settings.track_read_receipts;
760        if track_read_markers.is_enabled() {
761            state.populate_initial_user_receipt(&self.room_data_provider, ReceiptType::Read).await;
762            state
763                .populate_initial_user_receipt(&self.room_data_provider, ReceiptType::ReadPrivate)
764                .await;
765        }
766
767        // Replace the events if either the current event list or the new one aren't
768        // empty.
769        // Previously we just had to check the new one wasn't empty because
770        // we did a clear operation before so the current one would always be empty, but
771        // now we may want to replace a populated timeline with an empty one.
772        let mut events = events.into_iter().peekable();
773        if !state.items.is_empty() || events.peek().is_some() {
774            state
775                .replace_with_remote_events(
776                    events,
777                    origin,
778                    &self.room_data_provider,
779                    &self.settings,
780                )
781                .await;
782        }
783
784        if track_read_markers.is_enabled() {
785            if let Some(fully_read_event_id) =
786                self.room_data_provider.load_fully_read_marker().await
787            {
788                state.handle_fully_read_marker(fully_read_event_id);
789            } else if let Some(latest_receipt_event_id) = state
790                .latest_user_read_receipt_timeline_event_id(self.room_data_provider.own_user_id())
791            {
792                // Fall back to read receipt if no fully read marker exists.
793                debug!("no `m.fully_read` marker found, falling back to read receipt");
794                state.handle_fully_read_marker(latest_receipt_event_id);
795            }
796        }
797    }
798
799    pub(super) async fn handle_fully_read_marker(&self, fully_read_event_id: OwnedEventId) {
800        self.state.write().await.handle_fully_read_marker(fully_read_event_id);
801    }
802
803    pub(super) async fn handle_active_call_update(
804        &self,
805        maybe_active_call: Option<ActiveCallInfo>,
806    ) {
807        let mut state = self.state.write().await;
808        let mut txn = state.transaction();
809
810        // Store the current active call info in metadata for new RtcNotification items
811        txn.meta.active_call = maybe_active_call.clone();
812
813        if let Some(existing_event_id) = &txn.meta.active_rtc_notification_event_id {
814            // Clean up the notification event
815            let last_notification = rfind_event_by_id(&txn.items, existing_event_id);
816            if let Some((last_idx, last_notification)) = last_notification {
817                let updated_content = match last_notification.content() {
818                    TimelineItemContent::RtcNotification {
819                        call_intent,
820                        declined_by,
821                        active_call_info: _active_call_info,
822                    } => Some(TimelineItemContent::RtcNotification {
823                        call_intent: call_intent.to_owned(),
824                        declined_by: declined_by.clone(),
825                        active_call_info: maybe_active_call
826                            .clone()
827                            .map(|info| info.with_start_time(last_notification.timestamp.into())),
828                    }),
829                    _ => None,
830                };
831                if let Some(new_content) = updated_content {
832                    let new_event_item = last_notification.inner.with_content(new_content);
833                    let new_timeline_item =
834                        TimelineItem::new(new_event_item, last_notification.internal_id.clone());
835                    txn.items.replace(last_idx, new_timeline_item);
836                }
837
838                if maybe_active_call.is_none() {
839                    // There is no active rtc_notification anymore
840                    txn.meta.active_rtc_notification_event_id = None;
841                }
842            }
843        }
844
845        txn.commit();
846    }
847
848    pub(super) async fn handle_read_receipt_event(&self, event: ReceiptEventContent) {
849        // Don't even take the lock if there are no events to process.
850        if event.is_empty() {
851            return;
852        }
853
854        let mut state = self.state.write().await;
855        state.handle_read_receipt(event, &self.room_data_provider).await;
856    }
857
858    /// Creates the local echo for an event we're sending.
859    #[instrument(skip_all)]
860    pub(super) async fn handle_local_event(
861        &self,
862        txn_id: OwnedTransactionId,
863        content: AnyMessageLikeEventContent,
864        send_handle: Option<SendHandle>,
865    ) {
866        let sender = self.room_data_provider.own_user_id().to_owned();
867        let profile = self.room_data_provider.profile_from_user_id(&sender).await;
868
869        let date_divider_mode = self.settings.date_divider_mode.clone();
870
871        let mut state = self.state.write().await;
872        state
873            .handle_local_event(sender, profile, date_divider_mode, txn_id, send_handle, content)
874            .await;
875    }
876
877    /// Update the send state of a local event represented by a transaction ID.
878    ///
879    /// If the corresponding local timeline item is missing, a warning is
880    /// raised.
881    #[instrument(skip(self))]
882    pub(super) async fn update_event_send_state(
883        &self,
884        txn_id: &TransactionId,
885        send_state: EventSendState,
886    ) {
887        let mut state = self.state.write().await;
888        let mut txn = state.transaction();
889
890        let new_event_id: Option<&EventId> =
891            as_variant!(&send_state, EventSendState::Sent { event_id } => event_id);
892
893        // The local echoes are always at the end of the timeline, we must first make
894        // sure the remote echo hasn't showed up yet.
895        if rfind_event_item(&txn.items, |it| {
896            new_event_id.is_some() && it.event_id() == new_event_id && it.as_remote().is_some()
897        })
898        .is_some()
899        {
900            // Remote echo already received. This is very unlikely.
901            trace!("Remote echo received before send-event response");
902
903            let local_echo = rfind_event_item(&txn.items, |it| it.transaction_id() == Some(txn_id));
904
905            // If there's both the remote echo and a local echo, that means the
906            // remote echo was received before the response *and* contained no
907            // transaction ID (and thus duplicated the local echo).
908            if let Some((idx, _)) = local_echo {
909                warn!("Message echo got duplicated, removing the local one");
910                txn.items.remove(idx);
911
912                // Adjust the date dividers, if needs be.
913                let mut adjuster =
914                    DateDividerAdjuster::new(self.settings.date_divider_mode.clone());
915                adjuster.run(&mut txn.items, &mut txn.meta);
916            }
917
918            txn.commit();
919            return;
920        }
921
922        // Look for the local event by the transaction ID or event ID.
923        let result = rfind_event_item(&txn.items, |it| {
924            it.transaction_id() == Some(txn_id)
925                || new_event_id.is_some()
926                    && it.event_id() == new_event_id
927                    && it.as_local().is_some()
928        });
929
930        let Some((idx, item)) = result else {
931            // Not a standalone item: maybe one of our aggregations.
932            if txn.meta.aggregations.update_send_state(
933                txn_id.to_owned(),
934                send_state,
935                &mut txn.items,
936                &txn.meta.room_version_rules,
937            ) {
938                trace!("Updated the send state of an aggregation");
939                txn.commit();
940                return;
941            }
942
943            warn!("Timeline item not found, can't update send state");
944            return;
945        };
946
947        let Some(local_item) = item.as_local() else {
948            warn!("We looked for a local item, but it transitioned to remote.");
949            return;
950        };
951
952        // The event was already marked as sent, that's a broken state, let's
953        // emit an error but also override to the given sent state.
954        if let EventSendState::Sent { event_id: existing_event_id } = &local_item.send_state {
955            error!(?existing_event_id, ?new_event_id, "Local echo already marked as sent");
956        }
957
958        // If the event has just been marked as sent, update the aggregations mapping to
959        // take that into account.
960        if let Some(new_event_id) = new_event_id {
961            txn.meta.aggregations.mark_target_as_sent(txn_id.to_owned(), new_event_id.to_owned());
962        }
963
964        let new_item = item.with_inner_kind(local_item.with_send_state(send_state));
965        txn.items.replace(idx, new_item);
966
967        txn.commit();
968    }
969
970    pub(super) async fn discard_local_echo(&self, txn_id: &TransactionId) -> bool {
971        let mut state = self.state.write().await;
972
973        if let Some((idx, _)) =
974            rfind_event_item(&state.items, |it| it.transaction_id() == Some(txn_id))
975        {
976            let mut txn = state.transaction();
977
978            txn.items.remove(idx);
979
980            // A read marker or a date divider may have been inserted before the local echo.
981            // Ensure both are up to date.
982            let mut adjuster = DateDividerAdjuster::new(self.settings.date_divider_mode.clone());
983            adjuster.run(&mut txn.items, &mut txn.meta);
984
985            txn.meta.update_read_marker(&mut txn.items);
986
987            txn.commit();
988
989            debug!("discarded local echo");
990            return true;
991        }
992
993        // Avoid multiple mutable and immutable borrows of the lock guard by explicitly
994        // dereferencing it once.
995        let mut txn = state.transaction();
996
997        // Look if this was a local aggregation.
998        let found_aggregation = match txn.meta.aggregations.try_remove_aggregation(
999            &TimelineEventItemId::TransactionId(txn_id.to_owned()),
1000            &mut txn.items,
1001        ) {
1002            Ok(val) => val,
1003            Err(err) => {
1004                warn!("error when discarding local echo for an aggregation: {err}");
1005                // The aggregation has been found, it's just that we couldn't discard it.
1006                true
1007            }
1008        };
1009
1010        if found_aggregation {
1011            txn.commit();
1012        }
1013
1014        found_aggregation
1015    }
1016
1017    pub(super) async fn replace_local_echo(
1018        &self,
1019        txn_id: &TransactionId,
1020        content: AnyMessageLikeEventContent,
1021    ) -> bool {
1022        let AnyMessageLikeEventContent::RoomMessage(content) = content else {
1023            // Ideally, we'd support replacing local echoes for a reaction, etc., but
1024            // handling RoomMessage should be sufficient in most cases. Worst
1025            // case, the local echo will be sent Soonâ„¢ and we'll get another chance at
1026            // editing the event then.
1027            warn!("Replacing a local echo for a non-RoomMessage-like event NYI");
1028            return false;
1029        };
1030
1031        let mut state = self.state.write().await;
1032        let mut txn = state.transaction();
1033
1034        let Some((idx, prev_item)) =
1035            rfind_event_item(&txn.items, |it| it.transaction_id() == Some(txn_id))
1036        else {
1037            debug!("Can't find local echo to replace");
1038            return false;
1039        };
1040
1041        // Reuse the previous local echo's state, but reset the send state to not sent
1042        // (per API contract).
1043        let ti_kind = {
1044            let Some(prev_local_item) = prev_item.as_local() else {
1045                warn!("We looked for a local item, but it transitioned as remote??");
1046                return false;
1047            };
1048            // If the local echo had an upload progress, retain it.
1049            let progress = as_variant!(&prev_local_item.send_state,
1050                EventSendState::NotSentYet { progress } => progress.clone())
1051            .flatten();
1052            prev_local_item.with_send_state(EventSendState::NotSentYet { progress })
1053        };
1054
1055        // Replace the local-related state (kind) and the content state.
1056        let new_item = TimelineItem::new(
1057            prev_item.with_kind(ti_kind).with_content(TimelineItemContent::message(
1058                content.msgtype,
1059                content.mentions,
1060                prev_item.content().reactions().cloned().unwrap_or_default(),
1061                prev_item.content().thread_root(),
1062                prev_item.content().in_reply_to(),
1063                prev_item.content().thread_summary(),
1064            )),
1065            prev_item.internal_id.to_owned(),
1066        );
1067
1068        txn.items.replace(idx, new_item);
1069
1070        // This doesn't change the original sending time, so there's no need to adjust
1071        // date dividers.
1072
1073        txn.commit();
1074
1075        debug!("Replaced local echo");
1076        true
1077    }
1078
1079    pub(super) async fn compute_redecryption_candidates(
1080        &self,
1081    ) -> (BTreeSet<String>, BTreeSet<String>) {
1082        let state = self.state.read().await;
1083        compute_redecryption_candidates(&state.items)
1084    }
1085
1086    pub(super) async fn set_sender_profiles_pending(&self) {
1087        self.set_non_ready_sender_profiles(TimelineDetails::Pending).await;
1088    }
1089
1090    pub(super) async fn set_sender_profiles_error(&self, error: Arc<matrix_sdk::Error>) {
1091        self.set_non_ready_sender_profiles(TimelineDetails::Error(error)).await;
1092    }
1093
1094    async fn set_non_ready_sender_profiles(&self, profile_state: TimelineDetails<Profile>) {
1095        self.state.write().await.items.for_each(|mut entry| {
1096            let Some(event_item) = entry.as_event() else { return };
1097            if !matches!(event_item.sender_profile(), TimelineDetails::Ready(_)) {
1098                let new_item = entry.with_kind(TimelineItemKind::Event(
1099                    event_item.with_sender_profile(profile_state.clone()),
1100                ));
1101                ObservableItemsEntry::replace(&mut entry, new_item);
1102            }
1103        });
1104    }
1105
1106    pub(super) async fn update_missing_sender_profiles(&self) {
1107        trace!("Updating missing sender profiles");
1108
1109        let mut state = self.state.write().await;
1110        let mut entries = state.items.entries();
1111        while let Some(mut entry) = entries.next() {
1112            let Some(event_item) = entry.as_event() else { continue };
1113            let event_id = event_item.event_id().map(debug);
1114            let transaction_id = event_item.transaction_id().map(debug);
1115
1116            if event_item.sender_profile().is_ready() {
1117                trace!(event_id, transaction_id, "Profile already set");
1118                continue;
1119            }
1120
1121            match self.room_data_provider.profile_from_user_id(event_item.sender()).await {
1122                Some(profile) => {
1123                    trace!(event_id, transaction_id, "Adding profile");
1124                    let updated_item =
1125                        event_item.with_sender_profile(TimelineDetails::Ready(profile));
1126                    let new_item = entry.with_kind(updated_item);
1127                    ObservableItemsEntry::replace(&mut entry, new_item);
1128                }
1129                None => {
1130                    if !event_item.sender_profile().is_unavailable() {
1131                        trace!(event_id, transaction_id, "Marking profile unavailable");
1132                        let updated_item =
1133                            event_item.with_sender_profile(TimelineDetails::Unavailable);
1134                        let new_item = entry.with_kind(updated_item);
1135                        ObservableItemsEntry::replace(&mut entry, new_item);
1136                    } else {
1137                        debug!(event_id, transaction_id, "Profile already marked unavailable");
1138                    }
1139                }
1140            }
1141        }
1142
1143        trace!("Done updating missing sender profiles");
1144    }
1145
1146    /// Update the profiles of the given senders, even if they are ready.
1147    pub(super) async fn force_update_sender_profiles(&self, sender_ids: &BTreeSet<&UserId>) {
1148        trace!("Forcing update of sender profiles: {sender_ids:?}");
1149
1150        let mut state = self.state.write().await;
1151        let mut entries = state.items.entries();
1152        while let Some(mut entry) = entries.next() {
1153            let Some(event_item) = entry.as_event() else { continue };
1154            if !sender_ids.contains(event_item.sender()) {
1155                continue;
1156            }
1157
1158            let event_id = event_item.event_id().map(debug);
1159            let transaction_id = event_item.transaction_id().map(debug);
1160
1161            match self.room_data_provider.profile_from_user_id(event_item.sender()).await {
1162                Some(profile) => {
1163                    if matches!(event_item.sender_profile(), TimelineDetails::Ready(old_profile) if *old_profile == profile)
1164                    {
1165                        debug!(event_id, transaction_id, "Profile already up-to-date");
1166                    } else {
1167                        trace!(event_id, transaction_id, "Updating profile");
1168                        let updated_item =
1169                            event_item.with_sender_profile(TimelineDetails::Ready(profile));
1170                        let new_item = entry.with_kind(updated_item);
1171                        ObservableItemsEntry::replace(&mut entry, new_item);
1172                    }
1173                }
1174                None => {
1175                    if !event_item.sender_profile().is_unavailable() {
1176                        trace!(event_id, transaction_id, "Marking profile unavailable");
1177                        let updated_item =
1178                            event_item.with_sender_profile(TimelineDetails::Unavailable);
1179                        let new_item = entry.with_kind(updated_item);
1180                        ObservableItemsEntry::replace(&mut entry, new_item);
1181                    } else {
1182                        debug!(event_id, transaction_id, "Profile already marked unavailable");
1183                    }
1184                }
1185            }
1186        }
1187
1188        trace!("Done forcing update of sender profiles");
1189    }
1190
1191    #[cfg(test)]
1192    pub(super) async fn handle_read_receipts(&self, receipt_event_content: ReceiptEventContent) {
1193        let own_user_id = self.room_data_provider.own_user_id();
1194        self.state.write().await.handle_read_receipts(receipt_event_content, own_user_id);
1195    }
1196
1197    /// Get the latest read receipt for the given user.
1198    ///
1199    /// Useful to get the latest read receipt, whether it's private or public.
1200    pub(super) async fn latest_user_read_receipt(
1201        &self,
1202        user_id: &UserId,
1203    ) -> Option<(OwnedEventId, Receipt)> {
1204        let receipt_thread = self.focus.receipt_thread();
1205
1206        self.state
1207            .read()
1208            .await
1209            .latest_user_read_receipt(
1210                user_id,
1211                receipt_thread,
1212                &self.room_data_provider,
1213                read_receipts::ImplicitReadReceipts::Include,
1214            )
1215            .await
1216    }
1217
1218    /// Get the ID of the timeline event with the latest read receipt for the
1219    /// given user.
1220    pub(super) async fn latest_user_read_receipt_timeline_event_id(
1221        &self,
1222        user_id: &UserId,
1223    ) -> Option<OwnedEventId> {
1224        self.state.read().await.latest_user_read_receipt_timeline_event_id(user_id)
1225    }
1226
1227    /// Subscribe to changes in the read receipts of our own user.
1228    pub async fn subscribe_own_user_read_receipts_changed(
1229        &self,
1230    ) -> impl Stream<Item = ()> + use<P> {
1231        self.state.read().await.meta.read_receipts.subscribe_own_user_read_receipts_changed()
1232    }
1233
1234    /// Handle a room send update that's a new local echo.
1235    pub(crate) async fn handle_local_echo(&self, echo: LocalEcho) {
1236        match echo.content {
1237            LocalEchoContent::Event { serialized_event, send_handle, send_error } => {
1238                let content = match serialized_event.deserialize() {
1239                    Ok(d) => d,
1240                    Err(err) => {
1241                        warn!("error deserializing local echo: {err}");
1242                        return;
1243                    }
1244                };
1245
1246                self.handle_local_event(echo.transaction_id.clone(), content, Some(send_handle))
1247                    .await;
1248
1249                if let Some(send_error) = send_error {
1250                    self.update_event_send_state(
1251                        &echo.transaction_id,
1252                        EventSendState::SendingFailed {
1253                            error: Arc::new(matrix_sdk::Error::SendQueueWedgeError(Box::new(
1254                                send_error,
1255                            ))),
1256                            is_recoverable: false,
1257                        },
1258                    )
1259                    .await;
1260                }
1261            }
1262
1263            LocalEchoContent::React { key, send_handle, applies_to } => {
1264                self.handle_local_reaction(key, send_handle, applies_to).await;
1265            }
1266
1267            LocalEchoContent::Redaction { redacts, send_handle, send_error, .. } => {
1268                self.handle_local_redaction(
1269                    echo.transaction_id.clone(),
1270                    redacts,
1271                    Some(send_handle),
1272                )
1273                .await;
1274
1275                if let Some(send_error) = send_error {
1276                    self.update_event_send_state(
1277                        &echo.transaction_id,
1278                        EventSendState::SendingFailed {
1279                            error: Arc::new(matrix_sdk::Error::SendQueueWedgeError(Box::new(
1280                                send_error,
1281                            ))),
1282                            is_recoverable: false,
1283                        },
1284                    )
1285                    .await;
1286                }
1287            }
1288        }
1289    }
1290
1291    /// Adds a reaction (local echo) to a local echo.
1292    #[instrument(skip(self, send_handle))]
1293    async fn handle_local_reaction(
1294        &self,
1295        reaction_key: String,
1296        send_handle: SendReactionHandle,
1297        applies_to: OwnedTransactionId,
1298    ) {
1299        let mut state = self.state.write().await;
1300        let mut tr = state.transaction();
1301
1302        let target = TimelineEventItemId::TransactionId(applies_to);
1303
1304        let reaction_txn_id = send_handle.transaction_id().to_owned();
1305        let aggregation = Aggregation::new_local(
1306            TimelineEventItemId::TransactionId(reaction_txn_id),
1307            AggregationKind::Reaction {
1308                key: reaction_key.clone(),
1309                sender: self.room_data_provider.own_user_id().to_owned(),
1310                timestamp: MilliSecondsSinceUnixEpoch::now(),
1311            },
1312            Some(AggregationSendHandle::Reaction(send_handle)),
1313        );
1314
1315        tr.meta.aggregations.add(target.clone(), aggregation.clone());
1316        find_item_and_apply_aggregation(
1317            &tr.meta.aggregations,
1318            &mut tr.items,
1319            &target,
1320            aggregation,
1321            &tr.meta.room_version_rules,
1322        );
1323
1324        tr.commit();
1325    }
1326
1327    /// Applies a local echo of a redaction.
1328    pub(super) async fn handle_local_redaction(
1329        &self,
1330        txn_id: OwnedTransactionId,
1331        redacts: OwnedEventId,
1332        send_handle: Option<SendRedactionHandle>,
1333    ) {
1334        let mut state = self.state.write().await;
1335        let mut tr = state.transaction();
1336
1337        let target = TimelineEventItemId::EventId(redacts);
1338
1339        let aggregation = Aggregation::new_local(
1340            TimelineEventItemId::TransactionId(txn_id),
1341            AggregationKind::Redaction,
1342            send_handle.map(AggregationSendHandle::Redaction),
1343        );
1344
1345        tr.meta.aggregations.add(target.clone(), aggregation.clone());
1346        find_item_and_apply_aggregation(
1347            &tr.meta.aggregations,
1348            &mut tr.items,
1349            &target,
1350            aggregation,
1351            &tr.meta.room_version_rules,
1352        );
1353
1354        tr.commit();
1355    }
1356
1357    /// Handle a single room send queue update.
1358    pub(crate) async fn handle_room_send_queue_update(&self, update: RoomSendQueueUpdate) {
1359        match update {
1360            RoomSendQueueUpdate::NewLocalEvent(echo) => {
1361                self.handle_local_echo(echo).await;
1362            }
1363
1364            RoomSendQueueUpdate::CancelledLocalEvent { transaction_id } => {
1365                if !self.discard_local_echo(&transaction_id).await {
1366                    warn!("couldn't find the local echo to discard");
1367                }
1368            }
1369
1370            RoomSendQueueUpdate::ReplacedLocalEvent { transaction_id, new_content } => {
1371                let content = match new_content.deserialize() {
1372                    Ok(d) => d,
1373                    Err(err) => {
1374                        warn!("error deserializing local echo (upon edit): {err}");
1375                        return;
1376                    }
1377                };
1378
1379                if !self.replace_local_echo(&transaction_id, content).await {
1380                    warn!("couldn't find the local echo to replace");
1381                }
1382            }
1383
1384            RoomSendQueueUpdate::SendError { transaction_id, error, is_recoverable } => {
1385                self.update_event_send_state(
1386                    &transaction_id,
1387                    EventSendState::SendingFailed { error, is_recoverable },
1388                )
1389                .await;
1390            }
1391
1392            RoomSendQueueUpdate::RetryEvent { transaction_id } => {
1393                self.update_event_send_state(
1394                    &transaction_id,
1395                    EventSendState::NotSentYet { progress: None },
1396                )
1397                .await;
1398            }
1399
1400            RoomSendQueueUpdate::SentEvent { transaction_id, event_id } => {
1401                self.update_event_send_state(&transaction_id, EventSendState::Sent { event_id })
1402                    .await;
1403            }
1404
1405            RoomSendQueueUpdate::MediaUpload { related_to, index, progress, .. } => {
1406                self.update_event_send_state(
1407                    &related_to,
1408                    EventSendState::NotSentYet {
1409                        progress: Some(MediaUploadProgress { index, progress }),
1410                    },
1411                )
1412                .await;
1413            }
1414        }
1415    }
1416
1417    /// Insert a timeline start item at the beginning of the room, if it's
1418    /// missing.
1419    pub async fn insert_timeline_start_if_missing(&self) {
1420        let mut state = self.state.write().await;
1421        let mut txn = state.transaction();
1422        txn.items.push_timeline_start_if_missing(
1423            txn.meta.new_timeline_item(VirtualTimelineItem::TimelineStart),
1424        );
1425        txn.commit();
1426    }
1427
1428    /// Create a [`EmbeddedEvent`] from an arbitrary event, be it in the
1429    /// timeline or not.
1430    ///
1431    /// Can be `None` if the event cannot be represented as a standalone item,
1432    /// because it's an aggregation.
1433    pub(super) async fn make_replied_to(
1434        &self,
1435        event: TimelineEvent,
1436    ) -> Result<Option<EmbeddedEvent>, Error> {
1437        let state = self.state.read().await;
1438        EmbeddedEvent::try_from_timeline_event(event, &self.room_data_provider, &state.meta).await
1439    }
1440}
1441
1442impl TimelineController {
1443    pub(super) fn room(&self) -> &Room {
1444        &self.room_data_provider
1445    }
1446
1447    /// Initializes the configured timeline focus with appropriate data.
1448    ///
1449    /// Should be called only once after creation of the [`TimelineController`],
1450    /// with all its fields set.
1451    pub(super) async fn init_focus(&self) -> Result<InitFocusResult, Error> {
1452        match self.focus.deref() {
1453            TimelineFocusKind::Live { event_cache, .. } => {
1454                // Retrieve the cached events, and add them to the timeline.
1455                let events = event_cache.events().await?;
1456
1457                let has_events = !events.is_empty();
1458
1459                self.replace_with_initial_remote_events(events, RemoteEventOrigin::Cache).await;
1460
1461                match event_cache.pagination().status().get() {
1462                    PaginationStatus::Idle { hit_timeline_start } => {
1463                        if hit_timeline_start {
1464                            // Eagerly insert the timeline start item, since pagination claims
1465                            // we've already hit the timeline start.
1466                            self.insert_timeline_start_if_missing().await;
1467                        }
1468                    }
1469                    PaginationStatus::Paginating => {}
1470                }
1471
1472                Ok(InitFocusResult { has_events, focus_task: None })
1473            }
1474
1475            TimelineFocusKind::Event {
1476                focused_event_id: event_id,
1477                thread_mode,
1478                thread_root: focus_thread_root,
1479                event_cache,
1480                ..
1481            } => {
1482                let (events, receiver) = event_cache.subscribe().await?;
1483
1484                let has_events = !events.is_empty();
1485
1486                // Ask the cache for the thread root, if it managed to extract one or decided
1487                // that the target event was the thread root.
1488                if let Some(thread_root) = event_cache.thread_root().await? {
1489                    focus_thread_root.get_or_init(|| thread_root);
1490                }
1491
1492                self.replace_with_initial_remote_events(events, RemoteEventOrigin::Pagination)
1493                    .await;
1494
1495                let task = self
1496                    .room_data_provider
1497                    .client()
1498                    .task_monitor()
1499                    .spawn_infinite_task(
1500                        "timeline::event_focused_cache_updates",
1501                        event_focused_task(
1502                            event_id.clone(),
1503                            (*thread_mode).into(),
1504                            event_cache.clone(),
1505                            self.clone(),
1506                            receiver,
1507                        ),
1508                    )
1509                    .abort_on_drop();
1510
1511                Ok(InitFocusResult { has_events, focus_task: Some(task) })
1512            }
1513
1514            TimelineFocusKind::Thread { event_cache, .. } => {
1515                let (has_events, subscriber) = self.init_with_thread_root(event_cache).await?;
1516
1517                let room = &self.room_data_provider;
1518                let span = info_span!(
1519                    parent: Span::none(),
1520                    "thread_live_update_handler",
1521                    room_id = ?room.room_id(),
1522                );
1523                span.follows_from(Span::current());
1524
1525                let task = room
1526                    .client()
1527                    .task_monitor()
1528                    .spawn_infinite_task(
1529                        "timeline::thread_event_cache_updates",
1530                        thread_updates_task(subscriber, event_cache.clone(), self.clone())
1531                            .instrument(span),
1532                    )
1533                    .abort_on_drop();
1534
1535                Ok(InitFocusResult { has_events, focus_task: Some(task) })
1536            }
1537
1538            TimelineFocusKind::PinnedEvents { event_cache } => {
1539                let (initial_events, pinned_events_recv) = event_cache.subscribe().await?;
1540
1541                let has_events = !initial_events.is_empty();
1542
1543                self.replace_with_initial_remote_events(
1544                    initial_events,
1545                    RemoteEventOrigin::Pagination,
1546                )
1547                .await;
1548
1549                let task = self
1550                    .room_data_provider
1551                    .client()
1552                    .task_monitor()
1553                    .spawn_infinite_task(
1554                        "timeline::pinned_events_cache_updates",
1555                        pinned_events_task(event_cache.clone(), self.clone(), pinned_events_recv),
1556                    )
1557                    .abort_on_drop();
1558
1559                Ok(InitFocusResult { has_events, focus_task: Some(task) })
1560            }
1561        }
1562    }
1563
1564    /// (Re-)initialise a timeline using [`TimelineFocus::Thread`] with cached
1565    /// threaded events and secondary relations.
1566    ///
1567    /// Returns whether there were any events added to the timeline, and a
1568    /// receiver to return updates after the initial events have been
1569    /// inserted in the timeline.
1570    pub(super) async fn init_with_thread_root(
1571        &self,
1572        event_cache: &ThreadEventCache,
1573    ) -> Result<(bool, EventCacheSubscriber<ThreadEventCacheUpdate>), Error> {
1574        let (events, subscriber) = event_cache.subscribe().await?;
1575        let has_events = !events.is_empty();
1576
1577        // For each event, we also need to find the related events, as they don't
1578        // include the thread relationship, they won't be included in
1579        // the initial list of events.
1580        //
1581        // The lookups are independent store queries, so run them together
1582        // rather than awaiting them one after the other. `try_join_all`
1583        // keeps the input order, so the related events are collected in the
1584        // same order as before.
1585        let lookups = events
1586            .iter()
1587            .filter_map(|event| event.event_id())
1588            .map(|event_id| event_cache.find_event_with_relations(event_id, None));
1589
1590        let mut related_events = Vector::new();
1591        for (_original, related) in try_join_all(lookups).await?.into_iter().flatten() {
1592            related_events.extend(related);
1593        }
1594
1595        self.replace_with_initial_remote_events(events, RemoteEventOrigin::Cache).await;
1596
1597        // Now that we've inserted the thread events, add the aggregations too.
1598        if !related_events.is_empty() {
1599            self.handle_remote_aggregations(
1600                vec![VectorDiff::Append { values: related_events }],
1601                RemoteEventOrigin::Cache,
1602            )
1603            .await;
1604        }
1605
1606        Ok((has_events, subscriber))
1607    }
1608
1609    /// Given an event identifier, will fetch the details for the event it's
1610    /// replying to, if applicable.
1611    #[instrument(skip(self))]
1612    pub(super) async fn fetch_in_reply_to_details(&self, event_id: &EventId) -> Result<(), Error> {
1613        let state_guard = self.state.write().await;
1614        let (index, item) = rfind_event_by_id(&state_guard.items, event_id)
1615            .ok_or(Error::EventNotInTimeline(TimelineEventItemId::EventId(event_id.to_owned())))?;
1616        let remote_item = item
1617            .as_remote()
1618            .ok_or(Error::EventNotInTimeline(TimelineEventItemId::EventId(event_id.to_owned())))?
1619            .clone();
1620
1621        let TimelineItemContent::MsgLike(msglike) = item.content().clone() else {
1622            debug!("Event is not a message");
1623            return Ok(());
1624        };
1625        let Some(in_reply_to) = msglike.in_reply_to.clone() else {
1626            debug!("Event is not a reply");
1627            return Ok(());
1628        };
1629        if let TimelineDetails::Pending = &in_reply_to.event {
1630            debug!("Replied-to event is already being fetched");
1631            return Ok(());
1632        }
1633        if let TimelineDetails::Ready(_) = &in_reply_to.event {
1634            debug!("Replied-to event has already been fetched");
1635            return Ok(());
1636        }
1637
1638        let internal_id = item.internal_id.to_owned();
1639        let item = item.clone();
1640        let event = fetch_replied_to_event(
1641            state_guard,
1642            &self.state,
1643            index,
1644            &item,
1645            internal_id,
1646            &msglike,
1647            &in_reply_to.event_id,
1648            self.room(),
1649        )
1650        .await?;
1651
1652        // We need to be sure to have the latest position of the event as it might have
1653        // changed while waiting for the request.
1654        let mut state = self.state.write().await;
1655        let (index, item) = rfind_event_by_id(&state.items, &remote_item.event_id)
1656            .ok_or(Error::EventNotInTimeline(TimelineEventItemId::EventId(event_id.to_owned())))?;
1657
1658        // Check the state of the event again, it might have been redacted while
1659        // the request was in-flight.
1660        let TimelineItemContent::MsgLike(MsgLikeContent {
1661            kind: MsgLikeKind::Message(message),
1662            reactions,
1663            thread_root,
1664            in_reply_to,
1665            thread_summary,
1666        }) = item.content().clone()
1667        else {
1668            info!("Event is no longer a message (redacted?)");
1669            return Ok(());
1670        };
1671        let Some(in_reply_to) = in_reply_to else {
1672            warn!("Event no longer has a reply (bug?)");
1673            return Ok(());
1674        };
1675
1676        // Now that we've received the content of the replied-to event, replace the
1677        // replied-to content in the item with it.
1678        trace!("Updating in-reply-to details");
1679        let internal_id = item.internal_id.to_owned();
1680        let mut item = item.clone();
1681        item.set_content(TimelineItemContent::MsgLike(MsgLikeContent {
1682            kind: MsgLikeKind::Message(message),
1683            reactions,
1684            thread_root,
1685            in_reply_to: Some(InReplyToDetails { event_id: in_reply_to.event_id, event }),
1686            thread_summary,
1687        }));
1688        state.items.replace(index, TimelineItem::new(item, internal_id));
1689
1690        Ok(())
1691    }
1692
1693    /// Returns the thread that should be used for a read receipt based on the
1694    /// current focus of the timeline and the receipt type.
1695    ///
1696    /// A `SendReceiptType::FullyRead` will always use
1697    /// `ReceiptThread::Unthreaded`
1698    pub(super) fn infer_thread_for_read_receipt(
1699        &self,
1700        receipt_type: &SendReceiptType,
1701    ) -> ReceiptThread {
1702        if matches!(receipt_type, SendReceiptType::FullyRead) {
1703            ReceiptThread::Unthreaded
1704        } else {
1705            self.focus.receipt_thread()
1706        }
1707    }
1708
1709    /// Decide whether a read receipt should be sent, and which event it should
1710    /// target.
1711    ///
1712    /// The returned event may differ from `event_id`: a read receipt should not
1713    /// point at one of the user's own events (see the Matrix spec's [Receipts
1714    /// module]), so if `event_id` is one of theirs, the latest unread event
1715    /// before it that is targeted instead.
1716    ///
1717    /// - When there's no such earlier event and `is_marking_room_as_read` is
1718    ///   `false`, [`SendReceiptDecision::DoNotSend`] is returned.
1719    /// - When `is_marking_room_as_read` is `true`, the receipt falls back to
1720    ///   `event_id` itself if it is explicitly unread, so that the homeserver
1721    ///   still recomputes the push/badge count.
1722    ///
1723    /// [Receipts module]: https://spec.matrix.org/latest/client-server-api/#receipts
1724    pub(super) async fn should_send_receipt(
1725        &self,
1726        receipt_type: &SendReceiptType,
1727        receipt_thread: &ReceiptThread,
1728        event_id: &EventId,
1729        is_marking_room_as_read: bool,
1730    ) -> SendReceiptDecision {
1731        let own_user_id = self.room().own_user_id();
1732        let state = self.state.read().await;
1733        let room = self.room();
1734        let all_remote_events = state.items.all_remote_events();
1735
1736        // Resolve the event the receipt should target, redirecting away from the
1737        // user's own events for read receipts.
1738        let target_event_id = match receipt_type {
1739            SendReceiptType::Read | SendReceiptType::ReadPrivate => {
1740                let is_own_event = all_remote_events
1741                    .get_by_event_id(event_id)
1742                    .and_then(|event_meta| event_meta.sender.as_deref())
1743                    == Some(own_user_id);
1744
1745                if is_own_event {
1746                    let filter_out_thread_events = match self.focus() {
1747                        TimelineFocusKind::Thread { .. } | TimelineFocusKind::Event { .. } => false,
1748                        TimelineFocusKind::Live { hide_threaded_events, .. } => {
1749                            *hide_threaded_events
1750                        }
1751                        TimelineFocusKind::PinnedEvents { .. } => true,
1752                    };
1753
1754                    let previous_event = all_remote_events
1755                        .iter()
1756                        .rev()
1757                        // Only consider the events that precede the requested one.
1758                        .skip_while(|event_meta| event_meta.event_id != *event_id)
1759                        .skip(1)
1760                        // Never point a read receipt at one of the user's own events.
1761                        .filter(|event_meta| event_meta.sender.as_deref() != Some(own_user_id))
1762                        .find_map(|event_meta| {
1763                            if !filter_out_thread_events {
1764                                Some(event_meta.event_id.clone())
1765                            } else if event_meta.thread_root_id.is_none() {
1766                                if let Some(TimelineEventItemId::EventId(aggregated_event_id)) =
1767                                    state.meta.aggregations.is_aggregation_of(
1768                                        &TimelineEventItemId::EventId(event_meta.event_id.clone()),
1769                                    )
1770                                    && let Some(target_meta) =
1771                                        all_remote_events.get_by_event_id(aggregated_event_id)
1772                                    && target_meta.thread_root_id.is_some()
1773                                {
1774                                    None
1775                                } else {
1776                                    Some(event_meta.event_id.clone())
1777                                }
1778                            } else {
1779                                None
1780                            }
1781                        });
1782
1783                    match previous_event {
1784                        Some(event_id) => event_id,
1785                        // Nothing from another user to point at. When marking the room as read,
1786                        // fall back to the user's own event so the homeserver still recomputes
1787                        // its push/badge count; otherwise there's nothing to send.
1788                        None if is_marking_room_as_read => event_id.to_owned(),
1789                        None => return SendReceiptDecision::DoNotSend,
1790                    }
1791                } else {
1792                    event_id.to_owned()
1793                }
1794            }
1795
1796            _ => event_id.to_owned(),
1797        };
1798
1799        // Find the real receipt the homeserver already knows about.
1800        let previous_event_id = match receipt_type {
1801            SendReceiptType::Read => state
1802                .meta
1803                .user_receipt(
1804                    own_user_id,
1805                    ReceiptType::Read,
1806                    receipt_thread.clone(),
1807                    room,
1808                    all_remote_events,
1809                    read_receipts::ImplicitReadReceipts::Exclude,
1810                )
1811                .await
1812                .map(|(event_id, _)| event_id),
1813
1814            // Implicit read receipts are saved as public read receipts, so get the latest. It also
1815            // doesn't make sense to have a private read receipt behind a public one.
1816            SendReceiptType::ReadPrivate => state
1817                .latest_user_read_receipt(
1818                    own_user_id,
1819                    receipt_thread.clone(),
1820                    room,
1821                    read_receipts::ImplicitReadReceipts::Exclude,
1822                )
1823                .await
1824                .map(|(event_id, _)| event_id),
1825
1826            SendReceiptType::FullyRead => self.room_data_provider.load_fully_read_marker().await,
1827
1828            _ => None,
1829        };
1830
1831        // Don't send anything if the resolved event isn't more recent than that.
1832        if let Some(previous_event_id) = previous_event_id {
1833            trace!(%previous_event_id, "found a previous receipt");
1834            if let Some(relative_pos) = TimelineMetadata::compare_events_positions(
1835                &previous_event_id,
1836                &target_event_id,
1837                all_remote_events,
1838            ) && relative_pos != RelativePosition::After
1839            {
1840                return SendReceiptDecision::DoNotSend;
1841            }
1842        }
1843
1844        // No previous receipt was found (or it's an unknown one): let the server
1845        // handle it.
1846        SendReceiptDecision::SendTo(target_event_id)
1847    }
1848
1849    /// Returns the latest event identifier, even if it's not visible, or if
1850    /// it's folded into another timeline item.
1851    pub(crate) async fn latest_event_id(&self) -> Option<OwnedEventId> {
1852        let state = self.state.read().await;
1853        let filter_out_thread_events = match self.focus() {
1854            TimelineFocusKind::Thread { .. } => false,
1855            TimelineFocusKind::Live { hide_threaded_events, .. } => *hide_threaded_events,
1856            TimelineFocusKind::Event { .. } => {
1857                // For event-focused timelines, filtering is handled in the event cache layer.
1858                false
1859            }
1860            TimelineFocusKind::PinnedEvents { .. } => true,
1861        };
1862
1863        state
1864            .items
1865            .all_remote_events()
1866            .iter()
1867            .rev()
1868            .filter_map(|event_meta| {
1869                if !filter_out_thread_events {
1870                    // For an unthreaded timeline, the last event is always the latest event.
1871                    Some(event_meta.event_id.clone())
1872                } else if event_meta.thread_root_id.is_none() {
1873                    // For the main-thread timeline, only non-threaded events are valid candidates
1874                    // for the latest event.
1875                    //
1876                    // But! An event could be an aggregation that relate to an in-thread
1877                    // event. In this case, it's not a valid latest event.
1878                    if let Some(TimelineEventItemId::EventId(target_event_id)) =
1879                        state.meta.aggregations.is_aggregation_of(&TimelineEventItemId::EventId(
1880                            event_meta.event_id.clone(),
1881                        ))
1882                        && let Some(target_meta) =
1883                            state.items.all_remote_events().get_by_event_id(target_event_id)
1884                        && target_meta.thread_root_id.is_some()
1885                    {
1886                        // This event is an aggregation of an in-thread event, so skip it.
1887                        None
1888                    } else {
1889                        // Not in a thread, and not the aggregation of an in-thread event, so it's
1890                        // a valid candidate for the latest event.
1891                        Some(event_meta.event_id.clone())
1892                    }
1893                } else {
1894                    // An in-thread event, when we're filtering out threaded events, is never a
1895                    // valid candidate for the latest event.
1896                    None
1897                }
1898            })
1899            .next()
1900    }
1901
1902    #[instrument(skip(self), fields(room_id = ?self.room().room_id()))]
1903    pub(super) async fn retry_event_decryption(&self, session_ids: Option<BTreeSet<String>>) {
1904        let (utds, decrypted) = self.compute_redecryption_candidates().await;
1905
1906        let request = DecryptionRetryRequest {
1907            room_id: self.room().room_id().to_owned(),
1908            utd_session_ids: utds,
1909            refresh_info_session_ids: decrypted,
1910        };
1911
1912        self.room().client().event_cache().request_decryption(request);
1913    }
1914
1915    /// Combine the global (event cache) pagination status with the local state
1916    /// of the timeline.
1917    ///
1918    /// This only changes the global pagination status of this room, in one
1919    /// case: if the timeline has a skip count greater than 0, it will
1920    /// ensure that the pagination status says that we haven't reached the
1921    /// timeline start yet.
1922    pub(super) async fn map_pagination_status(&self, status: PaginationStatus) -> PaginationStatus {
1923        match status {
1924            PaginationStatus::Idle { hit_timeline_start } => {
1925                if hit_timeline_start {
1926                    let state = self.state.read().await;
1927                    // If the skip count is greater than 0, it means that a subsequent pagination
1928                    // could return more items, so pretend we didn't get the information that the
1929                    // timeline start was hit.
1930                    if state.meta.subscriber_skip_count.get() > 0 {
1931                        return PaginationStatus::Idle { hit_timeline_start: false };
1932                    }
1933                }
1934            }
1935            PaginationStatus::Paginating => {}
1936        }
1937
1938        // You're perfect, just the way you are.
1939        status
1940    }
1941}
1942
1943impl<P: RoomDataProvider> TimelineController<P> {
1944    /// Returns the timeline focus of the [`TimelineController`].
1945    pub(super) fn focus(&self) -> &TimelineFocusKind {
1946        &self.focus
1947    }
1948
1949    /// Find an event by ID in this timeline, along with its related events.
1950    ///
1951    /// The related events can be filtered by relation type.
1952    pub(in crate::timeline) async fn find_event_with_relations(
1953        &self,
1954        event_id: &EventId,
1955        filter: Option<Vec<RelationType>>,
1956    ) -> Result<(TimelineEvent, Vec<TimelineEvent>), Error> {
1957        self.room_data_provider
1958            .load_or_fetch_event_with_relations(event_id, filter)
1959            .await
1960            .map_err(Into::into)
1961    }
1962}
1963
1964#[allow(clippy::too_many_arguments)]
1965async fn fetch_replied_to_event<P: RoomDataProvider>(
1966    mut state_guard: RwLockWriteGuard<'_, TimelineState<P>>,
1967    state_lock: &RwLock<TimelineState<P>>,
1968    index: usize,
1969    item: &EventTimelineItem,
1970    internal_id: TimelineUniqueId,
1971    msglike: &MsgLikeContent,
1972    in_reply_to: &EventId,
1973    room: &Room,
1974) -> Result<TimelineDetails<Box<EmbeddedEvent>>, Error> {
1975    if let Some((_, item)) = rfind_event_by_id(&state_guard.items, in_reply_to) {
1976        let details = TimelineDetails::Ready(Box::new(EmbeddedEvent::from_timeline_item(&item)));
1977        trace!("Found replied-to event locally");
1978        return Ok(details);
1979    }
1980
1981    // Replace the item with a new timeline item that has the fetching status of the
1982    // replied-to event to pending.
1983    trace!("Setting in-reply-to details to pending");
1984    let in_reply_to_details =
1985        InReplyToDetails { event_id: in_reply_to.to_owned(), event: TimelineDetails::Pending };
1986
1987    let event_item = item
1988        .with_content(TimelineItemContent::MsgLike(msglike.with_in_reply_to(in_reply_to_details)));
1989
1990    let new_timeline_item = TimelineItem::new(event_item, internal_id);
1991    state_guard.items.replace(index, new_timeline_item);
1992
1993    // Don't hold the state lock while the network request is made.
1994    drop(state_guard);
1995
1996    trace!("Fetching replied-to event");
1997    let res = match room.load_or_fetch_event(in_reply_to, None).await {
1998        Ok(timeline_event) => {
1999            let state = state_lock.read().await;
2000
2001            let replied_to_item =
2002                EmbeddedEvent::try_from_timeline_event(timeline_event, room, &state.meta).await?;
2003
2004            if let Some(item) = replied_to_item {
2005                TimelineDetails::Ready(Box::new(item))
2006            } else {
2007                // The replied-to item is an aggregation, not a standalone item.
2008                return Err(Error::UnsupportedEvent);
2009            }
2010        }
2011
2012        Err(e) => TimelineDetails::Error(Arc::new(e)),
2013    };
2014
2015    Ok(res)
2016}