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