diff --git a/crates/matrix-sdk/src/event_cache/room/mod.rs b/crates/matrix-sdk/src/event_cache/room/mod.rs index 3e026062e..af7da5559 100644 --- a/crates/matrix-sdk/src/event_cache/room/mod.rs +++ b/crates/matrix-sdk/src/event_cache/room/mod.rs @@ -845,9 +845,16 @@ mod private { } Ok(None) => { - // No previous chunk: no events to insert. Better, it means we've reached - // the start of the timeline! - return Ok(LoadMoreEventsBackwardsOutcome::StartOfTimeline); + // No previous chunk: no events to insert. This means one of two things: + // - either the linked chunk is at the start of the timeline, + // - or we haven't received any back-pagination token yet, and we should + // wait for one. + if self.waited_for_initial_prev_token { + return Ok(LoadMoreEventsBackwardsOutcome::StartOfTimeline); + } + // If we haven't waited yet, we request to resolve the gap, once we get the + // previous-batch token from sync. + return Ok(LoadMoreEventsBackwardsOutcome::Gap); } Err(err) => { @@ -1072,7 +1079,6 @@ mod private { #[instrument(skip_all)] async fn propagate_changes(&mut self) -> Result<(), EventCacheError> { let updates = self.events.store_updates().take(); - self.send_updates_to_store(updates).await } @@ -1190,8 +1196,19 @@ mod private { func: F, ) -> Result>, EventCacheError> { func(&mut self.events); + self.propagate_changes().await?; + + // If we've never waited for an initial previous-batch token, and we now have at + // least one gap in the chunk, no need to wait for a previous-batch token later. + if !self.waited_for_initial_prev_token + && self.events.chunks().any(|chunk| chunk.is_gap()) + { + self.waited_for_initial_prev_token = true; + } + let updates_as_vector_diffs = self.events.updates_as_vector_diffs(); + Ok(updates_as_vector_diffs) } } diff --git a/crates/matrix-sdk/tests/integration/event_cache.rs b/crates/matrix-sdk/tests/integration/event_cache.rs index e7eefc8d0..146992843 100644 --- a/crates/matrix-sdk/tests/integration/event_cache.rs +++ b/crates/matrix-sdk/tests/integration/event_cache.rs @@ -833,6 +833,72 @@ async fn test_limited_timeline_resets_pagination() { assert!(room_stream.is_empty()); } +#[async_test] +async fn test_persistent_storage_waits_for_pagination_token() { + let server = MatrixMockServer::new().await; + let client = server.client_builder().build().await; + + let event_cache = client.event_cache(); + + // Immediately subscribe the event cache to sync updates. + event_cache.subscribe().unwrap(); + // TODO: remove this test when persistent storage is enabled by default, as it's + // doing the same as the one above. + event_cache.enable_storage().unwrap(); + + // If I sync and get informed I've joined The Room, without a previous batch + // token, + let room_id = room_id!("!omelette:fromage.fr"); + let f = EventFactory::new().room(room_id).sender(user_id!("@a:b.c")); + let room = server.sync_joined_room(&client, room_id).await; + + let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap(); + + let (events, mut room_stream) = room_event_cache.subscribe().await; + + assert!(events.is_empty()); + assert!(room_stream.is_empty()); + + server + .mock_room_messages() + .ok(RoomMessagesResponseTemplate::default() + .events(vec![f.text_msg("hi").event_id(event_id!("$2")).into_raw_timeline()])) + .mock_once() + .mount() + .await; + + // At the beginning, the paginator is in the initial state. + let pagination = room_event_cache.pagination(); + let mut pagination_status = pagination.status(); + assert_eq!(pagination_status.get(), RoomPaginationStatus::Idle { hit_timeline_start: false }); + + // If we try to back-paginate with a token, it will hit the end of the timeline + // and give us the resulting event. + let BackPaginationOutcome { events, reached_start } = + pagination.run_backwards_once(20).await.unwrap(); + + assert_eq!(events.len(), 1); + assert!(reached_start); + + assert_let_timeout!( + Ok(RoomEventCacheUpdate::UpdateTimelineEvents { diffs, .. }) = room_stream.recv() + ); + assert_eq!(diffs.len(), 1); + assert_matches!(&diffs[0], VectorDiff::Append { values: events } => { + assert_eq!(events.len(), 1); + assert_event_matches_msg(&events[0], "hi"); + }); + + // And the paginator state delivers this as an update, and is internally + // consistent with it: + assert_next_matches_with_timeout!( + pagination_status, + RoomPaginationStatus::Idle { hit_timeline_start: true } + ); + + assert!(room_stream.is_empty()); +} + #[async_test] async fn test_limited_timeline_with_storage() { let server = MatrixMockServer::new().await;