state_store: load/store each room type (invite, join, left), add type for returning 3 room maps

This commit is contained in:
Devin R 2020-05-11 15:32:58 -04:00
parent 22c4a1f2e7
commit 21712d0930
9 changed files with 220 additions and 121 deletions

View file

@ -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, .. }),
..

View file

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

View file

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

View file

@ -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<RwLock<Room>>`
pub enum RoomState<R> {
/// 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.
Left(Arc<RwLock<Room>>),
Left(R),
/// A room from the `invite` section of a sync response.
Invited(Arc<RwLock<Room>>),
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?;
}
}

View file

@ -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<Arc<RwLock<Room>>>;
/// 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<Mutex<Vec<String>>>);
#[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())
}
}

View file

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

View file

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

View file

@ -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<HashMap<RoomId, Room>>;
async fn load_all_rooms(&self) -> Result<AllRooms>;
/// 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();

View file

@ -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<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
/// 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();
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::<Room>(&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::<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<()> {
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]