Merge branch 'store-room'

master
Damir Jelić 2020-05-13 09:23:01 +02:00
commit 9b38033cec
10 changed files with 240 additions and 118 deletions

View File

@ -3,7 +3,7 @@ use std::{env, process::exit};
use matrix_sdk::{ use matrix_sdk::{
self, self,
events::room::message::{MessageEvent, MessageEventContent, TextMessageEventContent}, events::room::message::{MessageEvent, MessageEventContent, TextMessageEventContent},
Client, ClientConfig, EventEmitter, JsonStore, RoomState, SyncSettings, Client, ClientConfig, EventEmitter, JsonStore, SyncRoom, SyncSettings,
}; };
use url::Url; use url::Url;
@ -21,8 +21,8 @@ impl CommandBot {
#[async_trait::async_trait] #[async_trait::async_trait]
impl EventEmitter for CommandBot { impl EventEmitter for CommandBot {
async fn on_room_message(&self, room: RoomState, event: &MessageEvent) { async fn on_room_message(&self, room: SyncRoom, event: &MessageEvent) {
if let RoomState::Joined(room) = room { if let SyncRoom::Joined(room) = room {
let msg_body = if let MessageEvent { let msg_body = if let MessageEvent {
content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }), content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }),
.. ..

View File

@ -4,15 +4,15 @@ use url::Url;
use matrix_sdk::{ use matrix_sdk::{
self, self,
events::room::message::{MessageEvent, MessageEventContent, TextMessageEventContent}, events::room::message::{MessageEvent, MessageEventContent, TextMessageEventContent},
Client, ClientConfig, EventEmitter, RoomState, SyncSettings, Client, ClientConfig, EventEmitter, SyncRoom, SyncSettings,
}; };
struct EventCallback; struct EventCallback;
#[async_trait::async_trait] #[async_trait::async_trait]
impl EventEmitter for EventCallback { impl EventEmitter for EventCallback {
async fn on_room_message(&self, room: RoomState, event: &MessageEvent) { async fn on_room_message(&self, room: SyncRoom, event: &MessageEvent) {
if let RoomState::Joined(room) = room { if let SyncRoom::Joined(room) = room {
if let MessageEvent { if let MessageEvent {
content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }), content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }),
sender, sender,

View File

@ -404,6 +404,17 @@ impl Client {
Ok(self.base_client.sync_with_state_store().await?) 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. /// Login to the server.
/// ///
/// # Arguments /// # Arguments

View File

@ -28,7 +28,7 @@
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub use matrix_sdk_base::JsonStore; pub use matrix_sdk_base::JsonStore;
pub use matrix_sdk_base::{EventEmitter, Room, Session}; pub use matrix_sdk_base::{EventEmitter, Room, Session, SyncRoom};
pub use matrix_sdk_base::{RoomState, StateStore}; pub use matrix_sdk_base::{RoomState, StateStore};
pub use matrix_sdk_common::*; pub use matrix_sdk_common::*;
pub use reqwest::header::InvalidHeaderValue; pub use reqwest::header::InvalidHeaderValue;

View File

@ -36,7 +36,7 @@ use crate::events::EventJson;
use crate::identifiers::{RoomId, UserId}; use crate::identifiers::{RoomId, UserId};
use crate::models::Room; use crate::models::Room;
use crate::session::Session; use crate::session::Session;
use crate::state::{ClientState, StateStore}; use crate::state::{AllRooms, ClientState, StateStore};
use crate::EventEmitter; use crate::EventEmitter;
#[cfg(feature = "encryption")] #[cfg(feature = "encryption")]
@ -73,14 +73,15 @@ pub enum RoomStateType {
/// An enum that represents the state of the given `Room`. /// 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 /// If the event came from the `join`, `invite` or `leave` rooms map from the server
/// the variant that holds the corresponding room is used. /// the variant that holds the corresponding room is used. `RoomState` is generic
pub enum RoomState { /// so it can be used to represent a `Room` or an `Arc<RwLock<Room>>`
pub enum RoomState<R> {
/// A room from the `join` section of a sync response. /// A room from the `join` section of a sync response.
Joined(Arc<RwLock<Room>>), Joined(R),
/// A room from the `leave` section of a sync response. /// A room from the `leave` section of a sync response.
Left(Arc<RwLock<Room>>), Left(R),
/// A room from the `invite` section of a sync response. /// A room from the `invite` section of a sync response.
Invited(Arc<RwLock<Room>>), Invited(R),
} }
/// A no IO Client implementation. /// A no IO Client implementation.
@ -229,8 +230,20 @@ impl BaseClient {
return Ok(false); return Ok(false);
} }
let mut rooms = store.load_all_rooms().await?; let AllRooms {
*self.joined_rooms.write().await = rooms 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() .drain()
.map(|(k, room)| (k, Arc::new(RwLock::new(room)))) .map(|(k, room)| (k, Arc::new(RwLock::new(room))))
.collect(); .collect();
@ -241,6 +254,33 @@ impl BaseClient {
Ok(!self.needs_state_store_sync.load(Ordering::Relaxed)) 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(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(())
}
/// Receive a login response and update the session of the client. /// Receive a login response and update the session of the client.
/// ///
/// # Arguments /// # Arguments
@ -619,7 +659,7 @@ impl BaseClient {
// TODO do we want to move the rooms to the appropriate HashMaps when the corresponding // 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? // 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_joined = self.iter_joined_rooms(response).await?;
let updated_invited = self.iter_invited_rooms(&response).await?; let updated_invited = self.iter_invited_rooms(&response).await?;
let updated_left = self.iter_left_rooms(response).await?; let updated_left = self.iter_left_rooms(response).await?;
@ -741,7 +781,7 @@ impl BaseClient {
if updated { if updated {
if let Some(store) = self.state_store.read().await.as_ref() { if let Some(store) = self.state_store.read().await.as_ref() {
store store
.store_room_state(matrix_room.read().await.deref()) .store_room_state(RoomState::Joined(matrix_room.read().await.deref()))
.await?; .await?;
} }
} }
@ -788,7 +828,7 @@ impl BaseClient {
if updated { if updated {
if let Some(store) = self.state_store.read().await.as_ref() { if let Some(store) = self.state_store.read().await.as_ref() {
store store
.store_room_state(matrix_room.read().await.deref()) .store_room_state(RoomState::Left(matrix_room.read().await.deref()))
.await?; .await?;
} }
} }
@ -824,7 +864,7 @@ impl BaseClient {
if updated { if updated {
if let Some(store) = self.state_store.read().await.as_ref() { if let Some(store) = self.state_store.read().await.as_ref() {
store store
.store_room_state(matrix_room.read().await.deref()) .store_room_state(RoomState::Invited(matrix_room.read().await.deref()))
.await?; .await?;
} }
} }

View File

@ -12,6 +12,9 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::sync::Arc;
use tokio::sync::RwLock;
use crate::events::{ use crate::events::{
fully_read::FullyReadEvent, fully_read::FullyReadEvent,
@ -37,7 +40,10 @@ use crate::events::{
}, },
typing::TypingEvent, typing::TypingEvent,
}; };
use crate::RoomState; use crate::{Room, RoomState};
/// Type alias for `RoomState` enum when passed to `EventEmitter` methods.
pub type SyncRoom = RoomState<Arc<RwLock<Room>>>;
/// This trait allows any type implementing `EventEmitter` to specify event callbacks for each event. /// 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. /// The `AsyncClient` calls each method when the corresponding event is received.
@ -52,7 +58,7 @@ use crate::RoomState;
/// # events::{ /// # events::{
/// # room::message::{MessageEvent, MessageEventContent, TextMessageEventContent}, /// # room::message::{MessageEvent, MessageEventContent, TextMessageEventContent},
/// # }, /// # },
/// # EventEmitter, RoomState /// # EventEmitter, SyncRoom
/// # }; /// # };
/// use tokio::sync::RwLock; /// use tokio::sync::RwLock;
/// ///
@ -60,8 +66,8 @@ use crate::RoomState;
/// ///
/// #[async_trait::async_trait] /// #[async_trait::async_trait]
/// impl EventEmitter for EventCallback { /// impl EventEmitter for EventCallback {
/// async fn on_room_message(&self, room: RoomState, event: &MessageEvent) { /// async fn on_room_message(&self, room: SyncRoom, event: &MessageEvent) {
/// if let RoomState::Joined(room) = room { /// if let SyncRoom::Joined(room) = room {
/// if let MessageEvent { /// if let MessageEvent {
/// content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }), /// content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }),
/// sender, /// sender,
@ -87,82 +93,78 @@ use crate::RoomState;
pub trait EventEmitter: Send + Sync { pub trait EventEmitter: Send + Sync {
// ROOM EVENTS from `IncomingTimeline` // ROOM EVENTS from `IncomingTimeline`
/// Fires when `AsyncClient` receives a `RoomEvent::RoomMember` event. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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` // `RoomEvent`s from `IncomingState`
/// Fires when `AsyncClient` receives a `StateEvent::RoomMember` event. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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 // `AnyStrippedStateEvent`s
/// Fires when `AsyncClient` receives a `AnyStrippedStateEvent::StrippedRoomMember` event. /// 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. /// 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. /// Fires when `AsyncClient` receives a `AnyStrippedStateEvent::StrippedRoomCanonicalAlias` event.
async fn on_stripped_state_canonical_alias( async fn on_stripped_state_canonical_alias(&self, _: SyncRoom, _: &StrippedRoomCanonicalAlias) {
&self,
_: RoomState,
_: &StrippedRoomCanonicalAlias,
) {
} }
/// Fires when `AsyncClient` receives a `AnyStrippedStateEvent::StrippedRoomAliases` event. /// 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. /// 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. /// 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. /// 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) // `NonRoomEvent` (this is a type alias from ruma_events)
/// Fires when `AsyncClient` receives a `NonRoomEvent::RoomMember` event. /// 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. /// 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. /// 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. /// 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. /// 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. /// Fires when `AsyncClient` receives a `NonRoomEvent::Receipt` event.
/// ///
/// This is always a read receipt. /// 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 // `PresenceEvent` is a struct so there is only the one method
/// Fires when `AsyncClient` receives a `NonRoomEvent::RoomAliases` event. /// 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)] #[cfg(test)]
@ -180,71 +182,71 @@ mod test {
#[async_trait::async_trait] #[async_trait::async_trait]
impl EventEmitter for EvEmitterTest { 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()) 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()) 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()) 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()) 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()) 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()) 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()) 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()) 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()) 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()) 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()) 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()) 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()) 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()) 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()) 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()) 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()) 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 self.0
.lock() .lock()
.await .await
.push("stripped state member".to_string()) .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()) self.0.lock().await.push("stripped state name".to_string())
} }
async fn on_stripped_state_canonical_alias( async fn on_stripped_state_canonical_alias(
&self, &self,
_: RoomState, _: SyncRoom,
_: &StrippedRoomCanonicalAlias, _: &StrippedRoomCanonicalAlias,
) { ) {
self.0 self.0
@ -252,38 +254,38 @@ mod test {
.await .await
.push("stripped state canonical".to_string()) .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 self.0
.lock() .lock()
.await .await
.push("stripped state aliases".to_string()) .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 self.0
.lock() .lock()
.await .await
.push("stripped state avatar".to_string()) .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()) 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()) 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()) 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()) 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()) 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()) 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()) self.0.lock().await.push("presence event".to_string())
} }
} }

View File

@ -37,7 +37,7 @@ mod session;
mod state; mod state;
pub use client::{BaseClient, RoomState, RoomStateType}; pub use client::{BaseClient, RoomState, RoomStateType};
pub use event_emitter::EventEmitter; pub use event_emitter::{EventEmitter, SyncRoom};
#[cfg(feature = "encryption")] #[cfg(feature = "encryption")]
pub use matrix_sdk_crypto::{Device, TrustState}; pub use matrix_sdk_crypto::{Device, TrustState};
pub use models::Room; pub use models::Room;

View File

@ -170,7 +170,9 @@ mod test {
let mut joined_rooms = HashMap::new(); let mut joined_rooms = HashMap::new();
joined_rooms.insert(id, room); 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 // this is the correct JSON string changes to `ruma-events` have not been released
// that would fix the doubling of fields // that would fix the doubling of fields
// TODO uncomment when fixed // TODO uncomment when fixed

View File

@ -14,17 +14,17 @@
// limitations under the License. // limitations under the License.
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub mod state_store; pub mod state_store;
pub use state_store::AllRooms;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub use state_store::JsonStore; pub use state_store::JsonStore;
use crate::client::{BaseClient, Token}; use crate::client::{BaseClient, Token};
use crate::events::push_rules::Ruleset; use crate::events::push_rules::Ruleset;
use crate::identifiers::{RoomId, UserId}; use crate::identifiers::UserId;
use crate::{Result, Room, Session}; use crate::{Result, Room, RoomState, Session};
/// `ClientState` holds all the information to restore a `BaseClient` /// `ClientState` holds all the information to restore a `BaseClient`
/// except the `access_token` as the default store is not secure. /// except the `access_token` as the default store is not secure.
@ -75,11 +75,11 @@ pub trait StateStore: Send + Sync {
/// Load the state of all `Room`s. /// 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. /// 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<HashMap<RoomId, Room>>; async fn load_all_rooms(&self) -> Result<AllRooms>;
/// Save the current state of the `BaseClient` using the `StateStore::Store` type. /// Save the current state of the `BaseClient` using the `StateStore::Store` type.
async fn store_client_state(&self, _: ClientState) -> Result<()>; async fn store_client_state(&self, _: ClientState) -> Result<()>;
/// Save the state a single `Room`. /// 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)] #[cfg(test)]
@ -89,6 +89,8 @@ mod test {
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::TryFrom; use std::convert::TryFrom;
use crate::identifiers::RoomId;
#[test] #[test]
fn serialize() { fn serialize() {
let id = RoomId::try_from("!roomid:example.com").unwrap(); let id = RoomId::try_from("!roomid:example.com").unwrap();

View File

@ -12,7 +12,20 @@ use tokio::io::AsyncWriteExt;
use super::{ClientState, StateStore}; use super::{ClientState, StateStore};
use crate::identifiers::RoomId; 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<RoomId, Room>,
/// The invited room mapping of `RoomId` to `Room`.
pub invited: HashMap<RoomId, Room>,
/// The left room mapping of `RoomId` to `Room`.
pub left: HashMap<RoomId, Room>,
}
/// A default `StateStore` implementation that serializes state as json /// A default `StateStore` implementation that serializes state as json
/// and saves it to disk. /// and saves it to disk.
/// ///
@ -60,34 +73,55 @@ impl StateStore for JsonStore {
} }
} }
async fn load_all_rooms(&self) -> Result<HashMap<RoomId, Room>> { async fn load_all_rooms(&self) -> Result<AllRooms> {
let mut path = self.path.read().await.clone(); let mut path = self.path.read().await.clone();
path.push("rooms"); path.push("rooms");
let mut rooms_map = HashMap::new(); let mut joined = HashMap::new();
for file in fs::read_dir(&path)? { let mut left = HashMap::new();
let file = file?.path(); let mut invited = HashMap::new();
for room_state_type in &["joined", "invited", "left"] {
if file.is_dir() { path.push(room_state_type);
// don't load rooms that aren't saved yet
if !path.exists() {
path.pop();
continue; 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::<Room>(&json).map_err(Error::from)?; if file.is_dir() {
let room_id = room.room_id.clone(); continue;
}
rooms_map.insert(room_id, room); let json = async_fs::read_to_string(&file).await?;
let room = serde_json::from_str::<Room>(&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<()> { async fn store_client_state(&self, state: ClientState) -> Result<()> {
let mut path = self.path.read().await.clone(); let mut path = self.path.read().await.clone();
path.push("client.json"); path.push("client.json");
if !Path::new(&path).exists() { if !path.exists() {
let mut dir = path.clone(); let mut dir = path.clone();
dir.pop(); dir.pop();
async_fs::create_dir_all(dir).await?; 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) 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) { if !self.user_path_set.load(Ordering::SeqCst) {
self.user_path_set.swap(true, Ordering::SeqCst); self.user_path_set.swap(true, Ordering::SeqCst);
self.path.write().await.push(room.own_user_id.localpart()) self.path.write().await.push(room.own_user_id.localpart())
} }
let mut path = self.path.read().await.clone(); 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(); let mut dir = path.clone();
dir.pop(); dir.pop();
async_fs::create_dir_all(dir).await?; async_fs::create_dir_all(dir).await?;
@ -187,7 +228,7 @@ mod test {
} }
#[tokio::test] #[tokio::test]
async fn test_store_room_state() { async fn test_store_load_joined_room_state() {
let dir = tempdir().unwrap(); let dir = tempdir().unwrap();
let path: &Path = dir.path(); let path: &Path = dir.path();
let store = JsonStore::open(path).unwrap(); let store = JsonStore::open(path).unwrap();
@ -196,13 +237,16 @@ mod test {
let user = UserId::try_from("@example:example.com").unwrap(); let user = UserId::try_from("@example:example.com").unwrap();
let room = Room::new(&id, &user); let room = Room::new(&id, &user);
store.store_room_state(&room).await.unwrap(); store
let loaded = store.load_all_rooms().await.unwrap(); .store_room_state(RoomState::Joined(&room))
assert_eq!(loaded.get(&id), Some(&Room::new(&id, &user))); .await
.unwrap();
let AllRooms { joined, .. } = store.load_all_rooms().await.unwrap();
assert_eq!(joined.get(&id), Some(&Room::new(&id, &user)));
} }
#[tokio::test] #[tokio::test]
async fn test_load_rooms() { async fn test_store_load_left_room_state() {
let dir = tempdir().unwrap(); let dir = tempdir().unwrap();
let path: &Path = dir.path(); let path: &Path = dir.path();
let store = JsonStore::open(path).unwrap(); let store = JsonStore::open(path).unwrap();
@ -211,9 +255,30 @@ mod test {
let user = UserId::try_from("@example:example.com").unwrap(); let user = UserId::try_from("@example:example.com").unwrap();
let room = Room::new(&id, &user); let room = Room::new(&id, &user);
store.store_room_state(&room).await.unwrap(); store
let loaded = store.load_all_rooms().await.unwrap(); .store_room_state(RoomState::Left(&room))
assert_eq!(&room, loaded.get(&id).unwrap()); .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] #[tokio::test]