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