base: First working version of the new state store.

master
Damir Jelić 2020-10-24 20:01:39 +02:00
parent 9ce7feea1a
commit c1e679147d
6 changed files with 406 additions and 224 deletions

View File

@ -40,7 +40,7 @@ use zeroize::Zeroizing;
use tracing::{debug, warn};
use tracing::{error, info, instrument};
use matrix_sdk_base::{BaseClient, BaseClientConfig, Room, Session, StateStore};
use matrix_sdk_base::{BaseClient, BaseClientConfig, RoomSummary, Session};
#[cfg(feature = "encryption")]
use matrix_sdk_base::crypto::{
@ -117,7 +117,7 @@ use matrix_sdk_common::{
use crate::{
http_client::{client_with_config, HttpClient, HttpSend},
Error, EventEmitter, OutgoingRequest, Result,
Error, OutgoingRequest, Result,
};
#[cfg(feature = "encryption")]
@ -246,13 +246,13 @@ impl ClientConfig {
Ok(self)
}
/// Set a custom implementation of a `StateStore`.
///
/// The state store should be opened before being set.
pub fn state_store(mut self, store: Box<dyn StateStore>) -> Self {
self.base_config = self.base_config.state_store(store);
self
}
///// Set a custom implementation of a `StateStore`.
/////
///// The state store should be opened before being set.
//pub fn state_store(mut self, store: Box<dyn StateStore>) -> Self {
// self.base_config = self.base_config.state_store(store);
// self
//}
/// Set the path for storage.
///
@ -429,65 +429,54 @@ impl Client {
session.as_ref().cloned().map(|s| s.user_id)
}
/// Add `EventEmitter` to `Client`.
///
/// The methods of `EventEmitter` are called when the respective `RoomEvents` occur.
pub async fn add_event_emitter(&mut self, emitter: Box<dyn EventEmitter>) {
self.base_client.add_event_emitter(emitter).await;
}
///// Add `EventEmitter` to `Client`.
/////
///// The methods of `EventEmitter` are called when the respective `RoomEvents` occur.
//pub async fn add_event_emitter(&mut self, emitter: Box<dyn EventEmitter>) {
// self.base_client.add_event_emitter(emitter).await;
//}
/// Returns the joined rooms this client knows about.
pub fn joined_rooms(&self) -> Arc<RwLock<HashMap<RoomId, Arc<RwLock<Room>>>>> {
self.base_client.joined_rooms()
}
// /// Returns the joined rooms this client knows about.
// pub fn joined_rooms(&self) -> Arc<RwLock<HashMap<RoomId, Arc<RwLock<Room>>>>> {
// self.base_client.joined_rooms()
// }
/// Returns the invited rooms this client knows about.
pub fn invited_rooms(&self) -> Arc<RwLock<HashMap<RoomId, Arc<RwLock<Room>>>>> {
self.base_client.invited_rooms()
}
// /// Returns the invited rooms this client knows about.
// pub fn invited_rooms(&self) -> Arc<RwLock<HashMap<RoomId, Arc<RwLock<Room>>>>> {
// self.base_client.invited_rooms()
// }
/// Returns the left rooms this client knows about.
pub fn left_rooms(&self) -> Arc<RwLock<HashMap<RoomId, Arc<RwLock<Room>>>>> {
self.base_client.left_rooms()
}
// /// Returns the left rooms this client knows about.
// pub fn left_rooms(&self) -> Arc<RwLock<HashMap<RoomId, Arc<RwLock<Room>>>>> {
// self.base_client.left_rooms()
// }
/// Get a joined room with the given room id.
///
/// # Arguments
///
/// `room_id` - The unique id of the room that should be fetched.
pub async fn get_joined_room(&self, room_id: &RoomId) -> Option<Arc<RwLock<Room>>> {
self.base_client.get_joined_room(room_id).await
pub fn get_joined_room(&self, room_id: &RoomId) -> Option<RoomSummary> {
self.base_client.get_joined_room(room_id)
}
/// Get an invited room with the given room id.
///
/// # Arguments
///
/// `room_id` - The unique id of the room that should be fetched.
pub async fn get_invited_room(&self, room_id: &RoomId) -> Option<Arc<RwLock<Room>>> {
self.base_client.get_invited_room(room_id).await
}
///// Get an invited room with the given room id.
/////
///// # Arguments
/////
///// `room_id` - The unique id of the room that should be fetched.
//pub async fn get_invited_room(&self, room_id: &RoomId) -> Option<Arc<RwLock<Room>>> {
// self.base_client.get_invited_room(room_id).await
//}
/// Get a left room with the given room id.
///
/// # Arguments
///
/// `room_id` - The unique id of the room that should be fetched.
pub async fn get_left_room(&self, room_id: &RoomId) -> Option<Arc<RwLock<Room>>> {
self.base_client.get_left_room(room_id).await
}
/// This allows `Client` 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)
}
///// Get a left room with the given room id.
/////
///// # Arguments
/////
///// `room_id` - The unique id of the room that should be fetched.
//pub async fn get_left_room(&self, room_id: &RoomId) -> Option<Arc<RwLock<Room>>> {
// self.base_client.get_left_room(room_id).await
//}
/// Login to the server.
///
@ -1021,13 +1010,10 @@ impl Client {
let _guard = mutex.lock().await;
{
let room = self.base_client.get_joined_room(room_id).await;
let room = room.as_ref().unwrap().read().await;
let mut members = room
.joined_members
.keys()
.chain(room.invited_members.keys());
self.claim_one_time_keys(&mut members).await?;
let room = self.base_client.get_joined_room(room_id).unwrap();
let members = room.joined_user_ids().await;
let mut members_iter = members.iter();
self.claim_one_time_keys(&mut members_iter).await?;
};
let response = self.share_group_session(room_id).await;
@ -1120,10 +1106,8 @@ impl Client {
/// Returns true if a room with the given id was found and the room is
/// encrypted, false if the room wasn't found or isn't encrypted.
async fn is_room_encrypted(&self, room_id: &RoomId) -> bool {
let room = self.base_client.get_joined_room(room_id).await;
match room {
Some(r) => r.read().await.is_encrypted(),
match self.base_client.get_joined_room(room_id) {
Some(r) => r.is_encrypted(),
None => false,
}
}
@ -1937,7 +1921,6 @@ mod test {
get_public_rooms, get_public_rooms_filtered, register::RegistrationKind, Client,
ClientConfig, Invite3pid, Session, SyncSettings, Url,
};
use matrix_sdk_base::JsonStore;
use matrix_sdk_common::{
api::r0::{
account::register::Request as RegistrationRequest,
@ -2005,78 +1988,78 @@ mod test {
assert!(client.devices().await.is_ok());
}
#[tokio::test]
async fn test_join_leave_room() {
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
// #[tokio::test]
// async fn test_join_leave_room() {
// let homeserver = Url::from_str(&mockito::server_url()).unwrap();
let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost");
// let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost");
let session = Session {
access_token: "1234".to_owned(),
user_id: user_id!("@example:localhost"),
device_id: "DEVICEID".into(),
};
// let session = Session {
// access_token: "1234".to_owned(),
// user_id: user_id!("@example:localhost"),
// device_id: "DEVICEID".into(),
// };
let _m = mock(
"GET",
Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
)
.with_status(200)
.with_body(test_json::SYNC.to_string())
.create();
// let _m = mock(
// "GET",
// Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
// )
// .with_status(200)
// .with_body(test_json::SYNC.to_string())
// .create();
let dir = tempdir().unwrap();
let path: &Path = dir.path();
let store = Box::new(JsonStore::open(path).unwrap());
// let dir = tempdir().unwrap();
// let path: &Path = dir.path();
// let store = Box::new(JsonStore::open(path).unwrap());
let config = ClientConfig::default().state_store(store);
let client = Client::new_with_config(homeserver.clone(), config).unwrap();
client.restore_login(session.clone()).await.unwrap();
// let config = ClientConfig::default().state_store(store);
// let client = Client::new_with_config(homeserver.clone(), config).unwrap();
// client.restore_login(session.clone()).await.unwrap();
let room = client.get_joined_room(&room_id).await;
assert!(room.is_none());
// let room = client.get_joined_room(&room_id).await;
// assert!(room.is_none());
client.sync_once(SyncSettings::default()).await.unwrap();
// client.sync_once(SyncSettings::default()).await.unwrap();
let room = client.get_left_room(&room_id).await;
assert!(room.is_none());
// let room = client.get_left_room(&room_id).await;
// assert!(room.is_none());
let room = client.get_joined_room(&room_id).await;
assert!(room.is_some());
// let room = client.get_joined_room(&room_id).await;
// assert!(room.is_some());
// test store reloads with correct room state from JsonStore
let store = Box::new(JsonStore::open(path).unwrap());
let config = ClientConfig::default().state_store(store);
let joined_client = Client::new_with_config(homeserver, config).unwrap();
joined_client.restore_login(session).await.unwrap();
// // test store reloads with correct room state from JsonStore
// let store = Box::new(JsonStore::open(path).unwrap());
// let config = ClientConfig::default().state_store(store);
// let joined_client = Client::new_with_config(homeserver, config).unwrap();
// joined_client.restore_login(session).await.unwrap();
// joined room reloaded from state store
joined_client
.sync_once(SyncSettings::default())
.await
.unwrap();
let room = joined_client.get_joined_room(&room_id).await;
assert!(room.is_some());
// // joined room reloaded from state store
// joined_client
// .sync_once(SyncSettings::default())
// .await
// .unwrap();
// let room = joined_client.get_joined_room(&room_id).await;
// assert!(room.is_some());
let _m = mock(
"GET",
Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
)
.with_status(200)
.with_body(test_json::LEAVE_SYNC_EVENT.to_string())
.create();
// let _m = mock(
// "GET",
// Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
// )
// .with_status(200)
// .with_body(test_json::LEAVE_SYNC_EVENT.to_string())
// .create();
joined_client
.sync_once(SyncSettings::default())
.await
.unwrap();
// joined_client
// .sync_once(SyncSettings::default())
// .await
// .unwrap();
let room = joined_client.get_joined_room(&room_id).await;
assert!(room.is_none());
// let room = joined_client.get_joined_room(&room_id).await;
// assert!(room.is_none());
let room = joined_client.get_left_room(&room_id).await;
assert!(room.is_some());
}
// let room = joined_client.get_left_room(&room_id).await;
// assert!(room.is_some());
// }
#[tokio::test]
async fn account_data() {
@ -2649,62 +2632,62 @@ mod test {
.is_some())
}
#[tokio::test]
async fn test_client_sync_store() {
let homeserver = url::Url::from_str(&mockito::server_url()).unwrap();
// #[tokio::test]
// async fn test_client_sync_store() {
// let homeserver = url::Url::from_str(&mockito::server_url()).unwrap();
let session = Session {
access_token: "1234".to_owned(),
user_id: user_id!("@cheeky_monkey:matrix.org"),
device_id: "DEVICEID".into(),
};
// let session = Session {
// access_token: "1234".to_owned(),
// user_id: user_id!("@cheeky_monkey:matrix.org"),
// device_id: "DEVICEID".into(),
// };
let _m = mock(
"GET",
Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
)
.with_status(200)
.with_body(test_json::SYNC.to_string())
.create();
// let _m = mock(
// "GET",
// Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
// )
// .with_status(200)
// .with_body(test_json::SYNC.to_string())
// .create();
let _m = mock("POST", "/_matrix/client/r0/login")
.with_status(200)
.with_body(test_json::LOGIN.to_string())
.create();
// let _m = mock("POST", "/_matrix/client/r0/login")
// .with_status(200)
// .with_body(test_json::LOGIN.to_string())
// .create();
let dir = tempdir().unwrap();
// a sync response to populate our JSON store
let config =
ClientConfig::default().state_store(Box::new(JsonStore::open(dir.path()).unwrap()));
let client = Client::new_with_config(homeserver.clone(), config).unwrap();
client.restore_login(session.clone()).await.unwrap();
let sync_settings = SyncSettings::new().timeout(std::time::Duration::from_millis(3000));
// let dir = tempdir().unwrap();
// // a sync response to populate our JSON store
// let config =
// ClientConfig::default().state_store(Box::new(JsonStore::open(dir.path()).unwrap()));
// let client = Client::new_with_config(homeserver.clone(), config).unwrap();
// client.restore_login(session.clone()).await.unwrap();
// let sync_settings = SyncSettings::new().timeout(std::time::Duration::from_millis(3000));
// gather state to save to the db, the first time through loading will be skipped
let _ = client.sync_once(sync_settings.clone()).await.unwrap();
// // gather state to save to the db, the first time through loading will be skipped
// let _ = client.sync_once(sync_settings.clone()).await.unwrap();
// now syncing the client will update from the state store
let config =
ClientConfig::default().state_store(Box::new(JsonStore::open(dir.path()).unwrap()));
let client = Client::new_with_config(homeserver, config).unwrap();
client.restore_login(session.clone()).await.unwrap();
client.sync_once(sync_settings).await.unwrap();
// // now syncing the client will update from the state store
// let config =
// ClientConfig::default().state_store(Box::new(JsonStore::open(dir.path()).unwrap()));
// let client = Client::new_with_config(homeserver, config).unwrap();
// client.restore_login(session.clone()).await.unwrap();
// client.sync_once(sync_settings).await.unwrap();
let base_client = &client.base_client;
// let base_client = &client.base_client;
// assert the synced client and the logged in client are equal
assert_eq!(*base_client.session().read().await, Some(session));
assert_eq!(
base_client.sync_token().await,
Some("s526_47314_0_7_1_1_1_11444_1".to_string())
);
// This is commented out because this field is private...
// // assert the synced client and the logged in client are equal
// assert_eq!(*base_client.session().read().await, Some(session));
// assert_eq!(
// *base_client.ignored_users.read().await,
// vec![user_id!("@someone:example.org")]
// base_client.sync_token().await,
// Some("s526_47314_0_7_1_1_1_11444_1".to_string())
// );
}
// // This is commented out because this field is private...
// // assert_eq!(
// // *base_client.ignored_users.read().await,
// // vec![user_id!("@someone:example.org")]
// // );
// }
#[tokio::test]
async fn sync() {

View File

@ -66,16 +66,7 @@ compile_error!("only one of 'native-tls' or 'rustls-tls' features can be enabled
#[cfg(feature = "encryption")]
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
pub use matrix_sdk_base::crypto::LocalTrust;
#[cfg(not(target_arch = "wasm32"))]
pub use matrix_sdk_base::JsonStore;
pub use matrix_sdk_base::{
CustomEvent, Error as BaseError, EventEmitter, Room, RoomMember, RoomState, Session,
StateStore, SyncRoom,
};
#[cfg(feature = "messages")]
#[cfg_attr(feature = "docs", doc(cfg(messages)))]
pub use matrix_sdk_base::{MessageQueue, PossiblyRedactedExt};
pub use matrix_sdk_base::{Error as BaseError, RoomSummary, Session};
pub use matrix_sdk_common::*;
pub use reqwest;

View File

@ -26,6 +26,7 @@ docs = ["encryption", "sqlite_cryptostore", "messages"]
[dependencies]
async-trait = "0.1.41"
serde = "1.0.116"
dashmap= "*"
serde_json = "1.0.58"
zeroize = "1.1.1"
tracing = "0.1.21"
@ -37,6 +38,7 @@ matrix-sdk-crypto = { version = "0.1.0", path = "../matrix_sdk_crypto", optional
# Misc dependencies
thiserror = "1.0.21"
sled = "0.34.4"
futures = "0.3.6"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio]
version = "0.2.22"
@ -44,7 +46,6 @@ default-features = false
features = ["sync", "fs"]
[dev-dependencies]
futures = "0.3.6"
matrix-sdk-test = { version = "0.1.0", path = "../matrix_sdk_test" }
http = "0.2.1"
tracing-subscriber = "0.2.13"

View File

@ -21,6 +21,8 @@ use std::{
sync::Arc,
};
use dashmap::DashMap;
#[cfg(feature = "encryption")]
use matrix_sdk_common::locks::Mutex;
use matrix_sdk_common::{
@ -43,15 +45,16 @@ use matrix_sdk_common::{
#[cfg(feature = "encryption")]
use matrix_sdk_crypto::{
store::{CryptoStore, CryptoStoreError},
Device, IncomingResponse, OlmError, OlmMachine, OutgoingRequest, Sas, ToDeviceRequest,
UserDevices,
Device, EncryptionSettings, IncomingResponse, OlmError, OlmMachine, OutgoingRequest, Sas,
ToDeviceRequest, UserDevices,
};
use tracing::info;
use zeroize::Zeroizing;
use crate::{
error::Result,
session::Session,
store::{StateChanges, Store},
store::{RoomSummary, StateChanges, Store},
};
pub type Token = String;
@ -180,6 +183,7 @@ pub struct BaseClient {
pub(crate) sync_token: Arc<RwLock<Option<Token>>>,
/// Database
store: Store,
joined_rooms: Arc<DashMap<RoomId, RoomSummary>>,
#[cfg(feature = "encryption")]
olm: Arc<Mutex<Option<OlmMachine>>>,
#[cfg(feature = "encryption")]
@ -282,10 +286,18 @@ impl BaseClient {
/// * `config` - An optional session if the user already has one from a
/// previous login call.
pub fn new_with_config(config: BaseClientConfig) -> Result<Self> {
let store = if let Some(path) = &config.store_path {
info!("Opening store in path {}", path.display());
Store::open_with_path(path)
} else {
Store::open()
};
Ok(BaseClient {
session: Arc::new(RwLock::new(None)),
sync_token: Arc::new(RwLock::new(None)),
store: Store::open(),
store,
joined_rooms: Arc::new(DashMap::new()),
#[cfg(feature = "encryption")]
olm: Arc::new(Mutex::new(None)),
#[cfg(feature = "encryption")]
@ -429,27 +441,45 @@ impl BaseClient {
}
}
let changes = StateChanges::default();
let mut changes = StateChanges::default();
for (room_id, room) in &response.rooms.join {
for e in &room.state.events {
// info!("Handling event for room {} {:#?}", room_id, e);
if let Ok(event) = hoist_and_deserialize_state_event(e) {
match event {
AnySyncStateEvent::RoomMember(member) => {
let member_id = UserId::try_from(member.state_key).unwrap();
let prev_member =
self.store.get_member_event(room_id, &member_id).await;
// let member_id = UserId::try_from(member.state_key.clone()).unwrap();
// self.store.get_member_event(room_id, &member_id).await;
use matrix_sdk_common::events::room::member::MembershipState::*;
match member.content.membership {
// TODO this isn't right, check the diff against
// your previous state.
match &member.content.membership {
Join => {
info!("ADDING MEMBER {} to {}", member.state_key, room_id);
changes.add_joined_member(room_id, member)
// TODO check if the display name is
// ambigous
}
Invited => {
info!(
"ADDING INVITED MEMBER {} to {}",
member.state_key, room_id
);
changes.add_invited_member(room_id, member)
}
_ => (),
}
}
_ => (),
AnySyncStateEvent::RoomCreate(create) => {
info!("Creating new room {}", room_id);
let room =
RoomSummary::new(self.store.clone(), room_id, &create.content);
self.joined_rooms.insert(room_id.clone(), room.clone());
changes.add_room_summary(room_id.clone(), room);
}
_ => changes.add_state_event(room_id, event),
}
}
}
@ -542,28 +572,27 @@ impl BaseClient {
#[cfg(feature = "encryption")]
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
pub async fn share_group_session(&self, room_id: &RoomId) -> Result<Vec<Arc<ToDeviceRequest>>> {
todo!()
// let room = self.get_joined_room(room_id).await.expect("No room found");
// let olm = self.olm.lock().await;
let olm = self.olm.lock().await;
// match &*olm {
// Some(o) => {
// let room = room.write().await;
// // XXX: We construct members in a slightly roundabout way instead of chaining the
// // iterators directly because of https://github.com/rust-lang/rust/issues/64552
match &*olm {
Some(o) => {
// XXX: We construct members in a slightly roundabout way instead of chaining the
// iterators directly because of https://github.com/rust-lang/rust/issues/64552
// let joined_members = room.joined_members.keys();
// let invited_members = room.joined_members.keys();
// let members: Vec<&UserId> = joined_members.chain(invited_members).collect();
// Ok(o.share_group_session(
// room_id,
// members.into_iter(),
// room.encrypted.clone().unwrap_or_default(),
// )
// .await?)
// }
// None => panic!("Olm machine wasn't started"),
// }
let members = self.store.get_joined_members(room_id).await;
Ok(
o.share_group_session(room_id, members.iter(), EncryptionSettings::default())
.await?,
)
}
None => panic!("Olm machine wasn't started"),
}
}
pub fn get_joined_room(&self, room_id: &RoomId) -> Option<RoomSummary> {
self.joined_rooms.get(room_id).map(|r| r.clone())
}
/// Encrypt a message event content.

View File

@ -47,7 +47,7 @@ mod error;
mod session;
mod store;
pub use store::Store;
pub use store::{RoomSummary, Store};
pub use client::{BaseClient, BaseClientConfig, RoomState, RoomStateType};

View File

@ -1,9 +1,22 @@
use std::{collections::BTreeMap, convert::TryFrom};
use std::{
collections::BTreeMap,
convert::TryFrom,
path::Path,
sync::{Arc, Mutex as SyncMutex},
};
use futures::executor::block_on;
use matrix_sdk_common::{
events::{room::member::MemberEventContent, AnySyncStateEvent, SyncStateEvent},
events::{
room::{
create::CreateEventContent, encryption::EncryptionEventContent,
member::MemberEventContent,
},
AnySyncStateEvent, SyncStateEvent,
},
identifiers::{RoomId, UserId},
};
use serde::{Deserialize, Serialize};
use serde_json;
use sled::{transaction::TransactionResult, Config, Db, Transactional, Tree};
@ -13,7 +26,9 @@ pub struct Store {
inner: Db,
session: Tree,
members: Tree,
joined_user_ids: Tree,
room_state: Tree,
room_summaries: Tree,
}
use crate::Session;
@ -23,9 +38,9 @@ pub struct StateChanges {
session: Option<Session>,
members: BTreeMap<RoomId, BTreeMap<UserId, SyncStateEvent<MemberEventContent>>>,
state: BTreeMap<RoomId, BTreeMap<String, AnySyncStateEvent>>,
display_names: BTreeMap<RoomId, BTreeMap<String, BTreeMap<UserId, ()>>>,
added_user_ids: BTreeMap<RoomId, UserId>,
room_summaries: BTreeMap<RoomId, RoomSummary>,
// display_names: BTreeMap<RoomId, BTreeMap<String, BTreeMap<UserId, ()>>>,
joined_user_ids: BTreeMap<RoomId, UserId>,
invited_user_ids: BTreeMap<RoomId, UserId>,
removed_user_ids: BTreeMap<RoomId, UserId>,
}
@ -37,12 +52,39 @@ impl StateChanges {
event: SyncStateEvent<MemberEventContent>,
) {
let user_id = UserId::try_from(event.state_key.as_str()).unwrap();
self.joined_user_ids
.insert(room_id.to_owned(), user_id.clone());
self.members
.entry(room_id.to_owned())
.or_insert_with(BTreeMap::new)
.insert(user_id, event);
}
pub fn add_invited_member(
&mut self,
room_id: &RoomId,
event: SyncStateEvent<MemberEventContent>,
) {
let user_id = UserId::try_from(event.state_key.as_str()).unwrap();
self.invited_user_ids
.insert(room_id.to_owned(), user_id.clone());
self.members
.entry(room_id.to_owned())
.or_insert_with(BTreeMap::new)
.insert(user_id, event);
}
pub fn add_room_summary(&mut self, room_id: RoomId, summary: RoomSummary) {
self.room_summaries.insert(room_id, summary);
}
pub fn add_state_event(&mut self, room_id: &RoomId, event: AnySyncStateEvent) {
self.state
.entry(room_id.to_owned())
.or_insert_with(BTreeMap::new)
.insert(event.state_key().to_string(), event);
}
pub fn from_event(room_id: &RoomId, event: SyncStateEvent<MemberEventContent>) -> Self {
let mut changes = Self::default();
changes.add_joined_member(room_id, event);
@ -60,24 +102,136 @@ impl From<Session> for StateChanges {
}
}
#[derive(Debug, Clone)]
pub struct RoomSummary {
room_id: Arc<RoomId>,
inner: Arc<SyncMutex<InnerSummary>>,
store: Store,
}
impl RoomSummary {
pub fn new(store: Store, room_id: &RoomId, creation_event: &CreateEventContent) -> Self {
let room_id = Arc::new(room_id.clone());
Self {
room_id: room_id.clone(),
store,
inner: Arc::new(SyncMutex::new(InnerSummary {
creation_content: creation_event.clone(),
room_id,
encryption: None,
last_prev_batch: None,
})),
}
}
fn serialize(&self) -> Vec<u8> {
let inner = self.inner.lock().unwrap();
serde_json::to_vec(&*inner).unwrap()
}
pub async fn joined_user_ids(&self) -> Vec<UserId> {
self.store.get_joined_members(&self.room_id).await
}
pub fn is_encrypted(&self) -> bool {
self.inner.lock().unwrap().encryption.is_some()
}
pub fn get_member(&self, user_id: &UserId) -> Option<RoomMember> {
block_on(self.store.get_member_event(&self.room_id, user_id)).map(|e| e.into())
}
pub fn room_id(&self) -> &RoomId {
&self.room_id
}
pub fn display_name(&self) -> String {
"TEST ROOM NAME".to_string()
}
}
#[derive(Debug)]
pub struct RoomMember {
event: Arc<SyncStateEvent<MemberEventContent>>,
}
impl RoomMember {
pub fn display_name(&self) -> &Option<String> {
&self.event.content.displayname
}
pub fn disambiguated_name(&self) -> String {
self.event.state_key.clone()
}
pub fn name(&self) -> String {
self.event.state_key.clone()
}
pub fn unique_name(&self) -> String {
self.event.state_key.clone()
}
}
impl From<SyncStateEvent<MemberEventContent>> for RoomMember {
fn from(event: SyncStateEvent<MemberEventContent>) -> Self {
Self {
event: Arc::new(event),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
struct InnerSummary {
room_id: Arc<RoomId>,
creation_content: CreateEventContent,
encryption: Option<EncryptionEventContent>,
last_prev_batch: Option<String>,
}
impl Store {
pub fn open() -> Self {
let db = Config::new().temporary(true).open().unwrap();
fn open_helper(db: Db) -> Self {
let session = db.open_tree("session").unwrap();
let members = db.open_tree("members").unwrap();
let room_state = db.open_tree("members").unwrap();
let joined_user_ids = db.open_tree("joined_user_ids").unwrap();
let room_state = db.open_tree("room_state").unwrap();
let room_summaries = db.open_tree("room_summaries").unwrap();
Self {
inner: db,
session,
members,
joined_user_ids,
room_state,
room_summaries,
}
}
pub fn open() -> Self {
let db = Config::new().temporary(true).open().unwrap();
Store::open_helper(db)
}
pub fn open_with_path(path: impl AsRef<Path>) -> Self {
let path = path.as_ref().join("matrix-sdk-state");
let db = Config::new().temporary(false).path(path).open().unwrap();
Store::open_helper(db)
}
pub async fn save_changes(&self, changes: &StateChanges) {
let ret: TransactionResult<()> =
(&self.session, &self.members).transaction(|(session, members)| {
let ret: TransactionResult<()> = (
&self.session,
&self.members,
&self.joined_user_ids,
&self.room_state,
&self.room_summaries,
)
.transaction(|(session, members, joined, state, summaries)| {
if let Some(s) = &changes.session {
session.insert("session", serde_json::to_vec(s).unwrap())?;
}
@ -91,6 +245,23 @@ impl Store {
}
}
for (room, user) in &changes.joined_user_ids {
joined.insert(room.as_bytes(), user.as_bytes())?;
}
for (room, events) in &changes.state {
for (state_key, event) in events {
state.insert(
format!("{}{}", room.as_str(), state_key).as_bytes(),
serde_json::to_vec(&event).unwrap(),
)?;
}
}
for (room_id, summary) in &changes.room_summaries {
summaries.insert(room_id.as_str().as_bytes(), summary.serialize())?;
}
Ok(())
});
@ -110,6 +281,13 @@ impl Store {
.map(|v| serde_json::from_slice(&v).unwrap())
}
pub async fn get_joined_members(&self, room_id: &RoomId) -> Vec<UserId> {
self.joined_user_ids
.scan_prefix(room_id.as_bytes())
.map(|u| UserId::try_from(String::from_utf8_lossy(&u.unwrap().1).to_string()).unwrap())
.collect()
}
pub fn get_session(&self) -> Option<Session> {
self.session
.get("session")