From 0bddcfd4a442b0094445cefac9c13361fb3f3d51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 19 Sep 2024 18:10:11 +0200 Subject: [PATCH] sdk-ui: use `RoomInfo` to update the room encryption for the `Timeline` and its items --- crates/matrix-sdk-ui/src/timeline/builder.rs | 8 ++++++ .../src/timeline/controller/mod.rs | 22 +++++++++++++++ .../src/timeline/controller/state.rs | 28 +++++++++++++++++-- .../src/timeline/event_handler.rs | 9 ------ crates/matrix-sdk-ui/src/timeline/mod.rs | 2 ++ .../matrix-sdk-ui/src/timeline/tests/mod.rs | 10 +++++-- crates/matrix-sdk-ui/src/timeline/traits.rs | 9 +++++- .../tests/integration/timeline/mod.rs | 27 ++++++++++-------- 8 files changed, 88 insertions(+), 27 deletions(-) diff --git a/crates/matrix-sdk-ui/src/timeline/builder.rs b/crates/matrix-sdk-ui/src/timeline/builder.rs index de04b92c373..3553c2ecd71 100644 --- a/crates/matrix-sdk-ui/src/timeline/builder.rs +++ b/crates/matrix-sdk-ui/src/timeline/builder.rs @@ -195,6 +195,13 @@ impl TimelineBuilder { None }; + let encryption_changes_handle = spawn({ + let inner = controller.clone(); + async move { + inner.handle_encryption_state_changes().await; + } + }); + let room_update_join_handle = spawn({ let room_event_cache = room_event_cache.clone(); let inner = controller.clone(); @@ -420,6 +427,7 @@ impl TimelineBuilder { room_key_backup_enabled_join_handle, local_echo_listener_handle, _event_cache_drop_handle: event_cache_drop, + encryption_changes_handle, }), }; diff --git a/crates/matrix-sdk-ui/src/timeline/controller/mod.rs b/crates/matrix-sdk-ui/src/timeline/controller/mod.rs index c700b6a4366..99752513869 100644 --- a/crates/matrix-sdk-ui/src/timeline/controller/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/controller/mod.rs @@ -345,6 +345,28 @@ impl TimelineController

{ } } + /// Listens to encryption state changes for the room in [`matrix_sdk_base::RoomInfo`] + /// and applies the new value to the existing timeline items. + pub async fn handle_encryption_state_changes(&self) { + let mut room_info = self.room_data_provider.room_info(); + while let Some(info) = room_info.next().await { + let mut changed = false; + if let Ok(mut is_room_encrypted) = + self.state.read().await.meta.is_room_encrypted.write() + { + let is_encrypted_now = info.is_encrypted(); + if is_room_encrypted.is_none() || is_room_encrypted.unwrap() != is_encrypted_now { + changed = true; + *is_room_encrypted = Some(is_encrypted_now); + } + } + if changed { + let mut state = self.state.write().await; + state.reload_shields(); + } + } + } + pub(crate) async fn reload_pinned_events( &self, ) -> Result, PinnedEventsLoaderError> { diff --git a/crates/matrix-sdk-ui/src/timeline/controller/state.rs b/crates/matrix-sdk-ui/src/timeline/controller/state.rs index ff7a1ac7b69..7774fc2e8f3 100644 --- a/crates/matrix-sdk-ui/src/timeline/controller/state.rs +++ b/crates/matrix-sdk-ui/src/timeline/controller/state.rs @@ -300,6 +300,19 @@ impl TimelineState { result } + pub(super) fn reload_shields(&mut self) { + let Ok(is_room_encrypted) = self.meta.is_room_encrypted.read() else { + return; + }; + + if let Some(is_encrypted) = *is_room_encrypted { + drop(is_room_encrypted); + let mut txn = self.transaction(); + txn.replace_all_events_encryption(is_encrypted); + txn.commit(); + } + } + pub(super) fn transaction(&mut self) -> TimelineStateTransaction<'_> { let items = self.items.transaction(); let meta = self.meta.clone(); @@ -725,6 +738,18 @@ impl TimelineStateTransaction<'_> { fn adjust_day_dividers(&mut self, mut adjuster: DayDividerAdjuster) { adjuster.run(&mut self.items, &mut self.meta); } + + fn replace_all_events_encryption(&mut self, is_encrypted: bool) { + for idx in 0..self.items.len() { + let item = &self.items[idx]; + if let Some(event) = item.as_event() { + let mut cloned_event = event.clone(); + cloned_event.is_room_encrypted = Some(is_encrypted); + let item = item.with_kind(cloned_event); + self.items.set(idx, item); + } + } + } } #[derive(Clone)] @@ -759,9 +784,6 @@ pub(in crate::timeline) struct TimelineMetadata { /// A boolean indicating whether the room the timeline is attached to is /// actually encrypted or not. - /// TODO: this is misplaced, it should be part of the room provider as this - /// value can change over time when a room switches from non-encrypted - /// to encrypted, see also #3850. pub(crate) is_room_encrypted: Arc>>, /// Matrix room version of the timeline's room, or a sensible default. diff --git a/crates/matrix-sdk-ui/src/timeline/event_handler.rs b/crates/matrix-sdk-ui/src/timeline/event_handler.rs index 2b04e085674..7bd1ea253fc 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_handler.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_handler.rs @@ -404,9 +404,6 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> { TimelineEventKind::OtherState { state_key, content } => { // Update room encryption if a `m.room.encryption` event is found in the // timeline - if let AnyOtherFullStateEventContent::RoomEncryption(_) = &content { - self.handle_enabled_encryption(); - } if should_add { self.add_item(TimelineItemContent::OtherState(OtherState { state_key, @@ -913,12 +910,6 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> { false } - fn handle_enabled_encryption(&self) { - if let Ok(mut is_room_encrypted) = self.meta.is_room_encrypted.write() { - is_room_encrypted.replace(true); - } - } - /// Add a new event item in the timeline. fn add_item(&mut self, content: TimelineItemContent) { self.result.item_added = true; diff --git a/crates/matrix-sdk-ui/src/timeline/mod.rs b/crates/matrix-sdk-ui/src/timeline/mod.rs index 58e52de8845..8db2e657a83 100644 --- a/crates/matrix-sdk-ui/src/timeline/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/mod.rs @@ -839,6 +839,7 @@ struct TimelineDropHandle { room_key_backup_enabled_join_handle: JoinHandle<()>, local_echo_listener_handle: JoinHandle<()>, _event_cache_drop_handle: Arc, + encryption_changes_handle: JoinHandle<()>, } impl Drop for TimelineDropHandle { @@ -855,6 +856,7 @@ impl Drop for TimelineDropHandle { self.room_update_join_handle.abort(); self.room_key_from_backups_join_handle.abort(); self.room_key_backup_enabled_join_handle.abort(); + self.encryption_changes_handle.abort(); } } diff --git a/crates/matrix-sdk-ui/src/timeline/tests/mod.rs b/crates/matrix-sdk-ui/src/timeline/tests/mod.rs index a4080cbb4af..590bf74b669 100644 --- a/crates/matrix-sdk-ui/src/timeline/tests/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/tests/mod.rs @@ -20,6 +20,7 @@ use std::{ sync::Arc, }; +use eyeball::{SharedObservable, Subscriber}; use eyeball_im::VectorDiff; use futures_core::Stream; use futures_util::FutureExt as _; @@ -33,8 +34,8 @@ use matrix_sdk::{ test_utils::events::EventFactory, BoxFuture, }; -use matrix_sdk_base::latest_event::LatestEvent; -use matrix_sdk_test::{EventBuilder, ALICE, BOB}; +use matrix_sdk_base::{latest_event::LatestEvent, RoomInfo, RoomState}; +use matrix_sdk_test::{EventBuilder, ALICE, BOB, DEFAULT_TEST_ROOM_ID}; use ruma::{ event_id, events::{ @@ -444,4 +445,9 @@ impl RoomDataProvider for TestRoomDataProvider { } .boxed() } + + fn room_info(&self) -> Subscriber { + let info = RoomInfo::new(*DEFAULT_TEST_ROOM_ID, RoomState::Joined); + SharedObservable::new(info).subscribe() + } } diff --git a/crates/matrix-sdk-ui/src/timeline/traits.rs b/crates/matrix-sdk-ui/src/timeline/traits.rs index 49fa44163af..d8c3bd61dc0 100644 --- a/crates/matrix-sdk-ui/src/timeline/traits.rs +++ b/crates/matrix-sdk-ui/src/timeline/traits.rs @@ -14,6 +14,7 @@ use std::future::Future; +use eyeball::Subscriber; use futures_util::FutureExt as _; use indexmap::IndexMap; #[cfg(test)] @@ -22,7 +23,7 @@ use matrix_sdk::{ deserialized_responses::TimelineEvent, event_cache::paginator::PaginableRoom, BoxFuture, Result, Room, }; -use matrix_sdk_base::latest_event::LatestEvent; +use matrix_sdk_base::{latest_event::LatestEvent, RoomInfo}; use ruma::{ events::{ fully_read::FullyReadEventContent, @@ -107,6 +108,8 @@ pub(super) trait RoomDataProvider: reason: Option<&'a str>, transaction_id: Option, ) -> BoxFuture<'a, Result<(), super::Error>>; + + fn room_info(&self) -> Subscriber; } impl RoomDataProvider for Room { @@ -271,6 +274,10 @@ impl RoomDataProvider for Room { } .boxed() } + + fn room_info(&self) -> Subscriber { + self.subscribe_info() + } } // Internal helper to make most of retry_event_decryption independent of a room diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/mod.rs b/crates/matrix-sdk-ui/tests/integration/timeline/mod.rs index 6407feb3858..460badeb2ee 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/mod.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/mod.rs @@ -29,8 +29,11 @@ use matrix_sdk_test::{ SyncResponseBuilder, BOB, }; use matrix_sdk_ui::{ - timeline::{EventSendState, RoomExt, TimelineItemContent, VirtualTimelineItem}, - RoomListService, + timeline::{ + AnyOtherFullStateEventContent, EventSendState, RoomExt, TimelineItemContent, + VirtualTimelineItem, + }, + RoomListService, Timeline, }; use ruma::{ event_id, @@ -677,7 +680,9 @@ async fn test_timeline_without_encryption_can_update() { let room = client.get_room(room_id).unwrap(); // Previously this would have panicked. - let timeline = room.timeline().await.unwrap(); + // We're creating a timeline without read receipts tracking to check only the + // encryption changes + let timeline = Timeline::builder(&room).build().await.unwrap(); let (items, mut stream) = timeline.subscribe().await; assert_eq!(items.len(), 2); @@ -695,20 +700,18 @@ async fn test_timeline_without_encryption_can_update() { let _response = client.sync_once(sync_settings.clone()).await.unwrap(); server.reset().await; - // Previous timeline event keeps its value + // Previous timeline event now has a shield assert_next_matches!(stream, VectorDiff::Set { index, value } => { assert_eq!(index, 1); - assert!(value.as_event().unwrap().get_shield(false).is_none()); - }); - // New encryption event has shield info - assert_next_matches!(stream, VectorDiff::PushBack { value } => { assert!(value.as_event().unwrap().get_shield(false).is_some()); }); - assert_next_matches!(stream, VectorDiff::Set { index, value } => { - assert_eq!(index, 2); + // Room encryption event is received + assert_next_matches!(stream, VectorDiff::PushBack { value } => { + assert_let!(TimelineItemContent::OtherState(other_state) = value.as_event().unwrap().content()); + assert_let!(AnyOtherFullStateEventContent::RoomEncryption(_) = other_state.content()); assert!(value.as_event().unwrap().get_shield(false).is_some()); }); - // New message has shield info + // New message event is received and has a shield assert_next_matches!(stream, VectorDiff::PushBack { value } => { assert!(value.as_event().unwrap().get_shield(false).is_some()); }); @@ -741,7 +744,7 @@ impl PinningTestSetup<'_> { Self { event_id, room_id, client, server, sync_settings, sync_builder } } - async fn timeline(&self) -> matrix_sdk_ui::Timeline { + async fn timeline(&self) -> Timeline { mock_encryption_state(&self.server, false).await; let room = self.client.get_room(self.room_id).unwrap(); room.timeline().await.unwrap()