matrix_sdk_ui/timeline/controller/
state_transaction.rs

1// Copyright 2025 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::collections::{HashMap, HashSet};
16
17use eyeball_im::VectorDiff;
18use itertools::Itertools as _;
19use matrix_sdk::deserialized_responses::{
20    ThreadSummaryStatus, TimelineEvent, TimelineEventKind, UnsignedEventLocation,
21};
22use ruma::{
23    EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, OwnedUserId, UserId,
24    events::AnySyncTimelineEvent, push::Action, serde::Raw,
25};
26use tracing::{debug, instrument, trace, warn};
27
28use super::{
29    super::{
30        controller::ObservableItemsTransactionEntry,
31        date_dividers::DateDividerAdjuster,
32        event_handler::{Flow, TimelineEventContext, TimelineEventHandler, TimelineItemPosition},
33        event_item::RemoteEventOrigin,
34        traits::RoomDataProvider,
35    },
36    ObservableItems, ObservableItemsTransaction, TimelineMetadata, TimelineReadReceiptTracking,
37    TimelineSettings,
38    metadata::EventMeta,
39};
40use crate::timeline::{
41    EmbeddedEvent, Profile, ThreadSummary, TimelineDetails, VirtualTimelineItem,
42    controller::TimelineFocusKind,
43    event_handler::{FailedToParseEvent, RemovedItem, TimelineAction},
44};
45
46pub(in crate::timeline) struct TimelineStateTransaction<'a, P: RoomDataProvider> {
47    /// A vector transaction over the items themselves. Holds temporary state
48    /// until committed.
49    pub items: ObservableItemsTransaction<'a>,
50
51    /// Number of items when the transaction has been created/has started.
52    number_of_items_when_transaction_started: usize,
53
54    /// A clone of the previous meta, that we're operating on during the
55    /// transaction, and that will be committed to the previous meta location in
56    /// [`Self::commit`].
57    pub meta: TimelineMetadata,
58
59    /// Pointer to the previous meta, only used during [`Self::commit`].
60    previous_meta: &'a mut TimelineMetadata,
61
62    /// The kind of focus of this timeline.
63    pub focus: &'a TimelineFocusKind<P>,
64}
65
66impl<'a, P: RoomDataProvider> TimelineStateTransaction<'a, P> {
67    /// Create a new [`TimelineStateTransaction`].
68    pub(super) fn new(
69        items: &'a mut ObservableItems,
70        meta: &'a mut TimelineMetadata,
71        focus: &'a TimelineFocusKind<P>,
72    ) -> Self {
73        let previous_meta = meta;
74        let meta = previous_meta.clone();
75        let items = items.transaction();
76
77        Self {
78            number_of_items_when_transaction_started: items.len(),
79            items,
80            previous_meta,
81            meta,
82            focus,
83        }
84    }
85
86    /// Handle updates on events as [`VectorDiff`]s.
87    pub(super) async fn handle_remote_events_with_diffs(
88        &mut self,
89        diffs: Vec<VectorDiff<TimelineEvent>>,
90        origin: RemoteEventOrigin,
91        room_data_provider: &P,
92        settings: &TimelineSettings,
93    ) {
94        let mut date_divider_adjuster =
95            DateDividerAdjuster::new(settings.date_divider_mode.clone());
96
97        let mut cached_profiles: HashMap<OwnedUserId, Option<Profile>> = HashMap::new();
98
99        for diff in diffs {
100            match diff {
101                VectorDiff::Append { values: events } => {
102                    for event in events {
103                        self.handle_remote_event(
104                            event,
105                            TimelineItemPosition::End { origin },
106                            room_data_provider,
107                            settings,
108                            &mut date_divider_adjuster,
109                            &mut cached_profiles,
110                        )
111                        .await;
112                    }
113                }
114
115                VectorDiff::PushFront { value: event } => {
116                    self.handle_remote_event(
117                        event,
118                        TimelineItemPosition::Start { origin },
119                        room_data_provider,
120                        settings,
121                        &mut date_divider_adjuster,
122                        &mut cached_profiles,
123                    )
124                    .await;
125                }
126
127                VectorDiff::PushBack { value: event } => {
128                    self.handle_remote_event(
129                        event,
130                        TimelineItemPosition::End { origin },
131                        room_data_provider,
132                        settings,
133                        &mut date_divider_adjuster,
134                        &mut cached_profiles,
135                    )
136                    .await;
137                }
138
139                VectorDiff::Insert { index: event_index, value: event } => {
140                    self.handle_remote_event(
141                        event,
142                        TimelineItemPosition::At { event_index, origin },
143                        room_data_provider,
144                        settings,
145                        &mut date_divider_adjuster,
146                        &mut cached_profiles,
147                    )
148                    .await;
149                }
150
151                VectorDiff::Set { index: event_index, value: event } => {
152                    if let Some(timeline_item_index) = self
153                        .items
154                        .all_remote_events()
155                        .get(event_index)
156                        .and_then(|meta| meta.timeline_item_index)
157                    {
158                        self.handle_remote_event(
159                            event,
160                            TimelineItemPosition::UpdateAt { timeline_item_index },
161                            room_data_provider,
162                            settings,
163                            &mut date_divider_adjuster,
164                            &mut cached_profiles,
165                        )
166                        .await;
167                    } else {
168                        warn!(
169                            event_index,
170                            "Set update dropped because there wasn't any attached timeline item index."
171                        );
172                    }
173                }
174
175                VectorDiff::Remove { index: event_index } => {
176                    self.remove_timeline_item(event_index, &mut date_divider_adjuster);
177                }
178
179                VectorDiff::Clear => {
180                    self.clear();
181                }
182
183                v => unimplemented!("{v:?}"),
184            }
185        }
186
187        self.adjust_date_dividers(date_divider_adjuster);
188        self.check_invariants();
189    }
190
191    async fn handle_remote_aggregation(
192        &mut self,
193        event: TimelineEvent,
194        position: TimelineItemPosition,
195        room_data_provider: &P,
196        date_divider_adjuster: &mut DateDividerAdjuster,
197    ) {
198        let raw_event = event.raw();
199
200        let deserialized = match raw_event.deserialize() {
201            Ok(deserialized) => deserialized,
202            Err(err) => {
203                warn!("Failed to deserialize timeline event: {err}");
204                return;
205            }
206        };
207
208        let sender = deserialized.sender().to_owned();
209        let timestamp = deserialized.origin_server_ts();
210        let event_id = deserialized.event_id().to_owned();
211        let txn_id = deserialized.transaction_id().map(ToOwned::to_owned);
212
213        if let Some(action @ TimelineAction::HandleAggregation { .. }) = TimelineAction::from_event(
214            deserialized,
215            raw_event,
216            room_data_provider,
217            None,
218            None,
219            None,
220            None,
221        )
222        .await
223        {
224            let encryption_info = event.kind.encryption_info().cloned();
225
226            let sender_profile = room_data_provider.profile_from_user_id(&sender).await;
227
228            let ctx = TimelineEventContext {
229                sender,
230                sender_profile,
231                timestamp,
232                // These are not used when handling an aggregation.
233                read_receipts: Default::default(),
234                is_highlighted: false,
235                flow: Flow::Remote {
236                    event_id: event_id.clone(),
237                    raw_event: event.raw().clone(),
238                    encryption_info,
239                    txn_id,
240                    position,
241                },
242                // This field is not used when handling an aggregation.
243                should_add_new_items: false,
244            };
245
246            TimelineEventHandler::new(self, ctx).handle_event(date_divider_adjuster, action).await;
247        }
248    }
249
250    /// Handle a set of live remote aggregations on events as [`VectorDiff`]s.
251    ///
252    /// This is like `handle_remote_events`, with two key differences:
253    /// - it only applies to aggregated events, not all the sync events.
254    /// - it will also not add the events to the `all_remote_events` array
255    ///   itself.
256    pub(super) async fn handle_remote_aggregations(
257        &mut self,
258        diffs: Vec<VectorDiff<TimelineEvent>>,
259        origin: RemoteEventOrigin,
260        room_data_provider: &P,
261        settings: &TimelineSettings,
262    ) {
263        let mut date_divider_adjuster =
264            DateDividerAdjuster::new(settings.date_divider_mode.clone());
265
266        for diff in diffs {
267            match diff {
268                VectorDiff::Append { values: events } => {
269                    for event in events {
270                        self.handle_remote_aggregation(
271                            event,
272                            TimelineItemPosition::End { origin },
273                            room_data_provider,
274                            &mut date_divider_adjuster,
275                        )
276                        .await;
277                    }
278                }
279
280                VectorDiff::PushFront { value: event } => {
281                    self.handle_remote_aggregation(
282                        event,
283                        TimelineItemPosition::Start { origin },
284                        room_data_provider,
285                        &mut date_divider_adjuster,
286                    )
287                    .await;
288                }
289
290                VectorDiff::PushBack { value: event } => {
291                    self.handle_remote_aggregation(
292                        event,
293                        TimelineItemPosition::End { origin },
294                        room_data_provider,
295                        &mut date_divider_adjuster,
296                    )
297                    .await;
298                }
299
300                VectorDiff::Insert { index: event_index, value: event } => {
301                    self.handle_remote_aggregation(
302                        event,
303                        TimelineItemPosition::At { event_index, origin },
304                        room_data_provider,
305                        &mut date_divider_adjuster,
306                    )
307                    .await;
308                }
309
310                VectorDiff::Set { index: event_index, value: event } => {
311                    if let Some(timeline_item_index) = self
312                        .items
313                        .all_remote_events()
314                        .get(event_index)
315                        .and_then(|meta| meta.timeline_item_index)
316                    {
317                        self.handle_remote_aggregation(
318                            event,
319                            TimelineItemPosition::UpdateAt { timeline_item_index },
320                            room_data_provider,
321                            &mut date_divider_adjuster,
322                        )
323                        .await;
324                    } else {
325                        warn!(
326                            event_index,
327                            "Set update dropped because there wasn't any attached timeline item index."
328                        );
329                    }
330                }
331
332                VectorDiff::Remove { .. } | VectorDiff::Clear => {
333                    // Do nothing. An aggregated redaction comes with a
334                    // redaction event, or as a redacted event in the first
335                    // place.
336                }
337
338                v => unimplemented!("{v:?}"),
339            }
340        }
341
342        self.adjust_date_dividers(date_divider_adjuster);
343        self.check_invariants();
344    }
345
346    fn check_invariants(&self) {
347        self.check_no_duplicate_read_receipts();
348        self.check_no_unused_unique_ids();
349    }
350
351    fn check_no_duplicate_read_receipts(&self) {
352        let mut by_user_id = HashMap::new();
353        let mut duplicates = HashSet::new();
354
355        for item in self.items.iter_remotes_region().filter_map(|(_, item)| item.as_event()) {
356            if let Some(event_id) = item.event_id() {
357                for (user_id, _read_receipt) in item.read_receipts() {
358                    if let Some(prev_event_id) = by_user_id.insert(user_id, event_id) {
359                        duplicates.insert((user_id.clone(), prev_event_id, event_id));
360                    }
361                }
362            }
363        }
364
365        if !duplicates.is_empty() {
366            #[cfg(any(debug_assertions, test))]
367            panic!("duplicate read receipts in this timeline: {duplicates:?}\n{:?}", self.items);
368
369            #[cfg(not(any(debug_assertions, test)))]
370            tracing::error!(
371                ?duplicates,
372                items = ?self.items,
373                "duplicate read receipts in this timeline",
374            );
375        }
376    }
377
378    fn check_no_unused_unique_ids(&self) {
379        let duplicates = self
380            .items
381            .iter_all_regions()
382            .duplicates_by(|(_nth, item)| item.unique_id())
383            .map(|(_nth, item)| item.unique_id())
384            .collect::<Vec<_>>();
385
386        if !duplicates.is_empty() {
387            #[cfg(any(debug_assertions, test))]
388            panic!("duplicate unique ids in this timeline: {duplicates:?}\n{:?}", self.items);
389
390            #[cfg(not(any(debug_assertions, test)))]
391            tracing::error!(
392                ?duplicates,
393                items = ?self.items,
394                "duplicate unique ids in this timeline",
395            );
396        }
397    }
398
399    /// Whether the event should be added to the timeline as a new item.
400    fn should_add_event_item(
401        &self,
402        room_data_provider: &P,
403        settings: &TimelineSettings,
404        event: &AnySyncTimelineEvent,
405        thread_root: Option<&EventId>,
406        position: TimelineItemPosition,
407    ) -> bool {
408        let rules = room_data_provider.room_version_rules();
409
410        if !(settings.event_filter)(event, &rules) {
411            // The user filtered out the event.
412            return false;
413        }
414
415        match &self.focus {
416            TimelineFocusKind::PinnedEvents { .. } => {
417                // Only add pinned events for the pinned events timeline.
418                room_data_provider.is_pinned_event(event.event_id())
419            }
420
421            TimelineFocusKind::Event { paginator } => {
422                // If the timeline's filtering out in-thread events, don't add items for
423                // threaded events.
424                let hide_threaded_events =
425                    paginator.get().is_some_and(|paginator| paginator.hide_threaded_events());
426                if thread_root.is_some() && hide_threaded_events {
427                    return false;
428                }
429
430                // Retrieve the origin of the event.
431                let origin = match position {
432                    TimelineItemPosition::End { origin }
433                    | TimelineItemPosition::Start { origin }
434                    | TimelineItemPosition::At { origin, .. } => origin,
435
436                    TimelineItemPosition::UpdateAt { timeline_item_index: idx } => self
437                        .items
438                        .get(idx)
439                        .and_then(|item| item.as_event()?.as_remote())
440                        .map_or(RemoteEventOrigin::Unknown, |item| item.origin),
441                };
442
443                match origin {
444                    // Never add any item to a focused timeline when the item comes from sync.
445                    RemoteEventOrigin::Sync | RemoteEventOrigin::Unknown => false,
446                    RemoteEventOrigin::Cache | RemoteEventOrigin::Pagination => true,
447                }
448            }
449
450            TimelineFocusKind::Live { hide_threaded_events } => {
451                // If the timeline's filtering out in-thread events, don't add items for
452                // threaded events.
453                thread_root.is_none() || !hide_threaded_events
454            }
455
456            TimelineFocusKind::Thread { root_event_id, .. } => {
457                // Add new items only for the thread root and the thread replies.
458                event.event_id() == root_event_id
459                    || thread_root.as_ref().is_some_and(|r| r == root_event_id)
460            }
461        }
462    }
463
464    /// Whether this event can show read receipts, or if they should be moved
465    /// to the previous event.
466    fn can_show_read_receipts(
467        &self,
468        settings: &TimelineSettings,
469        event: &AnySyncTimelineEvent,
470    ) -> bool {
471        match event {
472            AnySyncTimelineEvent::State(_) => {
473                matches!(settings.track_read_receipts, TimelineReadReceiptTracking::AllEvents)
474            }
475            AnySyncTimelineEvent::MessageLike(_) => {
476                !matches!(settings.track_read_receipts, TimelineReadReceiptTracking::Disabled)
477            }
478        }
479    }
480
481    /// After a deserialization error, adds a failed-to-parse item to the
482    /// timeline if configured to do so, or logs the error (and optionally
483    /// save metadata) if not.
484    async fn maybe_add_error_item(
485        &mut self,
486        position: TimelineItemPosition,
487        room_data_provider: &P,
488        raw: &Raw<AnySyncTimelineEvent>,
489        deserialization_error: serde_json::Error,
490        settings: &TimelineSettings,
491    ) -> Option<(
492        OwnedEventId,
493        OwnedUserId,
494        MilliSecondsSinceUnixEpoch,
495        Option<OwnedTransactionId>,
496        Option<TimelineAction>,
497        Option<OwnedEventId>,
498        bool,
499        bool,
500    )> {
501        let state_key: Option<String> = raw.get_field("state_key").ok().flatten();
502
503        // A state event is an event that has a state key. Note that the two branches
504        // differ because the inferred return type for `get_field` is different
505        // in each case.
506        //
507        // If this was a state event but it didn't include a state_key, we'll assume it
508        // was a msg-like, because we can't do much more.
509        let event_type = if let Some(state_key) = state_key {
510            raw.get_field("type")
511                .ok()
512                .flatten()
513                .map(|event_type| FailedToParseEvent::State { event_type, state_key })
514        } else {
515            raw.get_field("type").ok().flatten().map(FailedToParseEvent::MsgLike)
516        };
517
518        let event_id: Option<OwnedEventId> = raw.get_field("event_id").ok().flatten();
519        let Some(event_id) = event_id else {
520            // If the event doesn't even have an event ID, we can't do anything with it.
521            warn!(
522                ?event_type,
523                "Failed to deserialize timeline event (with no ID): {deserialization_error}"
524            );
525            return None;
526        };
527
528        let sender: Option<OwnedUserId> = raw.get_field("sender").ok().flatten();
529        let origin_server_ts: Option<MilliSecondsSinceUnixEpoch> =
530            raw.get_field("origin_server_ts").ok().flatten();
531
532        match (sender, origin_server_ts, event_type) {
533            (Some(sender), Some(origin_server_ts), Some(event_type))
534                if settings.add_failed_to_parse =>
535            {
536                // We have sufficient information to show an item in the timeline, and we've
537                // been requested to show it, let's do it.
538                #[derive(serde::Deserialize)]
539                struct Unsigned {
540                    transaction_id: Option<OwnedTransactionId>,
541                }
542
543                let transaction_id: Option<OwnedTransactionId> = raw
544                    .get_field::<Unsigned>("unsigned")
545                    .ok()
546                    .flatten()
547                    .and_then(|unsigned| unsigned.transaction_id);
548
549                // The event can be partially deserialized, and it is allowed to be added to
550                // the timeline.
551                Some((
552                    event_id,
553                    sender,
554                    origin_server_ts,
555                    transaction_id,
556                    Some(TimelineAction::failed_to_parse(event_type, deserialization_error)),
557                    None,
558                    true,
559                    true,
560                ))
561            }
562
563            (sender, origin_server_ts, event_type) => {
564                // We either lack information for rendering an item, or we've been requested not
565                // to show it. Save it into the metadata and return.
566                warn!(
567                    ?event_type,
568                    ?event_id,
569                    "Failed to deserialize timeline event: {deserialization_error}"
570                );
571
572                // Remember the event before returning prematurely.
573                // See [`ObservableItems::all_remote_events`].
574                self.add_or_update_remote_event(
575                    EventMeta::new(event_id, false, false, None),
576                    sender.as_deref(),
577                    origin_server_ts,
578                    position,
579                    room_data_provider,
580                    settings,
581                )
582                .await;
583                None
584            }
585        }
586    }
587
588    // Attempt to load a thread's latest reply as an embedded timeline item, either
589    // using the event cache or the storage.
590    #[instrument(skip(self, room_data_provider))]
591    async fn fetch_latest_thread_reply(
592        &mut self,
593        event_id: &EventId,
594        room_data_provider: &P,
595    ) -> Option<Box<EmbeddedEvent>> {
596        let event = RoomDataProvider::load_event(room_data_provider, event_id)
597            .await
598            .inspect_err(|err| {
599                warn!("Failed to load thread latest event: {err}");
600            })
601            .ok()?;
602
603        EmbeddedEvent::try_from_timeline_event(event, room_data_provider, &self.meta)
604            .await
605            .inspect_err(|err| {
606                warn!("Failed to extract thread latest event into a timeline item content: {err}");
607            })
608            .ok()
609            .flatten()
610            .map(Box::new)
611    }
612
613    /// Handle a remote event.
614    ///
615    /// Returns whether an item has been removed from the timeline.
616    pub(super) async fn handle_remote_event(
617        &mut self,
618        event: TimelineEvent,
619        position: TimelineItemPosition,
620        room_data_provider: &P,
621        settings: &TimelineSettings,
622        date_divider_adjuster: &mut DateDividerAdjuster,
623        profiles: &mut HashMap<OwnedUserId, Option<Profile>>,
624    ) -> RemovedItem {
625        let is_highlighted =
626            event.push_actions().is_some_and(|actions| actions.iter().any(Action::is_highlight));
627
628        let thread_summary = if let ThreadSummaryStatus::Some(summary) = event.thread_summary {
629            let latest_reply_item = if let Some(latest_reply) = summary.latest_reply {
630                self.fetch_latest_thread_reply(&latest_reply, room_data_provider).await
631            } else {
632                None
633            };
634            Some(ThreadSummary {
635                latest_event: TimelineDetails::from_initial_value(latest_reply_item),
636                num_replies: summary.num_replies,
637            })
638        } else {
639            None
640        };
641
642        let encryption_info = event.kind.encryption_info().cloned();
643
644        let bundled_edit_encryption_info = event.kind.unsigned_encryption_map().and_then(|map| {
645            map.get(&UnsignedEventLocation::RelationsReplace)?.encryption_info().cloned()
646        });
647
648        let (raw, utd_info) = match event.kind {
649            TimelineEventKind::UnableToDecrypt { utd_info, event } => (event, Some(utd_info)),
650            _ => (event.kind.into_raw(), None),
651        };
652
653        let (
654            event_id,
655            sender,
656            timestamp,
657            txn_id,
658            timeline_action,
659            thread_root,
660            should_add,
661            can_show_read_receipts,
662        ) = match raw.deserialize() {
663            // Classical path: the event is valid, can be deserialized, everything is alright.
664            Ok(event) => {
665                let (in_reply_to, thread_root) = self.meta.process_event_relations(
666                    &event,
667                    &raw,
668                    bundled_edit_encryption_info,
669                    &self.items,
670                    self.focus.is_thread(),
671                );
672
673                let should_add = self.should_add_event_item(
674                    room_data_provider,
675                    settings,
676                    &event,
677                    thread_root.as_deref(),
678                    position,
679                );
680
681                let can_show_read_receipts = self.can_show_read_receipts(settings, &event);
682
683                (
684                    event.event_id().to_owned(),
685                    event.sender().to_owned(),
686                    event.origin_server_ts(),
687                    event.transaction_id().map(ToOwned::to_owned),
688                    TimelineAction::from_event(
689                        event,
690                        &raw,
691                        room_data_provider,
692                        utd_info
693                            .map(|utd_info| (utd_info, self.meta.unable_to_decrypt_hook.as_ref())),
694                        in_reply_to,
695                        thread_root.clone(),
696                        thread_summary,
697                    )
698                    .await,
699                    thread_root,
700                    should_add,
701                    can_show_read_receipts,
702                )
703            }
704
705            // The event seems invalid…
706            Err(e) => {
707                if let Some(tuple) =
708                    self.maybe_add_error_item(position, room_data_provider, &raw, e, settings).await
709                {
710                    tuple
711                } else {
712                    return false;
713                }
714            }
715        };
716
717        // Remember the event.
718        // See [`ObservableItems::all_remote_events`].
719        self.add_or_update_remote_event(
720            EventMeta::new(event_id.clone(), should_add, can_show_read_receipts, thread_root),
721            Some(&sender),
722            Some(timestamp),
723            position,
724            room_data_provider,
725            settings,
726        )
727        .await;
728
729        // Handle the event to create or update a timeline item.
730        let item_added = if let Some(timeline_action) = timeline_action {
731            let sender_profile = if let Some(profile) = profiles.get(&sender) {
732                profile.clone()
733            } else {
734                let profile = room_data_provider.profile_from_user_id(&sender).await;
735                profiles.insert(sender.clone(), profile.clone());
736                profile
737            };
738
739            let ctx = TimelineEventContext {
740                sender,
741                sender_profile,
742                timestamp,
743                read_receipts: if settings.track_read_receipts.is_enabled()
744                    && should_add
745                    && can_show_read_receipts
746                {
747                    self.meta.read_receipts.compute_event_receipts(
748                        &event_id,
749                        &mut self.items,
750                        matches!(position, TimelineItemPosition::End { .. }),
751                    )
752                } else {
753                    Default::default()
754                },
755                is_highlighted,
756                flow: Flow::Remote {
757                    event_id: event_id.clone(),
758                    raw_event: raw,
759                    encryption_info,
760                    txn_id,
761                    position,
762                },
763                should_add_new_items: should_add,
764            };
765
766            TimelineEventHandler::new(self, ctx)
767                .handle_event(date_divider_adjuster, timeline_action)
768                .await
769        } else {
770            // No item has been added to the timeline.
771            false
772        };
773
774        let mut item_removed = false;
775
776        if !item_added {
777            trace!("No new item added");
778
779            if let TimelineItemPosition::UpdateAt { timeline_item_index } = position {
780                // If add was not called, that means the UTD event is one that
781                // wouldn't normally be visible. Remove it.
782                trace!("Removing UTD that was successfully retried");
783                self.items.remove(timeline_item_index);
784                item_removed = true;
785            }
786        }
787
788        item_removed
789    }
790
791    /// Remove one timeline item by its `event_index`.
792    fn remove_timeline_item(
793        &mut self,
794        event_index: usize,
795        day_divider_adjuster: &mut DateDividerAdjuster,
796    ) {
797        day_divider_adjuster.mark_used();
798
799        // We need to be careful here.
800        //
801        // We must first remove the timeline item, which will update the mapping between
802        // remote events and timeline items. Removing the timeline item will “unlink”
803        // this mapping as the remote event will be updated to map to nothing. Only
804        // after that, we can remove the remote event. Doing this in the other order
805        // will update the mapping twice, and will result in a corrupted state.
806
807        // Remove the timeline item first.
808        if let Some(event_meta) = self.items.all_remote_events().get(event_index) {
809            // Fetch the `timeline_item_index` associated to the remote event.
810            if let Some(timeline_item_index) = event_meta.timeline_item_index {
811                let _ = self.items.remove(timeline_item_index);
812            }
813
814            // Now we can remove the remote event.
815            self.items.remove_remote_event(event_index);
816        }
817    }
818
819    pub(super) fn clear(&mut self) {
820        // By first checking if there are any local echoes first, we do a bit
821        // more work in case some are found, but it should be worth it because
822        // there will often not be any, and only emitting a single
823        // `VectorDiff::Clear` should be much more efficient to process for
824        // subscribers.
825        if self.items.has_local() {
826            // Remove all remote events and virtual items that aren't date dividers.
827            self.items.for_each(|entry| {
828                if entry.is_remote_event()
829                    || entry.as_virtual().is_some_and(|vitem| match vitem {
830                        VirtualTimelineItem::DateDivider(_) => false,
831                        VirtualTimelineItem::ReadMarker | VirtualTimelineItem::TimelineStart => {
832                            true
833                        }
834                    })
835                {
836                    ObservableItemsTransactionEntry::remove(entry);
837                }
838            });
839
840            // Remove stray date dividers
841            let mut idx = 0;
842            while idx < self.items.len() {
843                if self.items[idx].is_date_divider()
844                    && self.items.get(idx + 1).is_none_or(|item| item.is_date_divider())
845                {
846                    self.items.remove(idx);
847                    // don't increment idx because all elements have shifted
848                } else {
849                    idx += 1;
850                }
851            }
852        } else {
853            self.items.clear();
854        }
855
856        self.meta.clear();
857
858        debug!(remaining_items = self.items.len(), "Timeline cleared");
859    }
860
861    #[instrument(skip_all)]
862    pub(super) fn set_fully_read_event(&mut self, fully_read_event_id: OwnedEventId) {
863        // A similar event has been handled already. We can ignore it.
864        if self.meta.fully_read_event.as_ref().is_some_and(|id| *id == fully_read_event_id) {
865            return;
866        }
867
868        self.meta.fully_read_event = Some(fully_read_event_id);
869        self.meta.update_read_marker(&mut self.items);
870    }
871
872    pub(super) fn commit(self) {
873        // Update the `subscriber_skip_count` value.
874        let previous_number_of_items = self.number_of_items_when_transaction_started;
875        let next_number_of_items = self.items.len();
876
877        if previous_number_of_items != next_number_of_items {
878            let count = self
879                .meta
880                .subscriber_skip_count
881                .compute_next(previous_number_of_items, next_number_of_items);
882            self.meta
883                .subscriber_skip_count
884                .update(count, matches!(self.focus, TimelineFocusKind::Live { .. }));
885        }
886
887        // Replace the pointer to the previous meta with the new one.
888        *self.previous_meta = self.meta;
889
890        self.items.commit();
891    }
892
893    /// Add or update a remote event in the
894    /// [`ObservableItems::all_remote_events`] collection.
895    ///
896    /// This method also adjusts read receipt if needed.
897    async fn add_or_update_remote_event(
898        &mut self,
899        event_meta: EventMeta,
900        sender: Option<&UserId>,
901        timestamp: Option<MilliSecondsSinceUnixEpoch>,
902        position: TimelineItemPosition,
903        room_data_provider: &P,
904        settings: &TimelineSettings,
905    ) {
906        let event_id = event_meta.event_id.clone();
907
908        match position {
909            TimelineItemPosition::Start { .. } => self.items.push_front_remote_event(event_meta),
910
911            TimelineItemPosition::End { .. } => {
912                self.items.push_back_remote_event(event_meta);
913            }
914
915            TimelineItemPosition::At { event_index, .. } => {
916                self.items.insert_remote_event(event_index, event_meta);
917            }
918
919            TimelineItemPosition::UpdateAt { .. } => {
920                if let Some(event) =
921                    self.items.get_remote_event_by_event_id_mut(&event_meta.event_id)
922                    && (event.visible != event_meta.visible
923                        || event.can_show_read_receipts != event_meta.can_show_read_receipts)
924                {
925                    event.visible = event_meta.visible;
926                    event.can_show_read_receipts = event_meta.can_show_read_receipts;
927
928                    if settings.track_read_receipts.is_enabled() {
929                        // Since the event's visibility changed, we need to update the read
930                        // receipts of the previous visible event.
931                        self.maybe_update_read_receipts_of_prev_event(&event_meta.event_id);
932                    }
933                }
934            }
935        }
936
937        if settings.track_read_receipts.is_enabled()
938            && matches!(
939                position,
940                TimelineItemPosition::Start { .. }
941                    | TimelineItemPosition::End { .. }
942                    | TimelineItemPosition::At { .. }
943            )
944        {
945            self.load_read_receipts_for_event(&event_id, room_data_provider).await;
946
947            self.maybe_add_implicit_read_receipt(&event_id, sender, timestamp);
948        }
949    }
950
951    pub(super) fn adjust_date_dividers(&mut self, mut adjuster: DateDividerAdjuster) {
952        adjuster.run(&mut self.items, &mut self.meta);
953    }
954
955    /// This method replaces the `is_room_encrypted` value for all timeline
956    /// items to its updated version and creates a `VectorDiff::Set` operation
957    /// for each item which will be added to this transaction.
958    pub(super) fn mark_all_events_as_encrypted(&mut self) {
959        for idx in 0..self.items.len() {
960            let item = &self.items[idx];
961
962            if let Some(event) = item.as_event() {
963                if event.is_room_encrypted {
964                    continue;
965                }
966
967                let mut cloned_event = event.clone();
968                cloned_event.is_room_encrypted = true;
969
970                // Replace the existing item with a new version with the right encryption flag
971                let item = item.with_kind(cloned_event);
972                self.items.replace(idx, item);
973            }
974        }
975    }
976}