diff --git a/matrix_sdk_base/src/client.rs b/matrix_sdk_base/src/client.rs index 5460d1a8..81b339c8 100644 --- a/matrix_sdk_base/src/client.rs +++ b/matrix_sdk_base/src/client.rs @@ -955,6 +955,15 @@ impl BaseClient { } } + /// Receive a get member events response and convert it to a deserialized + /// `MembersResponse` + /// + /// + /// # Arguments + /// + /// * `room_id` - The room id this response belongs to. + /// + /// * `response` - The raw response that was received from the server. pub async fn receive_members( &self, room_id: &RoomId, @@ -1037,6 +1046,21 @@ impl BaseClient { }) } + /// Receive a successful filter upload response, the filter id will be + /// stored under the given name in the store. + /// + /// The filter id can later be retrieved with the [`get_filter`] method. + /// + /// + /// # Arguments + /// + /// * `filter_name` - The name that should be used to persist the filter id in + /// the store. + /// + /// * `response` - The successful filter upload response containing the + /// filter id. + /// + /// [`get_filter`]: #method.get_filter pub async fn receive_filter_upload( &self, filter_name: &str, @@ -1048,6 +1072,17 @@ impl BaseClient { .await?) } + /// Get the filter id of a previously uploaded filter. + /// + /// *Note*: A filter will first need to be uploaded and persisted using + /// [`receive_filter_upload`]. + /// + /// # Arguments + /// + /// * `filter_name` - The name of the filter that was previously used to + /// persist the filter. + /// + /// [`receive_filter_upload`]: #method.receive_filter_upload pub async fn get_filter(&self, filter_name: &str) -> StoreResult> { self.store.get_filter(filter_name).await } @@ -1131,6 +1166,11 @@ impl BaseClient { } } + /// Get the room with the given room id. + /// + /// # Arguments + /// + /// * `room_id` - The id of the room that should be fetched. pub fn get_room(&self, room_id: &RoomId) -> Option { self.store.get_room(room_id) } diff --git a/matrix_sdk_base/src/lib.rs b/matrix_sdk_base/src/lib.rs index 2f27b8e4..26041cce 100644 --- a/matrix_sdk_base/src/lib.rs +++ b/matrix_sdk_base/src/lib.rs @@ -52,7 +52,7 @@ mod store; pub use event_emitter::EventEmitter; pub use rooms::{InvitedRoom, JoinedRoom, LeftRoom, Room, RoomInfo, RoomMember, RoomState}; -pub use store::{Store, StoreError}; +pub use store::{StateStore, Store, StoreError}; pub use client::{BaseClient, BaseClientConfig, RoomStateType}; diff --git a/matrix_sdk_base/src/rooms/members.rs b/matrix_sdk_base/src/rooms/members.rs index 68c8ae51..7363b947 100644 --- a/matrix_sdk_base/src/rooms/members.rs +++ b/matrix_sdk_base/src/rooms/members.rs @@ -25,6 +25,7 @@ use matrix_sdk_common::{ use crate::deserialized_responses::MemberEvent; +/// A member of a room. #[derive(Clone, Debug)] pub struct RoomMember { pub(crate) event: Arc, @@ -37,10 +38,12 @@ pub struct RoomMember { } impl RoomMember { + /// Get the unique user id of this member. pub fn user_id(&self) -> &UserId { &self.event.state_key } + /// Get the display name of the member if ther is one. pub fn display_name(&self) -> Option<&str> { if let Some(p) = self.profile.as_ref() { p.displayname.as_deref() @@ -49,6 +52,10 @@ impl RoomMember { } } + /// Get the name of the member. + /// + /// This returns either the display name or the local part of the user id if + /// the member didn't set a display name. pub fn name(&self) -> &str { if let Some(d) = self.display_name() { d @@ -57,6 +64,7 @@ impl RoomMember { } } + /// Get the avatar url of the member, if ther is one. pub fn avatar_url(&self) -> Option<&str> { match self.profile.as_ref() { Some(p) => p.avatar_url.as_deref(), @@ -64,6 +72,10 @@ impl RoomMember { } } + /// Get the normalized power level of this member. + /// + /// The normalized power level depends on the maximum power level that can + /// be found in a certain room, it's always in the range of 0-100. pub fn normalized_power_level(&self) -> i64 { if self.max_power_level > 0 { (self.power_level() * 100) / self.max_power_level @@ -72,6 +84,7 @@ impl RoomMember { } } + /// Get the power level of this member. pub fn power_level(&self) -> i64 { self.power_levles .as_ref() @@ -85,4 +98,12 @@ impl RoomMember { }) .unwrap_or_else(|| if self.is_room_creator { 100 } else { 0 }) } + + /// Is the name that the member uses ambiguous in the room. + /// + /// A name is considered to be ambiguous if at least one other member shares + /// the same name. + pub fn name_ambiguous(&self) -> bool { + self.display_name_ambiguous + } } diff --git a/matrix_sdk_base/src/rooms/mod.rs b/matrix_sdk_base/src/rooms/mod.rs index 0eb5ca7d..6b7360ce 100644 --- a/matrix_sdk_base/src/rooms/mod.rs +++ b/matrix_sdk_base/src/rooms/mod.rs @@ -41,6 +41,8 @@ pub enum RoomState { } impl RoomState { + /// Destructure the room into a `JoinedRoom` if the room is in the joined + /// state. pub fn joined(self) -> Option { if let RoomState::Joined(r) = self { Some(r) @@ -49,6 +51,8 @@ impl RoomState { } } + /// Destructure the room into an `InvitedRoom` if the room is in the invited + /// state. pub fn invited(self) -> Option { if let RoomState::Invited(r) = self { Some(r) @@ -57,6 +61,8 @@ impl RoomState { } } + /// Destructure the room into a `LeftRoom` if the room is in the left + /// state. pub fn left(self) -> Option { if let RoomState::Left(r) = self { Some(r) @@ -65,6 +71,7 @@ impl RoomState { } } + /// Is the room encrypted. pub fn is_encrypted(&self) -> bool { match self { RoomState::Joined(r) => r.inner.is_encrypted(), @@ -73,6 +80,7 @@ impl RoomState { } } + /// Are the members for this room synced. pub fn are_members_synced(&self) -> bool { if let RoomState::Joined(r) = self { r.inner.are_members_synced() @@ -82,12 +90,12 @@ impl RoomState { } } +/// A room in a joined state. #[derive(Debug, Clone)] pub struct JoinedRoom { pub(crate) inner: Room, } -// TODO do we wan't to deref here or have separate implementations. impl Deref for JoinedRoom { type Target = Room; @@ -96,6 +104,7 @@ impl Deref for JoinedRoom { } } +/// A room in a left state. #[derive(Debug, Clone)] pub struct LeftRoom { pub(crate) inner: Room, @@ -109,6 +118,7 @@ impl Deref for LeftRoom { } } +/// A room in an invited state. #[derive(Debug, Clone)] pub struct InvitedRoom { pub(crate) inner: StrippedRoom, @@ -122,23 +132,40 @@ impl Deref for InvitedRoom { } } +/// A base room info struct that is the backbone of normal as well as stripped +/// rooms. Holds all the state events that are important to present a room to +/// users. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct BaseRoomInfo { + /// The avatar URL of this room. pub avatar_url: Option, + /// The canonical alias of this room. pub canonical_alias: Option, + /// The `m.room.create` event content of this room. pub create: Option, + /// The user id this room is sharing the direct message with, if the room is + /// a direct message. pub dm_target: Option, + /// The `m.room.encryption` event content that enabled E2EE in this room. pub encryption: Option, + /// The guest access policy of this room. pub guest_access: GuestAccess, + /// The history visiblity policy of this room. pub history_visibility: HistoryVisibility, + /// The join rule policy of this room. pub join_rule: JoinRule, + /// The maximal power level that can be found in this room. pub max_power_level: i64, + /// The `m.room.name` of this room. pub name: Option, + /// The `m.room.tombstone` event content of this room. pub tombstone: Option, + /// The topic of this room. pub topic: Option, } impl BaseRoomInfo { + /// Create a new, empty base room info. pub fn new() -> Self { Self::default() } @@ -181,6 +208,9 @@ impl BaseRoomInfo { } } + /// Handle a state event for this room and update our info accordingly. + /// + /// Returns true if the event modified the info, false otherwise. pub fn handle_state_event(&mut self, content: &AnyStateEventContent) -> bool { match content { AnyStateEventContent::RoomEncryption(encryption) => { diff --git a/matrix_sdk_base/src/rooms/normal.rs b/matrix_sdk_base/src/rooms/normal.rs index 458b598f..2d546100 100644 --- a/matrix_sdk_base/src/rooms/normal.rs +++ b/matrix_sdk_base/src/rooms/normal.rs @@ -43,6 +43,7 @@ use crate::{ use super::{BaseRoomInfo, RoomMember}; +/// The underlying room data structure collecting state for joined and left rooms. #[derive(Debug, Clone)] pub struct Room { room_id: Arc, @@ -51,21 +52,28 @@ pub struct Room { store: Arc>, } +/// The room summary containing member counts and members that should be used to +/// calculate the room display name. #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct RoomSummary { + /// The heroes of the room, members that should be used for the room display + /// name. heroes: Vec, + /// The number of members that are considered to be joined to the room. joined_member_count: u64, + /// The number of members that are considered to be invited to the room. invited_member_count: u64, } -/// Signals to the `BaseClient` which `RoomState` to send to `EventEmitter`. +/// Enum keeping track in which state the room is, e.g. if our own user is +/// joined, invited, or has left the room. #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub enum RoomType { - /// Represents a joined room, the `joined_rooms` HashMap will be used. + /// The room is in a joined state. Joined, - /// Represents a left room, the `left_rooms` HashMap will be used. + /// The room is in a left state. Left, - /// Represents an invited room, the `invited_rooms` HashMap will be used. + /// The room is in a invited state. Invited, } @@ -104,62 +112,92 @@ impl Room { } } + /// Get the unique room id of the room. pub fn room_id(&self) -> &RoomId { &self.room_id } + /// Get our own user id. pub fn own_user_id(&self) -> &UserId { &self.own_user_id } + /// Get the type of the room. pub fn room_type(&self) -> RoomType { self.inner.read().unwrap().room_type } + /// Get the unread notification counts. pub fn unread_notification_counts(&self) -> UnreadNotificationsCount { self.inner.read().unwrap().notification_counts } + /// Check if the room has it's members fully synced. + /// + /// Members might be missing if lazy member loading was enabled for the sync. + /// + /// Returns true if no members are missing, false otherwise. pub fn are_members_synced(&self) -> bool { self.inner.read().unwrap().members_synced } + /// Get the `prev_batch` token that was received from the last sync. May be + /// `None` if the last sync contained the full room history. pub fn last_prev_batch(&self) -> Option { self.inner.read().unwrap().last_prev_batch.clone() } + /// Get the avatar url of this room. pub fn avatar_url(&self) -> Option { self.inner.read().unwrap().base_info.avatar_url.clone() } + /// Get the canonical alias of this room. pub fn canonical_alias(&self) -> Option { self.inner.read().unwrap().base_info.canonical_alias.clone() } + /// Get the `m.room.create` content of this room. + /// + /// This usually isn't optional but some servers might not send an + /// `m.room.create` event as the first event for a given room, thus this can + /// be optional. pub fn create_content(&self) -> Option { self.inner.read().unwrap().base_info.create.clone() } + /// Is this room considered a direct message. pub fn is_direct(&self) -> bool { self.inner.read().unwrap().base_info.dm_target.is_some() } + /// If this room is a direct message, get the member that we're sharing the + /// room with. + /// + /// *Note*: The member list might have been moddified in the meantime and + /// the target might not even be in the room anymore. This setting should + /// only be considered as guidance. pub fn direct_target(&self) -> Option { self.inner.read().unwrap().base_info.dm_target.clone() } + /// Is the room encrypted. pub fn is_encrypted(&self) -> bool { self.inner.read().unwrap().is_encrypted() } + /// Get the `m.room.encryption` content that enabled end to end encryption + /// in the room. pub fn encryption_settings(&self) -> Option { self.inner.read().unwrap().base_info.encryption.clone() } + /// Get the guest access policy of this room. pub fn guest_access(&self) -> GuestAccess { self.inner.read().unwrap().base_info.guest_access.clone() } + /// Get the history visiblity policy of this room. pub fn history_visibility(&self) -> HistoryVisibility { self.inner .read() @@ -169,42 +207,62 @@ impl Room { .clone() } + /// Is the room considered to be public. pub fn is_public(&self) -> bool { matches!(self.join_rule(), JoinRule::Public) } + /// Get the join rule policy of this room. pub fn join_rule(&self) -> JoinRule { self.inner.read().unwrap().base_info.join_rule.clone() } + /// Get the maximum power level that this room contains. + /// + /// This is useful if one wishes to normalize the power levels, e.g. from + /// 0-100 where 100 would be the max power level. pub fn max_power_level(&self) -> i64 { self.inner.read().unwrap().base_info.max_power_level } + /// Get the `m.room.name` of this room. pub fn name(&self) -> Option { self.inner.read().unwrap().base_info.name.clone() } + /// Has the room been tombstoned. pub fn is_tombstoned(&self) -> bool { self.inner.read().unwrap().base_info.tombstone.is_some() } + /// Get the `m.room.tombstone` content of this room if there is one. pub fn tombstone(&self) -> Option { self.inner.read().unwrap().base_info.tombstone.clone() } + /// Get the topic of the room. pub fn topic(&self) -> Option { self.inner.read().unwrap().base_info.topic.clone() } + /// Calculate the canonical display name of the room, taking into account + /// its name, aliases and members. + /// + /// The display name is calculated according to [this algorithm][spec]. + /// + /// [spec]: pub async fn display_name(&self) -> StoreResult { self.calculate_name().await } + /// Get the list of users ids that are considered to be joined members of + /// this room. pub async fn joined_user_ids(&self) -> StoreResult> { self.store.get_joined_user_ids(self.room_id()).await } + /// Get the list of `RoomMember`s that are considered to be joined members + /// of this room. pub async fn joined_members(&self) -> StoreResult> { let joined = self.store.get_joined_user_ids(self.room_id()).await?; let mut members = Vec::new(); @@ -220,6 +278,8 @@ impl Room { Ok(members) } + /// Get the list of `RoomMember`s that are considered to be joined or + /// invited members of this room. pub async fn active_members(&self) -> StoreResult> { let joined = self.store.get_joined_user_ids(self.room_id()).await?; let invited = self.store.get_invited_user_ids(self.room_id()).await?; @@ -237,13 +297,6 @@ impl Room { Ok(members) } - /// Calculate the canonical display name of the room, taking into account - /// its name, aliases and members. - /// - /// The display name is calculated according to [this algorithm][spec]. - /// - /// [spec]: - /// async fn calculate_name(&self) -> StoreResult { let summary = { let inner = self.inner.read().unwrap(); @@ -306,11 +359,16 @@ impl Room { (*self.inner.read().unwrap()).clone() } - pub fn update_summary(&self, summary: RoomInfo) { + pub(crate) fn update_summary(&self, summary: RoomInfo) { let mut inner = self.inner.write().unwrap(); *inner = summary; } + /// Get the `RoomMember` with the given `user_id`. + /// + /// Returns `None` if the member was never part of this room, otherwise + /// return a `RoomMember` that can be in a joined, invited, left, banned + /// state. pub async fn get_member(&self, user_id: &UserId) -> StoreResult> { let member_event = if let Some(m) = self.store.get_member_event(self.room_id(), user_id).await? { @@ -370,16 +428,25 @@ impl Room { } } +/// The underlying pure data structure for joined and left rooms. +/// +/// Holds all the info needed to persist a room into the state store. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct RoomInfo { + /// The unique room id of the room. pub room_id: Arc, + /// The type of the room. pub room_type: RoomType, - + /// The unread notifications counts. pub notification_counts: UnreadNotificationsCount, + /// The summary of this room. pub summary: RoomSummary, + /// Flag remembering if the room members are synced. pub members_synced: bool, + /// The prev batch of this room we received durring the last sync. pub last_prev_batch: Option, - + /// Base room info which holds some basic event contents important for the + /// room state. pub base_info: BaseRoomInfo, } diff --git a/matrix_sdk_base/src/store/memory_store.rs b/matrix_sdk_base/src/store/memory_store.rs index 9edd49c6..dbe3100e 100644 --- a/matrix_sdk_base/src/store/memory_store.rs +++ b/matrix_sdk_base/src/store/memory_store.rs @@ -58,6 +58,7 @@ pub struct MemoryStore { } impl MemoryStore { + #[cfg(not(feature = "sled_state_store"))] pub fn new() -> Self { Self { sync_token: Arc::new(RwLock::new(None)), @@ -78,22 +79,22 @@ impl MemoryStore { } } - pub async fn save_filter(&self, filter_name: &str, filter_id: &str) -> Result<()> { + async fn save_filter(&self, filter_name: &str, filter_id: &str) -> Result<()> { self.filters .insert(filter_name.to_string(), filter_id.to_string()); Ok(()) } - pub async fn get_filter(&self, filter_name: &str) -> Result> { + async fn get_filter(&self, filter_name: &str) -> Result> { Ok(self.filters.get(filter_name).map(|f| f.to_string())) } - pub async fn get_sync_token(&self) -> Result> { + async fn get_sync_token(&self) -> Result> { Ok(self.sync_token.read().unwrap().clone()) } - pub async fn save_changes(&self, changes: &StateChanges) -> Result<()> { + async fn save_changes(&self, changes: &StateChanges) -> Result<()> { let now = Instant::now(); if let Some(s) = &changes.sync_token { @@ -227,12 +228,12 @@ impl MemoryStore { Ok(()) } - pub async fn get_presence_event(&self, user_id: &UserId) -> Result> { + async fn get_presence_event(&self, user_id: &UserId) -> Result> { #[allow(clippy::map_clone)] Ok(self.presence.get(user_id).map(|p| p.clone())) } - pub async fn get_state_event( + async fn get_state_event( &self, room_id: &RoomId, event_type: EventType, @@ -245,7 +246,7 @@ impl MemoryStore { })) } - pub async fn get_profile( + async fn get_profile( &self, room_id: &RoomId, user_id: &UserId, @@ -257,7 +258,7 @@ impl MemoryStore { .and_then(|p| p.get(user_id).map(|p| p.clone()))) } - pub async fn get_member_event( + async fn get_member_event( &self, room_id: &RoomId, state_key: &UserId, diff --git a/matrix_sdk_base/src/store/mod.rs b/matrix_sdk_base/src/store/mod.rs index 89097cf3..a96b40d4 100644 --- a/matrix_sdk_base/src/store/mod.rs +++ b/matrix_sdk_base/src/store/mod.rs @@ -44,23 +44,33 @@ mod memory_store; #[cfg(feature = "sled_state_store")] mod sled_store; +#[cfg(not(feature = "sled_state_store"))] use self::memory_store::MemoryStore; #[cfg(feature = "sled_state_store")] use self::sled_store::SledStore; +/// State store specific error type. #[derive(Debug, thiserror::Error)] pub enum StoreError { + /// An error happened in the underlying sled database. #[cfg(feature = "sled_state_store")] #[error(transparent)] Sled(#[from] sled::Error), + /// An error happened while serializing or deserializing some data. #[error(transparent)] Json(#[from] serde_json::Error), + /// An error happened while deserializing a Matrix identifier, e.g. an user + /// id. #[error(transparent)] Identifier(#[from] matrix_sdk_common::identifiers::Error), + /// The store is locked with a passphrase and an incorrect passphrase was + /// given. #[error("The store failed to be unlocked")] StoreLocked, + /// An unencrypted store was tried to be unlocked with a passphrase. #[error("The store is not encrypted but was tried to be opened with a passphrase")] UnencryptedStore, + /// The store failed to encrypt or decrypt some data. #[error("Error encrypting or decrypting data from the store: {0}")] Encryption(String), } @@ -157,7 +167,8 @@ impl Store { Ok(()) } - pub fn open_memory_store() -> Self { + #[cfg(not(feature = "sled_state_store"))] + pub(crate) fn open_memory_store() -> Self { let inner = Box::new(MemoryStore::new()); Self::new(inner) @@ -175,7 +186,7 @@ impl Store { } #[cfg(feature = "sled_state_store")] - pub fn open_temporary() -> Result<(Self, Db)> { + pub(crate) fn open_temporary() -> Result<(Self, Db)> { let inner = SledStore::open()?; Ok((Self::new(Box::new(inner.clone())), inner.inner)) diff --git a/matrix_sdk_common/src/deserialized_responses.rs b/matrix_sdk_common/src/deserialized_responses.rs index 1981e3f5..697df8a5 100644 --- a/matrix_sdk_common/src/deserialized_responses.rs +++ b/matrix_sdk_common/src/deserialized_responses.rs @@ -13,15 +13,24 @@ use super::{ identifiers::{DeviceKeyAlgorithm, EventId, RoomId, UserId}, }; +/// A change in ambiguity of room members that an `m.room.member` event +/// triggers. #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct AmbiguityChange { + /// Is the member that is contained in the state key of the `m.room.member` + /// event itself ambiguous because of the event. pub member_ambiguous: bool, + /// Has another user been disambiguated because of this event. pub disambiguated_member: Option, + /// Has another user become ambiguous because of this event. pub ambiguated_member: Option, } +/// Collection of ambiguioty changes that room member events trigger. #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct AmbiguityChanges { + /// A map from room id to a map of an event id to the `AmbiguityChange` that + /// the event with the given id caused. pub changes: BTreeMap>, } @@ -320,8 +329,12 @@ impl Into> for StrippedMemberEvent { } } +/// A deserialized response for the rooms members API call. +/// +/// [GET /_matrix/client/r0/rooms/{roomId}/members](https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-rooms-roomid-members) #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct MembersResponse { + /// The list of members events. pub chunk: Vec, /// Collection of ambiguioty changes that room member events trigger. pub ambiguity_changes: AmbiguityChanges,