1#![forbid(missing_docs)]
29
30use std::{
31 collections::BTreeMap,
32 fmt,
33 sync::{Arc, OnceLock},
34};
35
36use eyeball::{SharedObservable, Subscriber};
37use eyeball_im::VectorDiff;
38use futures_util::future::{join_all, try_join_all};
39use matrix_sdk_base::{
40 deserialized_responses::{AmbiguityChange, TimelineEvent},
41 event_cache::store::{EventCacheStoreError, EventCacheStoreLock},
42 linked_chunk::lazy_loader::LazyLoaderError,
43 store_locks::LockStoreError,
44 sync::RoomUpdates,
45 timer,
46};
47use matrix_sdk_common::executor::{spawn, JoinHandle};
48use room::RoomEventCacheState;
49use ruma::{events::AnySyncEphemeralRoomEvent, serde::Raw, OwnedEventId, OwnedRoomId, RoomId};
50use tokio::sync::{
51 broadcast::{channel, error::RecvError, Receiver, Sender},
52 mpsc, Mutex, RwLock,
53};
54use tracing::{debug, error, info, info_span, instrument, trace, warn, Instrument as _, Span};
55
56use crate::{client::WeakClient, Client};
57
58mod deduplicator;
59mod pagination;
60mod room;
61
62pub use pagination::{RoomPagination, RoomPaginationStatus};
63pub use room::{RoomEventCache, RoomEventCacheSubscriber, ThreadEventCacheUpdate};
64
65#[derive(thiserror::Error, Debug)]
67pub enum EventCacheError {
68 #[error(
71 "The EventCache hasn't subscribed to sync responses yet, call `EventCache::subscribe()`"
72 )]
73 NotSubscribedYet,
74
75 #[error("Room `{room_id}` is not found.")]
77 RoomNotFound {
78 room_id: OwnedRoomId,
80 },
81
82 #[error(transparent)]
84 BackpaginationError(Box<crate::Error>),
85
86 #[error("We were already back-paginating.")]
89 AlreadyBackpaginating,
90
91 #[error(transparent)]
93 Storage(#[from] EventCacheStoreError),
94
95 #[error(transparent)]
97 LockingStorage(#[from] LockStoreError),
98
99 #[error("The owning client of the event cache has been dropped.")]
103 ClientDropped,
104
105 #[error(transparent)]
110 LinkedChunkLoader(#[from] LazyLoaderError),
111
112 #[error("the linked chunk metadata is invalid: {details}")]
115 InvalidLinkedChunkMetadata {
116 details: String,
118 },
119}
120
121pub type Result<T> = std::result::Result<T, EventCacheError>;
123
124pub struct EventCacheDropHandles {
126 listen_updates_task: JoinHandle<()>,
128
129 ignore_user_list_update_task: JoinHandle<()>,
131
132 auto_shrink_linked_chunk_task: JoinHandle<()>,
134}
135
136impl fmt::Debug for EventCacheDropHandles {
137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138 f.debug_struct("EventCacheDropHandles").finish_non_exhaustive()
139 }
140}
141
142impl Drop for EventCacheDropHandles {
143 fn drop(&mut self) {
144 self.listen_updates_task.abort();
145 self.ignore_user_list_update_task.abort();
146 self.auto_shrink_linked_chunk_task.abort();
147 }
148}
149
150#[derive(Clone)]
156pub struct EventCache {
157 inner: Arc<EventCacheInner>,
159}
160
161impl fmt::Debug for EventCache {
162 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163 f.debug_struct("EventCache").finish_non_exhaustive()
164 }
165}
166
167impl EventCache {
168 pub(crate) fn new(client: WeakClient, event_cache_store: EventCacheStoreLock) -> Self {
170 let (room_event_cache_generic_update_sender, _) = channel(32);
171
172 Self {
173 inner: Arc::new(EventCacheInner {
174 client,
175 store: event_cache_store,
176 multiple_room_updates_lock: Default::default(),
177 by_room: Default::default(),
178 drop_handles: Default::default(),
179 auto_shrink_sender: Default::default(),
180 room_event_cache_generic_update_sender,
181 }),
182 }
183 }
184
185 pub fn subscribe(&self) -> Result<()> {
191 let client = self.inner.client()?;
192
193 let _ = self.inner.drop_handles.get_or_init(|| {
195 let listen_updates_task = spawn(Self::listen_task(
197 self.inner.clone(),
198 client.subscribe_to_all_room_updates(),
199 ));
200
201 let ignore_user_list_update_task = spawn(Self::ignore_user_list_update_task(
202 self.inner.clone(),
203 client.subscribe_to_ignore_user_list_changes(),
204 ));
205
206 let (auto_shrink_sender, auto_shrink_receiver) = mpsc::channel(32);
207
208 self.inner.auto_shrink_sender.get_or_init(|| auto_shrink_sender);
210
211 let auto_shrink_linked_chunk_task = spawn(Self::auto_shrink_linked_chunk_task(
212 self.inner.clone(),
213 auto_shrink_receiver,
214 ));
215
216 Arc::new(EventCacheDropHandles {
217 listen_updates_task,
218 ignore_user_list_update_task,
219 auto_shrink_linked_chunk_task,
220 })
221 });
222
223 Ok(())
224 }
225
226 #[instrument(skip_all)]
227 async fn ignore_user_list_update_task(
228 inner: Arc<EventCacheInner>,
229 mut ignore_user_list_stream: Subscriber<Vec<String>>,
230 ) {
231 let span = info_span!(parent: Span::none(), "ignore_user_list_update_task");
232 span.follows_from(Span::current());
233
234 async move {
235 while ignore_user_list_stream.next().await.is_some() {
236 info!("Received an ignore user list change");
237 if let Err(err) = inner.clear_all_rooms().await {
238 error!("when clearing room storage after ignore user list change: {err}");
239 }
240 }
241 info!("Ignore user list stream has closed");
242 }
243 .instrument(span)
244 .await;
245 }
246
247 #[doc(hidden)]
249 pub async fn handle_room_updates(&self, updates: RoomUpdates) -> Result<()> {
250 self.inner.handle_room_updates(updates).await
251 }
252
253 #[instrument(skip_all)]
254 async fn listen_task(
255 inner: Arc<EventCacheInner>,
256 mut room_updates_feed: Receiver<RoomUpdates>,
257 ) {
258 trace!("Spawning the listen task");
259 loop {
260 match room_updates_feed.recv().await {
261 Ok(updates) => {
262 trace!("Receiving `RoomUpdates`");
263
264 if let Err(err) = inner.handle_room_updates(updates).await {
265 match err {
266 EventCacheError::ClientDropped => {
267 info!(
269 "Closing the event cache global listen task because client dropped"
270 );
271 break;
272 }
273 err => {
274 error!("Error when handling room updates: {err}");
275 }
276 }
277 }
278 }
279
280 Err(RecvError::Lagged(num_skipped)) => {
281 warn!(num_skipped, "Lagged behind room updates, clearing all rooms");
285 if let Err(err) = inner.clear_all_rooms().await {
286 error!("when clearing storage after lag in listen_task: {err}");
287 }
288 }
289
290 Err(RecvError::Closed) => {
291 info!("Closing the event cache global listen task because receiver closed");
293 break;
294 }
295 }
296 }
297 }
298
299 #[instrument(skip_all)]
313 async fn auto_shrink_linked_chunk_task(
314 inner: Arc<EventCacheInner>,
315 mut rx: mpsc::Receiver<AutoShrinkChannelPayload>,
316 ) {
317 while let Some(room_id) = rx.recv().await {
318 trace!(for_room = %room_id, "received notification to shrink");
319
320 let room = match inner.for_room(&room_id).await {
321 Ok(room) => room,
322 Err(err) => {
323 warn!(for_room = %room_id, "Failed to get the `RoomEventCache`: {err}");
324 continue;
325 }
326 };
327
328 trace!("waiting for state lock…");
329 let mut state = room.inner.state.write().await;
330
331 match state.auto_shrink_if_no_subscribers().await {
332 Ok(diffs) => {
333 if let Some(diffs) = diffs {
334 if !diffs.is_empty() {
341 let _ = room.inner.sender.send(
342 RoomEventCacheUpdate::UpdateTimelineEvents {
343 diffs,
344 origin: EventsOrigin::Cache,
345 },
346 );
347 }
348 } else {
349 debug!("auto-shrinking didn't happen");
350 }
351 }
352
353 Err(err) => {
354 warn!(for_room = %room_id, "error when attempting to shrink linked chunk: {err}");
356 }
357 }
358 }
359 }
360
361 pub(crate) async fn for_room(
363 &self,
364 room_id: &RoomId,
365 ) -> Result<(RoomEventCache, Arc<EventCacheDropHandles>)> {
366 let Some(drop_handles) = self.inner.drop_handles.get().cloned() else {
367 return Err(EventCacheError::NotSubscribedYet);
368 };
369
370 let room = self.inner.for_room(room_id).await?;
371
372 Ok((room, drop_handles))
373 }
374
375 pub async fn clear_all_rooms(&self) -> Result<()> {
379 self.inner.clear_all_rooms().await
380 }
381
382 pub fn subscribe_to_room_generic_updates(&self) -> Receiver<RoomEventCacheGenericUpdate> {
394 self.inner.room_event_cache_generic_update_sender.subscribe()
395 }
396}
397
398struct EventCacheInner {
399 client: WeakClient,
402
403 store: EventCacheStoreLock,
405
406 multiple_room_updates_lock: Mutex<()>,
413
414 by_room: RwLock<BTreeMap<OwnedRoomId, RoomEventCache>>,
416
417 drop_handles: OnceLock<Arc<EventCacheDropHandles>>,
419
420 auto_shrink_sender: OnceLock<mpsc::Sender<AutoShrinkChannelPayload>>,
427
428 room_event_cache_generic_update_sender: Sender<RoomEventCacheGenericUpdate>,
433}
434
435type AutoShrinkChannelPayload = OwnedRoomId;
436
437impl EventCacheInner {
438 fn client(&self) -> Result<Client> {
439 self.client.get().ok_or(EventCacheError::ClientDropped)
440 }
441
442 async fn clear_all_rooms(&self) -> Result<()> {
444 let rooms = self.by_room.write().await;
484
485 let room_locks = join_all(
488 rooms.values().map(|room| async move { (room, room.inner.state.write().await) }),
489 )
490 .await;
491
492 self.store.lock().await?.clear_all_linked_chunks().await?;
494
495 try_join_all(room_locks.into_iter().map(|(room, mut state_guard)| async move {
499 let updates_as_vector_diffs = state_guard.reset().await?;
500
501 let _ = room.inner.sender.send(RoomEventCacheUpdate::UpdateTimelineEvents {
502 diffs: updates_as_vector_diffs,
503 origin: EventsOrigin::Cache,
504 });
505
506 let _ = room
507 .inner
508 .generic_update_sender
509 .send(RoomEventCacheGenericUpdate::Clear { room_id: room.inner.room_id.clone() });
510
511 Ok::<_, EventCacheError>(())
512 }))
513 .await?;
514
515 Ok(())
516 }
517
518 #[instrument(skip(self, updates))]
520 async fn handle_room_updates(&self, updates: RoomUpdates) -> Result<()> {
521 let _lock = {
524 let _timer = timer!("Taking the `multiple_room_updates_lock`");
525 self.multiple_room_updates_lock.lock().await
526 };
527
528 for (room_id, left_room_update) in updates.left {
535 let room = self.for_room(&room_id).await?;
536
537 if let Err(err) = room.inner.handle_left_room_update(left_room_update).await {
538 error!("handling left room update: {err}");
540 }
541 }
542
543 for (room_id, joined_room_update) in updates.joined {
545 trace!(?room_id, "Handling a `JoinedRoomUpdate`");
546
547 let room = self.for_room(&room_id).await?;
548
549 if let Err(err) = room.inner.handle_joined_room_update(joined_room_update).await {
550 error!(%room_id, "handling joined room update: {err}");
552 }
553 }
554
555 Ok(())
559 }
560
561 async fn for_room(&self, room_id: &RoomId) -> Result<RoomEventCache> {
563 let by_room_guard = self.by_room.read().await;
566
567 match by_room_guard.get(room_id) {
568 Some(room) => Ok(room.clone()),
569
570 None => {
571 drop(by_room_guard);
573 let mut by_room_guard = self.by_room.write().await;
574
575 if let Some(room) = by_room_guard.get(room_id) {
578 return Ok(room.clone());
579 }
580
581 let pagination_status =
582 SharedObservable::new(RoomPaginationStatus::Idle { hit_timeline_start: false });
583
584 let Some(client) = self.client.get() else {
585 return Err(EventCacheError::ClientDropped);
586 };
587
588 let room = client
589 .get_room(room_id)
590 .ok_or_else(|| EventCacheError::RoomNotFound { room_id: room_id.to_owned() })?;
591 let room_version_rules = room.clone_info().room_version_rules_or_default();
592
593 let room_state = RoomEventCacheState::new(
594 room_id.to_owned(),
595 room_version_rules,
596 self.store.clone(),
597 pagination_status.clone(),
598 )
599 .await?;
600
601 let timeline_is_not_empty =
602 room_state.room_linked_chunk().revents().next().is_some();
603
604 let auto_shrink_sender =
607 self.auto_shrink_sender.get().cloned().expect(
608 "we must have called `EventCache::subscribe()` before calling here.",
609 );
610
611 let room_event_cache = RoomEventCache::new(
612 self.client.clone(),
613 room_state,
614 pagination_status,
615 room_id.to_owned(),
616 auto_shrink_sender,
617 self.room_event_cache_generic_update_sender.clone(),
618 );
619
620 by_room_guard.insert(room_id.to_owned(), room_event_cache.clone());
621
622 if timeline_is_not_empty {
625 let _ = self.room_event_cache_generic_update_sender.send(
626 RoomEventCacheGenericUpdate::UpdateTimeline { room_id: room_id.to_owned() },
627 );
628 }
629
630 Ok(room_event_cache)
631 }
632 }
633 }
634}
635
636#[derive(Debug)]
638pub struct BackPaginationOutcome {
639 pub reached_start: bool,
641
642 pub events: Vec<TimelineEvent>,
649}
650
651#[derive(Clone, Debug)]
657pub enum RoomEventCacheGenericUpdate {
658 UpdateTimeline {
661 room_id: OwnedRoomId,
663 },
664
665 Clear {
667 room_id: OwnedRoomId,
669 },
670}
671
672impl RoomEventCacheGenericUpdate {
673 pub fn room_id(&self) -> &RoomId {
675 match self {
676 Self::UpdateTimeline { room_id } => room_id,
677 Self::Clear { room_id } => room_id,
678 }
679 }
680}
681
682#[derive(Debug, Clone)]
684pub enum RoomEventCacheUpdate {
685 MoveReadMarkerTo {
687 event_id: OwnedEventId,
689 },
690
691 UpdateMembers {
693 ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
698 },
699
700 UpdateTimelineEvents {
702 diffs: Vec<VectorDiff<TimelineEvent>>,
704
705 origin: EventsOrigin,
707 },
708
709 AddEphemeralEvents {
711 events: Vec<Raw<AnySyncEphemeralRoomEvent>>,
714 },
715}
716
717#[derive(Debug, Clone)]
719pub enum EventsOrigin {
720 Sync,
722
723 Pagination,
725
726 Cache,
728}
729
730#[cfg(test)]
731mod tests {
732 use std::ops::Not;
733
734 use assert_matches::assert_matches;
735 use futures_util::FutureExt as _;
736 use matrix_sdk_base::{
737 linked_chunk::{ChunkIdentifier, LinkedChunkId, Position, Update},
738 sync::{JoinedRoomUpdate, RoomUpdates, Timeline},
739 RoomState,
740 };
741 use matrix_sdk_test::{async_test, event_factory::EventFactory};
742 use ruma::{event_id, room_id, serde::Raw, user_id};
743 use serde_json::json;
744
745 use super::{EventCacheError, RoomEventCacheGenericUpdate, RoomEventCacheUpdate};
746 use crate::test_utils::{assert_event_matches_msg, logged_in_client};
747
748 #[async_test]
749 async fn test_must_explicitly_subscribe() {
750 let client = logged_in_client(None).await;
751
752 let event_cache = client.event_cache();
753
754 let room_id = room_id!("!omelette:fromage.fr");
757 let result = event_cache.for_room(room_id).await;
758
759 assert_matches!(result, Err(EventCacheError::NotSubscribedYet));
762 }
763
764 #[async_test]
765 async fn test_uniq_read_marker() {
766 let client = logged_in_client(None).await;
767 let room_id = room_id!("!galette:saucisse.bzh");
768 client.base_client().get_or_create_room(room_id, RoomState::Joined);
769
770 let event_cache = client.event_cache();
771
772 event_cache.subscribe().unwrap();
773
774 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
775 let (room_event_cache, _drop_handles) = event_cache.for_room(room_id).await.unwrap();
776 let (events, mut stream) = room_event_cache.subscribe().await;
777
778 assert!(events.is_empty());
779
780 let read_marker_event = Raw::from_json_string(
782 json!({
783 "content": {
784 "event_id": "$crepe:saucisse.bzh"
785 },
786 "room_id": "!galette:saucisse.bzh",
787 "type": "m.fully_read"
788 })
789 .to_string(),
790 )
791 .unwrap();
792 let account_data = vec![read_marker_event; 100];
793
794 room_event_cache
795 .inner
796 .handle_joined_room_update(JoinedRoomUpdate { account_data, ..Default::default() })
797 .await
798 .unwrap();
799
800 assert_matches!(
802 stream.recv().await.unwrap(),
803 RoomEventCacheUpdate::MoveReadMarkerTo { .. }
804 );
805
806 assert!(stream.recv().now_or_never().is_none());
807
808 assert!(generic_stream.recv().now_or_never().is_none());
810 }
811
812 #[async_test]
813 async fn test_get_event_by_id() {
814 let client = logged_in_client(None).await;
815 let room_id1 = room_id!("!galette:saucisse.bzh");
816 let room_id2 = room_id!("!crepe:saucisse.bzh");
817
818 client.base_client().get_or_create_room(room_id1, RoomState::Joined);
819 client.base_client().get_or_create_room(room_id2, RoomState::Joined);
820
821 let event_cache = client.event_cache();
822 event_cache.subscribe().unwrap();
823
824 let f = EventFactory::new().room(room_id1).sender(user_id!("@ben:saucisse.bzh"));
826
827 let eid1 = event_id!("$1");
828 let eid2 = event_id!("$2");
829 let eid3 = event_id!("$3");
830
831 let joined_room_update1 = JoinedRoomUpdate {
832 timeline: Timeline {
833 events: vec![
834 f.text_msg("hey").event_id(eid1).into(),
835 f.text_msg("you").event_id(eid2).into(),
836 ],
837 ..Default::default()
838 },
839 ..Default::default()
840 };
841
842 let joined_room_update2 = JoinedRoomUpdate {
843 timeline: Timeline {
844 events: vec![f.text_msg("bjr").event_id(eid3).into()],
845 ..Default::default()
846 },
847 ..Default::default()
848 };
849
850 let mut updates = RoomUpdates::default();
851 updates.joined.insert(room_id1.to_owned(), joined_room_update1);
852 updates.joined.insert(room_id2.to_owned(), joined_room_update2);
853
854 event_cache.inner.handle_room_updates(updates).await.unwrap();
856
857 let room1 = client.get_room(room_id1).unwrap();
859
860 let (room_event_cache, _drop_handles) = room1.event_cache().await.unwrap();
861
862 let found1 = room_event_cache.find_event(eid1).await.unwrap();
863 assert_event_matches_msg(&found1, "hey");
864
865 let found2 = room_event_cache.find_event(eid2).await.unwrap();
866 assert_event_matches_msg(&found2, "you");
867
868 assert!(room_event_cache.find_event(eid3).await.is_none());
871 }
872
873 #[async_test]
874 async fn test_save_event() {
875 let client = logged_in_client(None).await;
876 let room_id = room_id!("!galette:saucisse.bzh");
877
878 let event_cache = client.event_cache();
879 event_cache.subscribe().unwrap();
880
881 let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
882 let event_id = event_id!("$1");
883
884 client.base_client().get_or_create_room(room_id, RoomState::Joined);
885 let room = client.get_room(room_id).unwrap();
886
887 let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
888 room_event_cache.save_events([f.text_msg("hey there").event_id(event_id).into()]).await;
889
890 assert!(room_event_cache.find_event(event_id).await.is_some());
892 }
893
894 #[async_test]
895 async fn test_generic_update_when_loading_rooms() {
896 let user = user_id!("@mnt_io:matrix.org");
898 let client = logged_in_client(None).await;
899 let room_id_0 = room_id!("!raclette:patate.ch");
900 let room_id_1 = room_id!("!fondue:patate.ch");
901
902 let event_factory = EventFactory::new().room(room_id_0).sender(user);
903
904 let event_cache = client.event_cache();
905 event_cache.subscribe().unwrap();
906
907 client.base_client().get_or_create_room(room_id_0, RoomState::Joined);
908 client.base_client().get_or_create_room(room_id_1, RoomState::Joined);
909
910 client
911 .event_cache_store()
912 .lock()
913 .await
914 .unwrap()
915 .handle_linked_chunk_updates(
916 LinkedChunkId::Room(room_id_0),
917 vec![
918 Update::NewItemsChunk {
920 previous: None,
921 new: ChunkIdentifier::new(0),
922 next: None,
923 },
924 Update::PushItems {
925 at: Position::new(ChunkIdentifier::new(0), 0),
926 items: vec![event_factory
927 .text_msg("hello")
928 .sender(user)
929 .event_id(event_id!("$ev0"))
930 .into_event()],
931 },
932 ],
933 )
934 .await
935 .unwrap();
936
937 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
938
939 {
941 let _room_event_cache = event_cache.for_room(room_id_0).await.unwrap();
942
943 assert_matches!(
944 generic_stream.recv().await,
945 Ok(RoomEventCacheGenericUpdate::UpdateTimeline { room_id }) => {
946 assert_eq!(room_id, room_id_0);
947 }
948 );
949 }
950
951 {
953 let _room_event_cache = event_cache.for_room(room_id_1).await.unwrap();
954
955 assert!(generic_stream.recv().now_or_never().is_none());
956 }
957 }
958
959 #[async_test]
960 async fn test_generic_update_when_paginating_room() {
961 let user = user_id!("@mnt_io:matrix.org");
963 let client = logged_in_client(None).await;
964 let room_id = room_id!("!raclette:patate.ch");
965
966 let event_factory = EventFactory::new().room(room_id).sender(user);
967
968 let event_cache = client.event_cache();
969 event_cache.subscribe().unwrap();
970
971 client.base_client().get_or_create_room(room_id, RoomState::Joined);
972
973 client
974 .event_cache_store()
975 .lock()
976 .await
977 .unwrap()
978 .handle_linked_chunk_updates(
979 LinkedChunkId::Room(room_id),
980 vec![
981 Update::NewItemsChunk {
983 previous: None,
984 new: ChunkIdentifier::new(0),
985 next: None,
986 },
987 Update::NewItemsChunk {
989 previous: Some(ChunkIdentifier::new(0)),
990 new: ChunkIdentifier::new(1),
991 next: None,
992 },
993 Update::NewItemsChunk {
995 previous: Some(ChunkIdentifier::new(1)),
996 new: ChunkIdentifier::new(2),
997 next: None,
998 },
999 Update::PushItems {
1000 at: Position::new(ChunkIdentifier::new(2), 0),
1001 items: vec![event_factory
1002 .text_msg("hello")
1003 .sender(user)
1004 .event_id(event_id!("$ev0"))
1005 .into_event()],
1006 },
1007 Update::NewItemsChunk {
1009 previous: Some(ChunkIdentifier::new(2)),
1010 new: ChunkIdentifier::new(3),
1011 next: None,
1012 },
1013 Update::PushItems {
1014 at: Position::new(ChunkIdentifier::new(3), 0),
1015 items: vec![event_factory
1016 .text_msg("world")
1017 .sender(user)
1018 .event_id(event_id!("$ev1"))
1019 .into_event()],
1020 },
1021 ],
1022 )
1023 .await
1024 .unwrap();
1025
1026 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
1027
1028 let (room_event_cache, _) = event_cache.for_room(room_id).await.unwrap();
1030
1031 assert_matches!(
1032 generic_stream.recv().await,
1033 Ok(RoomEventCacheGenericUpdate::UpdateTimeline { room_id: expected_room_id }) => {
1034 assert_eq!(room_id, expected_room_id);
1035 }
1036 );
1037
1038 let pagination = room_event_cache.pagination();
1039
1040 let pagination_outcome = pagination.run_backwards_once(1).await.unwrap();
1042
1043 assert_eq!(pagination_outcome.events.len(), 1);
1044 assert!(pagination_outcome.reached_start.not());
1045 assert_matches!(
1046 generic_stream.recv().await,
1047 Ok(RoomEventCacheGenericUpdate::UpdateTimeline { room_id: expected_room_id }) => {
1048 assert_eq!(room_id, expected_room_id);
1049 }
1050 );
1051
1052 let pagination_outcome = pagination.run_backwards_once(1).await.unwrap();
1054
1055 assert!(pagination_outcome.events.is_empty());
1056 assert!(pagination_outcome.reached_start.not());
1057 assert!(generic_stream.recv().now_or_never().is_none());
1058
1059 let pagination_outcome = pagination.run_backwards_once(1).await.unwrap();
1061
1062 assert!(pagination_outcome.reached_start);
1063 assert!(generic_stream.recv().now_or_never().is_none());
1064 }
1065
1066 #[async_test]
1067 async fn test_for_room_when_room_is_not_found() {
1068 let client = logged_in_client(None).await;
1069 let room_id = room_id!("!raclette:patate.ch");
1070
1071 let event_cache = client.event_cache();
1072 event_cache.subscribe().unwrap();
1073
1074 assert_matches!(
1076 event_cache.for_room(room_id).await,
1077 Err(EventCacheError::RoomNotFound { room_id: not_found_room_id }) => {
1078 assert_eq!(room_id, not_found_room_id);
1079 }
1080 );
1081
1082 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1084
1085 assert!(event_cache.for_room(room_id).await.is_ok());
1087 }
1088}