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, 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 {
511 match self {
512 ThreadSubscriptionStatus::Subscribed { automatic } => {
513 if *automatic {
514 "automatic"
515 } else {
516 "manual"
517 }
518 }
519 ThreadSubscriptionStatus::Unsubscribed => "unsubscribed",
520 }
521 }
522}
523
524#[derive(Clone, Copy, Debug, PartialEq, Eq)]
526pub struct StoredThreadSubscription {
527 pub status: ThreadSubscriptionStatus,
529
530 pub bump_stamp: Option<u64>,
538}
539
540#[derive(Clone, Debug, Default)]
542pub struct StateChanges {
543 pub sync_token: Option<String>,
545 pub account_data: BTreeMap<GlobalAccountDataEventType, Raw<AnyGlobalAccountDataEvent>>,
547 pub presence: BTreeMap<OwnedUserId, Raw<PresenceEvent>>,
549
550 pub profiles: BTreeMap<OwnedRoomId, BTreeMap<OwnedUserId, MinimalRoomMemberEvent>>,
553
554 pub profiles_to_delete: BTreeMap<OwnedRoomId, Vec<OwnedUserId>>,
558
559 pub state:
562 BTreeMap<OwnedRoomId, BTreeMap<StateEventType, BTreeMap<String, Raw<AnySyncStateEvent>>>>,
563 pub room_account_data:
565 BTreeMap<OwnedRoomId, BTreeMap<RoomAccountDataEventType, Raw<AnyRoomAccountDataEvent>>>,
566
567 pub room_infos: BTreeMap<OwnedRoomId, RoomInfo>,
569
570 pub receipts: BTreeMap<OwnedRoomId, ReceiptEventContent>,
572
573 pub redactions: BTreeMap<OwnedRoomId, BTreeMap<OwnedEventId, Raw<SyncRoomRedactionEvent>>>,
576
577 pub stripped_state: BTreeMap<
580 OwnedRoomId,
581 BTreeMap<StateEventType, BTreeMap<String, Raw<AnyStrippedStateEvent>>>,
582 >,
583
584 pub ambiguity_maps: BTreeMap<OwnedRoomId, HashMap<DisplayName, BTreeSet<OwnedUserId>>>,
587
588 pub global_profiles: BTreeMap<OwnedUserId, UserProfileUpdate>,
594}
595
596impl StateChanges {
597 pub fn new(sync_token: String) -> Self {
599 Self { sync_token: Some(sync_token), ..Default::default() }
600 }
601
602 pub fn add_presence_event(&mut self, event: PresenceEvent, raw_event: Raw<PresenceEvent>) {
604 self.presence.insert(event.sender, raw_event);
605 }
606
607 pub fn add_room(&mut self, room: RoomInfo) {
609 self.room_infos.insert(room.room_id.clone(), room);
610 }
611
612 pub fn add_room_account_data(
615 &mut self,
616 room_id: &RoomId,
617 event: AnyRoomAccountDataEvent,
618 raw_event: Raw<AnyRoomAccountDataEvent>,
619 ) {
620 self.room_account_data
621 .entry(room_id.to_owned())
622 .or_default()
623 .insert(event.event_type(), raw_event);
624 }
625
626 pub fn add_stripped_member(
629 &mut self,
630 room_id: &RoomId,
631 user_id: &UserId,
632 event: Raw<StrippedRoomMemberEvent>,
633 ) {
634 self.stripped_state
635 .entry(room_id.to_owned())
636 .or_default()
637 .entry(StateEventType::RoomMember)
638 .or_default()
639 .insert(user_id.into(), event.cast());
640 }
641
642 pub fn add_state_event(
645 &mut self,
646 room_id: &RoomId,
647 event: AnySyncStateEvent,
648 raw_event: Raw<AnySyncStateEvent>,
649 ) {
650 self.state
651 .entry(room_id.to_owned())
652 .or_default()
653 .entry(event.event_type())
654 .or_default()
655 .insert(event.state_key().to_owned(), raw_event);
656 }
657
658 pub fn add_redaction(
660 &mut self,
661 room_id: &RoomId,
662 redacted_event_id: &EventId,
663 redaction: Raw<SyncRoomRedactionEvent>,
664 ) {
665 self.redactions
666 .entry(room_id.to_owned())
667 .or_default()
668 .insert(redacted_event_id.to_owned(), redaction);
669 }
670
671 pub fn add_receipts(&mut self, room_id: &RoomId, event: ReceiptEventContent) {
674 self.receipts.insert(room_id.to_owned(), event);
675 }
676
677 pub(crate) fn state_static_for_key<C, K>(
681 &self,
682 room_id: &RoomId,
683 state_key: &K,
684 ) -> Option<&Raw<SyncStateEvent<C>>>
685 where
686 C: StaticEventContent<IsPrefix = ruma::events::False>
687 + StaticStateEventContent
688 + RedactContent,
689 C::Redacted: RedactedStateEventContent,
690 C::StateKey: Borrow<K>,
691 K: AsRef<str> + ?Sized,
692 {
693 self.state
694 .get(room_id)?
695 .get(&C::TYPE.into())?
696 .get(state_key.as_ref())
697 .map(Raw::cast_ref_unchecked)
698 }
699
700 pub(crate) fn stripped_state_static_for_key<C, K>(
704 &self,
705 room_id: &RoomId,
706 state_key: &K,
707 ) -> Option<&Raw<StrippedStateEvent<C::PossiblyRedacted>>>
708 where
709 C: StaticEventContent<IsPrefix = ruma::events::False> + StaticStateEventContent,
710 C::StateKey: Borrow<K>,
711 K: AsRef<str> + ?Sized,
712 {
713 self.stripped_state
714 .get(room_id)?
715 .get(&C::TYPE.into())?
716 .get(state_key.as_ref())
717 .map(Raw::cast_ref_unchecked)
718 }
719
720 pub(crate) fn any_state_static_for_key<C, K>(
725 &self,
726 room_id: &RoomId,
727 state_key: &K,
728 ) -> Option<StrippedStateEvent<C::PossiblyRedacted>>
729 where
730 C: StaticEventContent<IsPrefix = ruma::events::False>
731 + StaticStateEventContent
732 + RedactContent,
733 C::Redacted: RedactedStateEventContent,
734 C::PossiblyRedacted: StaticEventContent + DeserializeOwned,
735 C::StateKey: Borrow<K>,
736 K: AsRef<str> + ?Sized,
737 {
738 self.state_static_for_key::<C, K>(room_id, state_key)
739 .map(Raw::cast_ref)
740 .or_else(|| self.stripped_state_static_for_key::<C, K>(room_id, state_key))?
741 .deserialize()
742 .ok()
743 }
744
745 pub(crate) fn member(
748 &self,
749 room_id: &RoomId,
750 user_id: &UserId,
751 ) -> Option<StrippedRoomMemberEvent> {
752 self.any_state_static_for_key::<RoomMemberEventContent, _>(room_id, user_id)
753 }
754
755 pub(crate) fn create(&self, room_id: &RoomId) -> Option<RoomCreateWithCreatorEventContent> {
758 self.any_state_static_for_key::<RoomCreateEventContent, _>(room_id, &EmptyStateKey)
759 .map(|event| {
760 RoomCreateWithCreatorEventContent::from_event_content(event.content, event.sender)
761 })
762 .or_else(|| self.room_infos.get(room_id)?.create().cloned())
764 }
765
766 pub(crate) fn power_levels(&self, room_id: &RoomId) -> Option<RoomPowerLevels> {
769 let power_levels_content = self
770 .any_state_static_for_key::<RoomPowerLevelsEventContent, _>(room_id, &EmptyStateKey)?;
771
772 let create_content = self.create(room_id)?;
773 let rules = create_content.room_version.rules().unwrap_or(ROOM_VERSION_RULES_FALLBACK);
774 let creators = create_content.creators();
775
776 Some(power_levels_content.power_levels(&rules.authorization, creators))
777 }
778}
779
780#[derive(Clone)]
797pub struct StoreConfig {
798 #[cfg(feature = "e2e-encryption")]
799 pub(crate) crypto_store: Arc<DynCryptoStore>,
800 pub(crate) state_store: Arc<DynStateStore>,
801 pub(crate) event_cache_store: event_cache_store::EventCacheStoreLock,
802 pub(crate) media_store: media_store::MediaStoreLock,
803 cross_process_lock_config: CrossProcessLockConfig,
804}
805
806#[cfg(not(tarpaulin_include))]
807impl fmt::Debug for StoreConfig {
808 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> StdResult<(), fmt::Error> {
809 fmt.debug_struct("StoreConfig").finish()
810 }
811}
812
813impl StoreConfig {
814 #[must_use]
819 pub fn new(cross_process_lock_config: CrossProcessLockConfig) -> Self {
820 Self {
821 #[cfg(feature = "e2e-encryption")]
822 crypto_store: matrix_sdk_crypto::store::MemoryStore::new().into_crypto_store(),
823 state_store: Arc::new(MemoryStore::new()),
824 event_cache_store: event_cache_store::EventCacheStoreLock::new(
825 event_cache_store::MemoryStore::new(),
826 cross_process_lock_config.clone(),
827 ),
828 media_store: media_store::MediaStoreLock::new(
829 media_store::MemoryMediaStore::new(),
830 cross_process_lock_config.clone(),
831 ),
832 cross_process_lock_config,
833 }
834 }
835
836 #[cfg(feature = "e2e-encryption")]
840 pub fn crypto_store(mut self, store: impl IntoCryptoStore) -> Self {
841 self.crypto_store = store.into_crypto_store();
842 self
843 }
844
845 pub fn state_store(mut self, store: impl IntoStateStore) -> Self {
847 self.state_store = store.into_state_store();
848 self
849 }
850
851 pub fn event_cache_store<S>(mut self, event_cache_store: S) -> Self
853 where
854 S: event_cache_store::IntoEventCacheStore,
855 {
856 self.event_cache_store = event_cache_store::EventCacheStoreLock::new(
857 event_cache_store,
858 self.cross_process_lock_config.clone(),
859 );
860 self
861 }
862
863 pub fn media_store<S>(mut self, media_store: S) -> Self
865 where
866 S: media_store::IntoMediaStore,
867 {
868 self.media_store =
869 media_store::MediaStoreLock::new(media_store, self.cross_process_lock_config.clone());
870 self
871 }
872}
873
874#[cfg(test)]
875mod tests {
876 use std::{ops::Not, sync::Arc};
877
878 use assert_matches::assert_matches;
879 use matrix_sdk_test::async_test;
880 use ruma::{owned_device_id, owned_user_id, room_id, user_id};
881
882 use super::{BaseStateStore, MemoryStore, RoomLoadSettings};
883 use crate::{RoomInfo, RoomState, SessionMeta, StateChanges, StateStore};
884
885 #[async_test]
886 async fn test_set_session_meta() {
887 let store = BaseStateStore::new(Arc::new(MemoryStore::new()));
888
889 let session_meta = SessionMeta {
890 user_id: owned_user_id!("@mnt_io:matrix.org"),
891 device_id: owned_device_id!("HELLOYOU"),
892 };
893
894 assert!(store.session_meta.get().is_none());
895
896 store.set_session_meta(session_meta.clone());
897
898 assert_eq!(store.session_meta.get(), Some(&session_meta));
899 }
900
901 #[async_test]
902 #[should_panic]
903 async fn test_set_session_meta_twice() {
904 let store = BaseStateStore::new(Arc::new(MemoryStore::new()));
905
906 let session_meta = SessionMeta {
907 user_id: owned_user_id!("@mnt_io:matrix.org"),
908 device_id: owned_device_id!("HELLOYOU"),
909 };
910
911 store.set_session_meta(session_meta.clone());
912 store.set_session_meta(session_meta);
914 }
915
916 #[async_test]
917 async fn test_derive_from_other() {
918 let other = BaseStateStore::new(Arc::new(MemoryStore::new()));
920
921 let session_meta = SessionMeta {
922 user_id: owned_user_id!("@mnt_io:matrix.org"),
923 device_id: owned_device_id!("HELLOYOU"),
924 };
925 let room_id_0 = room_id!("!r0");
926
927 other
928 .load_rooms(&session_meta.user_id, RoomLoadSettings::One(room_id_0.to_owned()))
929 .await
930 .unwrap();
931 other.set_session_meta(session_meta.clone());
932
933 let store = BaseStateStore::new(Arc::new(MemoryStore::new()));
935 store.derive_from_other(&other).await.unwrap();
936
937 assert_eq!(store.session_meta.get(), Some(&session_meta));
939 assert_matches!(*store.room_load_settings.read().await, RoomLoadSettings::One(ref room_id) => {
941 assert_eq!(room_id, room_id_0);
942 });
943
944 assert!(
946 store
947 .room_info_notable_update_sender
948 .same_channel(&other.room_info_notable_update_sender)
949 .not()
950 );
951 }
952
953 #[test]
954 fn test_room_load_settings_default() {
955 assert_matches!(RoomLoadSettings::default(), RoomLoadSettings::All);
956 }
957
958 #[async_test]
959 async fn test_load_all_rooms() {
960 let room_id_0 = room_id!("!r0");
961 let room_id_1 = room_id!("!r1");
962 let user_id = user_id!("@mnt_io:matrix.org");
963
964 let memory_state_store = Arc::new(MemoryStore::new());
965
966 {
968 let store = BaseStateStore::new(memory_state_store.clone());
969 let mut changes = StateChanges::default();
970 changes.add_room(RoomInfo::new(room_id_0, RoomState::Joined));
971 changes.add_room(RoomInfo::new(room_id_1, RoomState::Joined));
972
973 store.inner.save_changes(&changes).await.unwrap();
974 }
975
976 {
978 let store = BaseStateStore::new(memory_state_store.clone());
979
980 assert_matches!(*store.room_load_settings.read().await, RoomLoadSettings::All);
982
983 store.load_rooms(user_id, RoomLoadSettings::All).await.unwrap();
985
986 assert_matches!(*store.room_load_settings.read().await, RoomLoadSettings::All);
988
989 let mut rooms = store.rooms();
991 rooms.sort_by(|a, b| a.room_id().cmp(b.room_id()));
992
993 assert_eq!(rooms.len(), 2);
994
995 assert_eq!(rooms[0].room_id(), room_id_0);
996 assert_eq!(rooms[0].own_user_id(), user_id);
997
998 assert_eq!(rooms[1].room_id(), room_id_1);
999 assert_eq!(rooms[1].own_user_id(), user_id);
1000 }
1001 }
1002
1003 #[async_test]
1004 async fn test_load_one_room() {
1005 let room_id_0 = room_id!("!r0");
1006 let room_id_1 = room_id!("!r1");
1007 let user_id = user_id!("@mnt_io:matrix.org");
1008
1009 let memory_state_store = Arc::new(MemoryStore::new());
1010
1011 {
1013 let store = BaseStateStore::new(memory_state_store.clone());
1014 let mut changes = StateChanges::default();
1015 changes.add_room(RoomInfo::new(room_id_0, RoomState::Joined));
1016 changes.add_room(RoomInfo::new(room_id_1, RoomState::Joined));
1017
1018 store.inner.save_changes(&changes).await.unwrap();
1019 }
1020
1021 {
1023 let store = BaseStateStore::new(memory_state_store.clone());
1024
1025 assert_matches!(*store.room_load_settings.read().await, RoomLoadSettings::All);
1027
1028 store.load_rooms(user_id, RoomLoadSettings::One(room_id_1.to_owned())).await.unwrap();
1030
1031 assert_matches!(
1033 *store.room_load_settings.read().await,
1034 RoomLoadSettings::One(ref room_id) => {
1035 assert_eq!(room_id, room_id_1);
1036 }
1037 );
1038
1039 let rooms = store.rooms();
1041 assert_eq!(rooms.len(), 1);
1042
1043 assert_eq!(rooms[0].room_id(), room_id_1);
1044 assert_eq!(rooms[0].own_user_id(), user_id);
1045 }
1046 }
1047}