Commit 97fb5720d881
tarball3 files changed+149-7
@@ -144,7 +144,8 @@ corrected timestamp. the latest alone can't date a count. a summary from the
144144
event cache names its latest reply without the event, and continuwuity 26.9145145
bundles the latest as bare content, so with only that check every reply the146146
cache re-inserted read as new and a page's count doubled. a newer reply that147
-doesn't count still becomes the latest. replays, reloads from the store and147
+doesn't count still becomes the latest, and an entry built without a dated148
+latest takes the newest reply the event cache holds. replays, reloads from the store and148149
back pagination never count. a reply sync brings to a thread the list hasn't149150
got fetches its root, four at a time, and adds it. one from anywhere else150151
doesn't, since that@@ -12,7 +12,9 @@ use matrix_sdk::{
1212
serde_helpers::extract_thread_root,1313
};1414
use matrix_sdk_ui::eyeball_im::VectorDiff;15
-use ruma::{OwnedEventId, OwnedRoomId, OwnedUserId, UInt, push::Action};15
+use ruma::{16
+ OwnedEventId, OwnedRoomId, OwnedUserId, UInt, events::relation::RelationType, push::Action,17
+};1618
use tokio::sync::{broadcast::error::RecvError, mpsc};1719
1820
use super::{@@ -801,11 +803,16 @@ async fn build_entry(
801803
Some(latest) => normalise(room, &latest, current_user_id, media_prefix).await,802804
None => None,803805
};806
+ let latest = match latest {807
+ Some(latest) => Some(latest),808
+ None => cached_latest(room, &root.event_id, current_user_id, media_prefix).await,809
+ };804810
let summary = ThreadSummary {805811
reply_count: summary.as_ref().map_or(0, |summary| summary.num_replies),806
- latest_event_id: summary807
- .and_then(|summary| summary.latest_reply)808
- .or_else(|| latest.as_ref().map(|latest| latest.event_id.clone())),812
+ latest_event_id: latest813
+ .as_ref()814
+ .map(|latest| latest.event_id.clone())815
+ .or_else(|| summary.and_then(|summary| summary.latest_reply)),809816
latest_event_timestamp_ms: latest.as_ref().map(|latest| latest.timestamp_ms),810817
};811818
let mut media = root.media;@@ -829,6 +836,29 @@ async fn build_entry(
829836
})830837
}831838
839
+/// the newest reply the event cache holds, for a summary with no usable latest event.840
+/// continuwuity 26.9 bundles only the latest's content, which dates nothing841
+async fn cached_latest(842
+ room: &Room,843
+ root_event_id: &OwnedEventId,844
+ current_user_id: Option<&ruma::UserId>,845
+ media_prefix: &str,846
+) -> Option<Normalised> {847
+ let (cache, _drop_handles) = room.event_cache().await.ok()?;848
+ let replies = cache849
+ .find_event_relations(root_event_id, Some(vec![RelationType::Thread]))850
+ .await851
+ .inspect_err(852
+ |error| tracing::debug!(%root_event_id, %error, "reading a thread's replies failed"),853
+ )854
+ .ok()?;855
+ let newest = replies856
+ .iter()857
+ .filter(|reply| extract_thread_root(reply.raw()).as_ref() == Some(root_event_id))858
+ .max_by_key(|reply| reply.timestamp())?;859
+ normalise(room, newest, current_user_id, media_prefix).await860
+}861
+832862
fn now_ms() -> u64 {833863
u64::from(ruma::MilliSecondsSinceUnixEpoch::now().get())834864
}@@ -4,12 +4,12 @@ use std::time::Duration;
44
55
use macltok::{66
ConnectionState, Engine, EngineConfig, EngineError, Event, PackUsage, Retain, RoomMembership,7
- Termination, ThreadUnread,7
+ Termination, ThreadListEntry, ThreadUnread,88
};99
use matrix_sdk::{1010
ThreadingSupport,1111
ruma::{12
- MilliSecondsSinceUnixEpoch, RoomId,12
+ EventId, MilliSecondsSinceUnixEpoch, RoomId,1313
api::client::sync::sync_events::v5,1414
event_id,1515
events::{@@ -493,3 +493,114 @@ async fn a_thread_list_page_keeps_the_servers_count() {
493493
494494
support::shut_down(&handle, &mut events, running).await;495495
}496
+497
+/// with continuwuity's undated latest, the page has to date each thread from the replies the498
+/// cache already holds, or it sorts by the root499
+#[tokio::test]500
+async fn a_thread_list_page_dates_threads_from_cached_replies() {501
+ let server = MatrixMockServer::new().await;502
+ server503
+ .mock_sliding_sync()504
+ .ok(v5::Response::new("1".to_owned()))505
+ .mount()506
+ .await;507
+ let client = server508
+ .client_builder()509
+ .on_builder(|builder| {510
+ builder.with_threading_support(ThreadingSupport::Enabled {511
+ with_subscriptions: false,512
+ })513
+ })514
+ .build()515
+ .await;516
+ let room_id = room_id!("!thread-dates:localhost");517
+ let own = client.user_id().unwrap().to_owned();518
+ server.mock_room_state_encryption().plain().mount().await;519
+ client.event_cache().subscribe().unwrap();520
+ let factory = EventFactory::new().room(room_id).sender(&ALICE);521
+ let (early, first, second, late) = (522
+ event_id!("$dates-early"),523
+ event_id!("$dates-first"),524
+ event_id!("$dates-second"),525
+ event_id!("$dates-late"),526
+ );527
+ server528
+ .sync_room(529
+ &client,530
+ JoinedRoomBuilder::new(room_id)531
+ .add_state_event(EventFactory::new().room(room_id).member(&own))532
+ .add_timeline_event(factory.text_msg("early").event_id(early).server_ts(1_000))533
+ .add_timeline_event(534
+ factory535
+ .text_msg("one")536
+ .in_thread(early, early)537
+ .event_id(first)538
+ .server_ts(5_000),539
+ )540
+ .add_timeline_event(factory.text_msg("late").event_id(late).server_ts(7_000))541
+ .add_timeline_event(542
+ factory543
+ .text_msg("two")544
+ .in_thread(early, first)545
+ .event_id(second)546
+ .server_ts(9_000),547
+ ),548
+ )549
+ .await;550
+ let listed = |root: &EventId, timestamp: u64, count: u64| -> Raw<AnyTimelineEvent> {551
+ serde_json::from_value(json!({552
+ "type": "m.room.message",553
+ "event_id": root,554
+ "room_id": room_id,555
+ "sender": &*ALICE,556
+ "origin_server_ts": timestamp,557
+ "content": { "msgtype": "m.text", "body": "root" },558
+ "unsigned": { "m.relations": { "m.thread": {559
+ "latest_event": { "msgtype": "m.text", "body": "latest" },560
+ "count": count,561
+ "current_user_participated": false,562
+ } } },563
+ }))564
+ .unwrap()565
+ };566
+ server567
+ .mock_room_threads()568
+ .ok(vec![listed(late, 7_000, 1), listed(early, 1_000, 2)], None)569
+ .mount()570
+ .await;571
+ let config = EngineConfig::default().with_initial_history_limit(0);572
+ let (engine, handle, mut events) = Engine::from_client(client.clone(), config).await.unwrap();573
+ let running = tokio::spawn(engine.run());574
+575
+ handle.open_thread_list(room_id.to_owned()).await.unwrap();576
+ handle577
+ .paginate_thread_list(room_id.to_owned(), 20)578
+ .await579
+ .unwrap();580
+ let changed = support::receive_until(&mut events, |event| {581
+ matches!(event, Event::ThreadListChanged { .. })582
+ })583
+ .await;584
+ let Event::ThreadListChanged { upserts, .. } = changed else {585
+ unreachable!()586
+ };587
+ let mut entries = macltok::imbl::Vector::new();588
+ for entry in upserts {589
+ ThreadListEntry::upsert(&mut entries, entry);590
+ }591
+ let order: Vec<_> = entries592
+ .iter()593
+ .map(|entry| (entry.root_event_id.as_str(), entry.latest_activity_ms))594
+ .collect();595
+ assert_eq!(596
+ order,597
+ [(early.as_str(), 9_000), (late.as_str(), 7_000)],598
+ "a thread was dated by its root rather than its newest cached reply"599
+ );600
+ let dated = &entries[0];601
+ assert_eq!(dated.summary.latest_event_id.as_deref(), Some(second));602
+ assert_eq!(dated.summary.latest_event_timestamp_ms, Some(9_000));603
+ assert_eq!(dated.summary.reply_count, 2);604
+605
+ support::shut_down(&handle, &mut events, running).await;606
+}