diff --git a/matrix_sdk_base/examples/state_inspector.rs b/matrix_sdk_base/examples/state_inspector.rs index f4b5b35d..e0688b12 100644 --- a/matrix_sdk_base/examples/state_inspector.rs +++ b/matrix_sdk_base/examples/state_inspector.rs @@ -247,7 +247,7 @@ impl Printer { impl Inspector { fn new(database_path: &str, json: bool, color: bool) -> Self { let printer = Printer::new(json, color); - let store = Store::open_default(database_path).unwrap(); + let store = Store::open_default(database_path, None).unwrap(); Self { store, printer } } diff --git a/matrix_sdk_base/src/client.rs b/matrix_sdk_base/src/client.rs index 6c34912f..d56ef51b 100644 --- a/matrix_sdk_base/src/client.rs +++ b/matrix_sdk_base/src/client.rs @@ -61,7 +61,7 @@ use crate::{ event_emitter::Emitter, rooms::{RoomInfo, RoomType, StrippedRoomInfo}, session::Session, - store::{Result as StoreResult, SledStore, StateChanges, Store}, + store::{Result as StoreResult, StateChanges, Store}, EventEmitter, RoomState, }; @@ -280,24 +280,19 @@ impl BaseClient { /// previous login call. pub fn new_with_config(config: BaseClientConfig) -> Result { let store = if let Some(path) = &config.store_path { - if let Some(passphrase) = &config.passphrase { + if config.passphrase.is_some() { info!("Opening an encrypted store in path {}", path.display()); - SledStore::open_with_passphrase(path, passphrase)? } else { info!("Opening store in path {}", path.display()); - SledStore::open_with_path(path)? } + Store::open_default(path, config.passphrase.as_deref().map(|p| p.as_str()))? } else { - SledStore::open()? + Store::open_temporrary()? }; - let session = Arc::new(RwLock::new(None)); - let sync_token = Arc::new(RwLock::new(None)); - let store = Store::new(session.clone(), sync_token.clone(), store); - Ok(BaseClient { - session, - sync_token, + session: store.session.clone(), + sync_token: store.sync_token.clone(), store, #[cfg(feature = "encryption")] olm: Mutex::new(None).into(), diff --git a/matrix_sdk_base/src/store/memory_store.rs b/matrix_sdk_base/src/store/memory_store.rs new file mode 100644 index 00000000..fa3db415 --- /dev/null +++ b/matrix_sdk_base/src/store/memory_store.rs @@ -0,0 +1,341 @@ +// Copyright 2021 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// 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, RwLock}, + time::SystemTime, +}; + +use dashmap::{DashMap, DashSet}; +use matrix_sdk_common::{ + async_trait, + events::{ + presence::PresenceEvent, + room::member::{MemberEventContent, MembershipState}, + AnyBasicEvent, AnyStrippedStateEvent, AnySyncStateEvent, EventContent, EventType, + }, + identifiers::{RoomId, UserId}, +}; + +use tracing::info; + +use crate::deserialized_responses::{MemberEvent, StrippedMemberEvent}; + +use super::{Result, RoomInfo, StateChanges, StateStore, StrippedRoomInfo}; + +#[derive(Debug, Clone)] +pub struct MemoryStore { + sync_token: Arc>>, + filters: Arc>, + account_data: Arc>, + members: Arc>>, + profiles: Arc>>, + joined_user_ids: Arc>>, + invited_user_ids: Arc>>, + room_info: Arc>, + #[allow(clippy::type_complexity)] + room_state: Arc>>>, + room_account_data: Arc>>, + stripped_room_info: Arc>, + #[allow(clippy::type_complexity)] + stripped_room_state: + Arc>>>, + stripped_members: Arc>>, + presence: Arc>, +} + +impl MemoryStore { + pub fn new() -> Self { + Self { + sync_token: Arc::new(RwLock::new(None)), + filters: DashMap::new().into(), + account_data: DashMap::new().into(), + members: DashMap::new().into(), + profiles: DashMap::new().into(), + joined_user_ids: DashMap::new().into(), + invited_user_ids: DashMap::new().into(), + room_info: DashMap::new().into(), + room_state: DashMap::new().into(), + room_account_data: DashMap::new().into(), + stripped_room_info: DashMap::new().into(), + stripped_room_state: DashMap::new().into(), + stripped_members: DashMap::new().into(), + presence: DashMap::new().into(), + } + } + + pub async fn save_filter(&self, filter_name: &str, filter_id: &str) -> Result<()> { + self.filters + .insert(filter_name.to_string(), filter_id.to_string()); + + Ok(()) + } + + pub async fn get_filter(&self, filter_name: &str) -> Result> { + Ok(self.filters.get(filter_name).map(|f| f.to_string())) + } + + pub async fn get_sync_token(&self) -> Result> { + Ok(self.sync_token.read().unwrap().clone()) + } + + pub async fn save_changes(&self, changes: &StateChanges) -> Result<()> { + let now = SystemTime::now(); + + if let Some(s) = &changes.sync_token { + *self.sync_token.write().unwrap() = Some(s.to_owned()); + } + + for (room, events) in &changes.members { + for event in events.values() { + match event.content.membership { + MembershipState::Join => { + self.joined_user_ids + .entry(room.clone()) + .or_insert_with(DashSet::new) + .insert(event.state_key.clone()); + self.invited_user_ids + .entry(room.clone()) + .or_insert_with(DashSet::new) + .remove(&event.state_key); + } + MembershipState::Invite => { + self.invited_user_ids + .entry(room.clone()) + .or_insert_with(DashSet::new) + .insert(event.state_key.clone()); + self.joined_user_ids + .entry(room.clone()) + .or_insert_with(DashSet::new) + .remove(&event.state_key); + } + _ => { + self.joined_user_ids + .entry(room.clone()) + .or_insert_with(DashSet::new) + .remove(&event.state_key); + self.invited_user_ids + .entry(room.clone()) + .or_insert_with(DashSet::new) + .remove(&event.state_key); + } + } + + self.members + .entry(room.clone()) + .or_insert_with(DashMap::new) + .insert(event.state_key.clone(), event.clone()); + } + } + + for (room, users) in &changes.profiles { + for (user_id, profile) in users { + self.profiles + .entry(room.clone()) + .or_insert_with(DashMap::new) + .insert(user_id.clone(), profile.clone()); + } + } + + for (event_type, event) in &changes.account_data { + self.account_data + .insert(event_type.to_string(), event.clone()); + } + + for (room, events) in &changes.room_account_data { + for (event_type, event) in events { + self.room_account_data + .entry(room.clone()) + .or_insert_with(DashMap::new) + .insert(event_type.to_string(), event.clone()); + } + } + + for (room, event_types) in &changes.state { + for events in event_types.values() { + for event in events.values() { + self.room_state + .entry(room.clone()) + .or_insert_with(DashMap::new) + .entry(event.content().event_type().to_string()) + .or_insert_with(DashMap::new) + .insert(event.state_key().to_string(), event.clone()); + } + } + } + + for (room_id, room_info) in &changes.room_infos { + self.room_info.insert(room_id.clone(), room_info.clone()); + } + + for (sender, event) in &changes.presence { + self.presence.insert(sender.clone(), event.clone()); + } + + for (room_id, info) in &changes.invited_room_info { + self.stripped_room_info + .insert(room_id.clone(), info.clone()); + } + + for (room, events) in &changes.stripped_members { + for event in events.values() { + self.stripped_members + .entry(room.clone()) + .or_insert_with(DashMap::new) + .insert(event.state_key.clone(), event.clone()); + } + } + + for (room, event_types) in &changes.stripped_state { + for events in event_types.values() { + for event in events.values() { + self.stripped_room_state + .entry(room.clone()) + .or_insert_with(DashMap::new) + .entry(event.content().event_type().to_string()) + .or_insert_with(DashMap::new) + .insert(event.state_key().to_string(), event.clone()); + } + } + } + + info!("Saved changes in {:?}", now.elapsed()); + + Ok(()) + } + + pub async fn get_presence_event(&self, user_id: &UserId) -> Result> { + #[allow(clippy::map_clone)] + Ok(self.presence.get(user_id).map(|p| p.clone())) + } + + pub async fn get_state_event( + &self, + room_id: &RoomId, + event_type: EventType, + state_key: &str, + ) -> Result> { + #[allow(clippy::map_clone)] + Ok(self.room_state.get(room_id).and_then(|e| { + e.get(event_type.as_ref()) + .and_then(|s| s.get(state_key).map(|e| e.clone())) + })) + } + + pub async fn get_profile( + &self, + room_id: &RoomId, + user_id: &UserId, + ) -> Result> { + #[allow(clippy::map_clone)] + Ok(self + .profiles + .get(room_id) + .and_then(|p| p.get(user_id).map(|p| p.clone()))) + } + + pub async fn get_member_event( + &self, + room_id: &RoomId, + state_key: &UserId, + ) -> Result> { + #[allow(clippy::map_clone)] + Ok(self + .members + .get(room_id) + .and_then(|m| m.get(state_key).map(|m| m.clone()))) + } + + fn get_invited_user_ids(&self, room_id: &RoomId) -> Vec { + #[allow(clippy::map_clone)] + self.invited_user_ids + .get(room_id) + .map(|u| u.iter().map(|u| u.clone()).collect()) + .unwrap_or_default() + } + + fn get_joined_user_ids(&self, room_id: &RoomId) -> Vec { + #[allow(clippy::map_clone)] + self.joined_user_ids + .get(room_id) + .map(|u| u.iter().map(|u| u.clone()).collect()) + .unwrap_or_default() + } + + fn get_room_infos(&self) -> Vec { + #[allow(clippy::map_clone)] + self.room_info.iter().map(|r| r.clone()).collect() + } +} + +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl StateStore for MemoryStore { + async fn save_filter(&self, filter_name: &str, filter_id: &str) -> Result<()> { + self.save_filter(filter_name, filter_id).await + } + + async fn save_changes(&self, changes: &StateChanges) -> Result<()> { + self.save_changes(changes).await + } + + async fn get_filter(&self, filter_id: &str) -> Result> { + self.get_filter(filter_id).await + } + + async fn get_sync_token(&self) -> Result> { + self.get_sync_token().await + } + + async fn get_presence_event(&self, user_id: &UserId) -> Result> { + self.get_presence_event(user_id).await + } + + async fn get_state_event( + &self, + room_id: &RoomId, + event_type: EventType, + state_key: &str, + ) -> Result> { + self.get_state_event(room_id, event_type, state_key).await + } + + async fn get_profile( + &self, + room_id: &RoomId, + user_id: &UserId, + ) -> Result> { + self.get_profile(room_id, user_id).await + } + + async fn get_member_event( + &self, + room_id: &RoomId, + state_key: &UserId, + ) -> Result> { + self.get_member_event(room_id, state_key).await + } + + async fn get_invited_user_ids(&self, room_id: &RoomId) -> Result> { + Ok(self.get_invited_user_ids(room_id)) + } + + async fn get_joined_user_ids(&self, room_id: &RoomId) -> Result> { + Ok(self.get_joined_user_ids(room_id)) + } + + async fn get_room_infos(&self) -> Result> { + Ok(self.get_room_infos()) + } +} diff --git a/matrix_sdk_base/src/store/mod.rs b/matrix_sdk_base/src/store/mod.rs index 76787a97..3e00052d 100644 --- a/matrix_sdk_base/src/store/mod.rs +++ b/matrix_sdk_base/src/store/mod.rs @@ -32,8 +32,10 @@ use crate::{ InvitedRoom, JoinedRoom, LeftRoom, Room, RoomState, Session, }; +mod memory_store; mod sled_store; -pub use sled_store::SledStore; + +use self::{memory_store::MemoryStore, sled_store::SledStore}; #[derive(Debug, thiserror::Error)] pub enum StoreError { @@ -96,20 +98,19 @@ pub trait StateStore: AsyncTraitDeps { #[derive(Debug, Clone)] pub struct Store { inner: Arc>, - session: Arc>>, - sync_token: Arc>>, + pub(crate) session: Arc>>, + pub(crate) sync_token: Arc>>, rooms: Arc>, stripped_rooms: Arc>, } impl Store { - pub fn new( - session: Arc>>, - sync_token: Arc>>, - inner: SledStore, - ) -> Self { + fn new(inner: Box) -> Self { + let session = Arc::new(RwLock::new(None)); + let sync_token = Arc::new(RwLock::new(None)); + Self { - inner: Arc::new(Box::new(inner)), + inner: inner.into(), session, sync_token, rooms: DashMap::new().into(), @@ -131,13 +132,26 @@ impl Store { Ok(()) } - pub fn open_default(path: impl AsRef) -> Result { - let inner = SledStore::open_with_path(path)?; - Ok(Self::new( - Arc::new(RwLock::new(None)), - Arc::new(RwLock::new(None)), - inner, - )) + pub fn open_memory_store() -> Self { + let inner = Box::new(MemoryStore::new()); + + Self::new(inner) + } + + pub fn open_default(path: impl AsRef, passphrase: Option<&str>) -> Result { + let inner = if let Some(passphrase) = passphrase { + Box::new(SledStore::open_with_passphrase(path, passphrase)?) + } else { + Box::new(SledStore::open_with_path(path)?) + }; + + Ok(Self::new(inner)) + } + + pub fn open_temporrary() -> Result { + let inner = Box::new(SledStore::open()?); + + Ok(Self::new(inner)) } pub(crate) fn get_bare_room(&self, room_id: &RoomId) -> Option {