Skip to main content

matrix_sdk_ui/timeline/controller/
mod.rs

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