1use std::{
24 borrow::Borrow,
25 collections::{BTreeMap, BTreeSet, HashMap},
26 fmt,
27 ops::Deref,
28 result::Result as StdResult,
29 str::Utf8Error,
30 sync::{Arc, RwLock as StdRwLock},
31};
32
33use eyeball_im::{Vector, VectorDiff};
34use futures_util::Stream;
35use matrix_sdk_common::ROOM_VERSION_RULES_FALLBACK;
36use once_cell::sync::OnceCell;
37
38#[cfg(any(test, feature = "testing"))]
39#[macro_use]
40pub mod integration_tests;
41mod observable_map;
42mod traits;
43
44#[cfg(feature = "e2e-encryption")]
45use matrix_sdk_crypto::store::{DynCryptoStore, IntoCryptoStore};
46pub use matrix_sdk_store_encryption::Error as StoreEncryptionError;
47use observable_map::ObservableMap;
48use ruma::{
49 EventId, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UserId,
50 api::client::sync::sync_events::StrippedState,
51 events::{
52 AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnySyncStateEvent, EmptyStateKey,
53 GlobalAccountDataEventType, RedactContent, RedactedStateEventContent,
54 RoomAccountDataEventType, StateEventType, StaticEventContent, StaticStateEventContent,
55 StrippedStateEvent, SyncStateEvent,
56 presence::PresenceEvent,
57 receipt::ReceiptEventContent,
58 room::{
59 create::RoomCreateEventContent,
60 member::{RoomMemberEventContent, StrippedRoomMemberEvent},
61 power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
62 redaction::SyncRoomRedactionEvent,
63 },
64 },
65 serde::Raw,
66};
67use serde::de::DeserializeOwned;
68use tokio::sync::{Mutex, RwLock, broadcast};
69use tracing::warn;
70
71use crate::{
72 MinimalRoomMemberEvent, Room, RoomCreateWithCreatorEventContent, RoomStateFilter, SessionMeta,
73 deserialized_responses::DisplayName,
74 event_cache::store as event_cache_store,
75 room::{RoomInfo, RoomInfoNotableUpdate, RoomState},
76};
77
78pub(crate) mod ambiguity_map;
79mod memory_store;
80pub mod migration_helpers;
81mod send_queue;
82
83#[cfg(any(test, feature = "testing"))]
84pub use self::integration_tests::StateStoreIntegrationTests;
85#[cfg(feature = "unstable-msc4274")]
86pub use self::send_queue::{AccumulatedSentMediaInfo, FinishGalleryItemInfo};
87pub use self::{
88 memory_store::MemoryStore,
89 send_queue::{
90 ChildTransactionId, DependentQueuedRequest, DependentQueuedRequestKind,
91 FinishUploadThumbnailInfo, QueueWedgeError, QueuedRequest, QueuedRequestKind,
92 SentMediaInfo, SentRequestKey, SerializableEventContent,
93 },
94 traits::{
95 ComposerDraft, ComposerDraftType, DynStateStore, IntoStateStore, ServerInfo, StateStore,
96 StateStoreDataKey, StateStoreDataValue, StateStoreExt, WellKnownResponse,
97 },
98};
99
100#[derive(Debug, thiserror::Error)]
102pub enum StoreError {
103 #[error(transparent)]
105 Backend(Box<dyn std::error::Error + Send + Sync>),
106
107 #[error(transparent)]
109 Json(#[from] serde_json::Error),
110
111 #[error(transparent)]
114 Identifier(#[from] ruma::IdParseError),
115
116 #[error("The store failed to be unlocked")]
119 StoreLocked,
120
121 #[error("The store is not encrypted but was tried to be opened with a passphrase")]
123 UnencryptedStore,
124
125 #[error("Error encrypting or decrypting data from the store: {0}")]
127 Encryption(#[from] StoreEncryptionError),
128
129 #[error("Error encoding or decoding data from the store: {0}")]
131 Codec(#[from] Utf8Error),
132
133 #[error(
135 "The database format changed in an incompatible way, current \
136 version: {0}, latest version: {1}"
137 )]
138 UnsupportedDatabaseVersion(usize, usize),
139
140 #[error("Redaction failed: {0}")]
144 Redaction(#[source] ruma::canonical_json::RedactionError),
145
146 #[error("The store contains invalid data: {details}")]
148 InvalidData {
149 details: String,
151 },
152}
153
154impl StoreError {
155 #[inline]
159 pub fn backend<E>(error: E) -> Self
160 where
161 E: std::error::Error + Send + Sync + 'static,
162 {
163 Self::Backend(Box::new(error))
164 }
165}
166
167pub type Result<T, E = StoreError> = std::result::Result<T, E>;
169
170#[derive(Clone)]
175pub(crate) struct BaseStateStore {
176 pub(super) inner: Arc<DynStateStore>,
177 session_meta: Arc<OnceCell<SessionMeta>>,
178 room_load_settings: Arc<RwLock<RoomLoadSettings>>,
179 pub(super) sync_token: Arc<RwLock<Option<String>>>,
181 rooms: Arc<StdRwLock<ObservableMap<OwnedRoomId, Room>>>,
183 sync_lock: Arc<Mutex<()>>,
186}
187
188impl BaseStateStore {
189 pub fn new(inner: Arc<DynStateStore>) -> Self {
191 Self {
192 inner,
193 session_meta: Default::default(),
194 room_load_settings: Default::default(),
195 sync_token: Default::default(),
196 rooms: Arc::new(StdRwLock::new(ObservableMap::new())),
197 sync_lock: Default::default(),
198 }
199 }
200
201 pub fn sync_lock(&self) -> &Mutex<()> {
203 &self.sync_lock
204 }
205
206 pub(crate) fn set_session_meta(&self, session_meta: SessionMeta) {
212 self.session_meta.set(session_meta).expect("`SessionMeta` was already set");
213 }
214
215 pub(crate) async fn load_rooms(
218 &self,
219 user_id: &UserId,
220 room_load_settings: RoomLoadSettings,
221 room_info_notable_update_sender: &broadcast::Sender<RoomInfoNotableUpdate>,
222 ) -> Result<()> {
223 *self.room_load_settings.write().await = room_load_settings.clone();
224
225 let room_infos = self.load_and_migrate_room_infos(room_load_settings).await?;
226
227 let mut rooms = self.rooms.write().unwrap();
228
229 for room_info in room_infos {
230 let new_room = Room::restore(
231 user_id,
232 self.inner.clone(),
233 room_info,
234 room_info_notable_update_sender.clone(),
235 );
236 let new_room_id = new_room.room_id().to_owned();
237
238 rooms.insert(new_room_id, new_room);
239 }
240
241 Ok(())
242 }
243
244 async fn load_and_migrate_room_infos(
247 &self,
248 room_load_settings: RoomLoadSettings,
249 ) -> Result<Vec<RoomInfo>> {
250 let mut room_infos = self.inner.get_room_infos(&room_load_settings).await?;
251 let mut migrated_room_infos = Vec::with_capacity(room_infos.len());
252
253 for room_info in room_infos.iter_mut() {
254 if room_info.apply_migrations(self.inner.clone()).await {
255 migrated_room_infos.push(room_info.clone());
256 }
257 }
258
259 if !migrated_room_infos.is_empty() {
260 let changes = StateChanges {
261 room_infos: migrated_room_infos
262 .into_iter()
263 .map(|room_info| (room_info.room_id.clone(), room_info))
264 .collect(),
265 ..Default::default()
266 };
267
268 if let Err(error) = self.inner.save_changes(&changes).await {
269 warn!("Failed to save migrated room infos: {error}");
270 }
271 }
272
273 Ok(room_infos)
274 }
275
276 pub(crate) async fn load_sync_token(&self) -> Result<()> {
279 let token =
280 self.get_kv_data(StateStoreDataKey::SyncToken).await?.and_then(|s| s.into_sync_token());
281 *self.sync_token.write().await = token;
282
283 Ok(())
284 }
285
286 #[cfg(any(feature = "e2e-encryption", test))]
289 pub(crate) async fn derive_from_other(
290 &self,
291 other: &Self,
292 room_info_notable_update_sender: &broadcast::Sender<RoomInfoNotableUpdate>,
293 ) -> Result<()> {
294 let Some(session_meta) = other.session_meta.get() else {
295 return Ok(());
296 };
297
298 let room_load_settings = other.room_load_settings.read().await.clone();
299
300 self.load_rooms(&session_meta.user_id, room_load_settings, room_info_notable_update_sender)
301 .await?;
302 self.load_sync_token().await?;
303 self.set_session_meta(session_meta.clone());
304
305 Ok(())
306 }
307
308 pub fn session_meta(&self) -> Option<&SessionMeta> {
310 self.session_meta.get()
311 }
312
313 pub fn rooms(&self) -> Vec<Room> {
315 self.rooms.read().unwrap().iter().cloned().collect()
316 }
317
318 pub fn rooms_filtered(&self, filter: RoomStateFilter) -> Vec<Room> {
320 self.rooms
321 .read()
322 .unwrap()
323 .iter()
324 .filter(|room| filter.matches(room.state()))
325 .cloned()
326 .collect()
327 }
328
329 pub fn rooms_stream(
332 &self,
333 ) -> (Vector<Room>, impl Stream<Item = Vec<VectorDiff<Room>>> + use<>) {
334 self.rooms.read().unwrap().stream()
335 }
336
337 pub fn room(&self, room_id: &RoomId) -> Option<Room> {
339 self.rooms.read().unwrap().get(room_id).cloned()
340 }
341
342 pub(crate) fn room_exists(&self, room_id: &RoomId) -> bool {
344 self.rooms.read().unwrap().get(room_id).is_some()
345 }
346
347 pub fn get_or_create_room(
350 &self,
351 room_id: &RoomId,
352 room_state: RoomState,
353 room_info_notable_update_sender: broadcast::Sender<RoomInfoNotableUpdate>,
354 ) -> Room {
355 let user_id =
356 &self.session_meta.get().expect("Creating room while not being logged in").user_id;
357
358 self.rooms
359 .write()
360 .unwrap()
361 .get_or_create(room_id, || {
362 Room::new(
363 user_id,
364 self.inner.clone(),
365 room_id,
366 room_state,
367 room_info_notable_update_sender,
368 )
369 })
370 .clone()
371 }
372
373 pub(crate) async fn forget_room(&self, room_id: &RoomId) -> Result<()> {
379 self.inner.remove_room(room_id).await?;
380 self.rooms.write().unwrap().remove(room_id);
381 Ok(())
382 }
383}
384
385#[cfg(not(tarpaulin_include))]
386impl fmt::Debug for BaseStateStore {
387 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
388 f.debug_struct("Store")
389 .field("inner", &self.inner)
390 .field("session_meta", &self.session_meta)
391 .field("sync_token", &self.sync_token)
392 .field("rooms", &self.rooms)
393 .finish_non_exhaustive()
394 }
395}
396
397impl Deref for BaseStateStore {
398 type Target = DynStateStore;
399
400 fn deref(&self) -> &Self::Target {
401 self.inner.deref()
402 }
403}
404
405#[derive(Clone, Debug, Default)]
440pub enum RoomLoadSettings {
441 #[default]
446 All,
447
448 One(OwnedRoomId),
454}
455
456#[derive(Clone, Copy, Debug, PartialEq, Eq)]
458pub struct ThreadSubscription {
459 pub automatic: bool,
462}
463
464impl ThreadSubscription {
465 pub fn as_str(&self) -> &'static str {
467 if self.automatic { "automatic" } else { "manual" }
468 }
469
470 pub fn from_value(s: &str) -> Option<Self> {
473 match s {
474 "automatic" => Some(Self { automatic: true }),
475 "manual" => Some(Self { automatic: false }),
476 _ => None,
477 }
478 }
479}
480
481#[derive(Clone, Debug, Default)]
483pub struct StateChanges {
484 pub sync_token: Option<String>,
486 pub account_data: BTreeMap<GlobalAccountDataEventType, Raw<AnyGlobalAccountDataEvent>>,
488 pub presence: BTreeMap<OwnedUserId, Raw<PresenceEvent>>,
490
491 pub profiles: BTreeMap<OwnedRoomId, BTreeMap<OwnedUserId, MinimalRoomMemberEvent>>,
494
495 pub profiles_to_delete: BTreeMap<OwnedRoomId, Vec<OwnedUserId>>,
499
500 pub state:
503 BTreeMap<OwnedRoomId, BTreeMap<StateEventType, BTreeMap<String, Raw<AnySyncStateEvent>>>>,
504 pub room_account_data:
506 BTreeMap<OwnedRoomId, BTreeMap<RoomAccountDataEventType, Raw<AnyRoomAccountDataEvent>>>,
507
508 pub room_infos: BTreeMap<OwnedRoomId, RoomInfo>,
510
511 pub receipts: BTreeMap<OwnedRoomId, ReceiptEventContent>,
513
514 pub redactions: BTreeMap<OwnedRoomId, BTreeMap<OwnedEventId, Raw<SyncRoomRedactionEvent>>>,
517
518 pub stripped_state:
521 BTreeMap<OwnedRoomId, BTreeMap<StateEventType, BTreeMap<String, Raw<StrippedState>>>>,
522
523 pub ambiguity_maps: BTreeMap<OwnedRoomId, HashMap<DisplayName, BTreeSet<OwnedUserId>>>,
526}
527
528impl StateChanges {
529 pub fn new(sync_token: String) -> Self {
531 Self { sync_token: Some(sync_token), ..Default::default() }
532 }
533
534 pub fn add_presence_event(&mut self, event: PresenceEvent, raw_event: Raw<PresenceEvent>) {
536 self.presence.insert(event.sender, raw_event);
537 }
538
539 pub fn add_room(&mut self, room: RoomInfo) {
541 self.room_infos.insert(room.room_id.clone(), room);
542 }
543
544 pub fn add_room_account_data(
547 &mut self,
548 room_id: &RoomId,
549 event: AnyRoomAccountDataEvent,
550 raw_event: Raw<AnyRoomAccountDataEvent>,
551 ) {
552 self.room_account_data
553 .entry(room_id.to_owned())
554 .or_default()
555 .insert(event.event_type(), raw_event);
556 }
557
558 pub fn add_stripped_member(
561 &mut self,
562 room_id: &RoomId,
563 user_id: &UserId,
564 event: Raw<StrippedRoomMemberEvent>,
565 ) {
566 self.stripped_state
567 .entry(room_id.to_owned())
568 .or_default()
569 .entry(StateEventType::RoomMember)
570 .or_default()
571 .insert(user_id.into(), event.cast());
572 }
573
574 pub fn add_state_event(
577 &mut self,
578 room_id: &RoomId,
579 event: AnySyncStateEvent,
580 raw_event: Raw<AnySyncStateEvent>,
581 ) {
582 self.state
583 .entry(room_id.to_owned())
584 .or_default()
585 .entry(event.event_type())
586 .or_default()
587 .insert(event.state_key().to_owned(), raw_event);
588 }
589
590 pub fn add_redaction(
592 &mut self,
593 room_id: &RoomId,
594 redacted_event_id: &EventId,
595 redaction: Raw<SyncRoomRedactionEvent>,
596 ) {
597 self.redactions
598 .entry(room_id.to_owned())
599 .or_default()
600 .insert(redacted_event_id.to_owned(), redaction);
601 }
602
603 pub fn add_receipts(&mut self, room_id: &RoomId, event: ReceiptEventContent) {
606 self.receipts.insert(room_id.to_owned(), event);
607 }
608
609 pub(crate) fn state_static_for_key<C, K>(
613 &self,
614 room_id: &RoomId,
615 state_key: &K,
616 ) -> Option<&Raw<SyncStateEvent<C>>>
617 where
618 C: StaticEventContent<IsPrefix = ruma::events::False>
619 + StaticStateEventContent
620 + RedactContent,
621 C::Redacted: RedactedStateEventContent,
622 C::StateKey: Borrow<K>,
623 K: AsRef<str> + ?Sized,
624 {
625 self.state
626 .get(room_id)?
627 .get(&C::TYPE.into())?
628 .get(state_key.as_ref())
629 .map(Raw::cast_ref_unchecked)
630 }
631
632 pub(crate) fn stripped_state_static_for_key<C, K>(
636 &self,
637 room_id: &RoomId,
638 state_key: &K,
639 ) -> Option<&Raw<StrippedStateEvent<C::PossiblyRedacted>>>
640 where
641 C: StaticEventContent<IsPrefix = ruma::events::False> + StaticStateEventContent,
642 C::StateKey: Borrow<K>,
643 K: AsRef<str> + ?Sized,
644 {
645 self.stripped_state
646 .get(room_id)?
647 .get(&C::TYPE.into())?
648 .get(state_key.as_ref())
649 .map(Raw::cast_ref_unchecked)
650 }
651
652 pub(crate) fn any_state_static_for_key<C, K>(
657 &self,
658 room_id: &RoomId,
659 state_key: &K,
660 ) -> Option<StrippedStateEvent<C::PossiblyRedacted>>
661 where
662 C: StaticEventContent<IsPrefix = ruma::events::False>
663 + StaticStateEventContent
664 + RedactContent,
665 C::Redacted: RedactedStateEventContent,
666 C::PossiblyRedacted: StaticEventContent + DeserializeOwned,
667 C::StateKey: Borrow<K>,
668 K: AsRef<str> + ?Sized,
669 {
670 self.state_static_for_key::<C, K>(room_id, state_key)
671 .map(Raw::cast_ref)
672 .or_else(|| self.stripped_state_static_for_key::<C, K>(room_id, state_key))?
673 .deserialize()
674 .ok()
675 }
676
677 pub(crate) fn member(
680 &self,
681 room_id: &RoomId,
682 user_id: &UserId,
683 ) -> Option<StrippedRoomMemberEvent> {
684 self.any_state_static_for_key::<RoomMemberEventContent, _>(room_id, user_id)
685 }
686
687 pub(crate) fn create(&self, room_id: &RoomId) -> Option<RoomCreateWithCreatorEventContent> {
690 self.any_state_static_for_key::<RoomCreateEventContent, _>(room_id, &EmptyStateKey)
691 .map(|event| {
692 RoomCreateWithCreatorEventContent::from_event_content(event.content, event.sender)
693 })
694 .or_else(|| self.room_infos.get(room_id)?.create().cloned())
696 }
697
698 pub(crate) fn power_levels(&self, room_id: &RoomId) -> Option<RoomPowerLevels> {
701 let power_levels_content = self
702 .any_state_static_for_key::<RoomPowerLevelsEventContent, _>(room_id, &EmptyStateKey)?;
703
704 let create_content = self.create(room_id)?;
705 let rules = create_content.room_version.rules().unwrap_or(ROOM_VERSION_RULES_FALLBACK);
706 let creators = create_content.creators();
707
708 Some(power_levels_content.power_levels(&rules.authorization, creators))
709 }
710}
711
712#[derive(Clone)]
727pub struct StoreConfig {
728 #[cfg(feature = "e2e-encryption")]
729 pub(crate) crypto_store: Arc<DynCryptoStore>,
730 pub(crate) state_store: Arc<DynStateStore>,
731 pub(crate) event_cache_store: event_cache_store::EventCacheStoreLock,
732 cross_process_store_locks_holder_name: String,
733}
734
735#[cfg(not(tarpaulin_include))]
736impl fmt::Debug for StoreConfig {
737 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> StdResult<(), fmt::Error> {
738 fmt.debug_struct("StoreConfig").finish()
739 }
740}
741
742impl StoreConfig {
743 #[must_use]
748 pub fn new(cross_process_store_locks_holder_name: String) -> Self {
749 Self {
750 #[cfg(feature = "e2e-encryption")]
751 crypto_store: matrix_sdk_crypto::store::MemoryStore::new().into_crypto_store(),
752 state_store: Arc::new(MemoryStore::new()),
753 event_cache_store: event_cache_store::EventCacheStoreLock::new(
754 event_cache_store::MemoryStore::new(),
755 cross_process_store_locks_holder_name.clone(),
756 ),
757 cross_process_store_locks_holder_name,
758 }
759 }
760
761 #[cfg(feature = "e2e-encryption")]
765 pub fn crypto_store(mut self, store: impl IntoCryptoStore) -> Self {
766 self.crypto_store = store.into_crypto_store();
767 self
768 }
769
770 pub fn state_store(mut self, store: impl IntoStateStore) -> Self {
772 self.state_store = store.into_state_store();
773 self
774 }
775
776 pub fn event_cache_store<S>(mut self, event_cache_store: S) -> Self
778 where
779 S: event_cache_store::IntoEventCacheStore,
780 {
781 self.event_cache_store = event_cache_store::EventCacheStoreLock::new(
782 event_cache_store,
783 self.cross_process_store_locks_holder_name.clone(),
784 );
785 self
786 }
787}
788
789#[cfg(test)]
790mod tests {
791 use std::sync::Arc;
792
793 use assert_matches::assert_matches;
794 use matrix_sdk_test::async_test;
795 use ruma::{owned_device_id, owned_user_id, room_id, user_id};
796 use tokio::sync::broadcast;
797
798 use super::{BaseStateStore, MemoryStore, RoomLoadSettings};
799 use crate::{RoomInfo, RoomState, SessionMeta, StateChanges};
800
801 #[async_test]
802 async fn test_set_session_meta() {
803 let store = BaseStateStore::new(Arc::new(MemoryStore::new()));
804
805 let session_meta = SessionMeta {
806 user_id: owned_user_id!("@mnt_io:matrix.org"),
807 device_id: owned_device_id!("HELLOYOU"),
808 };
809
810 assert!(store.session_meta.get().is_none());
811
812 store.set_session_meta(session_meta.clone());
813
814 assert_eq!(store.session_meta.get(), Some(&session_meta));
815 }
816
817 #[async_test]
818 #[should_panic]
819 async fn test_set_session_meta_twice() {
820 let store = BaseStateStore::new(Arc::new(MemoryStore::new()));
821
822 let session_meta = SessionMeta {
823 user_id: owned_user_id!("@mnt_io:matrix.org"),
824 device_id: owned_device_id!("HELLOYOU"),
825 };
826
827 store.set_session_meta(session_meta.clone());
828 store.set_session_meta(session_meta);
830 }
831
832 #[async_test]
833 async fn test_derive_from_other() {
834 let other = BaseStateStore::new(Arc::new(MemoryStore::new()));
836
837 let session_meta = SessionMeta {
838 user_id: owned_user_id!("@mnt_io:matrix.org"),
839 device_id: owned_device_id!("HELLOYOU"),
840 };
841 let (room_info_notable_update_sender, _) = broadcast::channel(1);
842 let room_id_0 = room_id!("!r0");
843
844 other
845 .load_rooms(
846 &session_meta.user_id,
847 RoomLoadSettings::One(room_id_0.to_owned()),
848 &room_info_notable_update_sender,
849 )
850 .await
851 .unwrap();
852 other.set_session_meta(session_meta.clone());
853
854 let store = BaseStateStore::new(Arc::new(MemoryStore::new()));
856 store.derive_from_other(&other, &room_info_notable_update_sender).await.unwrap();
857
858 assert_eq!(store.session_meta.get(), Some(&session_meta));
860 assert_matches!(*store.room_load_settings.read().await, RoomLoadSettings::One(ref room_id) => {
862 assert_eq!(room_id, room_id_0);
863 });
864 }
865
866 #[test]
867 fn test_room_load_settings_default() {
868 assert_matches!(RoomLoadSettings::default(), RoomLoadSettings::All);
869 }
870
871 #[async_test]
872 async fn test_load_all_rooms() {
873 let room_id_0 = room_id!("!r0");
874 let room_id_1 = room_id!("!r1");
875 let user_id = user_id!("@mnt_io:matrix.org");
876
877 let memory_state_store = Arc::new(MemoryStore::new());
878
879 {
881 let store = BaseStateStore::new(memory_state_store.clone());
882 let mut changes = StateChanges::default();
883 changes.add_room(RoomInfo::new(room_id_0, RoomState::Joined));
884 changes.add_room(RoomInfo::new(room_id_1, RoomState::Joined));
885
886 store.inner.save_changes(&changes).await.unwrap();
887 }
888
889 {
891 let store = BaseStateStore::new(memory_state_store.clone());
892 let (room_info_notable_update_sender, _) = broadcast::channel(2);
893
894 assert_matches!(*store.room_load_settings.read().await, RoomLoadSettings::All);
896
897 store
899 .load_rooms(user_id, RoomLoadSettings::All, &room_info_notable_update_sender)
900 .await
901 .unwrap();
902
903 assert_matches!(*store.room_load_settings.read().await, RoomLoadSettings::All);
905
906 let mut rooms = store.rooms();
908 rooms.sort_by(|a, b| a.room_id().cmp(b.room_id()));
909
910 assert_eq!(rooms.len(), 2);
911
912 assert_eq!(rooms[0].room_id(), room_id_0);
913 assert_eq!(rooms[0].own_user_id(), user_id);
914
915 assert_eq!(rooms[1].room_id(), room_id_1);
916 assert_eq!(rooms[1].own_user_id(), user_id);
917 }
918 }
919
920 #[async_test]
921 async fn test_load_one_room() {
922 let room_id_0 = room_id!("!r0");
923 let room_id_1 = room_id!("!r1");
924 let user_id = user_id!("@mnt_io:matrix.org");
925
926 let memory_state_store = Arc::new(MemoryStore::new());
927
928 {
930 let store = BaseStateStore::new(memory_state_store.clone());
931 let mut changes = StateChanges::default();
932 changes.add_room(RoomInfo::new(room_id_0, RoomState::Joined));
933 changes.add_room(RoomInfo::new(room_id_1, RoomState::Joined));
934
935 store.inner.save_changes(&changes).await.unwrap();
936 }
937
938 {
940 let store = BaseStateStore::new(memory_state_store.clone());
941 let (room_info_notable_update_sender, _) = broadcast::channel(2);
942
943 assert_matches!(*store.room_load_settings.read().await, RoomLoadSettings::All);
945
946 store
948 .load_rooms(
949 user_id,
950 RoomLoadSettings::One(room_id_1.to_owned()),
951 &room_info_notable_update_sender,
952 )
953 .await
954 .unwrap();
955
956 assert_matches!(
958 *store.room_load_settings.read().await,
959 RoomLoadSettings::One(ref room_id) => {
960 assert_eq!(room_id, room_id_1);
961 }
962 );
963
964 let rooms = store.rooms();
966 assert_eq!(rooms.len(), 1);
967
968 assert_eq!(rooms[0].room_id(), room_id_1);
969 assert_eq!(rooms[0].own_user_id(), user_id);
970 }
971 }
972}