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