Commit 0f2f98298976
tarball2 files changed+48-6
@@ -199,10 +199,15 @@ impl PaginatedCache for Arc<RoomEventCacheInner> {
199199
// sync for that room, because every room must have *at least* a room creation200200
// event. Otherwise, we have reached the start of the timeline.201201
202
- if state.room_linked_chunk().events().next().is_some() {203
- // If there's at least one event, this means we've reached the start of the204
- // timeline, since the chunk is fully loaded.205
- trace!("chunk is fully loaded and non-empty: reached_start=true");202
+ // a sync that drops its prev_batch (not limited, or every event a duplicate)203
+ // leaves retained events with no gap before them, so only the creation event204
+ // proves the start. otherwise paginate from the end, which dedups what we hold205
+ let created = state.room_linked_chunk().events().next().is_some_and(|(_, event)| {206
+ event.raw().get_field::<String>("type").ok().flatten().as_deref()207
+ == Some("m.room.create")208
+ });209
+ if created {210
+ trace!("chunk is fully loaded and starts at creation: reached_start=true");206211
return Ok(LoadMoreEventsBackwardsOutcome::StartOfTimeline);207212
}208213
@@ -349,6 +354,7 @@ impl PaginatedCache for Arc<RoomEventCacheInner> {
349354
return Ok(None);350355
}351356
357
+ let from_end = prev_token.is_none();352358
// Check that the previous token still exists; otherwise it's a sign that the353359
// room's timeline has been cleared.354360
let prev_gap_id = if let Some(token) = prev_token {@@ -410,8 +416,11 @@ impl PaginatedCache for Arc<RoomEventCacheInner> {
410416
// All new events are duplicated, they can all be ignored.411417
events.clear();412418
// The gap can be ditched too, as it won't be useful to backpaginate any413
- // further.414
- new_token = None;419
+ // further. Not from the end though, where a known page only means we hold the420
+ // newest events, so the token still leads somewhere new.421
+ if !from_end {422
+ new_token = None;423
+ }415424
}416425
417426
// `/messages` has been called with `dir=b` (backwards), so the events are in@@ -610,3 +610,36 @@ async fn test_recent_history_joins_on_any_retained_event() {
610610
assert_eq!(outcome.continuity, RecentHistoryContinuity::Joined);611611
assert!(cache.history_gaps().await.unwrap().is_empty());612612
}613
+614
+#[async_test]615
+async fn test_retained_events_without_a_gap_paginate_from_the_end_until_creation() {616
+ let server = MatrixMockServer::new().await;617
+ let client = server.client_builder().build().await;618
+ client.event_cache().subscribe().unwrap();619
+ let room_id = room_id!("!quiet:example.org");620
+ let room = server.sync_joined_room(&client, room_id).await;621
+ let (cache, _handles) = room.event_cache().await.unwrap();622
+ let f = event_factory(room_id);623
+ server624
+ .sync_room(625
+ &client,626
+ JoinedRoomBuilder::new(room_id)627
+ .add_timeline_event(f.text_msg("quiet").event_id(event_id!("$quiet"))),628
+ )629
+ .await;630
+ server631
+ .mock_room_messages()632
+ .ok(RoomMessagesResponseTemplate::default().end_token("older").events(vec![633
+ f.text_msg("quiet").event_id(event_id!("$quiet")),634
+ f.text_msg("days ago").event_id(event_id!("$days-ago")),635
+ ]))636
+ .mock_once()637
+ .mount()638
+ .await;639
+640
+ let outcome = cache.pagination().run_backwards_once(20).await.unwrap();641
+ assert!(!outcome.reached_start);642
+ let ids: Vec<_> =643
+ cache.events().await.unwrap().iter().filter_map(|event| event.event_id()).collect();644
+ assert_eq!(ids, [event_id!("$days-ago"), event_id!("$quiet")]);645
+}