Skip to content

Commit 0dfc334

Browse files
committed
feat(timeline): handle aggregations only in non-live timelines
1 parent 73df64e commit 0dfc334

File tree

6 files changed

+233
-29
lines changed

6 files changed

+233
-29
lines changed

crates/matrix-sdk-ui/src/timeline/builder.rs

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -372,26 +372,22 @@ async fn room_event_cache_updates_task(
372372

373373
RoomEventCacheUpdate::UpdateTimelineEvents { diffs, origin } => {
374374
trace!("Received new timeline events diffs");
375-
376-
// We shouldn't use the general way of adding events to timelines to
377-
// non-live timelines, such as pinned events or focused timeline.
378-
// These timelines should handle any live updates by themselves.
379-
if !is_live {
380-
continue;
375+
let origin = match origin {
376+
EventsOrigin::Sync => RemoteEventOrigin::Sync,
377+
EventsOrigin::Pagination => RemoteEventOrigin::Pagination,
378+
EventsOrigin::Cache => RemoteEventOrigin::Cache,
379+
};
380+
381+
let has_diffs = !diffs.is_empty();
382+
383+
if is_live {
384+
timeline_controller.handle_remote_events_with_diffs(diffs, origin).await;
385+
} else {
386+
// Only handle the remote aggregation for a non-live timeline.
387+
timeline_controller.handle_remote_aggregations(diffs, origin).await;
381388
}
382389

383-
timeline_controller
384-
.handle_remote_events_with_diffs(
385-
diffs,
386-
match origin {
387-
EventsOrigin::Sync => RemoteEventOrigin::Sync,
388-
EventsOrigin::Pagination => RemoteEventOrigin::Pagination,
389-
EventsOrigin::Cache => RemoteEventOrigin::Cache,
390-
},
391-
)
392-
.await;
393-
394-
if matches!(origin, EventsOrigin::Cache) {
390+
if has_diffs && matches!(origin, RemoteEventOrigin::Cache) {
395391
timeline_controller.retry_event_decryption(None).await;
396392
}
397393
}

crates/matrix-sdk-ui/src/timeline/controller/mod.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,22 @@ impl<P: RoomDataProvider, D: Decryptor> TimelineController<P, D> {
748748
.await
749749
}
750750

751+
/// Only handle aggregations received as [`VectorDiff`]s.
752+
pub(super) async fn handle_remote_aggregations(
753+
&self,
754+
diffs: Vec<VectorDiff<TimelineEvent>>,
755+
origin: RemoteEventOrigin,
756+
) {
757+
if diffs.is_empty() {
758+
return;
759+
}
760+
761+
let mut state = self.state.write().await;
762+
state
763+
.handle_remote_aggregations(diffs, origin, &self.room_data_provider, &self.settings)
764+
.await
765+
}
766+
751767
pub(super) async fn clear(&self) {
752768
self.state.write().await.clear();
753769
}

crates/matrix-sdk-ui/src/timeline/controller/state.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,25 @@ impl TimelineState {
9191
transaction.commit();
9292
}
9393

94+
/// Handle remote aggregations on events as [`VectorDiff`]s.
95+
pub(super) async fn handle_remote_aggregations<RoomData>(
96+
&mut self,
97+
diffs: Vec<VectorDiff<TimelineEvent>>,
98+
origin: RemoteEventOrigin,
99+
room_data: &RoomData,
100+
settings: &TimelineSettings,
101+
) where
102+
RoomData: RoomDataProvider,
103+
{
104+
if diffs.is_empty() {
105+
return;
106+
}
107+
108+
let mut transaction = self.transaction();
109+
transaction.handle_remote_aggregations(diffs, origin, room_data, settings).await;
110+
transaction.commit();
111+
}
112+
94113
/// Marks the given event as fully read, using the read marker received from
95114
/// sync.
96115
pub(super) fn handle_fully_read_marker(&mut self, fully_read_event_id: OwnedEventId) {

crates/matrix-sdk-ui/src/timeline/controller/state_transaction.rs

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,159 @@ impl<'a> TimelineStateTransaction<'a> {
177177
self.check_invariants();
178178
}
179179

180+
async fn handle_remote_aggregation<RoomData>(
181+
&mut self,
182+
event: TimelineEvent,
183+
position: TimelineItemPosition,
184+
room_data_provider: &RoomData,
185+
date_divider_adjuster: &mut DateDividerAdjuster,
186+
) where
187+
RoomData: RoomDataProvider,
188+
{
189+
let deserialized = match event.raw().deserialize() {
190+
Ok(deserialized) => deserialized,
191+
Err(err) => {
192+
warn!("Failed to deserialize timeline event: {err}");
193+
return;
194+
}
195+
};
196+
197+
let sender = deserialized.sender().to_owned();
198+
let timestamp = deserialized.origin_server_ts();
199+
let event_id = deserialized.event_id().to_owned();
200+
let txn_id = deserialized.transaction_id().map(ToOwned::to_owned);
201+
202+
if let Some(action @ TimelineAction::HandleAggregation { .. }) = TimelineAction::from_event(
203+
deserialized,
204+
&event.raw(),
205+
room_data_provider,
206+
None,
207+
&self.items,
208+
&mut self.meta,
209+
)
210+
.await
211+
{
212+
let encryption_info = event.kind.encryption_info().cloned();
213+
214+
let sender_profile = room_data_provider.profile_from_user_id(&sender).await;
215+
216+
let ctx = TimelineEventContext {
217+
sender,
218+
sender_profile,
219+
timestamp,
220+
// These are not used when handling an aggregation.
221+
read_receipts: Default::default(),
222+
is_highlighted: false,
223+
flow: Flow::Remote {
224+
event_id: event_id.clone(),
225+
raw_event: event.raw().clone(),
226+
encryption_info,
227+
txn_id,
228+
position,
229+
},
230+
// This field is not used when handling an aggregation.
231+
should_add_new_items: false,
232+
};
233+
234+
TimelineEventHandler::new(self, ctx).handle_event(date_divider_adjuster, action).await;
235+
}
236+
}
237+
238+
/// Handle a set of live remote aggregations on events as [`VectorDiff`]s.
239+
///
240+
/// This is like `handle_remote_events`, with two key differences:
241+
/// - it only applies to aggregated events, not all the sync events.
242+
/// - it will also not add the events to the `all_remote_events` array
243+
/// itself.
244+
pub(super) async fn handle_remote_aggregations<RoomData>(
245+
&mut self,
246+
diffs: Vec<VectorDiff<TimelineEvent>>,
247+
origin: RemoteEventOrigin,
248+
room_data_provider: &RoomData,
249+
settings: &TimelineSettings,
250+
) where
251+
RoomData: RoomDataProvider,
252+
{
253+
let mut date_divider_adjuster =
254+
DateDividerAdjuster::new(settings.date_divider_mode.clone());
255+
256+
for diff in diffs {
257+
match diff {
258+
VectorDiff::Append { values: events } => {
259+
for event in events {
260+
self.handle_remote_aggregation(
261+
event,
262+
TimelineItemPosition::End { origin },
263+
room_data_provider,
264+
&mut date_divider_adjuster,
265+
)
266+
.await;
267+
}
268+
}
269+
270+
VectorDiff::PushFront { value: event } => {
271+
self.handle_remote_aggregation(
272+
event,
273+
TimelineItemPosition::Start { origin },
274+
room_data_provider,
275+
&mut date_divider_adjuster,
276+
)
277+
.await;
278+
}
279+
280+
VectorDiff::PushBack { value: event } => {
281+
self.handle_remote_aggregation(
282+
event,
283+
TimelineItemPosition::End { origin },
284+
room_data_provider,
285+
&mut date_divider_adjuster,
286+
)
287+
.await;
288+
}
289+
290+
VectorDiff::Insert { index: event_index, value: event } => {
291+
self.handle_remote_aggregation(
292+
event,
293+
TimelineItemPosition::At { event_index, origin },
294+
room_data_provider,
295+
&mut date_divider_adjuster,
296+
)
297+
.await;
298+
}
299+
300+
VectorDiff::Set { index: event_index, value: event } => {
301+
if let Some(timeline_item_index) = self
302+
.items
303+
.all_remote_events()
304+
.get(event_index)
305+
.and_then(|meta| meta.timeline_item_index)
306+
{
307+
self.handle_remote_aggregation(
308+
event,
309+
TimelineItemPosition::UpdateAt { timeline_item_index },
310+
room_data_provider,
311+
&mut date_divider_adjuster,
312+
)
313+
.await;
314+
} else {
315+
warn!(event_index, "Set update dropped because there wasn't any attached timeline item index.");
316+
}
317+
}
318+
319+
VectorDiff::Remove { .. } | VectorDiff::Clear => {
320+
// Do nothing. An aggregated redaction comes with a
321+
// redaction event, or as a redacted event in the first
322+
// place.
323+
}
324+
325+
v => unimplemented!("{v:?}"),
326+
}
327+
}
328+
329+
self.adjust_date_dividers(date_divider_adjuster);
330+
self.check_invariants();
331+
}
332+
180333
fn check_invariants(&self) {
181334
self.check_no_duplicate_read_receipts();
182335
self.check_no_unused_unique_ids();

crates/matrix-sdk-ui/tests/integration/timeline/focus_event.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ async fn test_new_focused() {
185185
}
186186

187187
#[async_test]
188-
async fn test_focused_timeline_does_not_react() {
188+
async fn test_live_aggregations_are_reflected_on_focused_timelines() {
189189
let room_id = room_id!("!a98sd12bjh:example.org");
190190
let (client, server) = logged_in_client_with_server().await;
191191
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
@@ -254,8 +254,16 @@ async fn test_focused_timeline_does_not_react() {
254254
let _response = client.sync_once(sync_settings.clone()).await.unwrap();
255255
server.reset().await;
256256

257-
// Nothing was received by the focused event timeline
258-
assert_pending!(timeline_stream);
257+
// We only receive one updated for the reaction, from the timeline stream.
258+
assert_let!(Some(timeline_updates) = timeline_stream.next().await);
259+
assert_eq!(timeline_updates.len(), 1);
260+
assert_let!(VectorDiff::Set { index: 1, value: item } = &timeline_updates[0]);
261+
262+
let event_item = item.as_event().unwrap();
263+
assert_eq!(event_item.content().as_message().unwrap().body(), "yolo");
264+
let reactions = event_item.content().reactions().cloned().unwrap_or_default();
265+
assert_eq!(reactions.len(), 1);
266+
let _ = reactions["👍"][*BOB];
259267
}
260268

261269
#[async_test]

crates/matrix-sdk-ui/tests/integration/timeline/pinned_event.rs

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ async fn test_pinned_timeline_with_pinned_utd_on_sync_contains_it() {
458458
}
459459

460460
#[async_test]
461-
async fn test_edited_events_are_not_reflected_in_sync() {
461+
async fn test_edited_events_are_reflected_in_sync() {
462462
let server = MatrixMockServer::new().await;
463463
let client = server.client_builder().build().await;
464464
let room_id = room_id!("!test:localhost");
@@ -497,10 +497,10 @@ async fn test_edited_events_are_not_reflected_in_sync() {
497497
assert_pending!(timeline_stream);
498498

499499
let edited_event = f
500-
.text_msg("edited message!")
500+
.text_msg("* edited message!")
501501
.edit(
502502
event_id!("$1"),
503-
RoomMessageEventContentWithoutRelation::text_plain("* edited message!"),
503+
RoomMessageEventContentWithoutRelation::text_plain("edited message!"),
504504
)
505505
.event_id(event_id!("$2"))
506506
.server_ts(MilliSecondsSinceUnixEpoch::now())
@@ -517,7 +517,7 @@ async fn test_edited_events_are_not_reflected_in_sync() {
517517
.expect("Sync failed");
518518

519519
assert_let!(Some(timeline_updates) = timeline_stream.next().await);
520-
assert_eq!(timeline_updates.len(), 3);
520+
assert_eq!(timeline_updates.len(), 4);
521521

522522
// The list is reloaded, so it's reset
523523
assert_let!(VectorDiff::Clear = &timeline_updates[0]);
@@ -526,16 +526,23 @@ async fn test_edited_events_are_not_reflected_in_sync() {
526526
assert_let!(VectorDiff::PushBack { value } = &timeline_updates[1]);
527527
let event = value.as_event().unwrap();
528528
assert_eq!(event.event_id().unwrap(), event_id!("$1"));
529+
assert_eq!(event.content().as_message().unwrap().body(), "in the end");
529530

530531
assert_let!(VectorDiff::PushFront { value } = &timeline_updates[2]);
531532
assert!(value.is_date_divider());
532533

533-
// The edit does not replace the original event
534+
// The edit does replace the original event.
535+
assert_let!(VectorDiff::Set { index: 1, value } = &timeline_updates[3]);
536+
let event = value.as_event().unwrap();
537+
assert_eq!(event.event_id().unwrap(), event_id!("$1"));
538+
assert_eq!(event.content().as_message().unwrap().body(), "edited message!");
539+
540+
// That's all, folks!
534541
assert_pending!(timeline_stream);
535542
}
536543

537544
#[async_test]
538-
async fn test_redacted_events_are_not_reflected_in_sync() {
545+
async fn test_redacted_events_are_reflected_in_sync() {
539546
let server = MatrixMockServer::new().await;
540547
let client = server.client_builder().build().await;
541548
let room_id = room_id!("!test:localhost");
@@ -590,7 +597,7 @@ async fn test_redacted_events_are_not_reflected_in_sync() {
590597
.expect("Sync failed");
591598

592599
assert_let!(Some(timeline_updates) = timeline_stream.next().await);
593-
assert_eq!(timeline_updates.len(), 3);
600+
assert_eq!(timeline_updates.len(), 4);
594601

595602
// The list is reloaded, so it's reset
596603
assert_let!(VectorDiff::Clear = &timeline_updates[0]);
@@ -603,7 +610,12 @@ async fn test_redacted_events_are_not_reflected_in_sync() {
603610
assert_let!(VectorDiff::PushFront { value } = &timeline_updates[2]);
604611
assert!(value.is_date_divider());
605612

606-
// The redaction does not replace the original event
613+
// The redaction takes place.
614+
assert_let!(VectorDiff::Set { index: 1, value } = &timeline_updates[3]);
615+
let event = value.as_event().unwrap();
616+
assert!(event.content().is_redacted());
617+
618+
// That's all, folks!
607619
assert_pending!(timeline_stream);
608620
}
609621

0 commit comments

Comments
 (0)