From b08d07fca01fdff65c09e0af2dd8e448c829f0ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 18 Jul 2024 12:20:55 +0200 Subject: [PATCH] sdk-base: add `Room::pinned_events(&self)` to subscribe to pinned event ids in a room. --- Cargo.lock | 4 +- crates/matrix-sdk-base/src/client.rs | 16 +++++ crates/matrix-sdk-base/src/rooms/normal.rs | 10 ++++ .../matrix-sdk-base/src/sliding_sync/mod.rs | 58 ++++++++++++++++++- crates/matrix-sdk-base/src/store/mod.rs | 3 + crates/matrix-sdk-sqlite/src/state_store.rs | 1 + 6 files changed, 89 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8d68133bc7f..b115e7ee9ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3482,7 +3482,7 @@ dependencies = [ [[package]] name = "matrix-sdk-qrcode" -version = "0.7.0" +version = "0.7.1" dependencies = [ "byteorder", "image", @@ -3494,7 +3494,7 @@ dependencies = [ [[package]] name = "matrix-sdk-sqlite" -version = "0.7.0" +version = "0.7.1" dependencies = [ "assert_matches", "async-trait", diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index 51f864a47df..5c89ddbf707 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -45,6 +45,7 @@ use ruma::{ push_rules::{PushRulesEvent, PushRulesEventContent}, room::{ member::{MembershipState, RoomMemberEventContent, SyncRoomMemberEvent}, + pinned_events::SyncRoomPinnedEventsEvent, power_levels::{ RoomPowerLevelsEvent, RoomPowerLevelsEventContent, StrippedRoomPowerLevelsEvent, }, @@ -549,6 +550,12 @@ impl BaseClient { } handle_room_member_event_for_profiles(&room_info.room_id, member, changes); + } else if let AnySyncStateEvent::RoomPinnedEvents( + SyncRoomPinnedEventsEvent::Original(ev), + ) = &event + { + let new_pinned_event_ids = ev.content.pinned.clone(); + changes.pinned_events.insert(room_info.room_id.to_owned(), new_pinned_event_ids); } state_events @@ -1173,6 +1180,15 @@ impl BaseClient { } } + for (room_id, pinned_event_ids) in changes.pinned_events.clone() { + if let Some(room) = self.store.room(&room_id) { + room.pinned_event_ids.update(|ids| { + ids.clear(); + ids.append(&mut pinned_event_ids.clone()); + }); + } + } + for (room_id, room_info) in &changes.room_infos { if let Some(room) = self.store.room(room_id) { let room_info_notable_update_reasons = diff --git a/crates/matrix-sdk-base/src/rooms/normal.rs b/crates/matrix-sdk-base/src/rooms/normal.rs index 777be9ad83b..61141dfe662 100644 --- a/crates/matrix-sdk-base/src/rooms/normal.rs +++ b/crates/matrix-sdk-base/src/rooms/normal.rs @@ -132,6 +132,10 @@ pub struct Room { /// to disk but held in memory. #[cfg(all(feature = "e2e-encryption", feature = "experimental-sliding-sync"))] pub latest_encrypted_events: Arc>>>, + + /// The list of pinned event ids in this room in an observable format so + /// they can be subscribed to. + pub pinned_event_ids: SharedObservable>, } /// The room summary containing member counts and members that should be used to @@ -240,6 +244,7 @@ impl Room { Self::MAX_ENCRYPTED_EVENTS, ))), room_info_notable_update_sender, + pinned_event_ids: SharedObservable::new(Vec::new()), } } @@ -947,6 +952,11 @@ impl Room { pub fn recency_stamp(&self) -> Option { self.inner.read().recency_stamp } + + /// Subscribe to the list of event ids for pinned events in this room. + pub fn pinned_events(&self) -> Subscriber> { + self.pinned_event_ids.subscribe() + } } /// The underlying pure data structure for joined and left rooms. diff --git a/crates/matrix-sdk-base/src/sliding_sync/mod.rs b/crates/matrix-sdk-base/src/sliding_sync/mod.rs index 5e327700474..7d8e4941c0a 100644 --- a/crates/matrix-sdk-base/src/sliding_sync/mod.rs +++ b/crates/matrix-sdk-base/src/sliding_sync/mod.rs @@ -844,6 +844,7 @@ mod tests { use std::{ collections::{BTreeMap, HashSet}, sync::{Arc, RwLock as SyncRwLock}, + time::Duration, }; use assert_matches::assert_matches; @@ -860,15 +861,17 @@ mod tests { member::{MembershipState, RoomMemberEventContent}, message::SyncRoomMessageEvent, name::RoomNameEventContent, + pinned_events::RoomPinnedEventsEventContent, }, AnySyncMessageLikeEvent, AnySyncTimelineEvent, GlobalAccountDataEventContent, StateEventContent, }, - mxc_uri, owned_mxc_uri, owned_user_id, room_alias_id, room_id, + mxc_uri, owned_event_id, owned_mxc_uri, owned_user_id, room_alias_id, room_id, serde::Raw, uint, user_id, JsOption, MxcUri, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, UserId, }; use serde_json::json; + use tokio::time::timeout; use super::{cache_latest_events, http}; use crate::{ @@ -2178,6 +2181,59 @@ mod tests { ); } + #[async_test] + async fn test_pinned_events_are_updated_on_sync() { + let user_a_id = user_id!("@a:e.uk"); + let client = logged_in_base_client(Some(user_a_id)).await; + let room_id = room_id!("!r:e.uk"); + let pinned_event_id = owned_event_id!("$an-id:e.uk"); + + // Create room + let mut room_response = http::response::Room::new(); + set_room_joined(&mut room_response, user_a_id); + let response = response_with_room(room_id, room_response); + client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync"); + + // The newly created room has no pinned event ids + let room = client.get_room(room_id).unwrap(); + let mut pinned_events_subscriber = room.pinned_events(); + assert!(pinned_events_subscriber.get().is_empty()); + + // Load new pinned event id + let mut room_response = http::response::Room::new(); + room_response.required_state.push(make_state_event( + user_a_id, + "", + RoomPinnedEventsEventContent::new(vec![pinned_event_id.clone()]), + None, + )); + let response = response_with_room(room_id, room_response); + client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync"); + + let pinned_event_ids = timeout(Duration::from_secs(1), pinned_events_subscriber.next()) + .await + .unwrap() + .expect("No pinned event ids found"); + assert_eq!(!pinned_event_ids.len(), 1); + assert_eq!(pinned_event_ids[0], pinned_event_id); + + // Pinned event ids are now empty + let mut room_response = http::response::Room::new(); + room_response.required_state.push(make_state_event( + user_a_id, + "", + RoomPinnedEventsEventContent::new(Vec::new()), + None, + )); + let response = response_with_room(room_id, room_response); + client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync"); + let pinned_event_ids = timeout(Duration::from_secs(1), pinned_events_subscriber.next()) + .await + .unwrap() + .expect("No pinned event ids found"); + assert!(pinned_event_ids.is_empty()); + } + async fn choose_event_to_cache(events: &[SyncTimelineEvent]) -> Option { let room = make_room(); let mut room_info = room.clone_info(); diff --git a/crates/matrix-sdk-base/src/store/mod.rs b/crates/matrix-sdk-base/src/store/mod.rs index 560842e6511..d4307424731 100644 --- a/crates/matrix-sdk-base/src/store/mod.rs +++ b/crates/matrix-sdk-base/src/store/mod.rs @@ -340,6 +340,9 @@ pub struct StateChanges { /// A map from room id to a map of a display name and a set of user ids that /// share that display name in the given room. pub ambiguity_maps: BTreeMap>>, + + /// A map of `RoomId` to pinned `OwnedEventId`s for each room. + pub pinned_events: BTreeMap>, } impl StateChanges { diff --git a/crates/matrix-sdk-sqlite/src/state_store.rs b/crates/matrix-sdk-sqlite/src/state_store.rs index a6fa9a02b56..c3d723a0e82 100644 --- a/crates/matrix-sdk-sqlite/src/state_store.rs +++ b/crates/matrix-sdk-sqlite/src/state_store.rs @@ -1007,6 +1007,7 @@ impl StateStore for SqliteStateStore { redactions, stripped_state, ambiguity_maps, + pinned_events: _, } = changes; if let Some(sync_token) = sync_token {