matrix_sdk/event_cache/room/
mod.rs

1// Copyright 2024 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
15//! All event cache types for a single room.
16
17use std::{
18    collections::BTreeMap,
19    fmt,
20    ops::{Deref, DerefMut},
21    sync::{
22        atomic::{AtomicUsize, Ordering},
23        Arc,
24    },
25};
26
27use events::sort_positions_descending;
28use eyeball::SharedObservable;
29use eyeball_im::VectorDiff;
30use matrix_sdk_base::{
31    deserialized_responses::AmbiguityChange,
32    event_cache::Event,
33    linked_chunk::Position,
34    sync::{JoinedRoomUpdate, LeftRoomUpdate, Timeline},
35};
36use ruma::{
37    api::Direction,
38    events::{relation::RelationType, AnyRoomAccountDataEvent, AnySyncEphemeralRoomEvent},
39    serde::Raw,
40    EventId, OwnedEventId, OwnedRoomId,
41};
42use tokio::sync::{
43    broadcast::{Receiver, Sender},
44    mpsc, Notify, RwLock,
45};
46use tracing::{instrument, trace, warn};
47
48use super::{
49    AutoShrinkChannelPayload, EventsOrigin, Result, RoomEventCacheGenericUpdate,
50    RoomEventCacheUpdate, RoomPagination, RoomPaginationStatus,
51};
52use crate::{
53    client::WeakClient,
54    event_cache::EventCacheError,
55    room::{IncludeRelations, RelationsOptions, WeakRoom},
56};
57
58pub(super) mod events;
59mod threads;
60
61pub use threads::ThreadEventCacheUpdate;
62
63/// A subset of an event cache, for a room.
64///
65/// Cloning is shallow, and thus is cheap to do.
66#[derive(Clone)]
67pub struct RoomEventCache {
68    pub(super) inner: Arc<RoomEventCacheInner>,
69}
70
71impl fmt::Debug for RoomEventCache {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        f.debug_struct("RoomEventCache").finish_non_exhaustive()
74    }
75}
76
77/// Thin wrapper for a room event cache subscriber, so as to trigger
78/// side-effects when all subscribers are gone.
79///
80/// The current side-effect is: auto-shrinking the [`RoomEventCache`] when no
81/// more subscribers are active. This is an optimisation to reduce the number of
82/// data held in memory by a [`RoomEventCache`]: when no more subscribers are
83/// active, all data are reduced to the minimum.
84///
85/// The side-effect takes effect on `Drop`.
86#[allow(missing_debug_implementations)]
87pub struct RoomEventCacheSubscriber {
88    /// Underlying receiver of the room event cache's updates.
89    recv: Receiver<RoomEventCacheUpdate>,
90
91    /// To which room are we listening?
92    room_id: OwnedRoomId,
93
94    /// Sender to the auto-shrink channel.
95    auto_shrink_sender: mpsc::Sender<AutoShrinkChannelPayload>,
96
97    /// Shared instance of the auto-shrinker.
98    subscriber_count: Arc<AtomicUsize>,
99}
100
101impl Drop for RoomEventCacheSubscriber {
102    fn drop(&mut self) {
103        let previous_subscriber_count = self.subscriber_count.fetch_sub(1, Ordering::SeqCst);
104
105        trace!(
106            "dropping a room event cache subscriber; previous count: {previous_subscriber_count}"
107        );
108
109        if previous_subscriber_count == 1 {
110            // We were the last instance of the subscriber; let the auto-shrinker know by
111            // notifying it of our room id.
112
113            let mut room_id = self.room_id.clone();
114
115            // Try to send without waiting for channel capacity, and restart in a spin-loop
116            // if it failed (until a maximum number of attempts is reached, or
117            // the send was successful). The channel shouldn't be super busy in
118            // general, so this should resolve quickly enough.
119
120            let mut num_attempts = 0;
121
122            while let Err(err) = self.auto_shrink_sender.try_send(room_id) {
123                num_attempts += 1;
124
125                if num_attempts > 1024 {
126                    // If we've tried too many times, just give up with a warning; after all, this
127                    // is only an optimization.
128                    warn!(
129                        "couldn't send notification to the auto-shrink channel \
130                         after 1024 attempts; giving up"
131                    );
132                    return;
133                }
134
135                match err {
136                    mpsc::error::TrySendError::Full(stolen_room_id) => {
137                        room_id = stolen_room_id;
138                    }
139                    mpsc::error::TrySendError::Closed(_) => return,
140                }
141            }
142
143            trace!("sent notification to the parent channel that we were the last subscriber");
144        }
145    }
146}
147
148impl Deref for RoomEventCacheSubscriber {
149    type Target = Receiver<RoomEventCacheUpdate>;
150
151    fn deref(&self) -> &Self::Target {
152        &self.recv
153    }
154}
155
156impl DerefMut for RoomEventCacheSubscriber {
157    fn deref_mut(&mut self) -> &mut Self::Target {
158        &mut self.recv
159    }
160}
161
162impl RoomEventCache {
163    /// Create a new [`RoomEventCache`] using the given room and store.
164    pub(super) fn new(
165        client: WeakClient,
166        state: RoomEventCacheState,
167        pagination_status: SharedObservable<RoomPaginationStatus>,
168        room_id: OwnedRoomId,
169        auto_shrink_sender: mpsc::Sender<AutoShrinkChannelPayload>,
170        generic_update_sender: Sender<RoomEventCacheGenericUpdate>,
171    ) -> Self {
172        Self {
173            inner: Arc::new(RoomEventCacheInner::new(
174                client,
175                state,
176                pagination_status,
177                room_id,
178                auto_shrink_sender,
179                generic_update_sender,
180            )),
181        }
182    }
183
184    /// Read all current events.
185    ///
186    /// Use [`RoomEventCache::subscribe`] to get all current events, plus a
187    /// subscriber.
188    pub async fn events(&self) -> Vec<Event> {
189        let state = self.inner.state.read().await;
190
191        state.room_linked_chunk().events().map(|(_position, item)| item.clone()).collect()
192    }
193
194    /// Subscribe to this room updates, after getting the initial list of
195    /// events.
196    ///
197    /// Use [`RoomEventCache::events`] to get all current events without the
198    /// subscriber. Creating, and especially dropping, a
199    /// [`RoomEventCacheSubscriber`] isn't free, as it triggers side-effects.
200    pub async fn subscribe(&self) -> (Vec<Event>, RoomEventCacheSubscriber) {
201        let state = self.inner.state.read().await;
202        let events =
203            state.room_linked_chunk().events().map(|(_position, item)| item.clone()).collect();
204
205        let previous_subscriber_count = state.subscriber_count.fetch_add(1, Ordering::SeqCst);
206        trace!("added a room event cache subscriber; new count: {}", previous_subscriber_count + 1);
207
208        let recv = self.inner.sender.subscribe();
209        let subscriber = RoomEventCacheSubscriber {
210            recv,
211            room_id: self.inner.room_id.clone(),
212            auto_shrink_sender: self.inner.auto_shrink_sender.clone(),
213            subscriber_count: state.subscriber_count.clone(),
214        };
215
216        (events, subscriber)
217    }
218
219    /// Subscribe to thread for a given root event, and get a (maybe empty)
220    /// initially known list of events for that thread.
221    pub async fn subscribe_to_thread(
222        &self,
223        thread_root: OwnedEventId,
224    ) -> (Vec<Event>, Receiver<ThreadEventCacheUpdate>) {
225        let mut state = self.inner.state.write().await;
226        state.subscribe_to_thread(thread_root)
227    }
228
229    /// Paginate backwards in a thread, given its root event ID.
230    ///
231    /// Returns whether we've hit the start of the thread, in which case the
232    /// root event will be prepended to the thread.
233    #[instrument(skip(self), fields(room_id = %self.inner.room_id))]
234    pub async fn paginate_thread_backwards(
235        &self,
236        thread_root: OwnedEventId,
237        num_events: u16,
238    ) -> Result<bool> {
239        let room = self.inner.weak_room.get().ok_or(EventCacheError::ClientDropped)?;
240
241        // Take the lock only for a short time here.
242        let mut outcome =
243            self.inner.state.write().await.load_more_thread_events_backwards(thread_root.clone());
244
245        loop {
246            match outcome {
247                LoadMoreEventsBackwardsOutcome::Gap { prev_token } => {
248                    // Start a threaded pagination from this gap.
249                    let options = RelationsOptions {
250                        from: prev_token.clone(),
251                        dir: Direction::Backward,
252                        limit: Some(num_events.into()),
253                        include_relations: IncludeRelations::AllRelations,
254                        recurse: true,
255                    };
256
257                    let mut result = room
258                        .relations(thread_root.clone(), options)
259                        .await
260                        .map_err(|err| EventCacheError::BackpaginationError(Box::new(err)))?;
261
262                    let reached_start = result.next_batch_token.is_none();
263                    trace!(num_events = result.chunk.len(), %reached_start, "received a /relations response");
264
265                    // Because the state lock is taken again in `load_or_fetch_event`, we need
266                    // to do this *before* we take the state lock again.
267                    if reached_start {
268                        // Prepend the thread root event to the results.
269                        let root_event = room
270                            .load_or_fetch_event(&thread_root, None)
271                            .await
272                            .map_err(|err| EventCacheError::BackpaginationError(Box::new(err)))?;
273
274                        // Note: the events are still in the reversed order at this point, so
275                        // pushing will eventually make it so that the root event is the first.
276                        result.chunk.push(root_event);
277                    }
278
279                    let mut state = self.inner.state.write().await;
280
281                    if let Some(outcome) = state.finish_thread_network_pagination(
282                        thread_root.clone(),
283                        prev_token,
284                        result.next_batch_token,
285                        result.chunk,
286                    ) {
287                        return Ok(outcome.reached_start);
288                    }
289
290                    // fallthrough: restart the pagination.
291                    outcome = state.load_more_thread_events_backwards(thread_root.clone());
292                }
293
294                LoadMoreEventsBackwardsOutcome::StartOfTimeline => {
295                    // We're done!
296                    return Ok(true);
297                }
298
299                LoadMoreEventsBackwardsOutcome::Events { .. } => {
300                    // TODO: implement :)
301                    unimplemented!("loading from disk for threads is not implemented yet");
302                }
303
304                LoadMoreEventsBackwardsOutcome::WaitForInitialPrevToken => {
305                    unreachable!("unused for threads")
306                }
307            }
308        }
309    }
310
311    /// Return a [`RoomPagination`] API object useful for running
312    /// back-pagination queries in the current room.
313    pub fn pagination(&self) -> RoomPagination {
314        RoomPagination { inner: self.inner.clone() }
315    }
316
317    /// Try to find a single event in this room, starting from the most recent
318    /// event.
319    ///
320    /// **Warning**! It looks into the loaded events from the in-memory linked
321    /// chunk **only**. It doesn't look inside the storage.
322    pub async fn rfind_map_event_in_memory_by<O, P>(&self, predicate: P) -> Option<O>
323    where
324        P: FnMut(&Event) -> Option<O>,
325    {
326        self.inner.state.read().await.rfind_map_event_in_memory_by(predicate)
327    }
328
329    /// Try to find an event by ID in this room.
330    ///
331    /// It starts by looking into loaded events before looking inside the
332    /// storage.
333    pub async fn find_event(&self, event_id: &EventId) -> Option<Event> {
334        self.inner
335            .state
336            .read()
337            .await
338            .find_event(event_id)
339            .await
340            .ok()
341            .flatten()
342            .map(|(_loc, event)| event)
343    }
344
345    /// Try to find an event by ID in this room, along with its related events.
346    ///
347    /// You can filter which types of related events to retrieve using
348    /// `filter`. `None` will retrieve related events of any type.
349    ///
350    /// The related events are sorted like this:
351    ///
352    /// - events saved out-of-band (with `RoomEventCache::save_events`) will be
353    ///   located at the beginning of the array.
354    /// - events present in the linked chunk (be it in memory or in the storage)
355    ///   will be sorted according to their ordering in the linked chunk.
356    pub async fn find_event_with_relations(
357        &self,
358        event_id: &EventId,
359        filter: Option<Vec<RelationType>>,
360    ) -> Option<(Event, Vec<Event>)> {
361        // Search in all loaded or stored events.
362        self.inner
363            .state
364            .read()
365            .await
366            .find_event_with_relations(event_id, filter.clone())
367            .await
368            .ok()
369            .flatten()
370    }
371
372    /// Clear all the storage for this [`RoomEventCache`].
373    ///
374    /// This will get rid of all the events from the linked chunk and persisted
375    /// storage.
376    pub async fn clear(&self) -> Result<()> {
377        // Clear the linked chunk and persisted storage.
378        let updates_as_vector_diffs = self.inner.state.write().await.reset().await?;
379
380        // Notify observers about the update.
381        let _ = self.inner.sender.send(RoomEventCacheUpdate::UpdateTimelineEvents {
382            diffs: updates_as_vector_diffs,
383            origin: EventsOrigin::Cache,
384        });
385
386        // Notify observers about the generic update.
387        let _ = self
388            .inner
389            .generic_update_sender
390            .send(RoomEventCacheGenericUpdate::Clear { room_id: self.inner.room_id.clone() });
391
392        Ok(())
393    }
394
395    /// Save some events in the event cache, for further retrieval with
396    /// [`Self::event`].
397    pub(crate) async fn save_events(&self, events: impl IntoIterator<Item = Event>) {
398        if let Err(err) = self.inner.state.write().await.save_event(events).await {
399            warn!("couldn't save event in the event cache: {err}");
400        }
401    }
402
403    /// Return a nice debug string (a vector of lines) for the linked chunk of
404    /// events for this room.
405    pub async fn debug_string(&self) -> Vec<String> {
406        self.inner.state.read().await.room_linked_chunk().debug_string()
407    }
408}
409
410/// The (non-cloneable) details of the `RoomEventCache`.
411pub(super) struct RoomEventCacheInner {
412    /// The room id for this room.
413    pub(super) room_id: OwnedRoomId,
414
415    pub weak_room: WeakRoom,
416
417    /// Sender part for subscribers to this room.
418    pub sender: Sender<RoomEventCacheUpdate>,
419
420    /// State for this room's event cache.
421    pub state: RwLock<RoomEventCacheState>,
422
423    /// A notifier that we received a new pagination token.
424    pub pagination_batch_token_notifier: Notify,
425
426    pub pagination_status: SharedObservable<RoomPaginationStatus>,
427
428    /// Sender to the auto-shrink channel.
429    ///
430    /// See doc comment around [`EventCache::auto_shrink_linked_chunk_task`] for
431    /// more details.
432    auto_shrink_sender: mpsc::Sender<AutoShrinkChannelPayload>,
433
434    /// A clone of [`EventCacheInner::room_event_cache_generic_update_sender`].
435    ///
436    /// Whilst `EventCacheInner` handles the generic updates from the sync, or
437    /// the storage, it doesn't handle the update from pagination. Having a
438    /// clone here allows to access it from [`RoomPagination`].
439    pub(super) generic_update_sender: Sender<RoomEventCacheGenericUpdate>,
440}
441
442impl RoomEventCacheInner {
443    /// Creates a new cache for a room, and subscribes to room updates, so as
444    /// to handle new timeline events.
445    fn new(
446        client: WeakClient,
447        state: RoomEventCacheState,
448        pagination_status: SharedObservable<RoomPaginationStatus>,
449        room_id: OwnedRoomId,
450        auto_shrink_sender: mpsc::Sender<AutoShrinkChannelPayload>,
451        generic_update_sender: Sender<RoomEventCacheGenericUpdate>,
452    ) -> Self {
453        let sender = Sender::new(32);
454        let weak_room = WeakRoom::new(client, room_id);
455        Self {
456            room_id: weak_room.room_id().to_owned(),
457            weak_room,
458            state: RwLock::new(state),
459            sender,
460            pagination_batch_token_notifier: Default::default(),
461            auto_shrink_sender,
462            pagination_status,
463            generic_update_sender,
464        }
465    }
466
467    fn handle_account_data(&self, account_data: Vec<Raw<AnyRoomAccountDataEvent>>) {
468        if account_data.is_empty() {
469            return;
470        }
471
472        let mut handled_read_marker = false;
473
474        trace!("Handling account data");
475
476        for raw_event in account_data {
477            match raw_event.deserialize() {
478                Ok(AnyRoomAccountDataEvent::FullyRead(ev)) => {
479                    // If duplicated, do not forward read marker multiple times
480                    // to avoid clutter the update channel.
481                    if handled_read_marker {
482                        continue;
483                    }
484
485                    handled_read_marker = true;
486
487                    // Propagate to observers. (We ignore the error if there aren't any.)
488                    let _ = self.sender.send(RoomEventCacheUpdate::MoveReadMarkerTo {
489                        event_id: ev.content.event_id,
490                    });
491                }
492
493                Ok(_) => {
494                    // We're not interested in other room account data updates,
495                    // at this point.
496                }
497
498                Err(e) => {
499                    let event_type = raw_event.get_field::<String>("type").ok().flatten();
500                    warn!(event_type, "Failed to deserialize account data: {e}");
501                }
502            }
503        }
504    }
505
506    #[instrument(skip_all, fields(room_id = %self.room_id))]
507    pub(super) async fn handle_joined_room_update(&self, updates: JoinedRoomUpdate) -> Result<()> {
508        self.handle_timeline(
509            updates.timeline,
510            updates.ephemeral.clone(),
511            updates.ambiguity_changes,
512        )
513        .await?;
514        self.handle_account_data(updates.account_data);
515
516        Ok(())
517    }
518
519    #[instrument(skip_all, fields(room_id = %self.room_id))]
520    pub(super) async fn handle_left_room_update(&self, updates: LeftRoomUpdate) -> Result<()> {
521        self.handle_timeline(updates.timeline, Vec::new(), updates.ambiguity_changes).await?;
522
523        Ok(())
524    }
525
526    /// Handle a [`Timeline`], i.e. new events received by a sync for this
527    /// room.
528    async fn handle_timeline(
529        &self,
530        timeline: Timeline,
531        ephemeral_events: Vec<Raw<AnySyncEphemeralRoomEvent>>,
532        ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
533    ) -> Result<()> {
534        if timeline.events.is_empty()
535            && timeline.prev_batch.is_none()
536            && ephemeral_events.is_empty()
537            && ambiguity_changes.is_empty()
538        {
539            return Ok(());
540        }
541
542        // Add all the events to the backend.
543        trace!("adding new events");
544
545        let (stored_prev_batch_token, timeline_event_diffs) =
546            self.state.write().await.handle_sync(timeline).await?;
547
548        // Now that all events have been added, we can trigger the
549        // `pagination_token_notifier`.
550        if stored_prev_batch_token {
551            self.pagination_batch_token_notifier.notify_one();
552        }
553
554        let mut update_has_been_sent = false;
555
556        // The order matters here: first send the timeline event diffs, then only the
557        // related events (read receipts, etc.).
558        if !timeline_event_diffs.is_empty() {
559            let _ = self.sender.send(RoomEventCacheUpdate::UpdateTimelineEvents {
560                diffs: timeline_event_diffs,
561                origin: EventsOrigin::Sync,
562            });
563            update_has_been_sent = true;
564        }
565
566        if !ephemeral_events.is_empty() {
567            let _ = self
568                .sender
569                .send(RoomEventCacheUpdate::AddEphemeralEvents { events: ephemeral_events });
570            update_has_been_sent = true;
571        }
572
573        if !ambiguity_changes.is_empty() {
574            let _ = self.sender.send(RoomEventCacheUpdate::UpdateMembers { ambiguity_changes });
575            update_has_been_sent = true;
576        }
577
578        if update_has_been_sent {
579            let _ = self.generic_update_sender.send(RoomEventCacheGenericUpdate::UpdateTimeline {
580                room_id: self.room_id.clone(),
581            });
582        }
583
584        Ok(())
585    }
586}
587
588/// Internal type to represent the output of
589/// [`RoomEventCacheState::load_more_events_backwards`].
590#[derive(Debug)]
591pub(super) enum LoadMoreEventsBackwardsOutcome {
592    /// A gap has been inserted.
593    Gap {
594        /// The previous batch token to be used as the "end" parameter in the
595        /// back-pagination request.
596        prev_token: Option<String>,
597    },
598
599    /// The start of the timeline has been reached.
600    StartOfTimeline,
601
602    /// Events have been inserted.
603    Events { events: Vec<Event>, timeline_event_diffs: Vec<VectorDiff<Event>>, reached_start: bool },
604
605    /// The caller must wait for the initial previous-batch token, and retry.
606    WaitForInitialPrevToken,
607}
608
609// Use a private module to hide `events` to this parent module.
610mod private {
611    use std::{
612        collections::{BTreeMap, HashMap, HashSet},
613        sync::{atomic::AtomicUsize, Arc},
614    };
615
616    use eyeball::SharedObservable;
617    use eyeball_im::VectorDiff;
618    use matrix_sdk_base::{
619        apply_redaction,
620        deserialized_responses::{ThreadSummary, ThreadSummaryStatus, TimelineEventKind},
621        event_cache::{
622            store::{DynEventCacheStore, EventCacheStoreLock},
623            Event, Gap,
624        },
625        linked_chunk::{
626            lazy_loader::{self},
627            ChunkContent, ChunkIdentifierGenerator, ChunkMetadata, LinkedChunkId, Position, Update,
628        },
629        serde_helpers::extract_thread_root,
630        sync::Timeline,
631    };
632    use matrix_sdk_common::executor::spawn;
633    use ruma::{
634        events::{
635            relation::RelationType, room::redaction::SyncRoomRedactionEvent, AnySyncTimelineEvent,
636            MessageLikeEventType,
637        },
638        room_version_rules::RoomVersionRules,
639        serde::Raw,
640        EventId, OwnedEventId, OwnedRoomId,
641    };
642    use tokio::sync::broadcast::Receiver;
643    use tracing::{debug, error, instrument, trace, warn};
644
645    use super::{
646        super::{deduplicator::DeduplicationOutcome, EventCacheError},
647        events::EventLinkedChunk,
648        sort_positions_descending, EventLocation, LoadMoreEventsBackwardsOutcome,
649    };
650    use crate::event_cache::{
651        deduplicator::filter_duplicate_events, room::threads::ThreadEventCache,
652        BackPaginationOutcome, RoomPaginationStatus, ThreadEventCacheUpdate,
653    };
654
655    /// State for a single room's event cache.
656    ///
657    /// This contains all the inner mutable states that ought to be updated at
658    /// the same time.
659    pub struct RoomEventCacheState {
660        /// The room this state relates to.
661        room: OwnedRoomId,
662
663        /// The rules for the version of this room.
664        room_version_rules: RoomVersionRules,
665
666        /// Reference to the underlying backing store.
667        store: EventCacheStoreLock,
668
669        /// The loaded events for the current room, that is, the in-memory
670        /// linked chunk for this room.
671        room_linked_chunk: EventLinkedChunk,
672
673        /// Threads present in this room.
674        ///
675        /// Keyed by the thread root event ID.
676        threads: HashMap<OwnedEventId, ThreadEventCache>,
677
678        /// Have we ever waited for a previous-batch-token to come from sync, in
679        /// the context of pagination? We do this at most once per room,
680        /// the first time we try to run backward pagination. We reset
681        /// that upon clearing the timeline events.
682        pub waited_for_initial_prev_token: bool,
683
684        pagination_status: SharedObservable<RoomPaginationStatus>,
685
686        /// An atomic count of the current number of subscriber of the
687        /// [`super::RoomEventCache`].
688        pub(super) subscriber_count: Arc<AtomicUsize>,
689    }
690
691    impl RoomEventCacheState {
692        /// Create a new state, or reload it from storage if it's been enabled.
693        ///
694        /// Not all events are going to be loaded. Only a portion of them. The
695        /// [`EventLinkedChunk`] relies on a [`LinkedChunk`] to store all
696        /// events. Only the last chunk will be loaded. It means the
697        /// events are loaded from the most recent to the oldest. To
698        /// load more events, see [`Self::load_more_events_backwards`].
699        ///
700        /// [`LinkedChunk`]: matrix_sdk_common::linked_chunk::LinkedChunk
701        pub async fn new(
702            room_id: OwnedRoomId,
703            room_version_rules: RoomVersionRules,
704            store: EventCacheStoreLock,
705            pagination_status: SharedObservable<RoomPaginationStatus>,
706        ) -> Result<Self, EventCacheError> {
707            let store_lock = store.lock().await?;
708
709            let linked_chunk_id = LinkedChunkId::Room(&room_id);
710
711            // Load the full linked chunk's metadata, so as to feed the order tracker.
712            //
713            // If loading the full linked chunk failed, we'll clear the event cache, as it
714            // indicates that at some point, there's some malformed data.
715            let full_linked_chunk_metadata =
716                match Self::load_linked_chunk_metadata(&*store_lock, linked_chunk_id).await {
717                    Ok(metas) => metas,
718                    Err(err) => {
719                        error!(
720                            "error when loading a linked chunk's metadata from the store: {err}"
721                        );
722
723                        // Try to clear storage for this room.
724                        store_lock
725                            .handle_linked_chunk_updates(linked_chunk_id, vec![Update::Clear])
726                            .await?;
727
728                        // Restart with an empty linked chunk.
729                        None
730                    }
731                };
732
733            let linked_chunk = match store_lock
734                .load_last_chunk(linked_chunk_id)
735                .await
736                .map_err(EventCacheError::from)
737                .and_then(|(last_chunk, chunk_identifier_generator)| {
738                    lazy_loader::from_last_chunk(last_chunk, chunk_identifier_generator)
739                        .map_err(EventCacheError::from)
740                }) {
741                Ok(linked_chunk) => linked_chunk,
742                Err(err) => {
743                    error!(
744                        "error when loading a linked chunk's latest chunk from the store: {err}"
745                    );
746
747                    // Try to clear storage for this room.
748                    store_lock
749                        .handle_linked_chunk_updates(linked_chunk_id, vec![Update::Clear])
750                        .await?;
751
752                    None
753                }
754            };
755
756            let room_linked_chunk = EventLinkedChunk::with_initial_linked_chunk(
757                linked_chunk,
758                full_linked_chunk_metadata,
759            );
760
761            // The threads mapping is intentionally empty at start, since we're going to
762            // reload threads lazily, as soon as we need to (based on external
763            // subscribers) or when we get new information about those (from
764            // sync).
765            let threads = HashMap::new();
766
767            Ok(Self {
768                room: room_id,
769                room_version_rules,
770                store,
771                room_linked_chunk,
772                threads,
773                waited_for_initial_prev_token: false,
774                subscriber_count: Default::default(),
775                pagination_status,
776            })
777        }
778
779        /// Load a linked chunk's full metadata, making sure the chunks are
780        /// according to their their links.
781        ///
782        /// Returns `None` if there's no such linked chunk in the store, or an
783        /// error if the linked chunk is malformed.
784        async fn load_linked_chunk_metadata(
785            store: &DynEventCacheStore,
786            linked_chunk_id: LinkedChunkId<'_>,
787        ) -> Result<Option<Vec<ChunkMetadata>>, EventCacheError> {
788            let mut all_chunks = store
789                .load_all_chunks_metadata(linked_chunk_id)
790                .await
791                .map_err(EventCacheError::from)?;
792
793            if all_chunks.is_empty() {
794                // There are no chunks, so there's nothing to do.
795                return Ok(None);
796            }
797
798            // Transform the vector into a hashmap, for quick lookup of the predecessors.
799            let chunk_map: HashMap<_, _> =
800                all_chunks.iter().map(|meta| (meta.identifier, meta)).collect();
801
802            // Find a last chunk.
803            let mut iter = all_chunks.iter().filter(|meta| meta.next.is_none());
804            let Some(last) = iter.next() else {
805                return Err(EventCacheError::InvalidLinkedChunkMetadata {
806                    details: "no last chunk found".to_owned(),
807                });
808            };
809
810            // There must at most one last chunk.
811            if let Some(other_last) = iter.next() {
812                return Err(EventCacheError::InvalidLinkedChunkMetadata {
813                    details: format!(
814                        "chunks {} and {} both claim to be last chunks",
815                        last.identifier.index(),
816                        other_last.identifier.index()
817                    ),
818                });
819            }
820
821            // Rewind the chain back to the first chunk, and do some checks at the same
822            // time.
823            let mut seen = HashSet::new();
824            let mut current = last;
825            loop {
826                // If we've already seen this chunk, there's a cycle somewhere.
827                if !seen.insert(current.identifier) {
828                    return Err(EventCacheError::InvalidLinkedChunkMetadata {
829                        details: format!(
830                            "cycle detected in linked chunk at {}",
831                            current.identifier.index()
832                        ),
833                    });
834                }
835
836                let Some(prev_id) = current.previous else {
837                    // If there's no previous chunk, we're done.
838                    if seen.len() != all_chunks.len() {
839                        return Err(EventCacheError::InvalidLinkedChunkMetadata {
840                            details: format!(
841                                "linked chunk likely has multiple components: {} chunks seen through the chain of predecessors, but {} expected",
842                                seen.len(),
843                                all_chunks.len()
844                            ),
845                        });
846                    }
847                    break;
848                };
849
850                // If the previous chunk is not in the map, then it's unknown
851                // and missing.
852                let Some(pred_meta) = chunk_map.get(&prev_id) else {
853                    return Err(EventCacheError::InvalidLinkedChunkMetadata {
854                        details: format!(
855                            "missing predecessor {} chunk for {}",
856                            prev_id.index(),
857                            current.identifier.index()
858                        ),
859                    });
860                };
861
862                // If the previous chunk isn't connected to the next, then the link is invalid.
863                if pred_meta.next != Some(current.identifier) {
864                    return Err(EventCacheError::InvalidLinkedChunkMetadata {
865                        details: format!(
866                            "chunk {}'s next ({:?}) doesn't match the current chunk ({})",
867                            pred_meta.identifier.index(),
868                            pred_meta.next.map(|chunk_id| chunk_id.index()),
869                            current.identifier.index()
870                        ),
871                    });
872                }
873
874                current = *pred_meta;
875            }
876
877            // At this point, `current` is the identifier of the first chunk.
878            //
879            // Reorder the resulting vector, by going through the chain of `next` links, and
880            // swapping items into their final position.
881            //
882            // Invariant in this loop: all items in [0..i[ are in their final, correct
883            // position.
884            let mut current = current.identifier;
885            for i in 0..all_chunks.len() {
886                // Find the target metadata.
887                let j = all_chunks
888                    .iter()
889                    .rev()
890                    .position(|meta| meta.identifier == current)
891                    .map(|j| all_chunks.len() - 1 - j)
892                    .expect("the target chunk must be present in the metadata");
893                if i != j {
894                    all_chunks.swap(i, j);
895                }
896                if let Some(next) = all_chunks[i].next {
897                    current = next;
898                }
899            }
900
901            Ok(Some(all_chunks))
902        }
903
904        /// Given a fully-loaded linked chunk with no gaps, return the
905        /// [`LoadMoreEventsBackwardsOutcome`] expected for this room's cache.
906        fn conclude_load_more_for_fully_loaded_chunk(&mut self) -> LoadMoreEventsBackwardsOutcome {
907            // If we never received events for this room, this means we've never
908            // received a sync for that room, because every room must have at least a
909            // room creation event. Otherwise, we have reached the start of the
910            // timeline.
911            if self.room_linked_chunk.events().next().is_some() {
912                // If there's at least one event, this means we've reached the start of the
913                // timeline, since the chunk is fully loaded.
914                trace!("chunk is fully loaded and non-empty: reached_start=true");
915                LoadMoreEventsBackwardsOutcome::StartOfTimeline
916            } else if !self.waited_for_initial_prev_token {
917                // There's no events. Since we haven't yet, wait for an initial previous-token.
918                LoadMoreEventsBackwardsOutcome::WaitForInitialPrevToken
919            } else {
920                // Otherwise, we've already waited, *and* received no previous-batch token from
921                // the sync, *and* there are still no events in the fully-loaded
922                // chunk: start back-pagination from the end of the room.
923                LoadMoreEventsBackwardsOutcome::Gap { prev_token: None }
924            }
925        }
926
927        /// Load more events backwards if the last chunk is **not** a gap.
928        pub(in super::super) async fn load_more_events_backwards(
929            &mut self,
930        ) -> Result<LoadMoreEventsBackwardsOutcome, EventCacheError> {
931            // If any in-memory chunk is a gap, don't load more events, and let the caller
932            // resolve the gap.
933            if let Some(prev_token) = self.room_linked_chunk.rgap().map(|gap| gap.prev_token) {
934                return Ok(LoadMoreEventsBackwardsOutcome::Gap { prev_token: Some(prev_token) });
935            }
936
937            // Because `first_chunk` is `not `Send`, get this information before the
938            // `.await` point, so that this `Future` can implement `Send`.
939            let first_chunk_identifier = self
940                .room_linked_chunk
941                .chunks()
942                .next()
943                .expect("a linked chunk is never empty")
944                .identifier();
945
946            let store = self.store.lock().await?;
947
948            // The first chunk is not a gap, we can load its previous chunk.
949            let linked_chunk_id = LinkedChunkId::Room(&self.room);
950            let new_first_chunk = match store
951                .load_previous_chunk(linked_chunk_id, first_chunk_identifier)
952                .await
953            {
954                Ok(Some(new_first_chunk)) => {
955                    // All good, let's continue with this chunk.
956                    new_first_chunk
957                }
958
959                Ok(None) => {
960                    // There's no previous chunk. The chunk is now fully-loaded. Conclude.
961                    return Ok(self.conclude_load_more_for_fully_loaded_chunk());
962                }
963
964                Err(err) => {
965                    error!("error when loading the previous chunk of a linked chunk: {err}");
966
967                    // Clear storage for this room.
968                    store.handle_linked_chunk_updates(linked_chunk_id, vec![Update::Clear]).await?;
969
970                    // Return the error.
971                    return Err(err.into());
972                }
973            };
974
975            let chunk_content = new_first_chunk.content.clone();
976
977            // We've reached the start on disk, if and only if, there was no chunk prior to
978            // the one we just loaded.
979            //
980            // This value is correct, if and only if, it is used for a chunk content of kind
981            // `Items`.
982            let reached_start = new_first_chunk.previous.is_none();
983
984            if let Err(err) = self.room_linked_chunk.insert_new_chunk_as_first(new_first_chunk) {
985                error!("error when inserting the previous chunk into its linked chunk: {err}");
986
987                // Clear storage for this room.
988                store.handle_linked_chunk_updates(linked_chunk_id, vec![Update::Clear]).await?;
989
990                // Return the error.
991                return Err(err.into());
992            }
993
994            // ⚠️ Let's not propagate the updates to the store! We already have these data
995            // in the store! Let's drain them.
996            let _ = self.room_linked_chunk.store_updates().take();
997
998            // However, we want to get updates as `VectorDiff`s.
999            let timeline_event_diffs = self.room_linked_chunk.updates_as_vector_diffs();
1000
1001            Ok(match chunk_content {
1002                ChunkContent::Gap(gap) => {
1003                    trace!("reloaded chunk from disk (gap)");
1004                    LoadMoreEventsBackwardsOutcome::Gap { prev_token: Some(gap.prev_token) }
1005                }
1006
1007                ChunkContent::Items(events) => {
1008                    trace!(?reached_start, "reloaded chunk from disk ({} items)", events.len());
1009                    LoadMoreEventsBackwardsOutcome::Events {
1010                        events,
1011                        timeline_event_diffs,
1012                        reached_start,
1013                    }
1014                }
1015            })
1016        }
1017
1018        /// If storage is enabled, unload all the chunks, then reloads only the
1019        /// last one.
1020        ///
1021        /// If storage's enabled, return a diff update that starts with a clear
1022        /// of all events; as a result, the caller may override any
1023        /// pending diff updates with the result of this function.
1024        ///
1025        /// Otherwise, returns `None`.
1026        pub(super) async fn shrink_to_last_chunk(&mut self) -> Result<(), EventCacheError> {
1027            let store_lock = self.store.lock().await?;
1028
1029            // Attempt to load the last chunk.
1030            let linked_chunk_id = LinkedChunkId::Room(&self.room);
1031            let (last_chunk, chunk_identifier_generator) =
1032                match store_lock.load_last_chunk(linked_chunk_id).await {
1033                    Ok(pair) => pair,
1034
1035                    Err(err) => {
1036                        // If loading the last chunk failed, clear the entire linked chunk.
1037                        error!("error when reloading a linked chunk from memory: {err}");
1038
1039                        // Clear storage for this room.
1040                        store_lock
1041                            .handle_linked_chunk_updates(linked_chunk_id, vec![Update::Clear])
1042                            .await?;
1043
1044                        // Restart with an empty linked chunk.
1045                        (None, ChunkIdentifierGenerator::new_from_scratch())
1046                    }
1047                };
1048
1049            debug!("unloading the linked chunk, and resetting it to its last chunk");
1050
1051            // Remove all the chunks from the linked chunks, except for the last one, and
1052            // updates the chunk identifier generator.
1053            if let Err(err) =
1054                self.room_linked_chunk.replace_with(last_chunk, chunk_identifier_generator)
1055            {
1056                error!("error when replacing the linked chunk: {err}");
1057                return self.reset_internal().await;
1058            }
1059
1060            // Let pagination observers know that we may have not reached the start of the
1061            // timeline.
1062            // TODO: likely need to cancel any ongoing pagination.
1063            self.pagination_status.set(RoomPaginationStatus::Idle { hit_timeline_start: false });
1064
1065            // Don't propagate those updates to the store; this is only for the in-memory
1066            // representation that we're doing this. Let's drain those store updates.
1067            let _ = self.room_linked_chunk.store_updates().take();
1068
1069            Ok(())
1070        }
1071
1072        /// Automatically shrink the room if there are no more subscribers, as
1073        /// indicated by the atomic number of active subscribers.
1074        #[must_use = "Propagate `VectorDiff` updates via `RoomEventCacheUpdate`"]
1075        pub(crate) async fn auto_shrink_if_no_subscribers(
1076            &mut self,
1077        ) -> Result<Option<Vec<VectorDiff<Event>>>, EventCacheError> {
1078            let subscriber_count = self.subscriber_count.load(std::sync::atomic::Ordering::SeqCst);
1079
1080            trace!(subscriber_count, "received request to auto-shrink");
1081
1082            if subscriber_count == 0 {
1083                // If we are the last strong reference to the auto-shrinker, we can shrink the
1084                // events data structure to its last chunk.
1085                self.shrink_to_last_chunk().await?;
1086                Ok(Some(self.room_linked_chunk.updates_as_vector_diffs()))
1087            } else {
1088                Ok(None)
1089            }
1090        }
1091
1092        #[cfg(test)]
1093        pub(crate) async fn force_shrink_to_last_chunk(
1094            &mut self,
1095        ) -> Result<Vec<VectorDiff<Event>>, EventCacheError> {
1096            self.shrink_to_last_chunk().await?;
1097            Ok(self.room_linked_chunk.updates_as_vector_diffs())
1098        }
1099
1100        pub(crate) fn room_event_order(&self, event_pos: Position) -> Option<usize> {
1101            self.room_linked_chunk.event_order(event_pos)
1102        }
1103
1104        /// Removes the bundled relations from an event, if they were present.
1105        ///
1106        /// Only replaces the present if it contained bundled relations.
1107        fn strip_relations_if_present<T>(event: &mut Raw<T>) {
1108            // We're going to get rid of the `unsigned`/`m.relations` field, if it's
1109            // present.
1110            // Use a closure that returns an option so we can quickly short-circuit.
1111            let mut closure = || -> Option<()> {
1112                let mut val: serde_json::Value = event.deserialize_as().ok()?;
1113                let unsigned = val.get_mut("unsigned")?;
1114                let unsigned_obj = unsigned.as_object_mut()?;
1115                if unsigned_obj.remove("m.relations").is_some() {
1116                    *event = Raw::new(&val).ok()?.cast_unchecked();
1117                }
1118                None
1119            };
1120            let _ = closure();
1121        }
1122
1123        fn strip_relations_from_event(ev: &mut Event) {
1124            match &mut ev.kind {
1125                TimelineEventKind::Decrypted(decrypted) => {
1126                    // Remove all information about encryption info for
1127                    // the bundled events.
1128                    decrypted.unsigned_encryption_info = None;
1129
1130                    // Remove the `unsigned`/`m.relations` field, if needs be.
1131                    Self::strip_relations_if_present(&mut decrypted.event);
1132                }
1133
1134                TimelineEventKind::UnableToDecrypt { event, .. }
1135                | TimelineEventKind::PlainText { event } => {
1136                    Self::strip_relations_if_present(event);
1137                }
1138            }
1139        }
1140
1141        /// Strips the bundled relations from a collection of events.
1142        fn strip_relations_from_events(items: &mut [Event]) {
1143            for ev in items.iter_mut() {
1144                Self::strip_relations_from_event(ev);
1145            }
1146        }
1147
1148        /// Remove events by their position, in `EventLinkedChunk` and in
1149        /// `EventCacheStore`.
1150        ///
1151        /// This method is purposely isolated because it must ensure that
1152        /// positions are sorted appropriately or it can be disastrous.
1153        #[instrument(skip_all)]
1154        async fn remove_events(
1155            &mut self,
1156            in_memory_events: Vec<(OwnedEventId, Position)>,
1157            in_store_events: Vec<(OwnedEventId, Position)>,
1158        ) -> Result<(), EventCacheError> {
1159            // In-store events.
1160            if !in_store_events.is_empty() {
1161                let mut positions = in_store_events
1162                    .into_iter()
1163                    .map(|(_event_id, position)| position)
1164                    .collect::<Vec<_>>();
1165
1166                sort_positions_descending(&mut positions);
1167
1168                let updates = positions
1169                    .into_iter()
1170                    .map(|pos| Update::RemoveItem { at: pos })
1171                    .collect::<Vec<_>>();
1172
1173                self.apply_store_only_updates(updates).await?;
1174            }
1175
1176            // In-memory events.
1177            if in_memory_events.is_empty() {
1178                // Nothing else to do, return early.
1179                return Ok(());
1180            }
1181
1182            // `remove_events_by_position` is responsible of sorting positions.
1183            self.room_linked_chunk
1184                .remove_events_by_position(
1185                    in_memory_events.into_iter().map(|(_event_id, position)| position).collect(),
1186                )
1187                .expect("failed to remove an event");
1188
1189            self.propagate_changes().await
1190        }
1191
1192        /// Propagate changes to the underlying storage.
1193        async fn propagate_changes(&mut self) -> Result<(), EventCacheError> {
1194            let updates = self.room_linked_chunk.store_updates().take();
1195            self.send_updates_to_store(updates).await
1196        }
1197
1198        /// Apply some updates that are effective only on the store itself.
1199        ///
1200        /// This method should be used only for updates that happen *outside*
1201        /// the in-memory linked chunk. Such updates must be applied
1202        /// onto the ordering tracker as well as to the persistent
1203        /// storage.
1204        async fn apply_store_only_updates(
1205            &mut self,
1206            updates: Vec<Update<Event, Gap>>,
1207        ) -> Result<(), EventCacheError> {
1208            self.room_linked_chunk.order_tracker.map_updates(&updates);
1209            self.send_updates_to_store(updates).await
1210        }
1211
1212        async fn send_updates_to_store(
1213            &mut self,
1214            mut updates: Vec<Update<Event, Gap>>,
1215        ) -> Result<(), EventCacheError> {
1216            if updates.is_empty() {
1217                return Ok(());
1218            }
1219
1220            // Strip relations from updates which insert or replace items.
1221            for update in updates.iter_mut() {
1222                match update {
1223                    Update::PushItems { items, .. } => Self::strip_relations_from_events(items),
1224                    Update::ReplaceItem { item, .. } => Self::strip_relations_from_event(item),
1225                    // Other update kinds don't involve adding new events.
1226                    Update::NewItemsChunk { .. }
1227                    | Update::NewGapChunk { .. }
1228                    | Update::RemoveChunk(_)
1229                    | Update::RemoveItem { .. }
1230                    | Update::DetachLastItems { .. }
1231                    | Update::StartReattachItems
1232                    | Update::EndReattachItems
1233                    | Update::Clear => {}
1234                }
1235            }
1236
1237            // Spawn a task to make sure that all the changes are effectively forwarded to
1238            // the store, even if the call to this method gets aborted.
1239            //
1240            // The store cross-process locking involves an actual mutex, which ensures that
1241            // storing updates happens in the expected order.
1242
1243            let store = self.store.clone();
1244            let room_id = self.room.clone();
1245
1246            spawn(async move {
1247                let store = store.lock().await?;
1248
1249                trace!(?updates, "sending linked chunk updates to the store");
1250                let linked_chunk_id = LinkedChunkId::Room(&room_id);
1251                store.handle_linked_chunk_updates(linked_chunk_id, updates).await?;
1252                trace!("linked chunk updates applied");
1253
1254                super::Result::Ok(())
1255            })
1256            .await
1257            .expect("joining failed")?;
1258
1259            Ok(())
1260        }
1261
1262        /// Reset this data structure as if it were brand new.
1263        ///
1264        /// Return a single diff update that is a clear of all events; as a
1265        /// result, the caller may override any pending diff updates
1266        /// with the result of this function.
1267        #[must_use = "Propagate `VectorDiff` updates via `RoomEventCacheUpdate`"]
1268        pub async fn reset(&mut self) -> Result<Vec<VectorDiff<Event>>, EventCacheError> {
1269            self.reset_internal().await?;
1270
1271            let diff_updates = self.room_linked_chunk.updates_as_vector_diffs();
1272
1273            // Ensure the contract defined in the doc comment is true:
1274            debug_assert_eq!(diff_updates.len(), 1);
1275            debug_assert!(matches!(diff_updates[0], VectorDiff::Clear));
1276
1277            Ok(diff_updates)
1278        }
1279
1280        async fn reset_internal(&mut self) -> Result<(), EventCacheError> {
1281            self.room_linked_chunk.reset();
1282
1283            // No need to update the thread summaries: the room events are
1284            // gone because of the
1285            // reset of `room_linked_chunk`.
1286            //
1287            // Clear the threads.
1288            for thread in self.threads.values_mut() {
1289                thread.clear();
1290            }
1291
1292            self.propagate_changes().await?;
1293
1294            // Reset the pagination state too: pretend we never waited for the initial
1295            // prev-batch token, and indicate that we're not at the start of the
1296            // timeline, since we don't know about that anymore.
1297            self.waited_for_initial_prev_token = false;
1298            // TODO: likely must cancel any ongoing back-paginations too
1299            self.pagination_status.set(RoomPaginationStatus::Idle { hit_timeline_start: false });
1300
1301            Ok(())
1302        }
1303
1304        /// Returns a read-only reference to the underlying room linked chunk.
1305        pub fn room_linked_chunk(&self) -> &EventLinkedChunk {
1306            &self.room_linked_chunk
1307        }
1308
1309        //// Find a single event in this room, starting from the most recent event.
1310        ///
1311        /// **Warning**! It looks into the loaded events from the in-memory
1312        /// linked chunk **only**. It doesn't look inside the storage,
1313        /// contrary to [`Self::find_event`].
1314        pub fn rfind_map_event_in_memory_by<O, P>(&self, mut predicate: P) -> Option<O>
1315        where
1316            P: FnMut(&Event) -> Option<O>,
1317        {
1318            self.room_linked_chunk.revents().find_map(|(_position, event)| predicate(event))
1319        }
1320
1321        /// Find a single event in this room.
1322        ///
1323        /// It starts by looking into loaded events in `EventLinkedChunk` before
1324        /// looking inside the storage.
1325        pub async fn find_event(
1326            &self,
1327            event_id: &EventId,
1328        ) -> Result<Option<(EventLocation, Event)>, EventCacheError> {
1329            // There are supposedly fewer events loaded in memory than in the store. Let's
1330            // start by looking up in the `EventLinkedChunk`.
1331            for (position, event) in self.room_linked_chunk.revents() {
1332                if event.event_id().as_deref() == Some(event_id) {
1333                    return Ok(Some((EventLocation::Memory(position), event.clone())));
1334                }
1335            }
1336
1337            let store = self.store.lock().await?;
1338
1339            Ok(store
1340                .find_event(&self.room, event_id)
1341                .await?
1342                .map(|event| (EventLocation::Store, event)))
1343        }
1344
1345        /// Find an event and all its relations in the persisted storage.
1346        ///
1347        /// This goes straight to the database, as a simplification; we don't
1348        /// expect to need to have to look up in memory events, or that
1349        /// all the related events are actually loaded.
1350        ///
1351        /// The related events are sorted like this:
1352        /// - events saved out-of-band with
1353        ///   [`super::RoomEventCache::save_events`] will be located at the
1354        ///   beginning of the array.
1355        /// - events present in the linked chunk (be it in memory or in the
1356        ///   database) will be sorted according to their ordering in the linked
1357        ///   chunk.
1358        pub async fn find_event_with_relations(
1359            &self,
1360            event_id: &EventId,
1361            filters: Option<Vec<RelationType>>,
1362        ) -> Result<Option<(Event, Vec<Event>)>, EventCacheError> {
1363            let store = self.store.lock().await?;
1364
1365            // First, hit storage to get the target event and its related events.
1366            let found = store.find_event(&self.room, event_id).await?;
1367
1368            let Some(target) = found else {
1369                // We haven't found the event: return early.
1370                return Ok(None);
1371            };
1372
1373            // Then, initialize the stack with all the related events, to find the
1374            // transitive closure of all the related events.
1375            let mut related =
1376                store.find_event_relations(&self.room, event_id, filters.as_deref()).await?;
1377            let mut stack =
1378                related.iter().filter_map(|(event, _pos)| event.event_id()).collect::<Vec<_>>();
1379
1380            // Also keep track of already seen events, in case there's a loop in the
1381            // relation graph.
1382            let mut already_seen = HashSet::new();
1383            already_seen.insert(event_id.to_owned());
1384
1385            let mut num_iters = 1;
1386
1387            // Find the related event for each previously-related event.
1388            while let Some(event_id) = stack.pop() {
1389                if !already_seen.insert(event_id.clone()) {
1390                    // Skip events we've already seen.
1391                    continue;
1392                }
1393
1394                let other_related =
1395                    store.find_event_relations(&self.room, &event_id, filters.as_deref()).await?;
1396
1397                stack.extend(other_related.iter().filter_map(|(event, _pos)| event.event_id()));
1398                related.extend(other_related);
1399
1400                num_iters += 1;
1401            }
1402
1403            trace!(num_related = %related.len(), num_iters, "computed transitive closure of related events");
1404
1405            // Sort the results by their positions in the linked chunk, if available.
1406            //
1407            // If an event doesn't have a known position, it goes to the start of the array.
1408            related.sort_by(|(_, lhs), (_, rhs)| {
1409                use std::cmp::Ordering;
1410                match (lhs, rhs) {
1411                    (None, None) => Ordering::Equal,
1412                    (None, Some(_)) => Ordering::Less,
1413                    (Some(_), None) => Ordering::Greater,
1414                    (Some(lhs), Some(rhs)) => {
1415                        let lhs = self.room_event_order(*lhs);
1416                        let rhs = self.room_event_order(*rhs);
1417
1418                        // The events should have a definite position, but in the case they don't,
1419                        // still consider that not having a position means you'll end at the start
1420                        // of the array.
1421                        match (lhs, rhs) {
1422                            (None, None) => Ordering::Equal,
1423                            (None, Some(_)) => Ordering::Less,
1424                            (Some(_), None) => Ordering::Greater,
1425                            (Some(lhs), Some(rhs)) => lhs.cmp(&rhs),
1426                        }
1427                    }
1428                }
1429            });
1430
1431            // Keep only the events, not their positions.
1432            let related = related.into_iter().map(|(event, _pos)| event).collect();
1433
1434            Ok(Some((target, related)))
1435        }
1436
1437        /// Post-process new events, after they have been added to the in-memory
1438        /// linked chunk.
1439        ///
1440        /// Flushes updates to disk first.
1441        async fn post_process_new_events(
1442            &mut self,
1443            events: Vec<Event>,
1444            is_sync: bool,
1445        ) -> Result<(), EventCacheError> {
1446            // Update the store before doing the post-processing.
1447            self.propagate_changes().await?;
1448
1449            let mut new_events_by_thread: BTreeMap<_, Vec<_>> = BTreeMap::new();
1450
1451            for event in events {
1452                self.maybe_apply_new_redaction(&event).await?;
1453
1454                if let Some(thread_root) = extract_thread_root(event.raw()) {
1455                    new_events_by_thread.entry(thread_root).or_default().push(event.clone());
1456                } else if let Some(event_id) = event.event_id() {
1457                    // If we spot the root of a thread, add it to its linked chunk, in sync mode.
1458                    if self.threads.contains_key(&event_id) {
1459                        new_events_by_thread.entry(event_id).or_default().push(event.clone());
1460                    }
1461                }
1462
1463                // Save a bundled thread event, if there was one.
1464                if let Some(bundled_thread) = event.bundled_latest_thread_event {
1465                    self.save_event([*bundled_thread]).await?;
1466                }
1467            }
1468
1469            self.update_threads(new_events_by_thread, is_sync).await?;
1470
1471            Ok(())
1472        }
1473
1474        fn get_or_reload_thread(&mut self, root_event_id: OwnedEventId) -> &mut ThreadEventCache {
1475            // TODO: when there's persistent storage, try to lazily reload from disk, if
1476            // missing from memory.
1477            self.threads
1478                .entry(root_event_id.clone())
1479                .or_insert_with(|| ThreadEventCache::new(root_event_id))
1480        }
1481
1482        #[instrument(skip_all)]
1483        async fn update_threads(
1484            &mut self,
1485            new_events_by_thread: BTreeMap<OwnedEventId, Vec<Event>>,
1486            is_sync: bool,
1487        ) -> Result<(), EventCacheError> {
1488            for (thread_root, new_events) in new_events_by_thread {
1489                let thread_cache = self.get_or_reload_thread(thread_root.clone());
1490
1491                // If we're not in sync mode, we're receiving events from a room pagination: as
1492                // we don't know where they should be put in a thread linked
1493                // chunk, we don't try to be smart and include them. That's for
1494                // the best.
1495                if is_sync {
1496                    thread_cache.add_live_events(new_events);
1497                }
1498
1499                // Add a thread summary to the (room) event which has the thread root, if we
1500                // knew about it.
1501
1502                let last_event_id = thread_cache.latest_event_id();
1503
1504                let Some((location, mut target_event)) = self.find_event(&thread_root).await?
1505                else {
1506                    trace!(%thread_root, "thread root event is missing from the linked chunk");
1507                    continue;
1508                };
1509
1510                let prev_summary = target_event.thread_summary.summary();
1511                let mut latest_reply =
1512                    prev_summary.as_ref().and_then(|summary| summary.latest_reply.clone());
1513
1514                // Recompute the thread summary, if needs be.
1515
1516                // Read the latest number of thread replies from the store.
1517                //
1518                // Implementation note: since this is based on the `m.relates_to` field, and
1519                // that field can only be present on room messages, we don't have to
1520                // worry about filtering out aggregation events (like
1521                // reactions/edits/etc.). Pretty neat, huh?
1522                let num_replies = {
1523                    let store_guard = &*self.store.lock().await?;
1524                    let related_thread_events = store_guard
1525                        .find_event_relations(
1526                            &self.room,
1527                            &thread_root,
1528                            Some(&[RelationType::Thread]),
1529                        )
1530                        .await?;
1531                    related_thread_events.len().try_into().unwrap_or(u32::MAX)
1532                };
1533
1534                if let Some(last_event_id) = last_event_id {
1535                    latest_reply = Some(last_event_id);
1536                }
1537
1538                let new_summary = ThreadSummary { num_replies, latest_reply };
1539
1540                if prev_summary == Some(&new_summary) {
1541                    trace!(%thread_root, "thread summary is already up-to-date");
1542                    continue;
1543                }
1544
1545                // Trigger an update to observers.
1546                target_event.thread_summary = ThreadSummaryStatus::Some(new_summary);
1547                self.replace_event_at(location, target_event).await?;
1548            }
1549
1550            Ok(())
1551        }
1552
1553        /// Replaces a single event, be it saved in memory or in the store.
1554        ///
1555        /// If it was saved in memory, this will emit a notification to
1556        /// observers that a single item has been replaced. Otherwise,
1557        /// such a notification is not emitted, because observers are
1558        /// unlikely to observe the store updates directly.
1559        async fn replace_event_at(
1560            &mut self,
1561            location: EventLocation,
1562            event: Event,
1563        ) -> Result<(), EventCacheError> {
1564            match location {
1565                EventLocation::Memory(position) => {
1566                    self.room_linked_chunk
1567                        .replace_event_at(position, event)
1568                        .expect("should have been a valid position of an item");
1569                    // We just changed the in-memory representation; synchronize this with
1570                    // the store.
1571                    self.propagate_changes().await?;
1572                }
1573                EventLocation::Store => {
1574                    self.save_event([event]).await?;
1575                }
1576            }
1577
1578            Ok(())
1579        }
1580
1581        /// If the given event is a redaction, try to retrieve the
1582        /// to-be-redacted event in the chunk, and replace it by the
1583        /// redacted form.
1584        #[instrument(skip_all)]
1585        async fn maybe_apply_new_redaction(
1586            &mut self,
1587            event: &Event,
1588        ) -> Result<(), EventCacheError> {
1589            let raw_event = event.raw();
1590
1591            // Do not deserialise the entire event if we aren't certain it's a
1592            // `m.room.redaction`. It saves a non-negligible amount of computations.
1593            let Ok(Some(MessageLikeEventType::RoomRedaction)) =
1594                raw_event.get_field::<MessageLikeEventType>("type")
1595            else {
1596                return Ok(());
1597            };
1598
1599            // It is a `m.room.redaction`! We can deserialize it entirely.
1600
1601            let Ok(AnySyncTimelineEvent::MessageLike(
1602                ruma::events::AnySyncMessageLikeEvent::RoomRedaction(redaction),
1603            )) = raw_event.deserialize()
1604            else {
1605                return Ok(());
1606            };
1607
1608            let Some(event_id) = redaction.redacts(&self.room_version_rules.redaction) else {
1609                warn!("missing target event id from the redaction event");
1610                return Ok(());
1611            };
1612
1613            // Replace the redacted event by a redacted form, if we knew about it.
1614            let Some((location, mut target_event)) = self.find_event(event_id).await? else {
1615                trace!("redacted event is missing from the linked chunk");
1616                return Ok(());
1617            };
1618
1619            // Don't redact already redacted events.
1620            if let Ok(deserialized) = target_event.raw().deserialize() {
1621                match deserialized {
1622                    AnySyncTimelineEvent::MessageLike(ev) => {
1623                        if ev.is_redacted() {
1624                            return Ok(());
1625                        }
1626                    }
1627                    AnySyncTimelineEvent::State(ev) => {
1628                        if ev.is_redacted() {
1629                            return Ok(());
1630                        }
1631                    }
1632                }
1633            }
1634
1635            if let Some(redacted_event) = apply_redaction(
1636                target_event.raw(),
1637                event.raw().cast_ref_unchecked::<SyncRoomRedactionEvent>(),
1638                &self.room_version_rules.redaction,
1639            ) {
1640                // It's safe to cast `redacted_event` here:
1641                // - either the event was an `AnyTimelineEvent` cast to `AnySyncTimelineEvent`
1642                //   when calling .raw(), so it's still one under the hood.
1643                // - or it wasn't, and it's a plain `AnySyncTimelineEvent` in this case.
1644                target_event.replace_raw(redacted_event.cast_unchecked());
1645
1646                self.replace_event_at(location, target_event).await?;
1647            }
1648
1649            Ok(())
1650        }
1651
1652        /// Save a single event into the database, without notifying observers.
1653        ///
1654        /// Note: if the event was already saved as part of a linked chunk, and
1655        /// its event id may have changed, it's not safe to use this
1656        /// method because it may break the link between the chunk and
1657        /// the event. Instead, an update to the linked chunk must be used.
1658        pub async fn save_event(
1659            &self,
1660            events: impl IntoIterator<Item = Event>,
1661        ) -> Result<(), EventCacheError> {
1662            let store = self.store.clone();
1663            let room_id = self.room.clone();
1664            let events = events.into_iter().collect::<Vec<_>>();
1665
1666            // Spawn a task so the save is uninterrupted by task cancellation.
1667            spawn(async move {
1668                let store = store.lock().await?;
1669                for event in events {
1670                    store.save_event(&room_id, event).await?;
1671                }
1672                super::Result::Ok(())
1673            })
1674            .await
1675            .expect("joining failed")?;
1676
1677            Ok(())
1678        }
1679
1680        /// Handle the result of a sync.
1681        ///
1682        /// It may send room event cache updates to the given sender, if it
1683        /// generated any of those.
1684        ///
1685        /// Returns `true` for the first part of the tuple if a new gap
1686        /// (previous-batch token) has been inserted, `false` otherwise.
1687        #[must_use = "Propagate `VectorDiff` updates via `RoomEventCacheUpdate`"]
1688        pub async fn handle_sync(
1689            &mut self,
1690            mut timeline: Timeline,
1691        ) -> Result<(bool, Vec<VectorDiff<Event>>), EventCacheError> {
1692            let mut prev_batch = timeline.prev_batch.take();
1693
1694            let DeduplicationOutcome {
1695                all_events: events,
1696                in_memory_duplicated_event_ids,
1697                in_store_duplicated_event_ids,
1698                non_empty_all_duplicates: all_duplicates,
1699            } = filter_duplicate_events(
1700                &self.store,
1701                LinkedChunkId::Room(self.room.as_ref()),
1702                &self.room_linked_chunk,
1703                timeline.events,
1704            )
1705            .await?;
1706
1707            // If the timeline isn't limited, and we already knew about some past events,
1708            // then this definitely knows what the timeline head is (either we know
1709            // about all the events persisted in storage, or we have a gap
1710            // somewhere). In this case, we can ditch the previous-batch
1711            // token, which is an optimization to avoid unnecessary future back-pagination
1712            // requests.
1713            //
1714            // We can also ditch it if we knew about all the events that came from sync,
1715            // namely, they were all deduplicated. In this case, using the
1716            // previous-batch token would only result in fetching other events we
1717            // knew about. This is slightly incorrect in the presence of
1718            // network splits, but this has shown to be Good Enough™.
1719            if !timeline.limited && self.room_linked_chunk.events().next().is_some()
1720                || all_duplicates
1721            {
1722                prev_batch = None;
1723            }
1724
1725            if prev_batch.is_some() {
1726                // Sad time: there's a gap, somewhere, in the timeline, and there's at least one
1727                // non-duplicated event. We don't know which threads might have gappy, so we
1728                // must invalidate them all :(
1729                // TODO(bnjbvr): figure out a better catchup mechanism for threads.
1730                let mut summaries_to_update = Vec::new();
1731
1732                for (thread_root, thread) in self.threads.iter_mut() {
1733                    // Empty the thread's linked chunk.
1734                    thread.clear();
1735
1736                    summaries_to_update.push(thread_root.clone());
1737                }
1738
1739                // Now, update the summaries to indicate that we're not sure what the latest
1740                // thread event is. The thread count can remain as is, as it might still be
1741                // valid, and there's no good value to reset it to, anyways.
1742                for thread_root in summaries_to_update {
1743                    let Some((location, mut target_event)) = self.find_event(&thread_root).await?
1744                    else {
1745                        trace!(%thread_root, "thread root event is unknown, when updating thread summary after a gappy sync");
1746                        continue;
1747                    };
1748
1749                    if let Some(mut prev_summary) = target_event.thread_summary.summary().cloned() {
1750                        prev_summary.latest_reply = None;
1751
1752                        target_event.thread_summary = ThreadSummaryStatus::Some(prev_summary);
1753
1754                        self.replace_event_at(location, target_event).await?;
1755                    }
1756                }
1757            }
1758
1759            if all_duplicates {
1760                // No new events and no gap (per the previous check), thus no need to change the
1761                // room state. We're done!
1762                return Ok((false, Vec::new()));
1763            }
1764
1765            let has_new_gap = prev_batch.is_some();
1766
1767            // If we've never waited for an initial previous-batch token, and we've now
1768            // inserted a gap, no need to wait for a previous-batch token later.
1769            if !self.waited_for_initial_prev_token && has_new_gap {
1770                self.waited_for_initial_prev_token = true;
1771            }
1772
1773            // Remove the old duplicated events.
1774            //
1775            // We don't have to worry the removals can change the position of the existing
1776            // events, because we are pushing all _new_ `events` at the back.
1777            self.remove_events(in_memory_duplicated_event_ids, in_store_duplicated_event_ids)
1778                .await?;
1779
1780            self.room_linked_chunk
1781                .push_live_events(prev_batch.map(|prev_token| Gap { prev_token }), &events);
1782
1783            self.post_process_new_events(events, true).await?;
1784
1785            if timeline.limited && has_new_gap {
1786                // If there was a previous batch token for a limited timeline, unload the chunks
1787                // so it only contains the last one; otherwise, there might be a
1788                // valid gap in between, and observers may not render it (yet).
1789                //
1790                // We must do this *after* persisting these events to storage (in
1791                // `post_process_new_events`).
1792                self.shrink_to_last_chunk().await?;
1793            }
1794
1795            let timeline_event_diffs = self.room_linked_chunk.updates_as_vector_diffs();
1796
1797            Ok((has_new_gap, timeline_event_diffs))
1798        }
1799
1800        /// Handle the result of a single back-pagination request.
1801        ///
1802        /// If the `prev_token` is set, then this function will check that the
1803        /// corresponding gap is present in the in-memory linked chunk.
1804        /// If it's not the case, `Ok(None)` will be returned, and the
1805        /// caller may decide to do something based on that (e.g. restart a
1806        /// pagination).
1807        #[must_use = "Propagate `VectorDiff` updates via `RoomEventCacheUpdate`"]
1808        pub async fn handle_backpagination(
1809            &mut self,
1810            events: Vec<Event>,
1811            mut new_token: Option<String>,
1812            prev_token: Option<String>,
1813        ) -> Result<Option<(BackPaginationOutcome, Vec<VectorDiff<Event>>)>, EventCacheError>
1814        {
1815            // Check that the previous token still exists; otherwise it's a sign that the
1816            // room's timeline has been cleared.
1817            let prev_gap_id = if let Some(token) = prev_token {
1818                // Find the corresponding gap in the in-memory linked chunk.
1819                let gap_chunk_id = self.room_linked_chunk.chunk_identifier(|chunk| {
1820                    matches!(chunk.content(), ChunkContent::Gap(Gap { ref prev_token }) if *prev_token == token)
1821                });
1822
1823                if gap_chunk_id.is_none() {
1824                    // We got a previous-batch token from the linked chunk *before* running the
1825                    // request, but it is missing *after* completing the request.
1826                    //
1827                    // It may be a sign the linked chunk has been reset, but it's fine, per this
1828                    // function's contract.
1829                    return Ok(None);
1830                }
1831
1832                gap_chunk_id
1833            } else {
1834                None
1835            };
1836
1837            let DeduplicationOutcome {
1838                all_events: mut events,
1839                in_memory_duplicated_event_ids,
1840                in_store_duplicated_event_ids,
1841                non_empty_all_duplicates: all_duplicates,
1842            } = filter_duplicate_events(
1843                &self.store,
1844                LinkedChunkId::Room(self.room.as_ref()),
1845                &self.room_linked_chunk,
1846                events,
1847            )
1848            .await?;
1849
1850            // If not all the events have been back-paginated, we need to remove the
1851            // previous ones, otherwise we can end up with misordered events.
1852            //
1853            // Consider the following scenario:
1854            // - sync returns [D, E, F]
1855            // - then sync returns [] with a previous batch token PB1, so the internal
1856            //   linked chunk state is [D, E, F, PB1].
1857            // - back-paginating with PB1 may return [A, B, C, D, E, F].
1858            //
1859            // Only inserting the new events when replacing PB1 would result in a timeline
1860            // ordering of [D, E, F, A, B, C], which is incorrect. So we do have to remove
1861            // all the events, in case this happens (see also #4746).
1862
1863            if !all_duplicates {
1864                // Let's forget all the previous events.
1865                self.remove_events(in_memory_duplicated_event_ids, in_store_duplicated_event_ids)
1866                    .await?;
1867            } else {
1868                // All new events are duplicated, they can all be ignored.
1869                events.clear();
1870                // The gap can be ditched too, as it won't be useful to backpaginate any
1871                // further.
1872                new_token = None;
1873            }
1874
1875            // `/messages` has been called with `dir=b` (backwards), so the events are in
1876            // the inverted order; reorder them.
1877            let topo_ordered_events = events.iter().rev().cloned().collect::<Vec<_>>();
1878
1879            let new_gap = new_token.map(|prev_token| Gap { prev_token });
1880            let reached_start = self.room_linked_chunk.finish_back_pagination(
1881                prev_gap_id,
1882                new_gap,
1883                &topo_ordered_events,
1884            );
1885
1886            // Note: this flushes updates to the store.
1887            self.post_process_new_events(topo_ordered_events, false).await?;
1888
1889            let event_diffs = self.room_linked_chunk.updates_as_vector_diffs();
1890
1891            Ok(Some((BackPaginationOutcome { events, reached_start }, event_diffs)))
1892        }
1893
1894        /// Subscribe to thread for a given root event, and get a (maybe empty)
1895        /// initially known list of events for that thread.
1896        pub fn subscribe_to_thread(
1897            &mut self,
1898            root: OwnedEventId,
1899        ) -> (Vec<Event>, Receiver<ThreadEventCacheUpdate>) {
1900            self.get_or_reload_thread(root).subscribe()
1901        }
1902
1903        /// Back paginate in the given thread.
1904        ///
1905        /// Will always start from the end, unless we previously paginated.
1906        pub fn finish_thread_network_pagination(
1907            &mut self,
1908            root: OwnedEventId,
1909            prev_token: Option<String>,
1910            new_token: Option<String>,
1911            events: Vec<Event>,
1912        ) -> Option<BackPaginationOutcome> {
1913            self.get_or_reload_thread(root).finish_network_pagination(prev_token, new_token, events)
1914        }
1915
1916        pub fn load_more_thread_events_backwards(
1917            &mut self,
1918            root: OwnedEventId,
1919        ) -> LoadMoreEventsBackwardsOutcome {
1920            self.get_or_reload_thread(root).load_more_events_backwards()
1921        }
1922    }
1923}
1924
1925/// An enum representing where an event has been found.
1926pub(super) enum EventLocation {
1927    /// Event lives in memory (and likely in the store!).
1928    Memory(Position),
1929
1930    /// Event lives in the store only, it has not been loaded in memory yet.
1931    Store,
1932}
1933
1934pub(super) use private::RoomEventCacheState;
1935
1936#[cfg(test)]
1937mod tests {
1938    use matrix_sdk_base::event_cache::Event;
1939    use matrix_sdk_test::{async_test, event_factory::EventFactory};
1940    use ruma::{
1941        event_id,
1942        events::{relation::RelationType, room::message::RoomMessageEventContentWithoutRelation},
1943        room_id, user_id, RoomId,
1944    };
1945
1946    use crate::test_utils::logged_in_client;
1947
1948    #[async_test]
1949    async fn test_find_event_by_id_with_edit_relation() {
1950        let original_id = event_id!("$original");
1951        let related_id = event_id!("$related");
1952        let room_id = room_id!("!galette:saucisse.bzh");
1953        let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
1954
1955        assert_relations(
1956            room_id,
1957            f.text_msg("Original event").event_id(original_id).into(),
1958            f.text_msg("* An edited event")
1959                .edit(
1960                    original_id,
1961                    RoomMessageEventContentWithoutRelation::text_plain("And edited event"),
1962                )
1963                .event_id(related_id)
1964                .into(),
1965            f,
1966        )
1967        .await;
1968    }
1969
1970    #[async_test]
1971    async fn test_find_event_by_id_with_thread_reply_relation() {
1972        let original_id = event_id!("$original");
1973        let related_id = event_id!("$related");
1974        let room_id = room_id!("!galette:saucisse.bzh");
1975        let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
1976
1977        assert_relations(
1978            room_id,
1979            f.text_msg("Original event").event_id(original_id).into(),
1980            f.text_msg("A reply").in_thread(original_id, related_id).event_id(related_id).into(),
1981            f,
1982        )
1983        .await;
1984    }
1985
1986    #[async_test]
1987    async fn test_find_event_by_id_with_reaction_relation() {
1988        let original_id = event_id!("$original");
1989        let related_id = event_id!("$related");
1990        let room_id = room_id!("!galette:saucisse.bzh");
1991        let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
1992
1993        assert_relations(
1994            room_id,
1995            f.text_msg("Original event").event_id(original_id).into(),
1996            f.reaction(original_id, ":D").event_id(related_id).into(),
1997            f,
1998        )
1999        .await;
2000    }
2001
2002    #[async_test]
2003    async fn test_find_event_by_id_with_poll_response_relation() {
2004        let original_id = event_id!("$original");
2005        let related_id = event_id!("$related");
2006        let room_id = room_id!("!galette:saucisse.bzh");
2007        let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
2008
2009        assert_relations(
2010            room_id,
2011            f.poll_start("Poll start event", "A poll question", vec!["An answer"])
2012                .event_id(original_id)
2013                .into(),
2014            f.poll_response(vec!["1"], original_id).event_id(related_id).into(),
2015            f,
2016        )
2017        .await;
2018    }
2019
2020    #[async_test]
2021    async fn test_find_event_by_id_with_poll_end_relation() {
2022        let original_id = event_id!("$original");
2023        let related_id = event_id!("$related");
2024        let room_id = room_id!("!galette:saucisse.bzh");
2025        let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
2026
2027        assert_relations(
2028            room_id,
2029            f.poll_start("Poll start event", "A poll question", vec!["An answer"])
2030                .event_id(original_id)
2031                .into(),
2032            f.poll_end("Poll ended", original_id).event_id(related_id).into(),
2033            f,
2034        )
2035        .await;
2036    }
2037
2038    #[async_test]
2039    async fn test_find_event_by_id_with_filtered_relationships() {
2040        let original_id = event_id!("$original");
2041        let related_id = event_id!("$related");
2042        let associated_related_id = event_id!("$recursive_related");
2043        let room_id = room_id!("!galette:saucisse.bzh");
2044        let event_factory = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
2045
2046        let original_event = event_factory.text_msg("Original event").event_id(original_id).into();
2047        let related_event = event_factory
2048            .text_msg("* Edited event")
2049            .edit(original_id, RoomMessageEventContentWithoutRelation::text_plain("Edited event"))
2050            .event_id(related_id)
2051            .into();
2052        let associated_related_event =
2053            event_factory.reaction(related_id, "🤡").event_id(associated_related_id).into();
2054
2055        let client = logged_in_client(None).await;
2056
2057        let event_cache = client.event_cache();
2058        event_cache.subscribe().unwrap();
2059
2060        client.base_client().get_or_create_room(room_id, matrix_sdk_base::RoomState::Joined);
2061        let room = client.get_room(room_id).unwrap();
2062
2063        let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
2064
2065        // Save the original event.
2066        room_event_cache.save_events([original_event]).await;
2067
2068        // Save the related event.
2069        room_event_cache.save_events([related_event]).await;
2070
2071        // Save the associated related event, which redacts the related event.
2072        room_event_cache.save_events([associated_related_event]).await;
2073
2074        let filter = Some(vec![RelationType::Replacement]);
2075        let (event, related_events) =
2076            room_event_cache.find_event_with_relations(original_id, filter).await.unwrap();
2077        // Fetched event is the right one.
2078        let cached_event_id = event.event_id().unwrap();
2079        assert_eq!(cached_event_id, original_id);
2080
2081        // There's only the edit event (an edit event can't have its own edit event).
2082        assert_eq!(related_events.len(), 1);
2083
2084        let related_event_id = related_events[0].event_id().unwrap();
2085        assert_eq!(related_event_id, related_id);
2086
2087        // Now we'll filter threads instead, there should be no related events
2088        let filter = Some(vec![RelationType::Thread]);
2089        let (event, related_events) =
2090            room_event_cache.find_event_with_relations(original_id, filter).await.unwrap();
2091        // Fetched event is the right one.
2092        let cached_event_id = event.event_id().unwrap();
2093        assert_eq!(cached_event_id, original_id);
2094        // No Thread related events found
2095        assert!(related_events.is_empty());
2096    }
2097
2098    #[async_test]
2099    async fn test_find_event_by_id_with_recursive_relation() {
2100        let original_id = event_id!("$original");
2101        let related_id = event_id!("$related");
2102        let associated_related_id = event_id!("$recursive_related");
2103        let room_id = room_id!("!galette:saucisse.bzh");
2104        let event_factory = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
2105
2106        let original_event = event_factory.text_msg("Original event").event_id(original_id).into();
2107        let related_event = event_factory
2108            .text_msg("* Edited event")
2109            .edit(original_id, RoomMessageEventContentWithoutRelation::text_plain("Edited event"))
2110            .event_id(related_id)
2111            .into();
2112        let associated_related_event =
2113            event_factory.reaction(related_id, "👍").event_id(associated_related_id).into();
2114
2115        let client = logged_in_client(None).await;
2116
2117        let event_cache = client.event_cache();
2118        event_cache.subscribe().unwrap();
2119
2120        client.base_client().get_or_create_room(room_id, matrix_sdk_base::RoomState::Joined);
2121        let room = client.get_room(room_id).unwrap();
2122
2123        let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
2124
2125        // Save the original event.
2126        room_event_cache.save_events([original_event]).await;
2127
2128        // Save the related event.
2129        room_event_cache.save_events([related_event]).await;
2130
2131        // Save the associated related event, which redacts the related event.
2132        room_event_cache.save_events([associated_related_event]).await;
2133
2134        let (event, related_events) =
2135            room_event_cache.find_event_with_relations(original_id, None).await.unwrap();
2136        // Fetched event is the right one.
2137        let cached_event_id = event.event_id().unwrap();
2138        assert_eq!(cached_event_id, original_id);
2139
2140        // There are both the related id and the associatively related id
2141        assert_eq!(related_events.len(), 2);
2142
2143        let related_event_id = related_events[0].event_id().unwrap();
2144        assert_eq!(related_event_id, related_id);
2145        let related_event_id = related_events[1].event_id().unwrap();
2146        assert_eq!(related_event_id, associated_related_id);
2147    }
2148
2149    async fn assert_relations(
2150        room_id: &RoomId,
2151        original_event: Event,
2152        related_event: Event,
2153        event_factory: EventFactory,
2154    ) {
2155        let client = logged_in_client(None).await;
2156
2157        let event_cache = client.event_cache();
2158        event_cache.subscribe().unwrap();
2159
2160        client.base_client().get_or_create_room(room_id, matrix_sdk_base::RoomState::Joined);
2161        let room = client.get_room(room_id).unwrap();
2162
2163        let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
2164
2165        // Save the original event.
2166        let original_event_id = original_event.event_id().unwrap();
2167        room_event_cache.save_events([original_event]).await;
2168
2169        // Save an unrelated event to check it's not in the related events list.
2170        let unrelated_id = event_id!("$2");
2171        room_event_cache
2172            .save_events([event_factory
2173                .text_msg("An unrelated event")
2174                .event_id(unrelated_id)
2175                .into()])
2176            .await;
2177
2178        // Save the related event.
2179        let related_id = related_event.event_id().unwrap();
2180        room_event_cache.save_events([related_event]).await;
2181
2182        let (event, related_events) =
2183            room_event_cache.find_event_with_relations(&original_event_id, None).await.unwrap();
2184        // Fetched event is the right one.
2185        let cached_event_id = event.event_id().unwrap();
2186        assert_eq!(cached_event_id, original_event_id);
2187
2188        // There is only the actually related event in the related ones
2189        let related_event_id = related_events[0].event_id().unwrap();
2190        assert_eq!(related_event_id, related_id);
2191    }
2192}
2193
2194#[cfg(all(test, not(target_family = "wasm")))] // This uses the cross-process lock, so needs time support.
2195mod timed_tests {
2196    use std::sync::Arc;
2197
2198    use assert_matches::assert_matches;
2199    use assert_matches2::assert_let;
2200    use eyeball_im::VectorDiff;
2201    use futures_util::FutureExt;
2202    use matrix_sdk_base::{
2203        event_cache::{
2204            store::{EventCacheStore as _, MemoryStore},
2205            Gap,
2206        },
2207        linked_chunk::{
2208            lazy_loader::from_all_chunks, ChunkContent, ChunkIdentifier, LinkedChunkId, Position,
2209            Update,
2210        },
2211        store::StoreConfig,
2212        sync::{JoinedRoomUpdate, Timeline},
2213    };
2214    use matrix_sdk_test::{async_test, event_factory::EventFactory, ALICE, BOB};
2215    use ruma::{
2216        event_id,
2217        events::{AnySyncMessageLikeEvent, AnySyncTimelineEvent},
2218        room_id, user_id, OwnedUserId,
2219    };
2220    use tokio::task::yield_now;
2221
2222    use super::RoomEventCacheGenericUpdate;
2223    use crate::{
2224        assert_let_timeout,
2225        event_cache::{room::LoadMoreEventsBackwardsOutcome, RoomEventCacheUpdate},
2226        test_utils::client::MockClientBuilder,
2227    };
2228
2229    #[async_test]
2230    async fn test_write_to_storage() {
2231        let room_id = room_id!("!galette:saucisse.bzh");
2232        let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
2233
2234        let event_cache_store = Arc::new(MemoryStore::new());
2235
2236        let client = MockClientBuilder::new(None)
2237            .on_builder(|builder| {
2238                builder.store_config(
2239                    StoreConfig::new("hodlor".to_owned())
2240                        .event_cache_store(event_cache_store.clone()),
2241                )
2242            })
2243            .build()
2244            .await;
2245
2246        let event_cache = client.event_cache();
2247
2248        // Don't forget to subscribe and like^W enable storage!
2249        event_cache.subscribe().unwrap();
2250
2251        client.base_client().get_or_create_room(room_id, matrix_sdk_base::RoomState::Joined);
2252        let room = client.get_room(room_id).unwrap();
2253
2254        let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
2255        let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
2256
2257        // Propagate an update for a message and a prev-batch token.
2258        let timeline = Timeline {
2259            limited: true,
2260            prev_batch: Some("raclette".to_owned()),
2261            events: vec![f.text_msg("hey yo").sender(*ALICE).into_event()],
2262        };
2263
2264        room_event_cache
2265            .inner
2266            .handle_joined_room_update(JoinedRoomUpdate { timeline, ..Default::default() })
2267            .await
2268            .unwrap();
2269
2270        // Just checking the generic update is correct.
2271        assert_matches!(
2272            generic_stream.recv().await,
2273            Ok(RoomEventCacheGenericUpdate::UpdateTimeline { room_id: expected_room_id }) => {
2274                assert_eq!(expected_room_id, room_id);
2275            }
2276        );
2277
2278        // Check the storage.
2279        let linked_chunk = from_all_chunks::<3, _, _>(
2280            event_cache_store.load_all_chunks(LinkedChunkId::Room(room_id)).await.unwrap(),
2281        )
2282        .unwrap()
2283        .unwrap();
2284
2285        assert_eq!(linked_chunk.chunks().count(), 2);
2286
2287        let mut chunks = linked_chunk.chunks();
2288
2289        // We start with the gap.
2290        assert_matches!(chunks.next().unwrap().content(), ChunkContent::Gap(gap) => {
2291            assert_eq!(gap.prev_token, "raclette");
2292        });
2293
2294        // Then we have the stored event.
2295        assert_matches!(chunks.next().unwrap().content(), ChunkContent::Items(events) => {
2296            assert_eq!(events.len(), 1);
2297            let deserialized = events[0].raw().deserialize().unwrap();
2298            assert_let!(AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(msg)) = deserialized);
2299            assert_eq!(msg.as_original().unwrap().content.body(), "hey yo");
2300        });
2301
2302        // That's all, folks!
2303        assert!(chunks.next().is_none());
2304    }
2305
2306    #[async_test]
2307    async fn test_write_to_storage_strips_bundled_relations() {
2308        let room_id = room_id!("!galette:saucisse.bzh");
2309        let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
2310
2311        let event_cache_store = Arc::new(MemoryStore::new());
2312
2313        let client = MockClientBuilder::new(None)
2314            .on_builder(|builder| {
2315                builder.store_config(
2316                    StoreConfig::new("hodlor".to_owned())
2317                        .event_cache_store(event_cache_store.clone()),
2318                )
2319            })
2320            .build()
2321            .await;
2322
2323        let event_cache = client.event_cache();
2324
2325        // Don't forget to subscribe and like^W enable storage!
2326        event_cache.subscribe().unwrap();
2327
2328        client.base_client().get_or_create_room(room_id, matrix_sdk_base::RoomState::Joined);
2329        let room = client.get_room(room_id).unwrap();
2330
2331        let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
2332        let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
2333
2334        // Propagate an update for a message with bundled relations.
2335        let ev = f
2336            .text_msg("hey yo")
2337            .sender(*ALICE)
2338            .with_bundled_edit(f.text_msg("Hello, Kind Sir").sender(*ALICE))
2339            .into_event();
2340
2341        let timeline = Timeline { limited: false, prev_batch: None, events: vec![ev] };
2342
2343        room_event_cache
2344            .inner
2345            .handle_joined_room_update(JoinedRoomUpdate { timeline, ..Default::default() })
2346            .await
2347            .unwrap();
2348
2349        // Just checking the generic update is correct.
2350        assert_matches!(
2351            generic_stream.recv().await,
2352            Ok(RoomEventCacheGenericUpdate::UpdateTimeline { room_id: expected_room_id }) => {
2353                assert_eq!(expected_room_id, room_id);
2354            }
2355        );
2356
2357        // The in-memory linked chunk keeps the bundled relation.
2358        {
2359            let events = room_event_cache.events().await;
2360
2361            assert_eq!(events.len(), 1);
2362
2363            let ev = events[0].raw().deserialize().unwrap();
2364            assert_let!(
2365                AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(msg)) = ev
2366            );
2367
2368            let original = msg.as_original().unwrap();
2369            assert_eq!(original.content.body(), "hey yo");
2370            assert!(original.unsigned.relations.replace.is_some());
2371        }
2372
2373        // The one in storage does not.
2374        let linked_chunk = from_all_chunks::<3, _, _>(
2375            event_cache_store.load_all_chunks(LinkedChunkId::Room(room_id)).await.unwrap(),
2376        )
2377        .unwrap()
2378        .unwrap();
2379
2380        assert_eq!(linked_chunk.chunks().count(), 1);
2381
2382        let mut chunks = linked_chunk.chunks();
2383        assert_matches!(chunks.next().unwrap().content(), ChunkContent::Items(events) => {
2384            assert_eq!(events.len(), 1);
2385
2386            let ev = events[0].raw().deserialize().unwrap();
2387            assert_let!(AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(msg)) = ev);
2388
2389            let original = msg.as_original().unwrap();
2390            assert_eq!(original.content.body(), "hey yo");
2391            assert!(original.unsigned.relations.replace.is_none());
2392        });
2393
2394        // That's all, folks!
2395        assert!(chunks.next().is_none());
2396    }
2397
2398    #[async_test]
2399    async fn test_clear() {
2400        let room_id = room_id!("!galette:saucisse.bzh");
2401        let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
2402
2403        let event_cache_store = Arc::new(MemoryStore::new());
2404
2405        let event_id1 = event_id!("$1");
2406        let event_id2 = event_id!("$2");
2407
2408        let ev1 = f.text_msg("hello world").sender(*ALICE).event_id(event_id1).into_event();
2409        let ev2 = f.text_msg("how's it going").sender(*BOB).event_id(event_id2).into_event();
2410
2411        // Prefill the store with some data.
2412        event_cache_store
2413            .handle_linked_chunk_updates(
2414                LinkedChunkId::Room(room_id),
2415                vec![
2416                    // An empty items chunk.
2417                    Update::NewItemsChunk {
2418                        previous: None,
2419                        new: ChunkIdentifier::new(0),
2420                        next: None,
2421                    },
2422                    // A gap chunk.
2423                    Update::NewGapChunk {
2424                        previous: Some(ChunkIdentifier::new(0)),
2425                        // Chunk IDs aren't supposed to be ordered, so use a random value here.
2426                        new: ChunkIdentifier::new(42),
2427                        next: None,
2428                        gap: Gap { prev_token: "comté".to_owned() },
2429                    },
2430                    // Another items chunk, non-empty this time.
2431                    Update::NewItemsChunk {
2432                        previous: Some(ChunkIdentifier::new(42)),
2433                        new: ChunkIdentifier::new(1),
2434                        next: None,
2435                    },
2436                    Update::PushItems {
2437                        at: Position::new(ChunkIdentifier::new(1), 0),
2438                        items: vec![ev1.clone()],
2439                    },
2440                    // And another items chunk, non-empty again.
2441                    Update::NewItemsChunk {
2442                        previous: Some(ChunkIdentifier::new(1)),
2443                        new: ChunkIdentifier::new(2),
2444                        next: None,
2445                    },
2446                    Update::PushItems {
2447                        at: Position::new(ChunkIdentifier::new(2), 0),
2448                        items: vec![ev2.clone()],
2449                    },
2450                ],
2451            )
2452            .await
2453            .unwrap();
2454
2455        let client = MockClientBuilder::new(None)
2456            .on_builder(|builder| {
2457                builder.store_config(
2458                    StoreConfig::new("hodlor".to_owned())
2459                        .event_cache_store(event_cache_store.clone()),
2460                )
2461            })
2462            .build()
2463            .await;
2464
2465        let event_cache = client.event_cache();
2466
2467        // Don't forget to subscribe and like^W enable storage!
2468        event_cache.subscribe().unwrap();
2469
2470        client.base_client().get_or_create_room(room_id, matrix_sdk_base::RoomState::Joined);
2471        let room = client.get_room(room_id).unwrap();
2472
2473        let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
2474
2475        let (items, mut stream) = room_event_cache.subscribe().await;
2476        let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
2477
2478        // The rooms knows about all cached events.
2479        {
2480            assert!(room_event_cache.find_event(event_id1).await.is_some());
2481            assert!(room_event_cache.find_event(event_id2).await.is_some());
2482        }
2483
2484        // But only part of events are loaded from the store
2485        {
2486            // The room must contain only one event because only one chunk has been loaded.
2487            assert_eq!(items.len(), 1);
2488            assert_eq!(items[0].event_id().unwrap(), event_id2);
2489
2490            assert!(stream.is_empty());
2491        }
2492
2493        // Let's load more chunks to load all events.
2494        {
2495            room_event_cache.pagination().run_backwards_once(20).await.unwrap();
2496
2497            assert_let_timeout!(
2498                Ok(RoomEventCacheUpdate::UpdateTimelineEvents { diffs, .. }) = stream.recv()
2499            );
2500            assert_eq!(diffs.len(), 1);
2501            assert_matches!(&diffs[0], VectorDiff::Insert { index: 0, value: event } => {
2502                // Here you are `event_id1`!
2503                assert_eq!(event.event_id().unwrap(), event_id1);
2504            });
2505
2506            assert!(stream.is_empty());
2507        }
2508
2509        // After clearing,…
2510        room_event_cache.clear().await.unwrap();
2511
2512        //… we get an update that the content has been cleared.
2513        assert_let_timeout!(
2514            Ok(RoomEventCacheUpdate::UpdateTimelineEvents { diffs, .. }) = stream.recv()
2515        );
2516        assert_eq!(diffs.len(), 1);
2517        assert_let!(VectorDiff::Clear = &diffs[0]);
2518
2519        // … same with a generic update.
2520        assert_let_timeout!(
2521            Ok(RoomEventCacheGenericUpdate::UpdateTimeline { room_id: received_room_id }) =
2522                generic_stream.recv()
2523        );
2524        assert_eq!(received_room_id, room_id);
2525        assert_let_timeout!(
2526            Ok(RoomEventCacheGenericUpdate::Clear { room_id: received_room_id }) =
2527                generic_stream.recv()
2528        );
2529        assert_eq!(received_room_id, room_id);
2530
2531        // Events individually are not forgotten by the event cache, after clearing a
2532        // room.
2533        assert!(room_event_cache.find_event(event_id1).await.is_some());
2534
2535        // But their presence in a linked chunk is forgotten.
2536        let items = room_event_cache.events().await;
2537        assert!(items.is_empty());
2538
2539        // The event cache store too.
2540        let linked_chunk = from_all_chunks::<3, _, _>(
2541            event_cache_store.load_all_chunks(LinkedChunkId::Room(room_id)).await.unwrap(),
2542        )
2543        .unwrap()
2544        .unwrap();
2545
2546        // Note: while the event cache store could return `None` here, clearing it will
2547        // reset it to its initial form, maintaining the invariant that it
2548        // contains a single items chunk that's empty.
2549        assert_eq!(linked_chunk.num_items(), 0);
2550    }
2551
2552    #[async_test]
2553    async fn test_load_from_storage() {
2554        let room_id = room_id!("!galette:saucisse.bzh");
2555        let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
2556
2557        let event_cache_store = Arc::new(MemoryStore::new());
2558
2559        let event_id1 = event_id!("$1");
2560        let event_id2 = event_id!("$2");
2561
2562        let ev1 = f.text_msg("hello world").sender(*ALICE).event_id(event_id1).into_event();
2563        let ev2 = f.text_msg("how's it going").sender(*BOB).event_id(event_id2).into_event();
2564
2565        // Prefill the store with some data.
2566        event_cache_store
2567            .handle_linked_chunk_updates(
2568                LinkedChunkId::Room(room_id),
2569                vec![
2570                    // An empty items chunk.
2571                    Update::NewItemsChunk {
2572                        previous: None,
2573                        new: ChunkIdentifier::new(0),
2574                        next: None,
2575                    },
2576                    // A gap chunk.
2577                    Update::NewGapChunk {
2578                        previous: Some(ChunkIdentifier::new(0)),
2579                        // Chunk IDs aren't supposed to be ordered, so use a random value here.
2580                        new: ChunkIdentifier::new(42),
2581                        next: None,
2582                        gap: Gap { prev_token: "cheddar".to_owned() },
2583                    },
2584                    // Another items chunk, non-empty this time.
2585                    Update::NewItemsChunk {
2586                        previous: Some(ChunkIdentifier::new(42)),
2587                        new: ChunkIdentifier::new(1),
2588                        next: None,
2589                    },
2590                    Update::PushItems {
2591                        at: Position::new(ChunkIdentifier::new(1), 0),
2592                        items: vec![ev1.clone()],
2593                    },
2594                    // And another items chunk, non-empty again.
2595                    Update::NewItemsChunk {
2596                        previous: Some(ChunkIdentifier::new(1)),
2597                        new: ChunkIdentifier::new(2),
2598                        next: None,
2599                    },
2600                    Update::PushItems {
2601                        at: Position::new(ChunkIdentifier::new(2), 0),
2602                        items: vec![ev2.clone()],
2603                    },
2604                ],
2605            )
2606            .await
2607            .unwrap();
2608
2609        let client = MockClientBuilder::new(None)
2610            .on_builder(|builder| {
2611                builder.store_config(
2612                    StoreConfig::new("hodlor".to_owned())
2613                        .event_cache_store(event_cache_store.clone()),
2614                )
2615            })
2616            .build()
2617            .await;
2618
2619        let event_cache = client.event_cache();
2620
2621        // Don't forget to subscribe and like^W enable storage!
2622        event_cache.subscribe().unwrap();
2623
2624        // Let's check whether the generic updates are received for the initialisation.
2625        let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
2626
2627        client.base_client().get_or_create_room(room_id, matrix_sdk_base::RoomState::Joined);
2628        let room = client.get_room(room_id).unwrap();
2629
2630        let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
2631
2632        // The room event cache has been loaded. A generic update must have been
2633        // triggered.
2634        assert_matches!(
2635            generic_stream.recv().await,
2636            Ok(RoomEventCacheGenericUpdate::UpdateTimeline { room_id: expected_room_id }) => {
2637                assert_eq!(room_id, expected_room_id);
2638            }
2639        );
2640
2641        let (items, mut stream) = room_event_cache.subscribe().await;
2642
2643        // The initial items contain one event because only the last chunk is loaded by
2644        // default.
2645        assert_eq!(items.len(), 1);
2646        assert_eq!(items[0].event_id().unwrap(), event_id2);
2647        assert!(stream.is_empty());
2648
2649        // The event cache knows only all events though, even if they aren't loaded.
2650        assert!(room_event_cache.find_event(event_id1).await.is_some());
2651        assert!(room_event_cache.find_event(event_id2).await.is_some());
2652
2653        // Let's paginate to load more events.
2654        room_event_cache.pagination().run_backwards_once(20).await.unwrap();
2655
2656        assert_let_timeout!(
2657            Ok(RoomEventCacheUpdate::UpdateTimelineEvents { diffs, .. }) = stream.recv()
2658        );
2659        assert_eq!(diffs.len(), 1);
2660        assert_matches!(&diffs[0], VectorDiff::Insert { index: 0, value: event } => {
2661            assert_eq!(event.event_id().unwrap(), event_id1);
2662        });
2663
2664        assert!(stream.is_empty());
2665
2666        // A generic update is triggered too.
2667        assert_matches!(
2668            generic_stream.recv().await,
2669            Ok(RoomEventCacheGenericUpdate::UpdateTimeline { room_id: expected_room_id }) => {
2670                assert_eq!(expected_room_id, room_id);
2671            }
2672        );
2673
2674        // A new update with one of these events leads to deduplication.
2675        let timeline = Timeline { limited: false, prev_batch: None, events: vec![ev2] };
2676
2677        room_event_cache
2678            .inner
2679            .handle_joined_room_update(JoinedRoomUpdate { timeline, ..Default::default() })
2680            .await
2681            .unwrap();
2682
2683        // Just checking the generic update is correct. There is a duplicate event, so
2684        // no generic changes whatsoever!
2685        assert!(generic_stream.recv().now_or_never().is_none());
2686
2687        // The stream doesn't report these changes *yet*. Use the items vector given
2688        // when subscribing, to check that the items correspond to their new
2689        // positions. The duplicated item is removed (so it's not the first
2690        // element anymore), and it's added to the back of the list.
2691        let items = room_event_cache.events().await;
2692        assert_eq!(items.len(), 2);
2693        assert_eq!(items[0].event_id().unwrap(), event_id1);
2694        assert_eq!(items[1].event_id().unwrap(), event_id2);
2695    }
2696
2697    #[async_test]
2698    async fn test_load_from_storage_resilient_to_failure() {
2699        let room_id = room_id!("!fondue:patate.ch");
2700        let event_cache_store = Arc::new(MemoryStore::new());
2701
2702        let event = EventFactory::new()
2703            .room(room_id)
2704            .sender(user_id!("@ben:saucisse.bzh"))
2705            .text_msg("foo")
2706            .event_id(event_id!("$42"))
2707            .into_event();
2708
2709        // Prefill the store with invalid data: two chunks that form a cycle.
2710        event_cache_store
2711            .handle_linked_chunk_updates(
2712                LinkedChunkId::Room(room_id),
2713                vec![
2714                    Update::NewItemsChunk {
2715                        previous: None,
2716                        new: ChunkIdentifier::new(0),
2717                        next: None,
2718                    },
2719                    Update::PushItems {
2720                        at: Position::new(ChunkIdentifier::new(0), 0),
2721                        items: vec![event],
2722                    },
2723                    Update::NewItemsChunk {
2724                        previous: Some(ChunkIdentifier::new(0)),
2725                        new: ChunkIdentifier::new(1),
2726                        next: Some(ChunkIdentifier::new(0)),
2727                    },
2728                ],
2729            )
2730            .await
2731            .unwrap();
2732
2733        let client = MockClientBuilder::new(None)
2734            .on_builder(|builder| {
2735                builder.store_config(
2736                    StoreConfig::new("holder".to_owned())
2737                        .event_cache_store(event_cache_store.clone()),
2738                )
2739            })
2740            .build()
2741            .await;
2742
2743        let event_cache = client.event_cache();
2744
2745        // Don't forget to subscribe and like^W enable storage!
2746        event_cache.subscribe().unwrap();
2747
2748        client.base_client().get_or_create_room(room_id, matrix_sdk_base::RoomState::Joined);
2749        let room = client.get_room(room_id).unwrap();
2750
2751        let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
2752
2753        let items = room_event_cache.events().await;
2754
2755        // Because the persisted content was invalid, the room store is reset: there are
2756        // no events in the cache.
2757        assert!(items.is_empty());
2758
2759        // Storage doesn't contain anything. It would also be valid that it contains a
2760        // single initial empty items chunk.
2761        let raw_chunks =
2762            event_cache_store.load_all_chunks(LinkedChunkId::Room(room_id)).await.unwrap();
2763        assert!(raw_chunks.is_empty());
2764    }
2765
2766    #[async_test]
2767    async fn test_no_useless_gaps() {
2768        let room_id = room_id!("!galette:saucisse.bzh");
2769
2770        let client = MockClientBuilder::new(None).build().await;
2771
2772        let event_cache = client.event_cache();
2773        event_cache.subscribe().unwrap();
2774
2775        client.base_client().get_or_create_room(room_id, matrix_sdk_base::RoomState::Joined);
2776        let room = client.get_room(room_id).unwrap();
2777        let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
2778        let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
2779
2780        let f = EventFactory::new().room(room_id).sender(*ALICE);
2781
2782        // Propagate an update including a limited timeline with one message and a
2783        // prev-batch token.
2784        room_event_cache
2785            .inner
2786            .handle_joined_room_update(JoinedRoomUpdate {
2787                timeline: Timeline {
2788                    limited: true,
2789                    prev_batch: Some("raclette".to_owned()),
2790                    events: vec![f.text_msg("hey yo").into_event()],
2791                },
2792                ..Default::default()
2793            })
2794            .await
2795            .unwrap();
2796
2797        // Just checking the generic update is correct.
2798        assert_matches!(
2799            generic_stream.recv().await,
2800            Ok(RoomEventCacheGenericUpdate::UpdateTimeline { room_id: expected_room_id }) => {
2801                assert_eq!(expected_room_id, room_id);
2802            }
2803        );
2804
2805        {
2806            let mut state = room_event_cache.inner.state.write().await;
2807
2808            let mut num_gaps = 0;
2809            let mut num_events = 0;
2810
2811            for c in state.room_linked_chunk().chunks() {
2812                match c.content() {
2813                    ChunkContent::Items(items) => num_events += items.len(),
2814                    ChunkContent::Gap(_) => num_gaps += 1,
2815                }
2816            }
2817
2818            // The limited sync unloads the chunk, so it will appear as if there are only
2819            // the events.
2820            assert_eq!(num_gaps, 0);
2821            assert_eq!(num_events, 1);
2822
2823            // But if I manually reload more of the chunk, the gap will be present.
2824            assert_matches!(
2825                state.load_more_events_backwards().await.unwrap(),
2826                LoadMoreEventsBackwardsOutcome::Gap { .. }
2827            );
2828
2829            num_gaps = 0;
2830            num_events = 0;
2831            for c in state.room_linked_chunk().chunks() {
2832                match c.content() {
2833                    ChunkContent::Items(items) => num_events += items.len(),
2834                    ChunkContent::Gap(_) => num_gaps += 1,
2835                }
2836            }
2837
2838            // The gap must have been stored.
2839            assert_eq!(num_gaps, 1);
2840            assert_eq!(num_events, 1);
2841        }
2842
2843        // Now, propagate an update for another message, but the timeline isn't limited
2844        // this time.
2845        room_event_cache
2846            .inner
2847            .handle_joined_room_update(JoinedRoomUpdate {
2848                timeline: Timeline {
2849                    limited: false,
2850                    prev_batch: Some("fondue".to_owned()),
2851                    events: vec![f.text_msg("sup").into_event()],
2852                },
2853                ..Default::default()
2854            })
2855            .await
2856            .unwrap();
2857
2858        // Just checking the generic update is correct.
2859        assert_matches!(
2860            generic_stream.recv().await,
2861            Ok(RoomEventCacheGenericUpdate::UpdateTimeline { room_id: expected_room_id }) => {
2862                assert_eq!(expected_room_id, room_id);
2863            }
2864        );
2865
2866        {
2867            let state = room_event_cache.inner.state.read().await;
2868
2869            let mut num_gaps = 0;
2870            let mut num_events = 0;
2871
2872            for c in state.room_linked_chunk().chunks() {
2873                match c.content() {
2874                    ChunkContent::Items(items) => num_events += items.len(),
2875                    ChunkContent::Gap(gap) => {
2876                        assert_eq!(gap.prev_token, "raclette");
2877                        num_gaps += 1;
2878                    }
2879                }
2880            }
2881
2882            // There's only the previous gap, no new ones.
2883            assert_eq!(num_gaps, 1);
2884            assert_eq!(num_events, 2);
2885        }
2886    }
2887
2888    #[async_test]
2889    async fn test_shrink_to_last_chunk() {
2890        let room_id = room_id!("!galette:saucisse.bzh");
2891
2892        let client = MockClientBuilder::new(None).build().await;
2893
2894        let f = EventFactory::new().room(room_id);
2895
2896        let evid1 = event_id!("$1");
2897        let evid2 = event_id!("$2");
2898
2899        let ev1 = f.text_msg("hello world").sender(*ALICE).event_id(evid1).into_event();
2900        let ev2 = f.text_msg("howdy").sender(*BOB).event_id(evid2).into_event();
2901
2902        // Fill the event cache store with an initial linked chunk with 2 events chunks.
2903        {
2904            let store = client.event_cache_store();
2905            let store = store.lock().await.unwrap();
2906            store
2907                .handle_linked_chunk_updates(
2908                    LinkedChunkId::Room(room_id),
2909                    vec![
2910                        Update::NewItemsChunk {
2911                            previous: None,
2912                            new: ChunkIdentifier::new(0),
2913                            next: None,
2914                        },
2915                        Update::PushItems {
2916                            at: Position::new(ChunkIdentifier::new(0), 0),
2917                            items: vec![ev1],
2918                        },
2919                        Update::NewItemsChunk {
2920                            previous: Some(ChunkIdentifier::new(0)),
2921                            new: ChunkIdentifier::new(1),
2922                            next: None,
2923                        },
2924                        Update::PushItems {
2925                            at: Position::new(ChunkIdentifier::new(1), 0),
2926                            items: vec![ev2],
2927                        },
2928                    ],
2929                )
2930                .await
2931                .unwrap();
2932        }
2933
2934        let event_cache = client.event_cache();
2935        event_cache.subscribe().unwrap();
2936
2937        client.base_client().get_or_create_room(room_id, matrix_sdk_base::RoomState::Joined);
2938        let room = client.get_room(room_id).unwrap();
2939        let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
2940
2941        // Sanity check: lazily loaded, so only includes one item at start.
2942        let (events, mut stream) = room_event_cache.subscribe().await;
2943        assert_eq!(events.len(), 1);
2944        assert_eq!(events[0].event_id().as_deref(), Some(evid2));
2945        assert!(stream.is_empty());
2946
2947        let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
2948
2949        // Force loading the full linked chunk by back-paginating.
2950        let outcome = room_event_cache.pagination().run_backwards_once(20).await.unwrap();
2951        assert_eq!(outcome.events.len(), 1);
2952        assert_eq!(outcome.events[0].event_id().as_deref(), Some(evid1));
2953        assert!(outcome.reached_start);
2954
2955        // We also get an update about the loading from the store.
2956        assert_let_timeout!(
2957            Ok(RoomEventCacheUpdate::UpdateTimelineEvents { diffs, .. }) = stream.recv()
2958        );
2959        assert_eq!(diffs.len(), 1);
2960        assert_matches!(&diffs[0], VectorDiff::Insert { index: 0, value } => {
2961            assert_eq!(value.event_id().as_deref(), Some(evid1));
2962        });
2963
2964        assert!(stream.is_empty());
2965
2966        // Same for the generic update.
2967        assert_let_timeout!(
2968            Ok(RoomEventCacheGenericUpdate::UpdateTimeline { room_id: received_room_id }) =
2969                generic_stream.recv()
2970        );
2971        assert_eq!(received_room_id, room_id);
2972
2973        // Shrink the linked chunk to the last chunk.
2974        let diffs = room_event_cache
2975            .inner
2976            .state
2977            .write()
2978            .await
2979            .force_shrink_to_last_chunk()
2980            .await
2981            .expect("shrinking should succeed");
2982
2983        // We receive updates about the changes to the linked chunk.
2984        assert_eq!(diffs.len(), 2);
2985        assert_matches!(&diffs[0], VectorDiff::Clear);
2986        assert_matches!(&diffs[1], VectorDiff::Append { values} => {
2987            assert_eq!(values.len(), 1);
2988            assert_eq!(values[0].event_id().as_deref(), Some(evid2));
2989        });
2990
2991        assert!(stream.is_empty());
2992
2993        // No generic update is sent in this case.
2994        assert!(generic_stream.is_empty());
2995
2996        // When reading the events, we do get only the last one.
2997        let events = room_event_cache.events().await;
2998        assert_eq!(events.len(), 1);
2999        assert_eq!(events[0].event_id().as_deref(), Some(evid2));
3000
3001        // But if we back-paginate, we don't need access to network to find out about
3002        // the previous event.
3003        let outcome = room_event_cache.pagination().run_backwards_once(20).await.unwrap();
3004        assert_eq!(outcome.events.len(), 1);
3005        assert_eq!(outcome.events[0].event_id().as_deref(), Some(evid1));
3006        assert!(outcome.reached_start);
3007    }
3008
3009    #[async_test]
3010    async fn test_room_ordering() {
3011        let room_id = room_id!("!galette:saucisse.bzh");
3012
3013        let client = MockClientBuilder::new(None).build().await;
3014
3015        let f = EventFactory::new().room(room_id).sender(*ALICE);
3016
3017        let evid1 = event_id!("$1");
3018        let evid2 = event_id!("$2");
3019        let evid3 = event_id!("$3");
3020
3021        let ev1 = f.text_msg("hello world").event_id(evid1).into_event();
3022        let ev2 = f.text_msg("howdy").sender(*BOB).event_id(evid2).into_event();
3023        let ev3 = f.text_msg("yo").event_id(evid3).into_event();
3024
3025        // Fill the event cache store with an initial linked chunk with 2 events chunks.
3026        {
3027            let store = client.event_cache_store();
3028            let store = store.lock().await.unwrap();
3029            store
3030                .handle_linked_chunk_updates(
3031                    LinkedChunkId::Room(room_id),
3032                    vec![
3033                        Update::NewItemsChunk {
3034                            previous: None,
3035                            new: ChunkIdentifier::new(0),
3036                            next: None,
3037                        },
3038                        Update::PushItems {
3039                            at: Position::new(ChunkIdentifier::new(0), 0),
3040                            items: vec![ev1, ev2],
3041                        },
3042                        Update::NewItemsChunk {
3043                            previous: Some(ChunkIdentifier::new(0)),
3044                            new: ChunkIdentifier::new(1),
3045                            next: None,
3046                        },
3047                        Update::PushItems {
3048                            at: Position::new(ChunkIdentifier::new(1), 0),
3049                            items: vec![ev3.clone()],
3050                        },
3051                    ],
3052                )
3053                .await
3054                .unwrap();
3055        }
3056
3057        let event_cache = client.event_cache();
3058        event_cache.subscribe().unwrap();
3059
3060        client.base_client().get_or_create_room(room_id, matrix_sdk_base::RoomState::Joined);
3061        let room = client.get_room(room_id).unwrap();
3062        let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
3063
3064        // Initially, the linked chunk only contains the last chunk, so only ev3 is
3065        // loaded.
3066        {
3067            let state = room_event_cache.inner.state.read().await;
3068
3069            // But we can get the order of ev1.
3070            assert_eq!(state.room_event_order(Position::new(ChunkIdentifier::new(0), 0)), Some(0));
3071
3072            // And that of ev2 as well.
3073            assert_eq!(state.room_event_order(Position::new(ChunkIdentifier::new(0), 1)), Some(1));
3074
3075            // ev3, which is loaded, also has a known ordering.
3076            let mut events = state.room_linked_chunk().events();
3077            let (pos, ev) = events.next().unwrap();
3078            assert_eq!(pos, Position::new(ChunkIdentifier::new(1), 0));
3079            assert_eq!(ev.event_id().as_deref(), Some(evid3));
3080            assert_eq!(state.room_event_order(pos), Some(2));
3081
3082            // No other loaded events.
3083            assert!(events.next().is_none());
3084        }
3085
3086        // Force loading the full linked chunk by back-paginating.
3087        let outcome = room_event_cache.pagination().run_backwards_once(20).await.unwrap();
3088        assert!(outcome.reached_start);
3089
3090        // All events are now loaded, so their order is precisely their enumerated index
3091        // in a linear iteration.
3092        {
3093            let state = room_event_cache.inner.state.read().await;
3094            for (i, (pos, _)) in state.room_linked_chunk().events().enumerate() {
3095                assert_eq!(state.room_event_order(pos), Some(i));
3096            }
3097        }
3098
3099        // Handle a gappy sync with two events (including one duplicate, so
3100        // deduplication kicks in), so that the linked chunk is shrunk to the
3101        // last chunk, and that the linked chunk only contains the last two
3102        // events.
3103        let evid4 = event_id!("$4");
3104        room_event_cache
3105            .inner
3106            .handle_joined_room_update(JoinedRoomUpdate {
3107                timeline: Timeline {
3108                    limited: true,
3109                    prev_batch: Some("fondue".to_owned()),
3110                    events: vec![ev3, f.text_msg("sup").event_id(evid4).into_event()],
3111                },
3112                ..Default::default()
3113            })
3114            .await
3115            .unwrap();
3116
3117        {
3118            let state = room_event_cache.inner.state.read().await;
3119
3120            // After the shrink, only evid3 and evid4 are loaded.
3121            let mut events = state.room_linked_chunk().events();
3122
3123            let (pos, ev) = events.next().unwrap();
3124            assert_eq!(ev.event_id().as_deref(), Some(evid3));
3125            assert_eq!(state.room_event_order(pos), Some(2));
3126
3127            let (pos, ev) = events.next().unwrap();
3128            assert_eq!(ev.event_id().as_deref(), Some(evid4));
3129            assert_eq!(state.room_event_order(pos), Some(3));
3130
3131            // No other loaded events.
3132            assert!(events.next().is_none());
3133
3134            // But we can still get the order of previous events.
3135            assert_eq!(state.room_event_order(Position::new(ChunkIdentifier::new(0), 0)), Some(0));
3136            assert_eq!(state.room_event_order(Position::new(ChunkIdentifier::new(0), 1)), Some(1));
3137
3138            // ev3 doesn't have an order with its previous position, since it's been
3139            // deduplicated.
3140            assert_eq!(state.room_event_order(Position::new(ChunkIdentifier::new(1), 0)), None);
3141        }
3142    }
3143
3144    #[async_test]
3145    async fn test_auto_shrink_after_all_subscribers_are_gone() {
3146        let room_id = room_id!("!galette:saucisse.bzh");
3147
3148        let client = MockClientBuilder::new(None).build().await;
3149
3150        let f = EventFactory::new().room(room_id);
3151
3152        let evid1 = event_id!("$1");
3153        let evid2 = event_id!("$2");
3154
3155        let ev1 = f.text_msg("hello world").sender(*ALICE).event_id(evid1).into_event();
3156        let ev2 = f.text_msg("howdy").sender(*BOB).event_id(evid2).into_event();
3157
3158        // Fill the event cache store with an initial linked chunk with 2 events chunks.
3159        {
3160            let store = client.event_cache_store();
3161            let store = store.lock().await.unwrap();
3162            store
3163                .handle_linked_chunk_updates(
3164                    LinkedChunkId::Room(room_id),
3165                    vec![
3166                        Update::NewItemsChunk {
3167                            previous: None,
3168                            new: ChunkIdentifier::new(0),
3169                            next: None,
3170                        },
3171                        Update::PushItems {
3172                            at: Position::new(ChunkIdentifier::new(0), 0),
3173                            items: vec![ev1],
3174                        },
3175                        Update::NewItemsChunk {
3176                            previous: Some(ChunkIdentifier::new(0)),
3177                            new: ChunkIdentifier::new(1),
3178                            next: None,
3179                        },
3180                        Update::PushItems {
3181                            at: Position::new(ChunkIdentifier::new(1), 0),
3182                            items: vec![ev2],
3183                        },
3184                    ],
3185                )
3186                .await
3187                .unwrap();
3188        }
3189
3190        let event_cache = client.event_cache();
3191        event_cache.subscribe().unwrap();
3192
3193        client.base_client().get_or_create_room(room_id, matrix_sdk_base::RoomState::Joined);
3194        let room = client.get_room(room_id).unwrap();
3195        let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
3196
3197        // Sanity check: lazily loaded, so only includes one item at start.
3198        let (events1, mut stream1) = room_event_cache.subscribe().await;
3199        assert_eq!(events1.len(), 1);
3200        assert_eq!(events1[0].event_id().as_deref(), Some(evid2));
3201        assert!(stream1.is_empty());
3202
3203        // Force loading the full linked chunk by back-paginating.
3204        let outcome = room_event_cache.pagination().run_backwards_once(20).await.unwrap();
3205        assert_eq!(outcome.events.len(), 1);
3206        assert_eq!(outcome.events[0].event_id().as_deref(), Some(evid1));
3207        assert!(outcome.reached_start);
3208
3209        // We also get an update about the loading from the store. Ignore it, for this
3210        // test's sake.
3211        assert_let_timeout!(
3212            Ok(RoomEventCacheUpdate::UpdateTimelineEvents { diffs, .. }) = stream1.recv()
3213        );
3214        assert_eq!(diffs.len(), 1);
3215        assert_matches!(&diffs[0], VectorDiff::Insert { index: 0, value } => {
3216            assert_eq!(value.event_id().as_deref(), Some(evid1));
3217        });
3218
3219        assert!(stream1.is_empty());
3220
3221        // Have another subscriber.
3222        // Since it's not the first one, and the previous one loaded some more events,
3223        // the second subscribers sees them all.
3224        let (events2, stream2) = room_event_cache.subscribe().await;
3225        assert_eq!(events2.len(), 2);
3226        assert_eq!(events2[0].event_id().as_deref(), Some(evid1));
3227        assert_eq!(events2[1].event_id().as_deref(), Some(evid2));
3228        assert!(stream2.is_empty());
3229
3230        // Drop the first stream, and wait a bit.
3231        drop(stream1);
3232        yield_now().await;
3233
3234        // The second stream remains undisturbed.
3235        assert!(stream2.is_empty());
3236
3237        // Now drop the second stream, and wait a bit.
3238        drop(stream2);
3239        yield_now().await;
3240
3241        // The linked chunk must have auto-shrunk by now.
3242
3243        {
3244            // Check the inner state: there's no more shared auto-shrinker.
3245            let state = room_event_cache.inner.state.read().await;
3246            assert_eq!(state.subscriber_count.load(std::sync::atomic::Ordering::SeqCst), 0);
3247        }
3248
3249        // Getting the events will only give us the latest chunk.
3250        let events3 = room_event_cache.events().await;
3251        assert_eq!(events3.len(), 1);
3252        assert_eq!(events3[0].event_id().as_deref(), Some(evid2));
3253    }
3254
3255    #[async_test]
3256    async fn test_rfind_map_event_in_memory_by() {
3257        let user_id = user_id!("@mnt_io:matrix.org");
3258        let room_id = room_id!("!raclette:patate.ch");
3259        let client = MockClientBuilder::new(None).build().await;
3260
3261        let event_factory = EventFactory::new().room(room_id);
3262
3263        let event_id_0 = event_id!("$ev0");
3264        let event_id_1 = event_id!("$ev1");
3265        let event_id_2 = event_id!("$ev2");
3266        let event_id_3 = event_id!("$ev3");
3267
3268        let event_0 =
3269            event_factory.text_msg("hello").sender(*BOB).event_id(event_id_0).into_event();
3270        let event_1 =
3271            event_factory.text_msg("world").sender(*ALICE).event_id(event_id_1).into_event();
3272        let event_2 = event_factory.text_msg("!").sender(*ALICE).event_id(event_id_2).into_event();
3273        let event_3 =
3274            event_factory.text_msg("eh!").sender(user_id).event_id(event_id_3).into_event();
3275
3276        // Fill the event cache store with an initial linked chunk of 2 chunks, and 4
3277        // events.
3278        {
3279            let store = client.event_cache_store();
3280            let store = store.lock().await.unwrap();
3281            store
3282                .handle_linked_chunk_updates(
3283                    LinkedChunkId::Room(room_id),
3284                    vec![
3285                        Update::NewItemsChunk {
3286                            previous: None,
3287                            new: ChunkIdentifier::new(0),
3288                            next: None,
3289                        },
3290                        Update::PushItems {
3291                            at: Position::new(ChunkIdentifier::new(0), 0),
3292                            items: vec![event_3],
3293                        },
3294                        Update::NewItemsChunk {
3295                            previous: Some(ChunkIdentifier::new(0)),
3296                            new: ChunkIdentifier::new(1),
3297                            next: None,
3298                        },
3299                        Update::PushItems {
3300                            at: Position::new(ChunkIdentifier::new(1), 0),
3301                            items: vec![event_0, event_1, event_2],
3302                        },
3303                    ],
3304                )
3305                .await
3306                .unwrap();
3307        }
3308
3309        let event_cache = client.event_cache();
3310        event_cache.subscribe().unwrap();
3311
3312        client.base_client().get_or_create_room(room_id, matrix_sdk_base::RoomState::Joined);
3313        let room = client.get_room(room_id).unwrap();
3314        let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
3315
3316        // Look for an event from `BOB`: it must be `event_0`.
3317        assert_matches!(
3318            room_event_cache
3319                .rfind_map_event_in_memory_by(|event| {
3320                    (event.raw().get_field::<OwnedUserId>("sender").unwrap().as_deref() == Some(*BOB)).then(|| event.event_id())
3321                })
3322                .await,
3323            Some(event_id) => {
3324                assert_eq!(event_id.as_deref(), Some(event_id_0));
3325            }
3326        );
3327
3328        // Look for an event from `ALICE`: it must be `event_2`, right before `event_1`
3329        // because events are looked for in reverse order.
3330        assert_matches!(
3331            room_event_cache
3332                .rfind_map_event_in_memory_by(|event| {
3333                    (event.raw().get_field::<OwnedUserId>("sender").unwrap().as_deref() == Some(*ALICE)).then(|| event.event_id())
3334                })
3335                .await,
3336            Some(event_id) => {
3337                assert_eq!(event_id.as_deref(), Some(event_id_2));
3338            }
3339        );
3340
3341        // Look for an event that is inside the storage, but not loaded.
3342        assert!(room_event_cache
3343            .rfind_map_event_in_memory_by(|event| {
3344                (event.raw().get_field::<OwnedUserId>("sender").unwrap().as_deref()
3345                    == Some(user_id))
3346                .then(|| event.event_id())
3347            })
3348            .await
3349            .is_none());
3350
3351        // Look for an event that doesn't exist.
3352        assert!(room_event_cache.rfind_map_event_in_memory_by(|_| None::<()>).await.is_none());
3353    }
3354}