base: Initial set of docs

master
Damir Jelić 2021-01-28 14:10:26 +01:00
parent 10da61c567
commit 58691986a9
8 changed files with 209 additions and 26 deletions

View File

@ -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<Option<String>> {
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<RoomState> {
self.store.get_room(room_id)
}

View File

@ -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};

View File

@ -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<MemberEvent>,
@ -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
}
}

View File

@ -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<JoinedRoom> {
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<InvitedRoom> {
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<LeftRoom> {
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<String>,
/// The canonical alias of this room.
pub canonical_alias: Option<RoomAliasId>,
/// The `m.room.create` event content of this room.
pub create: Option<CreateEventContent>,
/// The user id this room is sharing the direct message with, if the room is
/// a direct message.
pub dm_target: Option<UserId>,
/// The `m.room.encryption` event content that enabled E2EE in this room.
pub encryption: Option<EncryptionEventContent>,
/// 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<String>,
/// The `m.room.tombstone` event content of this room.
pub tombstone: Option<TombstoneEventContent>,
/// The topic of this room.
pub topic: Option<String>,
}
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) => {

View File

@ -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<RoomId>,
@ -51,21 +52,28 @@ pub struct Room {
store: Arc<Box<dyn StateStore>>,
}
/// 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<String>,
/// 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<String> {
self.inner.read().unwrap().last_prev_batch.clone()
}
/// Get the avatar url of this room.
pub fn avatar_url(&self) -> Option<String> {
self.inner.read().unwrap().base_info.avatar_url.clone()
}
/// Get the canonical alias of this room.
pub fn canonical_alias(&self) -> Option<RoomAliasId> {
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<CreateEventContent> {
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<UserId> {
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<EncryptionEventContent> {
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<String> {
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<TombstoneEventContent> {
self.inner.read().unwrap().base_info.tombstone.clone()
}
/// Get the topic of the room.
pub fn topic(&self) -> Option<String> {
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]: <https://matrix.org/docs/spec/client_server/latest#calculating-the-display-name-for-a-room>
pub async fn display_name(&self) -> StoreResult<String> {
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<Vec<UserId>> {
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<Vec<RoomMember>> {
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<Vec<RoomMember>> {
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]:
/// <https://matrix.org/docs/spec/client_server/latest#calculating-the-display-name-for-a-room>
async fn calculate_name(&self) -> StoreResult<String> {
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<Option<RoomMember>> {
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<RoomId>,
/// 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<String>,
/// Base room info which holds some basic event contents important for the
/// room state.
pub base_info: BaseRoomInfo,
}

View File

@ -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<Option<String>> {
async fn get_filter(&self, filter_name: &str) -> Result<Option<String>> {
Ok(self.filters.get(filter_name).map(|f| f.to_string()))
}
pub async fn get_sync_token(&self) -> Result<Option<String>> {
async fn get_sync_token(&self) -> Result<Option<String>> {
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<Option<PresenceEvent>> {
async fn get_presence_event(&self, user_id: &UserId) -> Result<Option<PresenceEvent>> {
#[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,

View File

@ -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))

View File

@ -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<UserId>,
/// Has another user become ambiguous because of this event.
pub ambiguated_member: Option<UserId>,
}
/// 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<RoomId, BTreeMap<EventId, AmbiguityChange>>,
}
@ -320,8 +329,12 @@ impl Into<StrippedStateEvent<MemberEventContent>> 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<MemberEvent>,
/// Collection of ambiguioty changes that room member events trigger.
pub ambiguity_changes: AmbiguityChanges,