From bcd75362f78f530c875eecc555097a0859fef8cd Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 21 May 2025 10:50:40 +0200 Subject: [PATCH] chore(base): Move `Room::*knock*` methods into the new `knock` module. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch moves the `Room::*knock*` methods into the new `knock` module. The idea is to group API by “theme” to get smaller modules and more organised code. --- crates/matrix-sdk-base/src/rooms/knock.rs | 168 ++++++++++++++++++++++ crates/matrix-sdk-base/src/rooms/mod.rs | 144 +------------------ 2 files changed, 173 insertions(+), 139 deletions(-) create mode 100644 crates/matrix-sdk-base/src/rooms/knock.rs diff --git a/crates/matrix-sdk-base/src/rooms/knock.rs b/crates/matrix-sdk-base/src/rooms/knock.rs new file mode 100644 index 000000000..d9057cc4b --- /dev/null +++ b/crates/matrix-sdk-base/src/rooms/knock.rs @@ -0,0 +1,168 @@ +// Copyright 2025 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::collections::BTreeMap; + +use eyeball::{AsyncLock, ObservableWriteGuard}; +use ruma::{ + events::{ + room::member::{MembershipState, RoomMemberEventContent}, + StateEventType, SyncStateEvent, + }, + OwnedEventId, OwnedUserId, +}; +use tracing::warn; + +use super::Room; +use crate::{ + deserialized_responses::{MemberEvent, RawMemberEvent, SyncOrStrippedState}, + store::{Result as StoreResult, StateStoreExt}, + StateStoreDataKey, StateStoreDataValue, StoreError, +}; + +impl Room { + /// Mark a list of requests to join the room as seen, given their state + /// event ids. + pub async fn mark_knock_requests_as_seen(&self, user_ids: &[OwnedUserId]) -> StoreResult<()> { + let raw_user_ids: Vec<&str> = user_ids.iter().map(|id| id.as_str()).collect(); + let member_raw_events = self + .store + .get_state_events_for_keys(self.room_id(), StateEventType::RoomMember, &raw_user_ids) + .await?; + let mut event_to_user_ids = Vec::with_capacity(member_raw_events.len()); + + // Map the list of events ids to their user ids, if they are event ids for knock + // membership events. Log an error and continue otherwise. + for raw_event in member_raw_events { + let event = raw_event.cast::().deserialize()?; + match event { + SyncOrStrippedState::Sync(SyncStateEvent::Original(event)) => { + if event.content.membership == MembershipState::Knock { + event_to_user_ids.push((event.event_id, event.state_key)) + } else { + warn!("Could not mark knock event as seen: event {} for user {} is not in Knock membership state.", event.event_id, event.state_key); + } + } + _ => warn!( + "Could not mark knock event as seen: event for user {} is not valid.", + event.state_key() + ), + } + } + + let current_seen_events_guard = self.get_write_guarded_current_knock_request_ids().await?; + let mut current_seen_events = current_seen_events_guard.clone().unwrap_or_default(); + + current_seen_events.extend(event_to_user_ids); + + self.update_seen_knock_request_ids(current_seen_events_guard, current_seen_events).await?; + + Ok(()) + } + + /// Removes the seen knock request ids that are no longer valid given the + /// current room members. + pub async fn remove_outdated_seen_knock_requests_ids(&self) -> StoreResult<()> { + let current_seen_events_guard = self.get_write_guarded_current_knock_request_ids().await?; + let mut current_seen_events = current_seen_events_guard.clone().unwrap_or_default(); + + // Get and deserialize the member events for the seen knock requests + let keys: Vec = current_seen_events.values().map(|id| id.to_owned()).collect(); + let raw_member_events: Vec = + self.store.get_state_events_for_keys_static(self.room_id(), &keys).await?; + let member_events = raw_member_events + .into_iter() + .map(|raw| raw.deserialize()) + .collect::, _>>()?; + + let mut ids_to_remove = Vec::new(); + + for (event_id, user_id) in current_seen_events.iter() { + // Check the seen knock request ids against the current room member events for + // the room members associated to them + let matching_member = member_events.iter().find(|event| event.user_id() == user_id); + + if let Some(member) = matching_member { + let member_event_id = member.event_id(); + // If the member event is not a knock or it's different knock, it's outdated + if *member.membership() != MembershipState::Knock + || member_event_id.is_some_and(|id| id != event_id) + { + ids_to_remove.push(event_id.to_owned()); + } + } else { + ids_to_remove.push(event_id.to_owned()); + } + } + + // If there are no ids to remove, do nothing + if ids_to_remove.is_empty() { + return Ok(()); + } + + for event_id in ids_to_remove { + current_seen_events.remove(&event_id); + } + + self.update_seen_knock_request_ids(current_seen_events_guard, current_seen_events).await?; + + Ok(()) + } + + /// Get the list of seen knock request event ids in this room. + pub async fn get_seen_knock_request_ids( + &self, + ) -> Result, StoreError> { + Ok(self.get_write_guarded_current_knock_request_ids().await?.clone().unwrap_or_default()) + } + + async fn get_write_guarded_current_knock_request_ids( + &self, + ) -> StoreResult>, AsyncLock>> + { + let mut guard = self.seen_knock_request_ids_map.write().await; + // If there are no loaded request ids yet + if guard.is_none() { + // Load the values from the store and update the shared observable contents + let updated_seen_ids = self + .store + .get_kv_data(StateStoreDataKey::SeenKnockRequests(self.room_id())) + .await? + .and_then(|v| v.into_seen_knock_requests()) + .unwrap_or_default(); + + ObservableWriteGuard::set(&mut guard, Some(updated_seen_ids)); + } + Ok(guard) + } + + async fn update_seen_knock_request_ids( + &self, + mut guard: ObservableWriteGuard<'_, Option>, AsyncLock>, + new_value: BTreeMap, + ) -> StoreResult<()> { + // Save the new values to the shared observable + ObservableWriteGuard::set(&mut guard, Some(new_value.clone())); + + // Save them into the store too + self.store + .set_kv_data( + StateStoreDataKey::SeenKnockRequests(self.room_id()), + StateStoreDataValue::SeenKnockRequests(new_value), + ) + .await?; + + Ok(()) + } +} diff --git a/crates/matrix-sdk-base/src/rooms/mod.rs b/crates/matrix-sdk-base/src/rooms/mod.rs index 360853e90..681d3fb42 100644 --- a/crates/matrix-sdk-base/src/rooms/mod.rs +++ b/crates/matrix-sdk-base/src/rooms/mod.rs @@ -16,6 +16,7 @@ mod display_name; mod encryption; +mod knock; mod latest_event; mod members; mod room_info; @@ -23,18 +24,18 @@ mod state; mod tags; use crate::{ - deserialized_responses::{MemberEvent, RawMemberEvent, SyncOrStrippedState}, + deserialized_responses::{MemberEvent, SyncOrStrippedState}, notification_settings::RoomNotificationMode, read_receipts::RoomReadReceipts, store::{DynStateStore, Result as StoreResult, StateStoreExt}, sync::UnreadNotificationsCount, - Error, MinimalStateEvent, StateStoreDataKey, StateStoreDataValue, StoreError, + Error, MinimalStateEvent, }; use as_variant::as_variant; pub use display_name::{RoomDisplayName, RoomHero}; pub(crate) use display_name::{RoomSummary, UpdatedRoomDisplayName}; pub use encryption::EncryptionState; -use eyeball::{AsyncLock, ObservableWriteGuard, SharedObservable}; +use eyeball::{AsyncLock, SharedObservable}; use futures_util::{Stream, StreamExt}; #[cfg(feature = "e2e-encryption")] use matrix_sdk_common::ring_buffer::RingBuffer; @@ -56,11 +57,10 @@ use ruma::{ guest_access::GuestAccess, history_visibility::HistoryVisibility, join_rules::JoinRule, - member::{MembershipState, RoomMemberEventContent}, power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent}, tombstone::RoomTombstoneEventContent, }, - EmptyStateKey, RedactContent, RedactedStateEventContent, StateEventType, SyncStateEvent, + EmptyStateKey, RedactContent, RedactedStateEventContent, SyncStateEvent, }, room::RoomType, EventId, OwnedEventId, OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomId, @@ -515,140 +515,6 @@ impl Room { pub fn pinned_event_ids(&self) -> Option> { self.inner.read().pinned_event_ids() } - - /// Mark a list of requests to join the room as seen, given their state - /// event ids. - pub async fn mark_knock_requests_as_seen(&self, user_ids: &[OwnedUserId]) -> StoreResult<()> { - let raw_user_ids: Vec<&str> = user_ids.iter().map(|id| id.as_str()).collect(); - let member_raw_events = self - .store - .get_state_events_for_keys(self.room_id(), StateEventType::RoomMember, &raw_user_ids) - .await?; - let mut event_to_user_ids = Vec::with_capacity(member_raw_events.len()); - - // Map the list of events ids to their user ids, if they are event ids for knock - // membership events. Log an error and continue otherwise. - for raw_event in member_raw_events { - let event = raw_event.cast::().deserialize()?; - match event { - SyncOrStrippedState::Sync(SyncStateEvent::Original(event)) => { - if event.content.membership == MembershipState::Knock { - event_to_user_ids.push((event.event_id, event.state_key)) - } else { - warn!("Could not mark knock event as seen: event {} for user {} is not in Knock membership state.", event.event_id, event.state_key); - } - } - _ => warn!( - "Could not mark knock event as seen: event for user {} is not valid.", - event.state_key() - ), - } - } - - let current_seen_events_guard = self.get_write_guarded_current_knock_request_ids().await?; - let mut current_seen_events = current_seen_events_guard.clone().unwrap_or_default(); - - current_seen_events.extend(event_to_user_ids); - - self.update_seen_knock_request_ids(current_seen_events_guard, current_seen_events).await?; - - Ok(()) - } - - /// Removes the seen knock request ids that are no longer valid given the - /// current room members. - pub async fn remove_outdated_seen_knock_requests_ids(&self) -> StoreResult<()> { - let current_seen_events_guard = self.get_write_guarded_current_knock_request_ids().await?; - let mut current_seen_events = current_seen_events_guard.clone().unwrap_or_default(); - - // Get and deserialize the member events for the seen knock requests - let keys: Vec = current_seen_events.values().map(|id| id.to_owned()).collect(); - let raw_member_events: Vec = - self.store.get_state_events_for_keys_static(self.room_id(), &keys).await?; - let member_events = raw_member_events - .into_iter() - .map(|raw| raw.deserialize()) - .collect::, _>>()?; - - let mut ids_to_remove = Vec::new(); - - for (event_id, user_id) in current_seen_events.iter() { - // Check the seen knock request ids against the current room member events for - // the room members associated to them - let matching_member = member_events.iter().find(|event| event.user_id() == user_id); - - if let Some(member) = matching_member { - let member_event_id = member.event_id(); - // If the member event is not a knock or it's different knock, it's outdated - if *member.membership() != MembershipState::Knock - || member_event_id.is_some_and(|id| id != event_id) - { - ids_to_remove.push(event_id.to_owned()); - } - } else { - ids_to_remove.push(event_id.to_owned()); - } - } - - // If there are no ids to remove, do nothing - if ids_to_remove.is_empty() { - return Ok(()); - } - - for event_id in ids_to_remove { - current_seen_events.remove(&event_id); - } - - self.update_seen_knock_request_ids(current_seen_events_guard, current_seen_events).await?; - - Ok(()) - } - - /// Get the list of seen knock request event ids in this room. - pub async fn get_seen_knock_request_ids( - &self, - ) -> Result, StoreError> { - Ok(self.get_write_guarded_current_knock_request_ids().await?.clone().unwrap_or_default()) - } - - async fn get_write_guarded_current_knock_request_ids( - &self, - ) -> StoreResult>, AsyncLock>> - { - let mut guard = self.seen_knock_request_ids_map.write().await; - // If there are no loaded request ids yet - if guard.is_none() { - // Load the values from the store and update the shared observable contents - let updated_seen_ids = self - .store - .get_kv_data(StateStoreDataKey::SeenKnockRequests(self.room_id())) - .await? - .and_then(|v| v.into_seen_knock_requests()) - .unwrap_or_default(); - - ObservableWriteGuard::set(&mut guard, Some(updated_seen_ids)); - } - Ok(guard) - } - - async fn update_seen_knock_request_ids( - &self, - mut guard: ObservableWriteGuard<'_, Option>, AsyncLock>, - new_value: BTreeMap, - ) -> StoreResult<()> { - // Save the new values to the shared observable - ObservableWriteGuard::set(&mut guard, Some(new_value.clone())); - - // Save them into the store too - self.store - .set_kv_data( - StateStoreDataKey::SeenKnockRequests(self.room_id()), - StateStoreDataValue::SeenKnockRequests(new_value), - ) - .await?; - - Ok(()) - } } // See https://github.com/matrix-org/matrix-rust-sdk/pull/3749#issuecomment-2312939823.