From d9864373f39fbea07a77fc2d5381a98d9af3933c Mon Sep 17 00:00:00 2001 From: Benjamin Bouvier Date: Mon, 22 Apr 2024 18:24:47 +0200 Subject: [PATCH] timeline: add the ability to set a prefix for internal IDs --- bindings/matrix-sdk-ffi/src/room_list.rs | 8 +++ crates/matrix-sdk-ui/src/timeline/builder.rs | 18 ++++++- .../src/timeline/day_dividers.rs | 4 +- .../matrix-sdk-ui/src/timeline/inner/mod.rs | 8 ++- .../matrix-sdk-ui/src/timeline/inner/state.rs | 16 +++++- .../matrix-sdk-ui/src/timeline/tests/basic.rs | 52 +++++++++++++------ .../matrix-sdk-ui/src/timeline/tests/mod.rs | 11 +++- 7 files changed, 92 insertions(+), 25 deletions(-) diff --git a/bindings/matrix-sdk-ffi/src/room_list.rs b/bindings/matrix-sdk-ffi/src/room_list.rs index 362bb55267c..9f231e83269 100644 --- a/bindings/matrix-sdk-ffi/src/room_list.rs +++ b/bindings/matrix-sdk-ffi/src/room_list.rs @@ -537,9 +537,13 @@ impl RoomListItem { /// * `event_type_filter` - An optional [`TimelineEventTypeFilter`] to be /// used to filter timeline events besides the default timeline filter. If /// `None` is passed, only the default timeline filter will be used. + /// * `internal_id_prefix` - An optional String that will be prepended to + /// all the timeline item's internal IDs, making it possible to + /// distinguish different timeline instances from each other. async fn init_timeline( &self, event_type_filter: Option>, + internal_id_prefix: Option, ) -> Result<(), RoomListError> { let mut timeline_builder = self .inner @@ -554,6 +558,10 @@ impl RoomListItem { }); } + if let Some(internal_id_prefix) = internal_id_prefix { + timeline_builder = timeline_builder.with_internal_id_prefix(internal_id_prefix); + } + if let Some(utd_hook) = self.utd_hook.clone() { timeline_builder = timeline_builder.with_unable_to_decrypt_hook(utd_hook); } diff --git a/crates/matrix-sdk-ui/src/timeline/builder.rs b/crates/matrix-sdk-ui/src/timeline/builder.rs index 657d01537a6..acd1ebe9052 100644 --- a/crates/matrix-sdk-ui/src/timeline/builder.rs +++ b/crates/matrix-sdk-ui/src/timeline/builder.rs @@ -45,6 +45,9 @@ pub struct TimelineBuilder { /// An optional hook to call whenever we run into an unable-to-decrypt or a /// late-decryption event. unable_to_decrypt_hook: Option>, + + /// An optional prefix for internal IDs. + internal_id_prefix: Option, } impl TimelineBuilder { @@ -53,6 +56,7 @@ impl TimelineBuilder { room: room.clone(), settings: TimelineInnerSettings::default(), unable_to_decrypt_hook: None, + internal_id_prefix: None, } } @@ -65,6 +69,15 @@ impl TimelineBuilder { self } + /// Sets the internal id prefix for this timeline. + /// + /// The prefix will be prepended to any internal ID using when generating + /// timeline IDs for this timeline. + pub fn with_internal_id_prefix(mut self, prefix: String) -> Self { + self.internal_id_prefix = Some(prefix); + self + } + /// Enable tracking of the fully-read marker and the read receipts on the /// timeline. pub fn track_read_marker_and_receipts(mut self) -> Self { @@ -122,7 +135,7 @@ impl TimelineBuilder { ) )] pub async fn build(self) -> event_cache::Result { - let Self { room, settings, unable_to_decrypt_hook } = self; + let Self { room, settings, unable_to_decrypt_hook, internal_id_prefix } = self; let client = room.client(); let event_cache = client.event_cache(); @@ -135,7 +148,8 @@ impl TimelineBuilder { let has_events = !events.is_empty(); - let inner = TimelineInner::new(room, unable_to_decrypt_hook).with_settings(settings); + let inner = TimelineInner::new(room, internal_id_prefix, unable_to_decrypt_hook) + .with_settings(settings); inner.replace_with_initial_events(events).await; diff --git a/crates/matrix-sdk-ui/src/timeline/day_dividers.rs b/crates/matrix-sdk-ui/src/timeline/day_dividers.rs index 0c422ae9880..8eedf2103c4 100644 --- a/crates/matrix-sdk-ui/src/timeline/day_dividers.rs +++ b/crates/matrix-sdk-ui/src/timeline/day_dividers.rs @@ -573,7 +573,7 @@ mod tests { let mut items = ObservableVector::new(); let mut txn = items.transaction(); - let mut meta = TimelineInnerMetadata::new(ruma::RoomVersionId::V11, None); + let mut meta = TimelineInnerMetadata::new(ruma::RoomVersionId::V11, None, None); let timestamp = MilliSecondsSinceUnixEpoch(uint!(42)); let timestamp_next_day = @@ -607,7 +607,7 @@ mod tests { let mut items = ObservableVector::new(); let mut txn = items.transaction(); - let mut meta = TimelineInnerMetadata::new(ruma::RoomVersionId::V11, None); + let mut meta = TimelineInnerMetadata::new(ruma::RoomVersionId::V11, None, None); let timestamp = MilliSecondsSinceUnixEpoch(uint!(42)); let timestamp_next_day = diff --git a/crates/matrix-sdk-ui/src/timeline/inner/mod.rs b/crates/matrix-sdk-ui/src/timeline/inner/mod.rs index d78e38d84f0..2c47457d9a8 100644 --- a/crates/matrix-sdk-ui/src/timeline/inner/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/inner/mod.rs @@ -213,10 +213,14 @@ pub fn default_event_filter(event: &AnySyncTimelineEvent, room_version: &RoomVer impl TimelineInner

{ pub(super) fn new( room_data_provider: P, + internal_id_prefix: Option, unable_to_decrypt_hook: Option>, ) -> Self { - let state = - TimelineInnerState::new(room_data_provider.room_version(), unable_to_decrypt_hook); + let state = TimelineInnerState::new( + room_data_provider.room_version(), + internal_id_prefix, + unable_to_decrypt_hook, + ); Self { state: Arc::new(RwLock::new(state)), room_data_provider, diff --git a/crates/matrix-sdk-ui/src/timeline/inner/state.rs b/crates/matrix-sdk-ui/src/timeline/inner/state.rs index 5618981a0fc..c9a73457183 100644 --- a/crates/matrix-sdk-ui/src/timeline/inner/state.rs +++ b/crates/matrix-sdk-ui/src/timeline/inner/state.rs @@ -74,6 +74,7 @@ pub(in crate::timeline) struct TimelineInnerState { impl TimelineInnerState { pub(super) fn new( room_version: RoomVersionId, + internal_id_prefix: Option, unable_to_decrypt_hook: Option>, ) -> Self { Self { @@ -81,7 +82,11 @@ impl TimelineInnerState { // sliding-sync tests with 20 events lag. This should still be // small enough. items: ObservableVector::with_capacity(32), - meta: TimelineInnerMetadata::new(room_version, unable_to_decrypt_hook), + meta: TimelineInnerMetadata::new( + room_version, + internal_id_prefix, + unable_to_decrypt_hook, + ), } } @@ -678,6 +683,10 @@ pub(in crate::timeline) struct TimelineInnerMetadata { /// remote echoes. next_internal_id: u64, + /// An optional prefix for internal IDs, defined during construction of the + /// timeline. + internal_id_prefix: Option, + pub reactions: Reactions, pub poll_pending_events: PollPendingEvents, pub fully_read_event: Option, @@ -707,6 +716,7 @@ pub(in crate::timeline) struct TimelineInnerMetadata { impl TimelineInnerMetadata { pub(crate) fn new( room_version: RoomVersionId, + internal_id_prefix: Option, unable_to_decrypt_hook: Option>, ) -> Self { Self { @@ -723,6 +733,7 @@ impl TimelineInnerMetadata { in_flight_reaction: Default::default(), room_version, unable_to_decrypt_hook, + internal_id_prefix, } } @@ -760,7 +771,8 @@ impl TimelineInnerMetadata { pub fn next_internal_id(&mut self) -> String { let val = self.next_internal_id; self.next_internal_id += 1; - format!("{val}") + let prefix = self.internal_id_prefix.as_deref().unwrap_or(""); + format!("{prefix}{val}") } /// Returns a new timeline item with a fresh internal id. diff --git a/crates/matrix-sdk-ui/src/timeline/tests/basic.rs b/crates/matrix-sdk-ui/src/timeline/tests/basic.rs index 9afeb3ef620..ff101ffab85 100644 --- a/crates/matrix-sdk-ui/src/timeline/tests/basic.rs +++ b/crates/matrix-sdk-ui/src/timeline/tests/basic.rs @@ -268,21 +268,10 @@ async fn test_dedup_pagination() { async fn test_dedup_initial() { let timeline = TestTimeline::new(); - let event_a = SyncTimelineEvent::new( - timeline - .event_builder - .make_sync_message_event(*ALICE, RoomMessageEventContent::text_plain("A")), - ); - let event_b = SyncTimelineEvent::new( - timeline - .event_builder - .make_sync_message_event(*BOB, RoomMessageEventContent::text_plain("B")), - ); - let event_c = SyncTimelineEvent::new( - timeline - .event_builder - .make_sync_message_event(*CAROL, RoomMessageEventContent::text_plain("C")), - ); + let factory = EventFactory::new(); + let event_a = factory.text_msg("A").sender(*ALICE).into_sync(); + let event_b = factory.text_msg("B").sender(*BOB).into_sync(); + let event_c = factory.text_msg("C").sender(*CAROL).into_sync(); timeline .inner @@ -322,6 +311,39 @@ async fn test_dedup_initial() { assert_eq!(timeline_items[0].unique_id(), "3"); } +#[async_test] +async fn test_internal_id_prefix() { + let timeline = TestTimeline::with_internal_id_prefix("le_prefix_".to_owned()); + + let factory = EventFactory::new(); + let ev_a = factory.text_msg("A").sender(*ALICE).into_sync(); + let ev_b = factory.text_msg("B").sender(*BOB).into_sync(); + let ev_c = factory.text_msg("C").sender(*CAROL).into_sync(); + + timeline + .inner + .add_events_at(vec![ev_a, ev_b, ev_c], TimelineEnd::Back { from_cache: false }) + .await; + + let timeline_items = timeline.inner.items().await; + assert_eq!(timeline_items.len(), 4); + + assert!(timeline_items[0].is_day_divider()); + assert_eq!(timeline_items[0].unique_id(), "le_prefix_3"); + + let event1 = &timeline_items[1]; + assert_eq!(event1.as_event().unwrap().sender(), *ALICE); + assert_eq!(event1.unique_id(), "le_prefix_0"); + + let event2 = &timeline_items[2]; + assert_eq!(event2.as_event().unwrap().sender(), *BOB); + assert_eq!(event2.unique_id(), "le_prefix_1"); + + let event3 = &timeline_items[3]; + assert_eq!(event3.as_event().unwrap().sender(), *CAROL); + assert_eq!(event3.unique_id(), "le_prefix_2"); +} + #[async_test] async fn test_sanitized() { let timeline = TestTimeline::new(); diff --git a/crates/matrix-sdk-ui/src/timeline/tests/mod.rs b/crates/matrix-sdk-ui/src/timeline/tests/mod.rs index 7fa89fe73fe..1fc79592a9c 100644 --- a/crates/matrix-sdk-ui/src/timeline/tests/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/tests/mod.rs @@ -79,16 +79,23 @@ impl TestTimeline { Self::with_room_data_provider(TestRoomDataProvider::default()) } + fn with_internal_id_prefix(prefix: String) -> Self { + Self { + inner: TimelineInner::new(TestRoomDataProvider::default(), Some(prefix), None), + event_builder: EventBuilder::new(), + } + } + fn with_room_data_provider(room_data_provider: TestRoomDataProvider) -> Self { Self { - inner: TimelineInner::new(room_data_provider, None), + inner: TimelineInner::new(room_data_provider, None, None), event_builder: EventBuilder::new(), } } fn with_unable_to_decrypt_hook(hook: Arc) -> Self { Self { - inner: TimelineInner::new(TestRoomDataProvider::default(), Some(hook)), + inner: TimelineInner::new(TestRoomDataProvider::default(), None, Some(hook)), event_builder: EventBuilder::new(), } }