diff --git a/matrix_sdk_base/src/store.rs b/matrix_sdk_base/src/store.rs index b7164ed4..0e85d0e1 100644 --- a/matrix_sdk_base/src/store.rs +++ b/matrix_sdk_base/src/store.rs @@ -1,30 +1,55 @@ +use std::{collections::BTreeMap, convert::TryFrom}; + +use matrix_sdk_common::{ + events::{room::member::MemberEventContent, SyncStateEvent}, + identifiers::{RoomId, UserId}, +}; use serde_json; -use sled::{transaction::TransactionalTree, Config, Db, Tree}; +use sled::{transaction::TransactionResult, Config, Db, Transactional, Tree}; #[derive(Debug, Clone)] pub struct Store { inner: Db, session_tree: Tree, + member_tree: Tree, } use crate::Session; -pub struct TransactionalStore<'a> { - inner: &'a TransactionalTree, +#[derive(Debug, Default)] +pub struct StateChanges { + session: Option, + members: BTreeMap>>, + + added_user_ids: BTreeMap, + invited_user_ids: BTreeMap, + removed_user_ids: BTreeMap, } -impl<'a> std::fmt::Debug for TransactionalStore<'a> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("TransactionalStore").finish() +impl StateChanges { + pub fn add_event(&mut self, room_id: &RoomId, event: SyncStateEvent) { + let user_id = UserId::try_from(event.state_key.as_str()).unwrap(); + self.members + .entry(room_id.to_owned()) + .or_insert_with(BTreeMap::new) + .insert(user_id, event); + } + + pub fn from_event(room_id: &RoomId, event: SyncStateEvent) -> Self { + let mut changes = Self::default(); + changes.add_event(room_id, event); + + changes } } -impl<'a> TransactionalStore<'a> { - pub fn store_session(&self, session: &Session) { - self.inner - .insert("session", serde_json::to_vec(session).unwrap()) - .unwrap(); +impl From for StateChanges { + fn from(session: Session) -> Self { + Self { + session: Some(session), + ..Default::default() + } } } @@ -32,44 +57,72 @@ impl Store { pub fn open() -> Self { let db = Config::new().temporary(true).open().unwrap(); let session_tree = db.open_tree("session").unwrap(); + let member_tree = db.open_tree("members").unwrap(); Self { inner: db, session_tree, + member_tree, } } + pub async fn save_changes(&self, changes: &StateChanges) { + let ret: TransactionResult<()> = + (&self.session_tree, &self.member_tree).transaction(|(session, members)| { + if let Some(s) = &changes.session { + session.insert("session", serde_json::to_vec(s).unwrap())?; + } + + for (room, events) in &changes.members { + for (user_id, event) in events { + members.insert( + format!("{}{}", room.as_str(), user_id.as_str()).as_str(), + serde_json::to_vec(&event).unwrap(), + )?; + } + } + + Ok(()) + }); + + ret.unwrap(); + + self.inner.flush_async().await.unwrap(); + } + + pub fn get_member_event( + &self, + room_id: &RoomId, + state_key: &UserId, + ) -> Option> { + self.member_tree + .get(format!("{}{}", room_id.as_str(), state_key.as_str())) + .unwrap() + .map(|v| serde_json::from_slice(&v).unwrap()) + } + pub fn get_session(&self) -> Option { self.session_tree .get("session") .unwrap() .map(|s| serde_json::from_slice(&s).unwrap()) } - - pub async fn transaction(&self, callback: F) -> R - where - F: Fn(&TransactionalStore) -> R, - { - let ret = self - .session_tree - .transaction::<_, _, ()>(|t| { - let transaction = TransactionalStore { inner: t }; - Ok(callback(&transaction)) - }) - .unwrap(); - - self.inner.flush_async().await.unwrap(); - - ret - } } #[cfg(test)] mod test { - use matrix_sdk_common::identifiers::{user_id, DeviceIdBox, UserId}; + use std::{convert::TryFrom, time::SystemTime}; + + use matrix_sdk_common::{ + events::{ + room::member::{MemberEventContent, MembershipState}, + SyncStateEvent, Unsigned, + }, + identifiers::{room_id, user_id, DeviceIdBox, EventId, UserId}, + }; use matrix_sdk_test::async_test; - use super::Store; + use super::{StateChanges, Store}; use crate::Session; fn user_id() -> UserId { @@ -80,6 +133,26 @@ mod test { "DEVICEID".into() } + fn membership_event() -> SyncStateEvent { + let content = MemberEventContent { + avatar_url: None, + displayname: None, + is_direct: None, + third_party_invite: None, + membership: MembershipState::Join, + }; + + SyncStateEvent { + event_id: EventId::try_from("$h29iv0s8:example.com").unwrap(), + content, + sender: user_id(), + origin_server_ts: SystemTime::now(), + state_key: user_id().to_string(), + prev_content: None, + unsigned: Unsigned::default(), + } + } + #[async_test] async fn test_session_saving() { let session = Session { @@ -90,14 +163,22 @@ mod test { let store = Store::open(); - store - .transaction(|t| { - t.store_session(&session); - () - }) - .await; + store.save_changes(&session.clone().into()).await; let stored_session = store.get_session().unwrap(); assert_eq!(session, stored_session); } + + #[async_test] + async fn test_member_saving() { + let store = Store::open(); + let room_id = room_id!("!test:localhost"); + let user_id = user_id(); + + assert!(store.get_member_event(&room_id, &user_id).is_none()); + let changes = StateChanges::from_event(&room_id!("!test:localhost"), membership_event()); + + store.save_changes(&changes).await; + assert!(store.get_member_event(&room_id, &user_id).is_some()); + } }