matrix_sdk_base/store/
mod.rs

1// Copyright 2021 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! The state store holds the overall state for rooms, users and their
16//! profiles and their timelines. It is an overall cache for faster access
17//! and convenience- accessible through `Store`.
18//!
19//! Implementing the `StateStore` trait, you can plug any storage backend
20//! into the store for the actual storage. By default this brings an in-memory
21//! store.
22
23use 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/// State store specific error type.
100#[derive(Debug, thiserror::Error)]
101pub enum StoreError {
102    /// An error happened in the underlying database backend.
103    #[error(transparent)]
104    Backend(Box<dyn std::error::Error + Send + Sync>),
105
106    /// An error happened while serializing or deserializing some data.
107    #[error(transparent)]
108    Json(#[from] serde_json::Error),
109
110    /// An error happened while deserializing a Matrix identifier, e.g. an user
111    /// id.
112    #[error(transparent)]
113    Identifier(#[from] ruma::IdParseError),
114
115    /// The store is locked with a passphrase and an incorrect passphrase was
116    /// given.
117    #[error("The store failed to be unlocked")]
118    StoreLocked,
119
120    /// An unencrypted store was tried to be unlocked with a passphrase.
121    #[error("The store is not encrypted but was tried to be opened with a passphrase")]
122    UnencryptedStore,
123
124    /// The store failed to encrypt or decrypt some data.
125    #[error("Error encrypting or decrypting data from the store: {0}")]
126    Encryption(#[from] StoreEncryptionError),
127
128    /// The store failed to encode or decode some data.
129    #[error("Error encoding or decoding data from the store: {0}")]
130    Codec(#[from] Utf8Error),
131
132    /// The database format has changed in a backwards incompatible way.
133    #[error(
134        "The database format changed in an incompatible way, current \
135        version: {0}, latest version: {1}"
136    )]
137    UnsupportedDatabaseVersion(usize, usize),
138
139    /// Redacting an event in the store has failed.
140    ///
141    /// This should never happen.
142    #[error("Redaction failed: {0}")]
143    Redaction(#[source] ruma::canonical_json::RedactionError),
144
145    /// The store contains invalid data.
146    #[error("The store contains invalid data: {details}")]
147    InvalidData {
148        /// Details about which data is invalid, and how.
149        details: String,
150    },
151}
152
153impl StoreError {
154    /// Create a new [`Backend`][Self::Backend] error.
155    ///
156    /// Shorthand for `StoreError::Backend(Box::new(error))`.
157    #[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
166/// A `StateStore` specific result type.
167pub type Result<T, E = StoreError> = std::result::Result<T, E>;
168
169/// A state store wrapper for the SDK.
170///
171/// This adds additional higher level store functionality on top of a
172/// `StateStore` implementation.
173#[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    /// The current sync token that should be used for the next sync call.
179    pub(super) sync_token: Arc<RwLock<Option<String>>>,
180    /// All rooms the store knows about.
181    rooms: Arc<StdRwLock<ObservableMap<OwnedRoomId, Room>>>,
182    /// A lock to synchronize access to the store, such that data by the sync is
183    /// never overwritten.
184    sync_lock: Arc<Mutex<()>>,
185}
186
187impl BaseStateStore {
188    /// Create a new store, wrapping the given `StateStore`
189    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    /// Get access to the syncing lock.
201    pub fn sync_lock(&self) -> &Mutex<()> {
202        &self.sync_lock
203    }
204
205    /// Set the [`SessionMeta`] into [`BaseStateStore::session_meta`].
206    ///
207    /// # Panics
208    ///
209    /// Panics if called twice.
210    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    /// Loads rooms from the given [`DynStateStore`] (in
215    /// [`BaseStateStore::new`]) into [`BaseStateStore::rooms`].
216    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    /// Load room infos from the [`StateStore`] and applies migrations onto
244    /// them.
245    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    /// Load sync token from the [`StateStore`], and put it in
276    /// [`BaseStateStore::sync_token`].
277    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    /// Restore the session meta, sync token and rooms from an existing
286    /// [`BaseStateStore`].
287    #[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    /// The current [`SessionMeta`] containing our user ID and device ID.
308    pub fn session_meta(&self) -> Option<&SessionMeta> {
309        self.session_meta.get()
310    }
311
312    /// Get all the rooms this store knows about.
313    pub fn rooms(&self) -> Vec<Room> {
314        self.rooms.read().unwrap().iter().cloned().collect()
315    }
316
317    /// Get all the rooms this store knows about, filtered by state.
318    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    /// Get a stream of all the rooms changes, in addition to the existing
329    /// rooms.
330    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    /// Get the room with the given room id.
337    pub fn room(&self, room_id: &RoomId) -> Option<Room> {
338        self.rooms.read().unwrap().get(room_id).cloned()
339    }
340
341    /// Check if a room exists.
342    pub(crate) fn room_exists(&self, room_id: &RoomId) -> bool {
343        self.rooms.read().unwrap().get(room_id).is_some()
344    }
345
346    /// Lookup the `Room` for the given `RoomId`, or create one, if it didn't
347    /// exist yet in the store
348    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    /// Forget the room with the given room ID.
373    ///
374    /// # Arguments
375    ///
376    /// * `room_id` - The id of the room that should be forgotten.
377    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/// Configure how many rooms will be restored when restoring the session with
405/// `BaseStateStore::load_rooms`.
406///
407/// <div class="warning">
408///
409/// # ⚠️ Be careful!
410///
411/// When loading a single room with [`RoomLoadSettings::One`], the in-memory
412/// state may not reflect the store state (in the databases). Thus, when one
413/// will get a room that exists in the store state but _not_ in the in-memory
414/// state, it will be created from scratch and, when saved, will override the
415/// data in the store state (in the databases). This can lead to weird
416/// behaviours.
417///
418/// This option is expected to be used as follows:
419///
420/// 1. Create a `BaseStateStore` with a [`StateStore`] based on SQLite for
421///    example,
422/// 2. Restore a session and load one room from the [`StateStore`] (in the case
423///    of dealing with a notification for example),
424/// 3. Derive the `BaseStateStore`, with `BaseStateStore::derive_from_other`,
425///    into another one with an in-memory [`StateStore`], such as
426///    [`MemoryStore`],
427/// 4. Work on this derived `BaseStateStore`.
428///
429/// Now, all operations happen in the [`MemoryStore`], not on the original store
430/// (SQLite in this example), thus protecting original data.
431///
432/// From a higher-level point of view, this is what
433/// [`BaseClient::clone_with_in_memory_state_store`] does.
434///
435/// </div>
436///
437/// [`BaseClient::clone_with_in_memory_state_store`]: crate::BaseClient::clone_with_in_memory_state_store
438#[derive(Clone, Debug, Default)]
439pub enum RoomLoadSettings {
440    /// Load all rooms from the [`StateStore`] into the in-memory state store
441    /// `BaseStateStore`.
442    ///
443    /// This is the default variant.
444    #[default]
445    All,
446
447    /// Load a single room from the [`StateStore`] into the in-memory state
448    /// store `BaseStateStore`.
449    ///
450    /// Please, be careful with this option. Read the documentation of
451    /// [`RoomLoadSettings`].
452    One(OwnedRoomId),
453}
454
455/// A thread subscription, as saved in the state store.
456#[derive(Clone, Copy, Debug, PartialEq, Eq)]
457pub struct ThreadSubscription {
458    /// Whether the subscription was made automatically by a client, not by
459    /// manual user choice.
460    pub automatic: bool,
461}
462
463impl ThreadSubscription {
464    /// Convert the current [`ThreadSubscription`] into a string representation.
465    pub fn as_str(&self) -> &'static str {
466        if self.automatic { "automatic" } else { "manual" }
467    }
468
469    /// Convert a string representation into a [`ThreadSubscription`], if it is
470    /// a valid one, or `None` otherwise.
471    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/// Store state changes and pass them to the StateStore.
481#[derive(Clone, Debug, Default)]
482pub struct StateChanges {
483    /// The sync token that relates to this update.
484    pub sync_token: Option<String>,
485    /// A mapping of event type string to `AnyBasicEvent`.
486    pub account_data: BTreeMap<GlobalAccountDataEventType, Raw<AnyGlobalAccountDataEvent>>,
487    /// A mapping of `UserId` to `PresenceEvent`.
488    pub presence: BTreeMap<OwnedUserId, Raw<PresenceEvent>>,
489
490    /// A mapping of `RoomId` to a map of users and their
491    /// `MinimalRoomMemberEvent`.
492    pub profiles: BTreeMap<OwnedRoomId, BTreeMap<OwnedUserId, MinimalRoomMemberEvent>>,
493
494    /// A mapping of room profiles to delete.
495    ///
496    /// These are deleted *before* other room profiles are inserted.
497    pub profiles_to_delete: BTreeMap<OwnedRoomId, Vec<OwnedUserId>>,
498
499    /// A mapping of `RoomId` to a map of event type string to a state key and
500    /// `AnySyncStateEvent`.
501    pub state:
502        BTreeMap<OwnedRoomId, BTreeMap<StateEventType, BTreeMap<String, Raw<AnySyncStateEvent>>>>,
503    /// A mapping of `RoomId` to a map of event type string to `AnyBasicEvent`.
504    pub room_account_data:
505        BTreeMap<OwnedRoomId, BTreeMap<RoomAccountDataEventType, Raw<AnyRoomAccountDataEvent>>>,
506
507    /// A map of `OwnedRoomId` to `RoomInfo`.
508    pub room_infos: BTreeMap<OwnedRoomId, RoomInfo>,
509
510    /// A map of `RoomId` to `ReceiptEventContent`.
511    pub receipts: BTreeMap<OwnedRoomId, ReceiptEventContent>,
512
513    /// A map of `RoomId` to maps of `OwnedEventId` to be redacted by
514    /// `SyncRoomRedactionEvent`.
515    pub redactions: BTreeMap<OwnedRoomId, BTreeMap<OwnedEventId, Raw<SyncRoomRedactionEvent>>>,
516
517    /// A mapping of `RoomId` to a map of event type to a map of state key to
518    /// `StrippedState`.
519    pub stripped_state: BTreeMap<
520        OwnedRoomId,
521        BTreeMap<StateEventType, BTreeMap<String, Raw<AnyStrippedStateEvent>>>,
522    >,
523
524    /// A map from room id to a map of a display name and a set of user ids that
525    /// share that display name in the given room.
526    pub ambiguity_maps: BTreeMap<OwnedRoomId, HashMap<DisplayName, BTreeSet<OwnedUserId>>>,
527}
528
529impl StateChanges {
530    /// Create a new `StateChanges` struct with the given sync_token.
531    pub fn new(sync_token: String) -> Self {
532        Self { sync_token: Some(sync_token), ..Default::default() }
533    }
534
535    /// Update the `StateChanges` struct with the given `PresenceEvent`.
536    pub fn add_presence_event(&mut self, event: PresenceEvent, raw_event: Raw<PresenceEvent>) {
537        self.presence.insert(event.sender, raw_event);
538    }
539
540    /// Update the `StateChanges` struct with the given `RoomInfo`.
541    pub fn add_room(&mut self, room: RoomInfo) {
542        self.room_infos.insert(room.room_id.clone(), room);
543    }
544
545    /// Update the `StateChanges` struct with the given room with a new
546    /// `AnyBasicEvent`.
547    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    /// Update the `StateChanges` struct with the given room with a new
560    /// `StrippedMemberEvent`.
561    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    /// Update the `StateChanges` struct with the given room with a new
576    /// `AnySyncStateEvent`.
577    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    /// Redact an event in the room
592    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    /// Update the `StateChanges` struct with the given room with a new
605    /// `Receipts`.
606    pub fn add_receipts(&mut self, room_id: &RoomId, event: ReceiptEventContent) {
607        self.receipts.insert(room_id.to_owned(), event);
608    }
609
610    /// Get a specific state event of statically-known type with the given state
611    /// key in the given room, if it is present in the `state` map of these
612    /// `StateChanges`.
613    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    /// Get a specific stripped state event of statically-known type with the
634    /// given state key in the given room, if it is present in the
635    /// `stripped_state` map of these `StateChanges`.
636    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    /// Get a specific state event of statically-known type with the given state
654    /// key in the given room, if it is present in the `state` or
655    /// `stripped_state` map of these `StateChanges` and it deserializes
656    /// successfully.
657    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    /// Get the member for the given user in the given room from an event
679    /// contained in these `StateChanges`, if any.
680    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    /// Get the create event for the given room from an event contained in these
689    /// `StateChanges`, if any.
690    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            // Fallback to the content in the room info.
696            .or_else(|| self.room_infos.get(room_id)?.create().cloned())
697    }
698
699    /// Get the power levels for the given room from an event contained in these
700    /// `StateChanges`, if any.
701    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/// Configuration for the various stores.
714///
715/// By default, this always includes a state store and an event cache store.
716/// When the `e2e-encryption` feature is enabled, this also includes a crypto
717/// store.
718///
719/// # Examples
720///
721/// ```
722/// # use matrix_sdk_base::store::StoreConfig;
723/// #
724/// let store_config =
725///     StoreConfig::new("cross-process-store-locks-holder-name".to_owned());
726/// ```
727#[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    /// Create a new default `StoreConfig`.
745    ///
746    /// To learn more about `cross_process_store_locks_holder_name`, please read
747    /// [`CrossProcessStoreLock::new`](matrix_sdk_common::store_locks::CrossProcessStoreLock::new).
748    #[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    /// Set a custom implementation of a `CryptoStore`.
763    ///
764    /// The crypto store must be opened before being set.
765    #[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    /// Set a custom implementation of a `StateStore`.
772    pub fn state_store(mut self, store: impl IntoStateStore) -> Self {
773        self.state_store = store.into_state_store();
774        self
775    }
776
777    /// Set a custom implementation of an `EventCacheStore`.
778    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        // Kaboom.
830        store.set_session_meta(session_meta);
831    }
832
833    #[async_test]
834    async fn test_derive_from_other() {
835        // The first store.
836        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        // Derive another store.
856        let store = BaseStateStore::new(Arc::new(MemoryStore::new()));
857        store.derive_from_other(&other, &room_info_notable_update_sender).await.unwrap();
858
859        // `SessionMeta` is derived.
860        assert_eq!(store.session_meta.get(), Some(&session_meta));
861        // `RoomLoadSettings` is derived.
862        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        // Initial state.
881        {
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        // Check a `BaseStateStore` is able to load all rooms.
891        {
892            let store = BaseStateStore::new(memory_state_store.clone());
893            let (room_info_notable_update_sender, _) = broadcast::channel(2);
894
895            // Default value.
896            assert_matches!(*store.room_load_settings.read().await, RoomLoadSettings::All);
897
898            // Load rooms.
899            store
900                .load_rooms(user_id, RoomLoadSettings::All, &room_info_notable_update_sender)
901                .await
902                .unwrap();
903
904            // Check the last room load settings.
905            assert_matches!(*store.room_load_settings.read().await, RoomLoadSettings::All);
906
907            // Check the loaded rooms.
908            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        // Initial state.
930        {
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        // Check a `BaseStateStore` is able to load one room.
940        {
941            let store = BaseStateStore::new(memory_state_store.clone());
942            let (room_info_notable_update_sender, _) = broadcast::channel(2);
943
944            // Default value.
945            assert_matches!(*store.room_load_settings.read().await, RoomLoadSettings::All);
946
947            // Load rooms.
948            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            // Check the last room load settings.
958            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            // Check the loaded rooms.
966            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}