From 0d0d8fca2dfc9b4d79d02180a276ed6cdb45e8e5 Mon Sep 17 00:00:00 2001 From: Devin R Date: Sat, 9 May 2020 07:33:57 -0400 Subject: [PATCH 1/3] client: add store_room_state method --- matrix_sdk/src/client.rs | 11 +++++++++++ matrix_sdk_base/src/client.rs | 15 ++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/matrix_sdk/src/client.rs b/matrix_sdk/src/client.rs index e3caff7b..cef10e24 100644 --- a/matrix_sdk/src/client.rs +++ b/matrix_sdk/src/client.rs @@ -394,6 +394,17 @@ impl Client { Ok(self.base_client.sync_with_state_store().await?) } + /// This allows `AsyncClient` to manually store `Room` + /// state with the provided `StateStore`. + /// + /// Returns Ok when a successful `Room` store occurs. + pub async fn store_room_state(&self, room_id: &RoomId) -> Result<()> { + self.base_client + .store_room_state(room_id) + .await + .map_err(Into::into) + } + /// Login to the server. /// /// # Arguments diff --git a/matrix_sdk_base/src/client.rs b/matrix_sdk_base/src/client.rs index 621b0d80..842eabad 100644 --- a/matrix_sdk_base/src/client.rs +++ b/matrix_sdk_base/src/client.rs @@ -241,6 +241,19 @@ impl Client { Ok(!self.needs_state_store_sync.load(Ordering::Relaxed)) } + /// When a client is provided the state store will load state from the `StateStore`. + /// + /// Returns `true` when a state store sync has successfully completed. + pub async fn store_room_state(&self, room_id: &RoomId) -> Result<()> { + if let Some(store) = self.state_store.read().await.as_ref() { + if let Some(room) = self.get_joined_room(room_id).await { + let room = room.read().await; + store.store_room_state(room.deref()).await?; + } + } + Ok(()) + } + /// Receive a login response and update the session of the client. /// /// # Arguments @@ -619,7 +632,7 @@ impl Client { // TODO do we want to move the rooms to the appropriate HashMaps when the corresponding // event comes in e.g. move a joined room to a left room when leave event comes? - // when events change state, updated signals to StateStore to update database + // when events change state, updated_* signals to StateStore to update database let updated_joined = self.iter_joined_rooms(response).await?; let updated_invited = self.iter_invited_rooms(&response).await?; let updated_left = self.iter_left_rooms(response).await?; From 21712d093040c2b686f3e1b76125eccb511e29b8 Mon Sep 17 00:00:00 2001 From: Devin R Date: Mon, 11 May 2020 15:32:58 -0400 Subject: [PATCH 2/3] state_store: load/store each room type (invite, join, left), add type for returning 3 room maps --- matrix_sdk/examples/command_bot.rs | 6 +- matrix_sdk/examples/login.rs | 6 +- matrix_sdk/src/lib.rs | 4 +- matrix_sdk_base/src/client.rs | 51 +++++++-- matrix_sdk_base/src/event_emitter/mod.rs | 140 ++++++++++++----------- matrix_sdk_base/src/lib.rs | 4 +- matrix_sdk_base/src/models/message.rs | 4 +- matrix_sdk_base/src/state/mod.rs | 13 ++- matrix_sdk_base/src/state/state_store.rs | 113 ++++++++++++++---- 9 files changed, 220 insertions(+), 121 deletions(-) diff --git a/matrix_sdk/examples/command_bot.rs b/matrix_sdk/examples/command_bot.rs index c167cfb3..b08e0764 100644 --- a/matrix_sdk/examples/command_bot.rs +++ b/matrix_sdk/examples/command_bot.rs @@ -3,7 +3,7 @@ use std::{env, process::exit}; use matrix_sdk::{ self, events::room::message::{MessageEvent, MessageEventContent, TextMessageEventContent}, - Client, ClientConfig, EventEmitter, JsonStore, RoomState, SyncSettings, + Client, ClientConfig, EventEmitter, JsonStore, SyncRoom, SyncSettings, }; use url::Url; @@ -21,8 +21,8 @@ impl CommandBot { #[async_trait::async_trait] impl EventEmitter for CommandBot { - async fn on_room_message(&self, room: RoomState, event: &MessageEvent) { - if let RoomState::Joined(room) = room { + async fn on_room_message(&self, room: SyncRoom, event: &MessageEvent) { + if let SyncRoom::Joined(room) = room { let msg_body = if let MessageEvent { content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }), .. diff --git a/matrix_sdk/examples/login.rs b/matrix_sdk/examples/login.rs index c56b7a0c..182c8eea 100644 --- a/matrix_sdk/examples/login.rs +++ b/matrix_sdk/examples/login.rs @@ -4,15 +4,15 @@ use url::Url; use matrix_sdk::{ self, events::room::message::{MessageEvent, MessageEventContent, TextMessageEventContent}, - Client, ClientConfig, EventEmitter, RoomState, SyncSettings, + Client, ClientConfig, EventEmitter, SyncRoom, SyncSettings, }; struct EventCallback; #[async_trait::async_trait] impl EventEmitter for EventCallback { - async fn on_room_message(&self, room: RoomState, event: &MessageEvent) { - if let RoomState::Joined(room) = room { + async fn on_room_message(&self, room: SyncRoom, event: &MessageEvent) { + if let SyncRoom::Joined(room) = room { if let MessageEvent { content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }), sender, diff --git a/matrix_sdk/src/lib.rs b/matrix_sdk/src/lib.rs index 1b4a81d2..a2043d39 100644 --- a/matrix_sdk/src/lib.rs +++ b/matrix_sdk/src/lib.rs @@ -26,8 +26,8 @@ //! destroyed. #![deny(missing_docs)] -pub use matrix_sdk_base::{EventEmitter, Room, Session}; -pub use matrix_sdk_base::{JsonStore, RoomState, StateStore}; +pub use matrix_sdk_base::{AllRooms, JsonStore, RoomState, StateStore}; +pub use matrix_sdk_base::{EventEmitter, Room, Session, SyncRoom}; pub use matrix_sdk_common::*; pub use reqwest::header::InvalidHeaderValue; diff --git a/matrix_sdk_base/src/client.rs b/matrix_sdk_base/src/client.rs index f8a4c75a..7d6a126c 100644 --- a/matrix_sdk_base/src/client.rs +++ b/matrix_sdk_base/src/client.rs @@ -36,7 +36,7 @@ use crate::events::EventJson; use crate::identifiers::{RoomId, UserId}; use crate::models::Room; use crate::session::Session; -use crate::state::{ClientState, StateStore}; +use crate::state::{AllRooms, ClientState, StateStore}; use crate::EventEmitter; use std::ops::Deref; @@ -73,14 +73,15 @@ pub enum RoomStateType { /// An enum that represents the state of the given `Room`. /// /// If the event came from the `join`, `invite` or `leave` rooms map from the server -/// the variant that holds the corresponding room is used. -pub enum RoomState { +/// the variant that holds the corresponding room is used. `RoomState` is generic +/// so it can be used to represent a `Room` or an `Arc>` +pub enum RoomState { /// A room from the `join` section of a sync response. - Joined(Arc>), + Joined(R), /// A room from the `leave` section of a sync response. - Left(Arc>), + Left(R), /// A room from the `invite` section of a sync response. - Invited(Arc>), + Invited(R), } /// A no IO Client implementation. @@ -229,8 +230,20 @@ impl BaseClient { return Ok(false); } - let mut rooms = store.load_all_rooms().await?; - *self.joined_rooms.write().await = rooms + let AllRooms { + mut joined, + mut invited, + mut left, + } = store.load_all_rooms().await?; + *self.joined_rooms.write().await = joined + .drain() + .map(|(k, room)| (k, Arc::new(RwLock::new(room)))) + .collect(); + *self.invited_rooms.write().await = invited + .drain() + .map(|(k, room)| (k, Arc::new(RwLock::new(room)))) + .collect(); + *self.left_rooms.write().await = left .drain() .map(|(k, room)| (k, Arc::new(RwLock::new(room)))) .collect(); @@ -248,7 +261,21 @@ impl BaseClient { if let Some(store) = self.state_store.read().await.as_ref() { if let Some(room) = self.get_joined_room(room_id).await { let room = room.read().await; - store.store_room_state(room.deref()).await?; + store + .store_room_state(RoomState::Joined(room.deref())) + .await?; + } + if let Some(room) = self.get_invited_room(room_id).await { + let room = room.read().await; + store + .store_room_state(RoomState::Invited(room.deref())) + .await?; + } + if let Some(room) = self.get_left_room(room_id).await { + let room = room.read().await; + store + .store_room_state(RoomState::Left(room.deref())) + .await?; } } Ok(()) @@ -754,7 +781,7 @@ impl BaseClient { if updated { if let Some(store) = self.state_store.read().await.as_ref() { store - .store_room_state(matrix_room.read().await.deref()) + .store_room_state(RoomState::Joined(matrix_room.read().await.deref())) .await?; } } @@ -801,7 +828,7 @@ impl BaseClient { if updated { if let Some(store) = self.state_store.read().await.as_ref() { store - .store_room_state(matrix_room.read().await.deref()) + .store_room_state(RoomState::Left(matrix_room.read().await.deref())) .await?; } } @@ -837,7 +864,7 @@ impl BaseClient { if updated { if let Some(store) = self.state_store.read().await.as_ref() { store - .store_room_state(matrix_room.read().await.deref()) + .store_room_state(RoomState::Invited(matrix_room.read().await.deref())) .await?; } } diff --git a/matrix_sdk_base/src/event_emitter/mod.rs b/matrix_sdk_base/src/event_emitter/mod.rs index af7d179f..590d1602 100644 --- a/matrix_sdk_base/src/event_emitter/mod.rs +++ b/matrix_sdk_base/src/event_emitter/mod.rs @@ -12,6 +12,9 @@ // 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::sync::Arc; + +use tokio::sync::RwLock; use crate::events::{ fully_read::FullyReadEvent, @@ -37,7 +40,10 @@ use crate::events::{ }, typing::TypingEvent, }; -use crate::RoomState; +use crate::{Room, RoomState}; + +/// Type alias for `RoomState` enum when passed to `EventEmitter` methods. +pub type SyncRoom = RoomState>>; /// This trait allows any type implementing `EventEmitter` to specify event callbacks for each event. /// The `AsyncClient` calls each method when the corresponding event is received. @@ -52,7 +58,7 @@ use crate::RoomState; /// # events::{ /// # room::message::{MessageEvent, MessageEventContent, TextMessageEventContent}, /// # }, -/// # EventEmitter, RoomState +/// # EventEmitter, SyncRoom /// # }; /// use tokio::sync::RwLock; /// @@ -60,8 +66,8 @@ use crate::RoomState; /// /// #[async_trait::async_trait] /// impl EventEmitter for EventCallback { -/// async fn on_room_message(&self, room: RoomState, event: &MessageEvent) { -/// if let RoomState::Joined(room) = room { +/// async fn on_room_message(&self, room: SyncRoom, event: &MessageEvent) { +/// if let SyncRoom::Joined(room) = room { /// if let MessageEvent { /// content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }), /// sender, @@ -87,82 +93,78 @@ use crate::RoomState; pub trait EventEmitter: Send + Sync { // ROOM EVENTS from `IncomingTimeline` /// Fires when `AsyncClient` receives a `RoomEvent::RoomMember` event. - async fn on_room_member(&self, _: RoomState, _: &MemberEvent) {} + async fn on_room_member(&self, _: SyncRoom, _: &MemberEvent) {} /// Fires when `AsyncClient` receives a `RoomEvent::RoomName` event. - async fn on_room_name(&self, _: RoomState, _: &NameEvent) {} + async fn on_room_name(&self, _: SyncRoom, _: &NameEvent) {} /// Fires when `AsyncClient` receives a `RoomEvent::RoomCanonicalAlias` event. - async fn on_room_canonical_alias(&self, _: RoomState, _: &CanonicalAliasEvent) {} + async fn on_room_canonical_alias(&self, _: SyncRoom, _: &CanonicalAliasEvent) {} /// Fires when `AsyncClient` receives a `RoomEvent::RoomAliases` event. - async fn on_room_aliases(&self, _: RoomState, _: &AliasesEvent) {} + async fn on_room_aliases(&self, _: SyncRoom, _: &AliasesEvent) {} /// Fires when `AsyncClient` receives a `RoomEvent::RoomAvatar` event. - async fn on_room_avatar(&self, _: RoomState, _: &AvatarEvent) {} + async fn on_room_avatar(&self, _: SyncRoom, _: &AvatarEvent) {} /// Fires when `AsyncClient` receives a `RoomEvent::RoomMessage` event. - async fn on_room_message(&self, _: RoomState, _: &MessageEvent) {} + async fn on_room_message(&self, _: SyncRoom, _: &MessageEvent) {} /// Fires when `AsyncClient` receives a `RoomEvent::RoomMessageFeedback` event. - async fn on_room_message_feedback(&self, _: RoomState, _: &FeedbackEvent) {} + async fn on_room_message_feedback(&self, _: SyncRoom, _: &FeedbackEvent) {} /// Fires when `AsyncClient` receives a `RoomEvent::RoomRedaction` event. - async fn on_room_redaction(&self, _: RoomState, _: &RedactionEvent) {} + async fn on_room_redaction(&self, _: SyncRoom, _: &RedactionEvent) {} /// Fires when `AsyncClient` receives a `RoomEvent::RoomPowerLevels` event. - async fn on_room_power_levels(&self, _: RoomState, _: &PowerLevelsEvent) {} + async fn on_room_power_levels(&self, _: SyncRoom, _: &PowerLevelsEvent) {} /// Fires when `AsyncClient` receives a `RoomEvent::Tombstone` event. - async fn on_room_tombstone(&self, _: RoomState, _: &TombstoneEvent) {} + async fn on_room_tombstone(&self, _: SyncRoom, _: &TombstoneEvent) {} // `RoomEvent`s from `IncomingState` /// Fires when `AsyncClient` receives a `StateEvent::RoomMember` event. - async fn on_state_member(&self, _: RoomState, _: &MemberEvent) {} + async fn on_state_member(&self, _: SyncRoom, _: &MemberEvent) {} /// Fires when `AsyncClient` receives a `StateEvent::RoomName` event. - async fn on_state_name(&self, _: RoomState, _: &NameEvent) {} + async fn on_state_name(&self, _: SyncRoom, _: &NameEvent) {} /// Fires when `AsyncClient` receives a `StateEvent::RoomCanonicalAlias` event. - async fn on_state_canonical_alias(&self, _: RoomState, _: &CanonicalAliasEvent) {} + async fn on_state_canonical_alias(&self, _: SyncRoom, _: &CanonicalAliasEvent) {} /// Fires when `AsyncClient` receives a `StateEvent::RoomAliases` event. - async fn on_state_aliases(&self, _: RoomState, _: &AliasesEvent) {} + async fn on_state_aliases(&self, _: SyncRoom, _: &AliasesEvent) {} /// Fires when `AsyncClient` receives a `StateEvent::RoomAvatar` event. - async fn on_state_avatar(&self, _: RoomState, _: &AvatarEvent) {} + async fn on_state_avatar(&self, _: SyncRoom, _: &AvatarEvent) {} /// Fires when `AsyncClient` receives a `StateEvent::RoomPowerLevels` event. - async fn on_state_power_levels(&self, _: RoomState, _: &PowerLevelsEvent) {} + async fn on_state_power_levels(&self, _: SyncRoom, _: &PowerLevelsEvent) {} /// Fires when `AsyncClient` receives a `StateEvent::RoomJoinRules` event. - async fn on_state_join_rules(&self, _: RoomState, _: &JoinRulesEvent) {} + async fn on_state_join_rules(&self, _: SyncRoom, _: &JoinRulesEvent) {} // `AnyStrippedStateEvent`s /// Fires when `AsyncClient` receives a `AnyStrippedStateEvent::StrippedRoomMember` event. - async fn on_stripped_state_member(&self, _: RoomState, _: &StrippedRoomMember) {} + async fn on_stripped_state_member(&self, _: SyncRoom, _: &StrippedRoomMember) {} /// Fires when `AsyncClient` receives a `AnyStrippedStateEvent::StrippedRoomName` event. - async fn on_stripped_state_name(&self, _: RoomState, _: &StrippedRoomName) {} + async fn on_stripped_state_name(&self, _: SyncRoom, _: &StrippedRoomName) {} /// Fires when `AsyncClient` receives a `AnyStrippedStateEvent::StrippedRoomCanonicalAlias` event. - async fn on_stripped_state_canonical_alias( - &self, - _: RoomState, - _: &StrippedRoomCanonicalAlias, - ) { + async fn on_stripped_state_canonical_alias(&self, _: SyncRoom, _: &StrippedRoomCanonicalAlias) { } /// Fires when `AsyncClient` receives a `AnyStrippedStateEvent::StrippedRoomAliases` event. - async fn on_stripped_state_aliases(&self, _: RoomState, _: &StrippedRoomAliases) {} + async fn on_stripped_state_aliases(&self, _: SyncRoom, _: &StrippedRoomAliases) {} /// Fires when `AsyncClient` receives a `AnyStrippedStateEvent::StrippedRoomAvatar` event. - async fn on_stripped_state_avatar(&self, _: RoomState, _: &StrippedRoomAvatar) {} + async fn on_stripped_state_avatar(&self, _: SyncRoom, _: &StrippedRoomAvatar) {} /// Fires when `AsyncClient` receives a `AnyStrippedStateEvent::StrippedRoomPowerLevels` event. - async fn on_stripped_state_power_levels(&self, _: RoomState, _: &StrippedRoomPowerLevels) {} + async fn on_stripped_state_power_levels(&self, _: SyncRoom, _: &StrippedRoomPowerLevels) {} /// Fires when `AsyncClient` receives a `AnyStrippedStateEvent::StrippedRoomJoinRules` event. - async fn on_stripped_state_join_rules(&self, _: RoomState, _: &StrippedRoomJoinRules) {} + async fn on_stripped_state_join_rules(&self, _: SyncRoom, _: &StrippedRoomJoinRules) {} // `NonRoomEvent` (this is a type alias from ruma_events) /// Fires when `AsyncClient` receives a `NonRoomEvent::RoomMember` event. - async fn on_account_presence(&self, _: RoomState, _: &PresenceEvent) {} + async fn on_account_presence(&self, _: SyncRoom, _: &PresenceEvent) {} /// Fires when `AsyncClient` receives a `NonRoomEvent::RoomName` event. - async fn on_account_ignored_users(&self, _: RoomState, _: &IgnoredUserListEvent) {} + async fn on_account_ignored_users(&self, _: SyncRoom, _: &IgnoredUserListEvent) {} /// Fires when `AsyncClient` receives a `NonRoomEvent::RoomCanonicalAlias` event. - async fn on_account_push_rules(&self, _: RoomState, _: &PushRulesEvent) {} + async fn on_account_push_rules(&self, _: SyncRoom, _: &PushRulesEvent) {} /// Fires when `AsyncClient` receives a `NonRoomEvent::RoomAliases` event. - async fn on_account_data_fully_read(&self, _: RoomState, _: &FullyReadEvent) {} + async fn on_account_data_fully_read(&self, _: SyncRoom, _: &FullyReadEvent) {} /// Fires when `AsyncClient` receives a `NonRoomEvent::Typing` event. - async fn on_account_data_typing(&self, _: RoomState, _: &TypingEvent) {} + async fn on_account_data_typing(&self, _: SyncRoom, _: &TypingEvent) {} /// Fires when `AsyncClient` receives a `NonRoomEvent::Receipt` event. /// /// This is always a read receipt. - async fn on_account_data_receipt(&self, _: RoomState, _: &ReceiptEvent) {} + async fn on_account_data_receipt(&self, _: SyncRoom, _: &ReceiptEvent) {} // `PresenceEvent` is a struct so there is only the one method /// Fires when `AsyncClient` receives a `NonRoomEvent::RoomAliases` event. - async fn on_presence_event(&self, _: RoomState, _: &PresenceEvent) {} + async fn on_presence_event(&self, _: SyncRoom, _: &PresenceEvent) {} } #[cfg(test)] @@ -174,72 +176,74 @@ mod test { pub struct EvEmitterTest(Arc>>); #[async_trait::async_trait] + // we don't need to test our tests right? + #[cfg_attr(tarpaulin, skip)] impl EventEmitter for EvEmitterTest { - async fn on_room_member(&self, _: RoomState, _: &MemberEvent) { + async fn on_room_member(&self, _: SyncRoom, _: &MemberEvent) { self.0.lock().await.push("member".to_string()) } - async fn on_room_name(&self, _: RoomState, _: &NameEvent) { + async fn on_room_name(&self, _: SyncRoom, _: &NameEvent) { self.0.lock().await.push("name".to_string()) } - async fn on_room_canonical_alias(&self, _: RoomState, _: &CanonicalAliasEvent) { + async fn on_room_canonical_alias(&self, _: SyncRoom, _: &CanonicalAliasEvent) { self.0.lock().await.push("canonical".to_string()) } - async fn on_room_aliases(&self, _: RoomState, _: &AliasesEvent) { + async fn on_room_aliases(&self, _: SyncRoom, _: &AliasesEvent) { self.0.lock().await.push("aliases".to_string()) } - async fn on_room_avatar(&self, _: RoomState, _: &AvatarEvent) { + async fn on_room_avatar(&self, _: SyncRoom, _: &AvatarEvent) { self.0.lock().await.push("avatar".to_string()) } - async fn on_room_message(&self, _: RoomState, _: &MessageEvent) { + async fn on_room_message(&self, _: SyncRoom, _: &MessageEvent) { self.0.lock().await.push("message".to_string()) } - async fn on_room_message_feedback(&self, _: RoomState, _: &FeedbackEvent) { + async fn on_room_message_feedback(&self, _: SyncRoom, _: &FeedbackEvent) { self.0.lock().await.push("feedback".to_string()) } - async fn on_room_redaction(&self, _: RoomState, _: &RedactionEvent) { + async fn on_room_redaction(&self, _: SyncRoom, _: &RedactionEvent) { self.0.lock().await.push("redaction".to_string()) } - async fn on_room_power_levels(&self, _: RoomState, _: &PowerLevelsEvent) { + async fn on_room_power_levels(&self, _: SyncRoom, _: &PowerLevelsEvent) { self.0.lock().await.push("power".to_string()) } - async fn on_room_tombstone(&self, _: RoomState, _: &TombstoneEvent) { + async fn on_room_tombstone(&self, _: SyncRoom, _: &TombstoneEvent) { self.0.lock().await.push("tombstone".to_string()) } - async fn on_state_member(&self, _: RoomState, _: &MemberEvent) { + async fn on_state_member(&self, _: SyncRoom, _: &MemberEvent) { self.0.lock().await.push("state member".to_string()) } - async fn on_state_name(&self, _: RoomState, _: &NameEvent) { + async fn on_state_name(&self, _: SyncRoom, _: &NameEvent) { self.0.lock().await.push("state name".to_string()) } - async fn on_state_canonical_alias(&self, _: RoomState, _: &CanonicalAliasEvent) { + async fn on_state_canonical_alias(&self, _: SyncRoom, _: &CanonicalAliasEvent) { self.0.lock().await.push("state canonical".to_string()) } - async fn on_state_aliases(&self, _: RoomState, _: &AliasesEvent) { + async fn on_state_aliases(&self, _: SyncRoom, _: &AliasesEvent) { self.0.lock().await.push("state aliases".to_string()) } - async fn on_state_avatar(&self, _: RoomState, _: &AvatarEvent) { + async fn on_state_avatar(&self, _: SyncRoom, _: &AvatarEvent) { self.0.lock().await.push("state avatar".to_string()) } - async fn on_state_power_levels(&self, _: RoomState, _: &PowerLevelsEvent) { + async fn on_state_power_levels(&self, _: SyncRoom, _: &PowerLevelsEvent) { self.0.lock().await.push("state power".to_string()) } - async fn on_state_join_rules(&self, _: RoomState, _: &JoinRulesEvent) { + async fn on_state_join_rules(&self, _: SyncRoom, _: &JoinRulesEvent) { self.0.lock().await.push("state rules".to_string()) } - async fn on_stripped_state_member(&self, _: RoomState, _: &StrippedRoomMember) { + async fn on_stripped_state_member(&self, _: SyncRoom, _: &StrippedRoomMember) { self.0 .lock() .await .push("stripped state member".to_string()) } - async fn on_stripped_state_name(&self, _: RoomState, _: &StrippedRoomName) { + async fn on_stripped_state_name(&self, _: SyncRoom, _: &StrippedRoomName) { self.0.lock().await.push("stripped state name".to_string()) } async fn on_stripped_state_canonical_alias( &self, - _: RoomState, + _: SyncRoom, _: &StrippedRoomCanonicalAlias, ) { self.0 @@ -247,38 +251,38 @@ mod test { .await .push("stripped state canonical".to_string()) } - async fn on_stripped_state_aliases(&self, _: RoomState, _: &StrippedRoomAliases) { + async fn on_stripped_state_aliases(&self, _: SyncRoom, _: &StrippedRoomAliases) { self.0 .lock() .await .push("stripped state aliases".to_string()) } - async fn on_stripped_state_avatar(&self, _: RoomState, _: &StrippedRoomAvatar) { + async fn on_stripped_state_avatar(&self, _: SyncRoom, _: &StrippedRoomAvatar) { self.0 .lock() .await .push("stripped state avatar".to_string()) } - async fn on_stripped_state_power_levels(&self, _: RoomState, _: &StrippedRoomPowerLevels) { + async fn on_stripped_state_power_levels(&self, _: SyncRoom, _: &StrippedRoomPowerLevels) { self.0.lock().await.push("stripped state power".to_string()) } - async fn on_stripped_state_join_rules(&self, _: RoomState, _: &StrippedRoomJoinRules) { + async fn on_stripped_state_join_rules(&self, _: SyncRoom, _: &StrippedRoomJoinRules) { self.0.lock().await.push("stripped state rules".to_string()) } - async fn on_account_presence(&self, _: RoomState, _: &PresenceEvent) { + async fn on_account_presence(&self, _: SyncRoom, _: &PresenceEvent) { self.0.lock().await.push("account presence".to_string()) } - async fn on_account_ignored_users(&self, _: RoomState, _: &IgnoredUserListEvent) { + async fn on_account_ignored_users(&self, _: SyncRoom, _: &IgnoredUserListEvent) { self.0.lock().await.push("account ignore".to_string()) } - async fn on_account_push_rules(&self, _: RoomState, _: &PushRulesEvent) { + async fn on_account_push_rules(&self, _: SyncRoom, _: &PushRulesEvent) { self.0.lock().await.push("account push rules".to_string()) } - async fn on_account_data_fully_read(&self, _: RoomState, _: &FullyReadEvent) { + async fn on_account_data_fully_read(&self, _: SyncRoom, _: &FullyReadEvent) { self.0.lock().await.push("account read".to_string()) } - async fn on_presence_event(&self, _: RoomState, _: &PresenceEvent) { + async fn on_presence_event(&self, _: SyncRoom, _: &PresenceEvent) { self.0.lock().await.push("presence event".to_string()) } } diff --git a/matrix_sdk_base/src/lib.rs b/matrix_sdk_base/src/lib.rs index c1865a3c..0d386a37 100644 --- a/matrix_sdk_base/src/lib.rs +++ b/matrix_sdk_base/src/lib.rs @@ -37,8 +37,8 @@ mod session; mod state; pub use client::{BaseClient, RoomState, RoomStateType}; -pub use event_emitter::EventEmitter; +pub use event_emitter::{EventEmitter, SyncRoom}; #[cfg(feature = "encryption")] pub use matrix_sdk_crypto::{Device, TrustState}; pub use models::Room; -pub use state::{JsonStore, StateStore}; +pub use state::{AllRooms, JsonStore, StateStore}; diff --git a/matrix_sdk_base/src/models/message.rs b/matrix_sdk_base/src/models/message.rs index 2aed0ec5..47c2acdb 100644 --- a/matrix_sdk_base/src/models/message.rs +++ b/matrix_sdk_base/src/models/message.rs @@ -167,7 +167,9 @@ mod test { let mut joined_rooms = HashMap::new(); joined_rooms.insert(id, room); - println!("{}", serde_json::to_string_pretty(&joined_rooms).unwrap()); + + // println!("{}", serde_json::to_string_pretty(&joined_rooms).unwrap()); + // this is the correct JSON string changes to `ruma-events` have not been released // that would fix the doubling of fields // TODO uncomment when fixed diff --git a/matrix_sdk_base/src/state/mod.rs b/matrix_sdk_base/src/state/mod.rs index a53ec622..ca2fb04c 100644 --- a/matrix_sdk_base/src/state/mod.rs +++ b/matrix_sdk_base/src/state/mod.rs @@ -14,15 +14,14 @@ // limitations under the License. use serde::{Deserialize, Serialize}; -use std::collections::HashMap; pub mod state_store; -pub use state_store::JsonStore; +pub use state_store::{AllRooms, JsonStore}; use crate::client::{BaseClient, Token}; use crate::events::push_rules::Ruleset; -use crate::identifiers::{RoomId, UserId}; -use crate::{Result, Room, Session}; +use crate::identifiers::UserId; +use crate::{Result, Room, RoomState, Session}; /// `ClientState` holds all the information to restore a `BaseClient` /// except the `access_token` as the default store is not secure. @@ -73,11 +72,11 @@ pub trait StateStore: Send + Sync { /// Load the state of all `Room`s. /// /// This will be mapped over in the client in order to store `Room`s in an async safe way. - async fn load_all_rooms(&self) -> Result>; + async fn load_all_rooms(&self) -> Result; /// Save the current state of the `BaseClient` using the `StateStore::Store` type. async fn store_client_state(&self, _: ClientState) -> Result<()>; /// Save the state a single `Room`. - async fn store_room_state(&self, _: &Room) -> Result<()>; + async fn store_room_state(&self, _: RoomState<&Room>) -> Result<()>; } #[cfg(test)] @@ -87,6 +86,8 @@ mod test { use std::collections::HashMap; use std::convert::TryFrom; + use crate::identifiers::RoomId; + #[test] fn serialize() { let id = RoomId::try_from("!roomid:example.com").unwrap(); diff --git a/matrix_sdk_base/src/state/state_store.rs b/matrix_sdk_base/src/state/state_store.rs index e26f35b6..fe66c29e 100644 --- a/matrix_sdk_base/src/state/state_store.rs +++ b/matrix_sdk_base/src/state/state_store.rs @@ -12,7 +12,20 @@ use tokio::sync::RwLock; use super::{ClientState, StateStore}; use crate::identifiers::RoomId; -use crate::{Error, Result, Room, Session}; +use crate::{Error, Result, Room, RoomState, Session}; + +/// `JsonStore::load_all_rooms` returns `AllRooms`. +/// +/// `AllRooms` is made of the `joined`, `invited` and `left` room maps. +pub struct AllRooms { + /// The joined room mapping of `RoomId` to `Room`. + pub joined: HashMap, + /// The invited room mapping of `RoomId` to `Room`. + pub invited: HashMap, + /// The left room mapping of `RoomId` to `Room`. + pub left: HashMap, +} + /// A default `StateStore` implementation that serializes state as json /// and saves it to disk. /// @@ -60,34 +73,55 @@ impl StateStore for JsonStore { } } - async fn load_all_rooms(&self) -> Result> { + async fn load_all_rooms(&self) -> Result { let mut path = self.path.read().await.clone(); path.push("rooms"); - let mut rooms_map = HashMap::new(); - for file in fs::read_dir(&path)? { - let file = file?.path(); - - if file.is_dir() { + let mut joined = HashMap::new(); + let mut left = HashMap::new(); + let mut invited = HashMap::new(); + for room_state_type in &["joined", "invited", "left"] { + path.push(room_state_type); + // don't load rooms that aren't saved yet + if !path.exists() { + path.pop(); continue; } - let json = async_fs::read_to_string(&file).await?; + for file in fs::read_dir(&path)? { + let file = file?.path(); - let room = serde_json::from_str::(&json).map_err(Error::from)?; - let room_id = room.room_id.clone(); + if file.is_dir() { + continue; + } - rooms_map.insert(room_id, room); + let json = async_fs::read_to_string(&file).await?; + + let room = serde_json::from_str::(&json).map_err(Error::from)?; + let room_id = room.room_id.clone(); + + match *room_state_type { + "joined" => joined.insert(room_id, room), + "invited" => invited.insert(room_id, room), + "left" => left.insert(room_id, room), + _ => unreachable!("an array with 3 const elements was altered in JsonStore"), + }; + } + path.pop(); } - Ok(rooms_map) + Ok(AllRooms { + joined, + left, + invited, + }) } async fn store_client_state(&self, state: ClientState) -> Result<()> { let mut path = self.path.read().await.clone(); path.push("client.json"); - if !Path::new(&path).exists() { + if !path.exists() { let mut dir = path.clone(); dir.pop(); async_fs::create_dir_all(dir).await?; @@ -104,16 +138,23 @@ impl StateStore for JsonStore { file.write_all(json.as_bytes()).await.map_err(Error::from) } - async fn store_room_state(&self, room: &Room) -> Result<()> { + async fn store_room_state(&self, room: RoomState<&Room>) -> Result<()> { + let (room, room_state) = match room { + RoomState::Joined(room) => (room, "joined"), + RoomState::Invited(room) => (room, "invited"), + RoomState::Left(room) => (room, "left"), + }; + if !self.user_path_set.load(Ordering::SeqCst) { self.user_path_set.swap(true, Ordering::SeqCst); self.path.write().await.push(room.own_user_id.localpart()) } let mut path = self.path.read().await.clone(); - path.push(&format!("rooms/{}.json", room.room_id)); + path.push("rooms"); + path.push(&format!("{}/{}.json", room_state, room.room_id)); - if !Path::new(&path).exists() { + if !path.exists() { let mut dir = path.clone(); dir.pop(); async_fs::create_dir_all(dir).await?; @@ -187,7 +228,7 @@ mod test { } #[tokio::test] - async fn test_store_room_state() { + async fn test_store_load_joined_room_state() { let dir = tempdir().unwrap(); let path: &Path = dir.path(); let store = JsonStore::open(path).unwrap(); @@ -196,13 +237,16 @@ mod test { let user = UserId::try_from("@example:example.com").unwrap(); let room = Room::new(&id, &user); - store.store_room_state(&room).await.unwrap(); - let loaded = store.load_all_rooms().await.unwrap(); - assert_eq!(loaded.get(&id), Some(&Room::new(&id, &user))); + store + .store_room_state(RoomState::Joined(&room)) + .await + .unwrap(); + let AllRooms { joined, .. } = store.load_all_rooms().await.unwrap(); + assert_eq!(joined.get(&id), Some(&Room::new(&id, &user))); } #[tokio::test] - async fn test_load_rooms() { + async fn test_store_load_left_room_state() { let dir = tempdir().unwrap(); let path: &Path = dir.path(); let store = JsonStore::open(path).unwrap(); @@ -211,9 +255,30 @@ mod test { let user = UserId::try_from("@example:example.com").unwrap(); let room = Room::new(&id, &user); - store.store_room_state(&room).await.unwrap(); - let loaded = store.load_all_rooms().await.unwrap(); - assert_eq!(&room, loaded.get(&id).unwrap()); + store + .store_room_state(RoomState::Left(&room)) + .await + .unwrap(); + let AllRooms { left, .. } = store.load_all_rooms().await.unwrap(); + assert_eq!(left.get(&id), Some(&Room::new(&id, &user))); + } + + #[tokio::test] + async fn test_store_load_invited_room_state() { + let dir = tempdir().unwrap(); + let path: &Path = dir.path(); + let store = JsonStore::open(path).unwrap(); + + let id = RoomId::try_from("!roomid:example.com").unwrap(); + let user = UserId::try_from("@example:example.com").unwrap(); + + let room = Room::new(&id, &user); + store + .store_room_state(RoomState::Invited(&room)) + .await + .unwrap(); + let AllRooms { invited, .. } = store.load_all_rooms().await.unwrap(); + assert_eq!(invited.get(&id), Some(&Room::new(&id, &user))); } #[tokio::test] From 2c45513b976d01358f0e8515ae79a9ed4332b063 Mon Sep 17 00:00:00 2001 From: Devin R Date: Mon, 11 May 2020 17:38:18 -0400 Subject: [PATCH 3/3] remove tarpaulin skip attr --- matrix_sdk_base/src/event_emitter/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/matrix_sdk_base/src/event_emitter/mod.rs b/matrix_sdk_base/src/event_emitter/mod.rs index 590d1602..162f04cd 100644 --- a/matrix_sdk_base/src/event_emitter/mod.rs +++ b/matrix_sdk_base/src/event_emitter/mod.rs @@ -176,8 +176,6 @@ mod test { pub struct EvEmitterTest(Arc>>); #[async_trait::async_trait] - // we don't need to test our tests right? - #[cfg_attr(tarpaulin, skip)] impl EventEmitter for EvEmitterTest { async fn on_room_member(&self, _: SyncRoom, _: &MemberEvent) { self.0.lock().await.push("member".to_string())