1use std::{
24 borrow::Borrow,
25 collections::{BTreeMap, BTreeSet, HashMap, HashSet},
26 fmt,
27 ops::Deref,
28 result::Result as StdResult,
29 str::{FromStr, Utf8Error},
30 sync::{Arc, OnceLock, RwLock as StdRwLock},
31};
32
33use eyeball_im::{Vector, VectorDiff};
34use futures_util::Stream;
35use matrix_sdk_common::ROOM_VERSION_RULES_FALLBACK;
36
37#[cfg(any(test, feature = "testing"))]
38#[macro_use]
39pub mod integration_tests;
40mod observable_map;
41mod traits;
42
43use matrix_sdk_common::{cross_process_lock::CrossProcessLockConfig, locks::Mutex as SyncMutex};
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, MilliSecondsSinceUnixEpoch, 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 profile::UserProfileUpdate,
65 serde::Raw,
66};
67use serde::de::DeserializeOwned;
68use tokio::sync::{Mutex, RwLock, broadcast};
69use tracing::warn;
70pub use traits::compare_thread_subscription_bump_stamps;
71
72use crate::{
73 MinimalRoomMemberEvent, Room, RoomCreateWithCreatorEventContent, RoomStateFilter, SessionMeta,
74 deserialized_responses::DisplayName,
75 event_cache::store as event_cache_store,
76 media::store as media_store,
77 room::{RoomInfo, RoomInfoNotableUpdate, RoomState},
78};
79
80pub(crate) mod ambiguity_map;
81mod avatar_cache;
82mod memory_store;
83pub mod migration_helpers;
84mod send_queue;
85
86pub use avatar_cache::AvatarCache;
87
88#[cfg(any(test, feature = "testing"))]
89pub use self::integration_tests::StateStoreIntegrationTests;
90#[cfg(feature = "unstable-msc4274")]
91pub use self::send_queue::{AccumulatedSentMediaInfo, FinishGalleryItemInfo};
92pub use self::{
93 memory_store::MemoryStore,
94 send_queue::{
95 ChildTransactionId, DependentQueuedRequest, DependentQueuedRequestKind,
96 FinishUploadThumbnailInfo, QueueWedgeError, QueuedRequest, QueuedRequestKind,
97 SentMediaInfo, SentRequestKey, SerializableEventContent,
98 },
99 traits::{
100 ComposerDraft, ComposerDraftType, DraftAttachment, DraftAttachmentContent, DraftThumbnail,
101 DynStateStore, IncorrectMutexGuardError, IntoStateStore, SaveLockedStateStore, StateStore,
102 StateStoreDataKey, StateStoreDataValue, StateStoreExt, SupportedVersionsResponse,
103 ThreadSubscriptionCatchupToken, WellKnownResponse,
104 },
105};
106
107#[derive(Debug, thiserror::Error)]
109pub enum StoreError {
110 #[error(transparent)]
112 Backend(Box<dyn std::error::Error + Send + Sync>),
113
114 #[error(transparent)]
116 Json(#[from] serde_json::Error),
117
118 #[error(transparent)]
121 Identifier(#[from] ruma::IdParseError),
122
123 #[error("The store failed to be unlocked")]
126 StoreLocked,
127
128 #[error("The store is not encrypted but was tried to be opened with a passphrase")]
130 UnencryptedStore,
131
132 #[error("Error encrypting or decrypting data from the store: {0}")]
134 Encryption(#[from] StoreEncryptionError),
135
136 #[error("Error encoding or decoding data from the store: {0}")]
138 Codec(#[from] Utf8Error),
139
140 #[error(
142 "The database format changed in an incompatible way, current \
143 version: {0}, latest version: {1}"
144 )]
145 UnsupportedDatabaseVersion(usize, usize),
146
147 #[error("Redaction failed: {0}")]
151 Redaction(#[source] ruma::canonical_json::CanonicalJsonFieldError),
152
153 #[error("The store contains invalid data: {details}")]
155 InvalidData {
156 details: String,
158 },
159}
160
161impl StoreError {
162 #[inline]
166 pub fn backend<E>(error: E) -> Self
167 where
168 E: std::error::Error + Send + Sync + 'static,
169 {
170 Self::Backend(Box::new(error))
171 }
172}
173
174pub type Result<T, E = StoreError> = std::result::Result<T, E>;
176
177#[derive(Clone)]
182pub(crate) struct BaseStateStore {
183 pub(super) inner: SaveLockedStateStore,
184 session_meta: Arc<OnceLock<SessionMeta>>,
185 room_load_settings: Arc<RwLock<RoomLoadSettings>>,
186
187 pub(crate) room_info_notable_update_sender: broadcast::Sender<RoomInfoNotableUpdate>,
190
191 pub(super) sync_token: Arc<RwLock<Option<String>>>,
193
194 rooms: Arc<StdRwLock<ObservableMap<OwnedRoomId, Room>>>,
196
197 pub(crate) already_logged_missing_room: Arc<SyncMutex<HashSet<OwnedRoomId>>>,
200}
201
202impl BaseStateStore {
203 pub fn new(inner: Arc<DynStateStore>) -> Self {
205 let (room_info_notable_update_sender, _room_info_notable_update_receiver) =
215 broadcast::channel(500);
216
217 Self {
218 inner: SaveLockedStateStore::new(inner),
219 session_meta: Default::default(),
220 room_load_settings: Default::default(),
221 room_info_notable_update_sender,
222 sync_token: Default::default(),
223 rooms: Arc::new(StdRwLock::new(ObservableMap::new())),
224 already_logged_missing_room: Default::default(),
225 }
226 }
227
228 pub fn lock(&self) -> &Mutex<()> {
230 self.inner.lock()
231 }
232
233 pub(crate) fn set_session_meta(&self, session_meta: SessionMeta) {
239 self.session_meta.set(session_meta).expect("`SessionMeta` was already set");
240 }
241
242 pub(crate) async fn load_rooms(
245 &self,
246 user_id: &UserId,
247 room_load_settings: RoomLoadSettings,
248 ) -> Result<()> {
249 *self.room_load_settings.write().await = room_load_settings.clone();
250
251 let room_infos = self.load_and_migrate_room_infos(room_load_settings).await?;
252
253 let mut rooms = self.rooms.write().unwrap();
254
255 for room_info in room_infos {
256 let new_room = Room::restore(
257 user_id,
258 self.inner.clone(),
259 room_info,
260 self.room_info_notable_update_sender.clone(),
261 );
262 let new_room_id = new_room.room_id().to_owned();
263
264 rooms.insert(new_room_id, new_room);
265 }
266
267 Ok(())
268 }
269
270 async fn load_and_migrate_room_infos(
273 &self,
274 room_load_settings: RoomLoadSettings,
275 ) -> Result<Vec<RoomInfo>> {
276 let mut room_infos = self.inner.get_room_infos(&room_load_settings).await?;
277 let mut migrated_room_infos = Vec::with_capacity(room_infos.len());
278
279 for room_info in room_infos.iter_mut() {
280 if room_info.apply_migrations(self.inner.clone()).await {
281 migrated_room_infos.push(room_info.clone());
282 }
283 }
284
285 if !migrated_room_infos.is_empty() {
286 let changes = StateChanges {
287 room_infos: migrated_room_infos
288 .into_iter()
289 .map(|room_info| (room_info.room_id.clone(), room_info))
290 .collect(),
291 ..Default::default()
292 };
293
294 if let Err(error) = self.inner.save_changes(&changes).await {
295 warn!("Failed to save migrated room infos: {error}");
296 }
297 }
298
299 Ok(room_infos)
300 }
301
302 pub(crate) async fn load_sync_token(&self) -> Result<()> {
305 let token =
306 self.get_kv_data(StateStoreDataKey::SyncToken).await?.and_then(|s| s.into_sync_token());
307 *self.sync_token.write().await = token;
308
309 Ok(())
310 }
311
312 #[cfg(any(feature = "e2e-encryption", test))]
315 pub(crate) async fn derive_from_other(&self, other: &Self) -> Result<()> {
316 let Some(session_meta) = other.session_meta.get() else {
317 return Ok(());
318 };
319
320 let room_load_settings = other.room_load_settings.read().await.clone();
321
322 self.load_rooms(&session_meta.user_id, room_load_settings).await?;
323 self.load_sync_token().await?;
324 self.set_session_meta(session_meta.clone());
325
326 Ok(())
327 }
328
329 pub fn session_meta(&self) -> Option<&SessionMeta> {
331 self.session_meta.get()
332 }
333
334 pub fn rooms(&self) -> Vec<Room> {
336 self.rooms.read().unwrap().iter().cloned().collect()
337 }
338
339 pub fn rooms_filtered(&self, filter: RoomStateFilter) -> Vec<Room> {
341 self.rooms
342 .read()
343 .unwrap()
344 .iter()
345 .filter(|room| filter.matches(room.state()))
346 .cloned()
347 .collect()
348 }
349
350 pub fn rooms_stream(
353 &self,
354 ) -> (Vector<Room>, impl Stream<Item = Vec<VectorDiff<Room>>> + use<>) {
355 self.rooms.read().unwrap().stream()
356 }
357
358 pub fn room(&self, room_id: &RoomId) -> Option<Room> {
360 self.rooms.read().unwrap().get(room_id).cloned()
361 }
362
363 pub(crate) fn room_exists(&self, room_id: &RoomId) -> bool {
365 self.rooms.read().unwrap().get(room_id).is_some()
366 }
367
368 pub fn get_or_create_room(&self, room_id: &RoomId, room_state: RoomState) -> Room {
371 let user_id =
372 &self.session_meta.get().expect("Creating room while not being logged in").user_id;
373
374 self.rooms
375 .write()
376 .unwrap()
377 .get_or_create(room_id, || {
378 Room::new(
379 user_id,
380 self.inner.clone(),
381 room_id,
382 room_state,
383 self.room_info_notable_update_sender.clone(),
384 )
385 })
386 .clone()
387 }
388
389 pub(crate) async fn forget_room(&self, room_id: &RoomId) -> Result<()> {
395 self.inner.remove_room(room_id).await?;
396 self.rooms.write().unwrap().remove(room_id);
397 Ok(())
398 }
399}
400
401#[cfg(not(tarpaulin_include))]
402impl fmt::Debug for BaseStateStore {
403 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
404 f.debug_struct("Store")
405 .field("inner", &self.inner)
406 .field("session_meta", &self.session_meta)
407 .field("sync_token", &self.sync_token)
408 .field("rooms", &self.rooms)
409 .finish_non_exhaustive()
410 }
411}
412
413impl Deref for BaseStateStore {
414 type Target = SaveLockedStateStore;
415
416 fn deref(&self) -> &Self::Target {
417 &self.inner
418 }
419}
420
421#[derive(Clone, Debug, Default)]
456pub enum RoomLoadSettings {
457 #[default]
462 All,
463
464 One(OwnedRoomId),
470}
471
472#[derive(Clone, Copy, Debug, PartialEq, Eq)]
478pub enum ThreadSubscriptionStatus {
479 Subscribed {
481 automatic: bool,
484 },
485
486 Unsubscribed,
488}
489
490impl FromStr for ThreadSubscriptionStatus {
491 type Err = ();
492
493 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
494 match s {
495 "automatic" => Ok(ThreadSubscriptionStatus::Subscribed { automatic: true }),
496 "manual" => Ok(ThreadSubscriptionStatus::Subscribed { automatic: false }),
497 "unsubscribed" => Ok(ThreadSubscriptionStatus::Unsubscribed),
498 _ => Err(()),
499 }
500 }
501}
502
503impl ThreadSubscriptionStatus {
504 pub fn as_str(&self) -> &'static str {
510 match self {
511 ThreadSubscriptionStatus::Subscribed { automatic } => {
512 if *automatic {
513 "automatic"
514 } else {
515 "manual"
516 }
517 }
518 ThreadSubscriptionStatus::Unsubscribed => "unsubscribed",
519 }
520 }
521}
522
523#[derive(Clone, Copy, Debug, PartialEq, Eq)]
525pub struct StoredThreadSubscription {
526 pub status: ThreadSubscriptionStatus,
528
529 pub bump_stamp: Option<u64>,
537}
538
539#[derive(Clone, Debug, Default)]
541pub struct StateChanges {
542 pub sync_token: Option<String>,
544 pub account_data: BTreeMap<GlobalAccountDataEventType, Raw<AnyGlobalAccountDataEvent>>,
546 pub presence: BTreeMap<OwnedUserId, Raw<PresenceEvent>>,
548
549 pub profiles: BTreeMap<OwnedRoomId, BTreeMap<OwnedUserId, MinimalRoomMemberEvent>>,
552
553 pub profiles_to_delete: BTreeMap<OwnedRoomId, Vec<OwnedUserId>>,
557
558 pub state:
561 BTreeMap<OwnedRoomId, BTreeMap<StateEventType, BTreeMap<String, Raw<AnySyncStateEvent>>>>,
562 pub room_account_data:
564 BTreeMap<OwnedRoomId, BTreeMap<RoomAccountDataEventType, Raw<AnyRoomAccountDataEvent>>>,
565
566 pub room_infos: BTreeMap<OwnedRoomId, RoomInfo>,
568
569 pub receipts: BTreeMap<OwnedRoomId, ReceiptEventContent>,
571
572 pub redactions: BTreeMap<OwnedRoomId, BTreeMap<OwnedEventId, Raw<SyncRoomRedactionEvent>>>,
575
576 pub stripped_state: BTreeMap<
579 OwnedRoomId,
580 BTreeMap<StateEventType, BTreeMap<String, Raw<AnyStrippedStateEvent>>>,
581 >,
582
583 pub ambiguity_maps: BTreeMap<OwnedRoomId, HashMap<DisplayName, BTreeSet<OwnedUserId>>>,
586
587 pub global_profiles: BTreeMap<OwnedUserId, UserProfileUpdate>,
593
594 pub unavailable_global_profiles: BTreeMap<OwnedUserId, MilliSecondsSinceUnixEpoch>,
600}
601
602impl StateChanges {
603 pub fn new(sync_token: String) -> Self {
605 Self { sync_token: Some(sync_token), ..Default::default() }
606 }
607
608 pub fn add_presence_event(&mut self, event: PresenceEvent, raw_event: Raw<PresenceEvent>) {
610 self.presence.insert(event.sender, raw_event);
611 }
612
613 pub fn add_room(&mut self, room: RoomInfo) {
615 self.room_infos.insert(room.room_id.clone(), room);
616 }
617
618 pub fn add_room_account_data(
621 &mut self,
622 room_id: &RoomId,
623 event: AnyRoomAccountDataEvent,
624 raw_event: Raw<AnyRoomAccountDataEvent>,
625 ) {
626 self.room_account_data
627 .entry(room_id.to_owned())
628 .or_default()
629 .insert(event.event_type(), raw_event);
630 }
631
632 pub fn add_stripped_member(
635 &mut self,
636 room_id: &RoomId,
637 user_id: &UserId,
638 event: Raw<StrippedRoomMemberEvent>,
639 ) {
640 self.stripped_state
641 .entry(room_id.to_owned())
642 .or_default()
643 .entry(StateEventType::RoomMember)
644 .or_default()
645 .insert(user_id.into(), event.cast());
646 }
647
648 pub fn add_state_event(
651 &mut self,
652 room_id: &RoomId,
653 event: AnySyncStateEvent,
654 raw_event: Raw<AnySyncStateEvent>,
655 ) {
656 self.state
657 .entry(room_id.to_owned())
658 .or_default()
659 .entry(event.event_type())
660 .or_default()
661 .insert(event.state_key().to_owned(), raw_event);
662 }
663
664 pub fn add_redaction(
666 &mut self,
667 room_id: &RoomId,
668 redacted_event_id: &EventId,
669 redaction: Raw<SyncRoomRedactionEvent>,
670 ) {
671 self.redactions
672 .entry(room_id.to_owned())
673 .or_default()
674 .insert(redacted_event_id.to_owned(), redaction);
675 }
676
677 pub fn add_receipts(&mut self, room_id: &RoomId, event: ReceiptEventContent) {
680 self.receipts.insert(room_id.to_owned(), event);
681 }
682
683 pub(crate) fn state_static_for_key<C, K>(
687 &self,
688 room_id: &RoomId,
689 state_key: &K,
690 ) -> Option<&Raw<SyncStateEvent<C>>>
691 where
692 C: StaticEventContent<IsPrefix = ruma::events::False>
693 + StaticStateEventContent
694 + RedactContent,
695 C::Redacted: RedactedStateEventContent,
696 C::StateKey: Borrow<K>,
697 K: AsRef<str> + ?Sized,
698 {
699 self.state
700 .get(room_id)?
701 .get(&C::TYPE.into())?
702 .get(state_key.as_ref())
703 .map(Raw::cast_ref_unchecked)
704 }
705
706 pub(crate) fn stripped_state_static_for_key<C, K>(
710 &self,
711 room_id: &RoomId,
712 state_key: &K,
713 ) -> Option<&Raw<StrippedStateEvent<C::PossiblyRedacted>>>
714 where
715 C: StaticEventContent<IsPrefix = ruma::events::False> + StaticStateEventContent,
716 C::StateKey: Borrow<K>,
717 K: AsRef<str> + ?Sized,
718 {
719 self.stripped_state
720 .get(room_id)?
721 .get(&C::TYPE.into())?
722 .get(state_key.as_ref())
723 .map(Raw::cast_ref_unchecked)
724 }
725
726 pub(crate) fn any_state_static_for_key<C, K>(
731 &self,
732 room_id: &RoomId,
733 state_key: &K,
734 ) -> Option<StrippedStateEvent<C::PossiblyRedacted>>
735 where
736 C: StaticEventContent<IsPrefix = ruma::events::False>
737 + StaticStateEventContent
738 + RedactContent,
739 C::Redacted: RedactedStateEventContent,
740 C::PossiblyRedacted: StaticEventContent + DeserializeOwned,
741 C::StateKey: Borrow<K>,
742 K: AsRef<str> + ?Sized,
743 {
744 self.state_static_for_key::<C, K>(room_id, state_key)
745 .map(Raw::cast_ref)
746 .or_else(|| self.stripped_state_static_for_key::<C, K>(room_id, state_key))?
747 .deserialize()
748 .ok()
749 }
750
751 pub(crate) fn member(
754 &self,
755 room_id: &RoomId,
756 user_id: &UserId,
757 ) -> Option<StrippedRoomMemberEvent> {
758 self.any_state_static_for_key::<RoomMemberEventContent, _>(room_id, user_id)
759 }
760
761 pub(crate) fn create(&self, room_id: &RoomId) -> Option<RoomCreateWithCreatorEventContent> {
764 self.any_state_static_for_key::<RoomCreateEventContent, _>(room_id, &EmptyStateKey)
765 .map(|event| {
766 RoomCreateWithCreatorEventContent::from_event_content(event.content, event.sender)
767 })
768 .or_else(|| self.room_infos.get(room_id)?.create().cloned())
770 }
771
772 pub(crate) fn power_levels(&self, room_id: &RoomId) -> Option<RoomPowerLevels> {
775 let power_levels_content = self
776 .any_state_static_for_key::<RoomPowerLevelsEventContent, _>(room_id, &EmptyStateKey)?;
777
778 let create_content = self.create(room_id)?;
779 let rules = create_content.room_version.rules().unwrap_or(ROOM_VERSION_RULES_FALLBACK);
780 let creators = create_content.creators();
781
782 Some(power_levels_content.power_levels(&rules.authorization, creators))
783 }
784}
785
786#[derive(Clone)]
803pub struct StoreConfig {
804 #[cfg(feature = "e2e-encryption")]
805 pub(crate) crypto_store: Arc<DynCryptoStore>,
806 pub(crate) state_store: Arc<DynStateStore>,
807 pub(crate) event_cache_store: event_cache_store::EventCacheStoreLock,
808 pub(crate) media_store: media_store::MediaStoreLock,
809 cross_process_lock_config: CrossProcessLockConfig,
810}
811
812#[cfg(not(tarpaulin_include))]
813impl fmt::Debug for StoreConfig {
814 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> StdResult<(), fmt::Error> {
815 fmt.debug_struct("StoreConfig").finish()
816 }
817}
818
819impl StoreConfig {
820 #[must_use]
825 pub fn new(cross_process_lock_config: CrossProcessLockConfig) -> Self {
826 Self {
827 #[cfg(feature = "e2e-encryption")]
828 crypto_store: matrix_sdk_crypto::store::MemoryStore::new().into_crypto_store(),
829 state_store: Arc::new(MemoryStore::new()),
830 event_cache_store: event_cache_store::EventCacheStoreLock::new(
831 event_cache_store::MemoryStore::new(),
832 cross_process_lock_config.clone(),
833 ),
834 media_store: media_store::MediaStoreLock::new(
835 media_store::MemoryMediaStore::new(),
836 cross_process_lock_config.clone(),
837 ),
838 cross_process_lock_config,
839 }
840 }
841
842 #[cfg(feature = "e2e-encryption")]
846 pub fn crypto_store(mut self, store: impl IntoCryptoStore) -> Self {
847 self.crypto_store = store.into_crypto_store();
848 self
849 }
850
851 pub fn state_store(mut self, store: impl IntoStateStore) -> Self {
853 self.state_store = store.into_state_store();
854 self
855 }
856
857 pub fn event_cache_store<S>(mut self, event_cache_store: S) -> Self
859 where
860 S: event_cache_store::IntoEventCacheStore,
861 {
862 self.event_cache_store = event_cache_store::EventCacheStoreLock::new(
863 event_cache_store,
864 self.cross_process_lock_config.clone(),
865 );
866 self
867 }
868
869 pub fn media_store<S>(mut self, media_store: S) -> Self
871 where
872 S: media_store::IntoMediaStore,
873 {
874 self.media_store =
875 media_store::MediaStoreLock::new(media_store, self.cross_process_lock_config.clone());
876 self
877 }
878}
879
880#[cfg(test)]
881mod tests {
882 use std::{ops::Not, sync::Arc};
883
884 use assert_matches::assert_matches;
885 use matrix_sdk_test::async_test;
886 use ruma::{owned_device_id, owned_user_id, room_id, user_id};
887
888 use super::{BaseStateStore, MemoryStore, RoomLoadSettings};
889 use crate::{RoomInfo, RoomState, SessionMeta, StateChanges, StateStore};
890
891 #[async_test]
892 async fn test_set_session_meta() {
893 let store = BaseStateStore::new(Arc::new(MemoryStore::new()));
894
895 let session_meta = SessionMeta {
896 user_id: owned_user_id!("@mnt_io:matrix.org"),
897 device_id: owned_device_id!("HELLOYOU"),
898 };
899
900 assert!(store.session_meta.get().is_none());
901
902 store.set_session_meta(session_meta.clone());
903
904 assert_eq!(store.session_meta.get(), Some(&session_meta));
905 }
906
907 #[async_test]
908 #[should_panic]
909 async fn test_set_session_meta_twice() {
910 let store = BaseStateStore::new(Arc::new(MemoryStore::new()));
911
912 let session_meta = SessionMeta {
913 user_id: owned_user_id!("@mnt_io:matrix.org"),
914 device_id: owned_device_id!("HELLOYOU"),
915 };
916
917 store.set_session_meta(session_meta.clone());
918 store.set_session_meta(session_meta);
920 }
921
922 #[async_test]
923 async fn test_derive_from_other() {
924 let other = BaseStateStore::new(Arc::new(MemoryStore::new()));
926
927 let session_meta = SessionMeta {
928 user_id: owned_user_id!("@mnt_io:matrix.org"),
929 device_id: owned_device_id!("HELLOYOU"),
930 };
931 let room_id_0 = room_id!("!r0");
932
933 other
934 .load_rooms(&session_meta.user_id, RoomLoadSettings::One(room_id_0.to_owned()))
935 .await
936 .unwrap();
937 other.set_session_meta(session_meta.clone());
938
939 let store = BaseStateStore::new(Arc::new(MemoryStore::new()));
941 store.derive_from_other(&other).await.unwrap();
942
943 assert_eq!(store.session_meta.get(), Some(&session_meta));
945 assert_matches!(*store.room_load_settings.read().await, RoomLoadSettings::One(ref room_id) => {
947 assert_eq!(room_id, room_id_0);
948 });
949
950 assert!(
953 store
954 .room_info_notable_update_sender
955 .same_channel(&other.room_info_notable_update_sender)
956 .not()
957 );
958 }
959
960 #[test]
961 fn test_room_load_settings_default() {
962 assert_matches!(RoomLoadSettings::default(), RoomLoadSettings::All);
963 }
964
965 #[async_test]
966 async fn test_load_all_rooms() {
967 let room_id_0 = room_id!("!r0");
968 let room_id_1 = room_id!("!r1");
969 let user_id = user_id!("@mnt_io:matrix.org");
970
971 let memory_state_store = Arc::new(MemoryStore::new());
972
973 {
975 let store = BaseStateStore::new(memory_state_store.clone());
976 let mut changes = StateChanges::default();
977 changes.add_room(RoomInfo::new(room_id_0, RoomState::Joined));
978 changes.add_room(RoomInfo::new(room_id_1, RoomState::Joined));
979
980 store.inner.save_changes(&changes).await.unwrap();
981 }
982
983 {
985 let store = BaseStateStore::new(memory_state_store.clone());
986
987 assert_matches!(*store.room_load_settings.read().await, RoomLoadSettings::All);
989
990 store.load_rooms(user_id, RoomLoadSettings::All).await.unwrap();
992
993 assert_matches!(*store.room_load_settings.read().await, RoomLoadSettings::All);
995
996 let mut rooms = store.rooms();
998 rooms.sort_by(|a, b| a.room_id().cmp(b.room_id()));
999
1000 assert_eq!(rooms.len(), 2);
1001
1002 assert_eq!(rooms[0].room_id(), room_id_0);
1003 assert_eq!(rooms[0].own_user_id(), user_id);
1004
1005 assert_eq!(rooms[1].room_id(), room_id_1);
1006 assert_eq!(rooms[1].own_user_id(), user_id);
1007 }
1008 }
1009
1010 #[async_test]
1011 async fn test_load_one_room() {
1012 let room_id_0 = room_id!("!r0");
1013 let room_id_1 = room_id!("!r1");
1014 let user_id = user_id!("@mnt_io:matrix.org");
1015
1016 let memory_state_store = Arc::new(MemoryStore::new());
1017
1018 {
1020 let store = BaseStateStore::new(memory_state_store.clone());
1021 let mut changes = StateChanges::default();
1022 changes.add_room(RoomInfo::new(room_id_0, RoomState::Joined));
1023 changes.add_room(RoomInfo::new(room_id_1, RoomState::Joined));
1024
1025 store.inner.save_changes(&changes).await.unwrap();
1026 }
1027
1028 {
1030 let store = BaseStateStore::new(memory_state_store.clone());
1031
1032 assert_matches!(*store.room_load_settings.read().await, RoomLoadSettings::All);
1034
1035 store.load_rooms(user_id, RoomLoadSettings::One(room_id_1.to_owned())).await.unwrap();
1037
1038 assert_matches!(
1040 *store.room_load_settings.read().await,
1041 RoomLoadSettings::One(ref room_id) => {
1042 assert_eq!(room_id, room_id_1);
1043 }
1044 );
1045
1046 let rooms = store.rooms();
1048 assert_eq!(rooms.len(), 1);
1049
1050 assert_eq!(rooms[0].room_id(), room_id_1);
1051 assert_eq!(rooms[0].own_user_id(), user_id);
1052 }
1053 }
1054}