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    api::client::sync::sync_events::StrippedState,
51    events::{
52        AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnySyncStateEvent, EmptyStateKey,
53        GlobalAccountDataEventType, RedactContent, RedactedStateEventContent,
54        RoomAccountDataEventType, StateEventType, StaticEventContent, StaticStateEventContent,
55        StrippedStateEvent, SyncStateEvent,
56        presence::PresenceEvent,
57        receipt::ReceiptEventContent,
58        room::{
59            create::RoomCreateEventContent,
60            member::{RoomMemberEventContent, StrippedRoomMemberEvent},
61            power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
62            redaction::SyncRoomRedactionEvent,
63        },
64    },
65    serde::Raw,
66};
67use serde::de::DeserializeOwned;
68use tokio::sync::{Mutex, RwLock, broadcast};
69use tracing::warn;
70
71use crate::{
72    MinimalRoomMemberEvent, Room, RoomCreateWithCreatorEventContent, RoomStateFilter, SessionMeta,
73    deserialized_responses::DisplayName,
74    event_cache::store as event_cache_store,
75    room::{RoomInfo, RoomInfoNotableUpdate, RoomState},
76};
77
78pub(crate) mod ambiguity_map;
79mod memory_store;
80pub mod migration_helpers;
81mod send_queue;
82
83#[cfg(any(test, feature = "testing"))]
84pub use self::integration_tests::StateStoreIntegrationTests;
85#[cfg(feature = "unstable-msc4274")]
86pub use self::send_queue::{AccumulatedSentMediaInfo, FinishGalleryItemInfo};
87pub use self::{
88    memory_store::MemoryStore,
89    send_queue::{
90        ChildTransactionId, DependentQueuedRequest, DependentQueuedRequestKind,
91        FinishUploadThumbnailInfo, QueueWedgeError, QueuedRequest, QueuedRequestKind,
92        SentMediaInfo, SentRequestKey, SerializableEventContent,
93    },
94    traits::{
95        ComposerDraft, ComposerDraftType, DynStateStore, IntoStateStore, ServerInfo, StateStore,
96        StateStoreDataKey, StateStoreDataValue, StateStoreExt, WellKnownResponse,
97    },
98};
99
100/// State store specific error type.
101#[derive(Debug, thiserror::Error)]
102pub enum StoreError {
103    /// An error happened in the underlying database backend.
104    #[error(transparent)]
105    Backend(Box<dyn std::error::Error + Send + Sync>),
106
107    /// An error happened while serializing or deserializing some data.
108    #[error(transparent)]
109    Json(#[from] serde_json::Error),
110
111    /// An error happened while deserializing a Matrix identifier, e.g. an user
112    /// id.
113    #[error(transparent)]
114    Identifier(#[from] ruma::IdParseError),
115
116    /// The store is locked with a passphrase and an incorrect passphrase was
117    /// given.
118    #[error("The store failed to be unlocked")]
119    StoreLocked,
120
121    /// An unencrypted store was tried to be unlocked with a passphrase.
122    #[error("The store is not encrypted but was tried to be opened with a passphrase")]
123    UnencryptedStore,
124
125    /// The store failed to encrypt or decrypt some data.
126    #[error("Error encrypting or decrypting data from the store: {0}")]
127    Encryption(#[from] StoreEncryptionError),
128
129    /// The store failed to encode or decode some data.
130    #[error("Error encoding or decoding data from the store: {0}")]
131    Codec(#[from] Utf8Error),
132
133    /// The database format has changed in a backwards incompatible way.
134    #[error(
135        "The database format changed in an incompatible way, current \
136        version: {0}, latest version: {1}"
137    )]
138    UnsupportedDatabaseVersion(usize, usize),
139
140    /// Redacting an event in the store has failed.
141    ///
142    /// This should never happen.
143    #[error("Redaction failed: {0}")]
144    Redaction(#[source] ruma::canonical_json::RedactionError),
145
146    /// The store contains invalid data.
147    #[error("The store contains invalid data: {details}")]
148    InvalidData {
149        /// Details about which data is invalid, and how.
150        details: String,
151    },
152}
153
154impl StoreError {
155    /// Create a new [`Backend`][Self::Backend] error.
156    ///
157    /// Shorthand for `StoreError::Backend(Box::new(error))`.
158    #[inline]
159    pub fn backend<E>(error: E) -> Self
160    where
161        E: std::error::Error + Send + Sync + 'static,
162    {
163        Self::Backend(Box::new(error))
164    }
165}
166
167/// A `StateStore` specific result type.
168pub type Result<T, E = StoreError> = std::result::Result<T, E>;
169
170/// A state store wrapper for the SDK.
171///
172/// This adds additional higher level store functionality on top of a
173/// `StateStore` implementation.
174#[derive(Clone)]
175pub(crate) struct BaseStateStore {
176    pub(super) inner: Arc<DynStateStore>,
177    session_meta: Arc<OnceCell<SessionMeta>>,
178    room_load_settings: Arc<RwLock<RoomLoadSettings>>,
179    /// The current sync token that should be used for the next sync call.
180    pub(super) sync_token: Arc<RwLock<Option<String>>>,
181    /// All rooms the store knows about.
182    rooms: Arc<StdRwLock<ObservableMap<OwnedRoomId, Room>>>,
183    /// A lock to synchronize access to the store, such that data by the sync is
184    /// never overwritten.
185    sync_lock: Arc<Mutex<()>>,
186}
187
188impl BaseStateStore {
189    /// Create a new store, wrapping the given `StateStore`
190    pub fn new(inner: Arc<DynStateStore>) -> Self {
191        Self {
192            inner,
193            session_meta: Default::default(),
194            room_load_settings: Default::default(),
195            sync_token: Default::default(),
196            rooms: Arc::new(StdRwLock::new(ObservableMap::new())),
197            sync_lock: Default::default(),
198        }
199    }
200
201    /// Get access to the syncing lock.
202    pub fn sync_lock(&self) -> &Mutex<()> {
203        &self.sync_lock
204    }
205
206    /// Set the [`SessionMeta`] into [`BaseStateStore::session_meta`].
207    ///
208    /// # Panics
209    ///
210    /// Panics if called twice.
211    pub(crate) fn set_session_meta(&self, session_meta: SessionMeta) {
212        self.session_meta.set(session_meta).expect("`SessionMeta` was already set");
213    }
214
215    /// Loads rooms from the given [`DynStateStore`] (in
216    /// [`BaseStateStore::new`]) into [`BaseStateStore::rooms`].
217    pub(crate) async fn load_rooms(
218        &self,
219        user_id: &UserId,
220        room_load_settings: RoomLoadSettings,
221        room_info_notable_update_sender: &broadcast::Sender<RoomInfoNotableUpdate>,
222    ) -> Result<()> {
223        *self.room_load_settings.write().await = room_load_settings.clone();
224
225        let room_infos = self.load_and_migrate_room_infos(room_load_settings).await?;
226
227        let mut rooms = self.rooms.write().unwrap();
228
229        for room_info in room_infos {
230            let new_room = Room::restore(
231                user_id,
232                self.inner.clone(),
233                room_info,
234                room_info_notable_update_sender.clone(),
235            );
236            let new_room_id = new_room.room_id().to_owned();
237
238            rooms.insert(new_room_id, new_room);
239        }
240
241        Ok(())
242    }
243
244    /// Load room infos from the [`StateStore`] and applies migrations onto
245    /// them.
246    async fn load_and_migrate_room_infos(
247        &self,
248        room_load_settings: RoomLoadSettings,
249    ) -> Result<Vec<RoomInfo>> {
250        let mut room_infos = self.inner.get_room_infos(&room_load_settings).await?;
251        let mut migrated_room_infos = Vec::with_capacity(room_infos.len());
252
253        for room_info in room_infos.iter_mut() {
254            if room_info.apply_migrations(self.inner.clone()).await {
255                migrated_room_infos.push(room_info.clone());
256            }
257        }
258
259        if !migrated_room_infos.is_empty() {
260            let changes = StateChanges {
261                room_infos: migrated_room_infos
262                    .into_iter()
263                    .map(|room_info| (room_info.room_id.clone(), room_info))
264                    .collect(),
265                ..Default::default()
266            };
267
268            if let Err(error) = self.inner.save_changes(&changes).await {
269                warn!("Failed to save migrated room infos: {error}");
270            }
271        }
272
273        Ok(room_infos)
274    }
275
276    /// Load sync token from the [`StateStore`], and put it in
277    /// [`BaseStateStore::sync_token`].
278    pub(crate) async fn load_sync_token(&self) -> Result<()> {
279        let token =
280            self.get_kv_data(StateStoreDataKey::SyncToken).await?.and_then(|s| s.into_sync_token());
281        *self.sync_token.write().await = token;
282
283        Ok(())
284    }
285
286    /// Restore the session meta, sync token and rooms from an existing
287    /// [`BaseStateStore`].
288    #[cfg(any(feature = "e2e-encryption", test))]
289    pub(crate) async fn derive_from_other(
290        &self,
291        other: &Self,
292        room_info_notable_update_sender: &broadcast::Sender<RoomInfoNotableUpdate>,
293    ) -> Result<()> {
294        let Some(session_meta) = other.session_meta.get() else {
295            return Ok(());
296        };
297
298        let room_load_settings = other.room_load_settings.read().await.clone();
299
300        self.load_rooms(&session_meta.user_id, room_load_settings, room_info_notable_update_sender)
301            .await?;
302        self.load_sync_token().await?;
303        self.set_session_meta(session_meta.clone());
304
305        Ok(())
306    }
307
308    /// The current [`SessionMeta`] containing our user ID and device ID.
309    pub fn session_meta(&self) -> Option<&SessionMeta> {
310        self.session_meta.get()
311    }
312
313    /// Get all the rooms this store knows about.
314    pub fn rooms(&self) -> Vec<Room> {
315        self.rooms.read().unwrap().iter().cloned().collect()
316    }
317
318    /// Get all the rooms this store knows about, filtered by state.
319    pub fn rooms_filtered(&self, filter: RoomStateFilter) -> Vec<Room> {
320        self.rooms
321            .read()
322            .unwrap()
323            .iter()
324            .filter(|room| filter.matches(room.state()))
325            .cloned()
326            .collect()
327    }
328
329    /// Get a stream of all the rooms changes, in addition to the existing
330    /// rooms.
331    pub fn rooms_stream(
332        &self,
333    ) -> (Vector<Room>, impl Stream<Item = Vec<VectorDiff<Room>>> + use<>) {
334        self.rooms.read().unwrap().stream()
335    }
336
337    /// Get the room with the given room id.
338    pub fn room(&self, room_id: &RoomId) -> Option<Room> {
339        self.rooms.read().unwrap().get(room_id).cloned()
340    }
341
342    /// Check if a room exists.
343    pub(crate) fn room_exists(&self, room_id: &RoomId) -> bool {
344        self.rooms.read().unwrap().get(room_id).is_some()
345    }
346
347    /// Lookup the `Room` for the given `RoomId`, or create one, if it didn't
348    /// exist yet in the store
349    pub fn get_or_create_room(
350        &self,
351        room_id: &RoomId,
352        room_state: RoomState,
353        room_info_notable_update_sender: broadcast::Sender<RoomInfoNotableUpdate>,
354    ) -> Room {
355        let user_id =
356            &self.session_meta.get().expect("Creating room while not being logged in").user_id;
357
358        self.rooms
359            .write()
360            .unwrap()
361            .get_or_create(room_id, || {
362                Room::new(
363                    user_id,
364                    self.inner.clone(),
365                    room_id,
366                    room_state,
367                    room_info_notable_update_sender,
368                )
369            })
370            .clone()
371    }
372
373    /// Forget the room with the given room ID.
374    ///
375    /// # Arguments
376    ///
377    /// * `room_id` - The id of the room that should be forgotten.
378    pub(crate) async fn forget_room(&self, room_id: &RoomId) -> Result<()> {
379        self.inner.remove_room(room_id).await?;
380        self.rooms.write().unwrap().remove(room_id);
381        Ok(())
382    }
383}
384
385#[cfg(not(tarpaulin_include))]
386impl fmt::Debug for BaseStateStore {
387    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
388        f.debug_struct("Store")
389            .field("inner", &self.inner)
390            .field("session_meta", &self.session_meta)
391            .field("sync_token", &self.sync_token)
392            .field("rooms", &self.rooms)
393            .finish_non_exhaustive()
394    }
395}
396
397impl Deref for BaseStateStore {
398    type Target = DynStateStore;
399
400    fn deref(&self) -> &Self::Target {
401        self.inner.deref()
402    }
403}
404
405/// Configure how many rooms will be restored when restoring the session with
406/// `BaseStateStore::load_rooms`.
407///
408/// <div class="warning">
409///
410/// # ⚠️ Be careful!
411///
412/// When loading a single room with [`RoomLoadSettings::One`], the in-memory
413/// state may not reflect the store state (in the databases). Thus, when one
414/// will get a room that exists in the store state but _not_ in the in-memory
415/// state, it will be created from scratch and, when saved, will override the
416/// data in the store state (in the databases). This can lead to weird
417/// behaviours.
418///
419/// This option is expected to be used as follows:
420///
421/// 1. Create a `BaseStateStore` with a [`StateStore`] based on SQLite for
422///    example,
423/// 2. Restore a session and load one room from the [`StateStore`] (in the case
424///    of dealing with a notification for example),
425/// 3. Derive the `BaseStateStore`, with `BaseStateStore::derive_from_other`,
426///    into another one with an in-memory [`StateStore`], such as
427///    [`MemoryStore`],
428/// 4. Work on this derived `BaseStateStore`.
429///
430/// Now, all operations happen in the [`MemoryStore`], not on the original store
431/// (SQLite in this example), thus protecting original data.
432///
433/// From a higher-level point of view, this is what
434/// [`BaseClient::clone_with_in_memory_state_store`] does.
435///
436/// </div>
437///
438/// [`BaseClient::clone_with_in_memory_state_store`]: crate::BaseClient::clone_with_in_memory_state_store
439#[derive(Clone, Debug, Default)]
440pub enum RoomLoadSettings {
441    /// Load all rooms from the [`StateStore`] into the in-memory state store
442    /// `BaseStateStore`.
443    ///
444    /// This is the default variant.
445    #[default]
446    All,
447
448    /// Load a single room from the [`StateStore`] into the in-memory state
449    /// store `BaseStateStore`.
450    ///
451    /// Please, be careful with this option. Read the documentation of
452    /// [`RoomLoadSettings`].
453    One(OwnedRoomId),
454}
455
456/// A thread subscription, as saved in the state store.
457#[derive(Clone, Copy, Debug, PartialEq, Eq)]
458pub struct ThreadSubscription {
459    /// Whether the subscription was made automatically by a client, not by
460    /// manual user choice.
461    pub automatic: bool,
462}
463
464impl ThreadSubscription {
465    /// Convert the current [`ThreadSubscription`] into a string representation.
466    pub fn as_str(&self) -> &'static str {
467        if self.automatic { "automatic" } else { "manual" }
468    }
469
470    /// Convert a string representation into a [`ThreadSubscription`], if it is
471    /// a valid one, or `None` otherwise.
472    pub fn from_value(s: &str) -> Option<Self> {
473        match s {
474            "automatic" => Some(Self { automatic: true }),
475            "manual" => Some(Self { automatic: false }),
476            _ => None,
477        }
478    }
479}
480
481/// Store state changes and pass them to the StateStore.
482#[derive(Clone, Debug, Default)]
483pub struct StateChanges {
484    /// The sync token that relates to this update.
485    pub sync_token: Option<String>,
486    /// A mapping of event type string to `AnyBasicEvent`.
487    pub account_data: BTreeMap<GlobalAccountDataEventType, Raw<AnyGlobalAccountDataEvent>>,
488    /// A mapping of `UserId` to `PresenceEvent`.
489    pub presence: BTreeMap<OwnedUserId, Raw<PresenceEvent>>,
490
491    /// A mapping of `RoomId` to a map of users and their
492    /// `MinimalRoomMemberEvent`.
493    pub profiles: BTreeMap<OwnedRoomId, BTreeMap<OwnedUserId, MinimalRoomMemberEvent>>,
494
495    /// A mapping of room profiles to delete.
496    ///
497    /// These are deleted *before* other room profiles are inserted.
498    pub profiles_to_delete: BTreeMap<OwnedRoomId, Vec<OwnedUserId>>,
499
500    /// A mapping of `RoomId` to a map of event type string to a state key and
501    /// `AnySyncStateEvent`.
502    pub state:
503        BTreeMap<OwnedRoomId, BTreeMap<StateEventType, BTreeMap<String, Raw<AnySyncStateEvent>>>>,
504    /// A mapping of `RoomId` to a map of event type string to `AnyBasicEvent`.
505    pub room_account_data:
506        BTreeMap<OwnedRoomId, BTreeMap<RoomAccountDataEventType, Raw<AnyRoomAccountDataEvent>>>,
507
508    /// A map of `OwnedRoomId` to `RoomInfo`.
509    pub room_infos: BTreeMap<OwnedRoomId, RoomInfo>,
510
511    /// A map of `RoomId` to `ReceiptEventContent`.
512    pub receipts: BTreeMap<OwnedRoomId, ReceiptEventContent>,
513
514    /// A map of `RoomId` to maps of `OwnedEventId` to be redacted by
515    /// `SyncRoomRedactionEvent`.
516    pub redactions: BTreeMap<OwnedRoomId, BTreeMap<OwnedEventId, Raw<SyncRoomRedactionEvent>>>,
517
518    /// A mapping of `RoomId` to a map of event type to a map of state key to
519    /// `StrippedState`.
520    pub stripped_state:
521        BTreeMap<OwnedRoomId, BTreeMap<StateEventType, BTreeMap<String, Raw<StrippedState>>>>,
522
523    /// A map from room id to a map of a display name and a set of user ids that
524    /// share that display name in the given room.
525    pub ambiguity_maps: BTreeMap<OwnedRoomId, HashMap<DisplayName, BTreeSet<OwnedUserId>>>,
526}
527
528impl StateChanges {
529    /// Create a new `StateChanges` struct with the given sync_token.
530    pub fn new(sync_token: String) -> Self {
531        Self { sync_token: Some(sync_token), ..Default::default() }
532    }
533
534    /// Update the `StateChanges` struct with the given `PresenceEvent`.
535    pub fn add_presence_event(&mut self, event: PresenceEvent, raw_event: Raw<PresenceEvent>) {
536        self.presence.insert(event.sender, raw_event);
537    }
538
539    /// Update the `StateChanges` struct with the given `RoomInfo`.
540    pub fn add_room(&mut self, room: RoomInfo) {
541        self.room_infos.insert(room.room_id.clone(), room);
542    }
543
544    /// Update the `StateChanges` struct with the given room with a new
545    /// `AnyBasicEvent`.
546    pub fn add_room_account_data(
547        &mut self,
548        room_id: &RoomId,
549        event: AnyRoomAccountDataEvent,
550        raw_event: Raw<AnyRoomAccountDataEvent>,
551    ) {
552        self.room_account_data
553            .entry(room_id.to_owned())
554            .or_default()
555            .insert(event.event_type(), raw_event);
556    }
557
558    /// Update the `StateChanges` struct with the given room with a new
559    /// `StrippedMemberEvent`.
560    pub fn add_stripped_member(
561        &mut self,
562        room_id: &RoomId,
563        user_id: &UserId,
564        event: Raw<StrippedRoomMemberEvent>,
565    ) {
566        self.stripped_state
567            .entry(room_id.to_owned())
568            .or_default()
569            .entry(StateEventType::RoomMember)
570            .or_default()
571            .insert(user_id.into(), event.cast());
572    }
573
574    /// Update the `StateChanges` struct with the given room with a new
575    /// `AnySyncStateEvent`.
576    pub fn add_state_event(
577        &mut self,
578        room_id: &RoomId,
579        event: AnySyncStateEvent,
580        raw_event: Raw<AnySyncStateEvent>,
581    ) {
582        self.state
583            .entry(room_id.to_owned())
584            .or_default()
585            .entry(event.event_type())
586            .or_default()
587            .insert(event.state_key().to_owned(), raw_event);
588    }
589
590    /// Redact an event in the room
591    pub fn add_redaction(
592        &mut self,
593        room_id: &RoomId,
594        redacted_event_id: &EventId,
595        redaction: Raw<SyncRoomRedactionEvent>,
596    ) {
597        self.redactions
598            .entry(room_id.to_owned())
599            .or_default()
600            .insert(redacted_event_id.to_owned(), redaction);
601    }
602
603    /// Update the `StateChanges` struct with the given room with a new
604    /// `Receipts`.
605    pub fn add_receipts(&mut self, room_id: &RoomId, event: ReceiptEventContent) {
606        self.receipts.insert(room_id.to_owned(), event);
607    }
608
609    /// Get a specific state event of statically-known type with the given state
610    /// key in the given room, if it is present in the `state` map of these
611    /// `StateChanges`.
612    pub(crate) fn state_static_for_key<C, K>(
613        &self,
614        room_id: &RoomId,
615        state_key: &K,
616    ) -> Option<&Raw<SyncStateEvent<C>>>
617    where
618        C: StaticEventContent<IsPrefix = ruma::events::False>
619            + StaticStateEventContent
620            + RedactContent,
621        C::Redacted: RedactedStateEventContent,
622        C::StateKey: Borrow<K>,
623        K: AsRef<str> + ?Sized,
624    {
625        self.state
626            .get(room_id)?
627            .get(&C::TYPE.into())?
628            .get(state_key.as_ref())
629            .map(Raw::cast_ref_unchecked)
630    }
631
632    /// Get a specific stripped state event of statically-known type with the
633    /// given state key in the given room, if it is present in the
634    /// `stripped_state` map of these `StateChanges`.
635    pub(crate) fn stripped_state_static_for_key<C, K>(
636        &self,
637        room_id: &RoomId,
638        state_key: &K,
639    ) -> Option<&Raw<StrippedStateEvent<C::PossiblyRedacted>>>
640    where
641        C: StaticEventContent<IsPrefix = ruma::events::False> + StaticStateEventContent,
642        C::StateKey: Borrow<K>,
643        K: AsRef<str> + ?Sized,
644    {
645        self.stripped_state
646            .get(room_id)?
647            .get(&C::TYPE.into())?
648            .get(state_key.as_ref())
649            .map(Raw::cast_ref_unchecked)
650    }
651
652    /// Get a specific state event of statically-known type with the given state
653    /// key in the given room, if it is present in the `state` or
654    /// `stripped_state` map of these `StateChanges` and it deserializes
655    /// successfully.
656    pub(crate) fn any_state_static_for_key<C, K>(
657        &self,
658        room_id: &RoomId,
659        state_key: &K,
660    ) -> Option<StrippedStateEvent<C::PossiblyRedacted>>
661    where
662        C: StaticEventContent<IsPrefix = ruma::events::False>
663            + StaticStateEventContent
664            + RedactContent,
665        C::Redacted: RedactedStateEventContent,
666        C::PossiblyRedacted: StaticEventContent + DeserializeOwned,
667        C::StateKey: Borrow<K>,
668        K: AsRef<str> + ?Sized,
669    {
670        self.state_static_for_key::<C, K>(room_id, state_key)
671            .map(Raw::cast_ref)
672            .or_else(|| self.stripped_state_static_for_key::<C, K>(room_id, state_key))?
673            .deserialize()
674            .ok()
675    }
676
677    /// Get the member for the given user in the given room from an event
678    /// contained in these `StateChanges`, if any.
679    pub(crate) fn member(
680        &self,
681        room_id: &RoomId,
682        user_id: &UserId,
683    ) -> Option<StrippedRoomMemberEvent> {
684        self.any_state_static_for_key::<RoomMemberEventContent, _>(room_id, user_id)
685    }
686
687    /// Get the create event for the given room from an event contained in these
688    /// `StateChanges`, if any.
689    pub(crate) fn create(&self, room_id: &RoomId) -> Option<RoomCreateWithCreatorEventContent> {
690        self.any_state_static_for_key::<RoomCreateEventContent, _>(room_id, &EmptyStateKey)
691            .map(|event| {
692                RoomCreateWithCreatorEventContent::from_event_content(event.content, event.sender)
693            })
694            // Fallback to the content in the room info.
695            .or_else(|| self.room_infos.get(room_id)?.create().cloned())
696    }
697
698    /// Get the power levels for the given room from an event contained in these
699    /// `StateChanges`, if any.
700    pub(crate) fn power_levels(&self, room_id: &RoomId) -> Option<RoomPowerLevels> {
701        let power_levels_content = self
702            .any_state_static_for_key::<RoomPowerLevelsEventContent, _>(room_id, &EmptyStateKey)?;
703
704        let create_content = self.create(room_id)?;
705        let rules = create_content.room_version.rules().unwrap_or(ROOM_VERSION_RULES_FALLBACK);
706        let creators = create_content.creators();
707
708        Some(power_levels_content.power_levels(&rules.authorization, creators))
709    }
710}
711
712/// Configuration for the various stores.
713///
714/// By default, this always includes a state store and an event cache store.
715/// When the `e2e-encryption` feature is enabled, this also includes a crypto
716/// store.
717///
718/// # Examples
719///
720/// ```
721/// # use matrix_sdk_base::store::StoreConfig;
722/// #
723/// let store_config =
724///     StoreConfig::new("cross-process-store-locks-holder-name".to_owned());
725/// ```
726#[derive(Clone)]
727pub struct StoreConfig {
728    #[cfg(feature = "e2e-encryption")]
729    pub(crate) crypto_store: Arc<DynCryptoStore>,
730    pub(crate) state_store: Arc<DynStateStore>,
731    pub(crate) event_cache_store: event_cache_store::EventCacheStoreLock,
732    cross_process_store_locks_holder_name: String,
733}
734
735#[cfg(not(tarpaulin_include))]
736impl fmt::Debug for StoreConfig {
737    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> StdResult<(), fmt::Error> {
738        fmt.debug_struct("StoreConfig").finish()
739    }
740}
741
742impl StoreConfig {
743    /// Create a new default `StoreConfig`.
744    ///
745    /// To learn more about `cross_process_store_locks_holder_name`, please read
746    /// [`CrossProcessStoreLock::new`](matrix_sdk_common::store_locks::CrossProcessStoreLock::new).
747    #[must_use]
748    pub fn new(cross_process_store_locks_holder_name: String) -> Self {
749        Self {
750            #[cfg(feature = "e2e-encryption")]
751            crypto_store: matrix_sdk_crypto::store::MemoryStore::new().into_crypto_store(),
752            state_store: Arc::new(MemoryStore::new()),
753            event_cache_store: event_cache_store::EventCacheStoreLock::new(
754                event_cache_store::MemoryStore::new(),
755                cross_process_store_locks_holder_name.clone(),
756            ),
757            cross_process_store_locks_holder_name,
758        }
759    }
760
761    /// Set a custom implementation of a `CryptoStore`.
762    ///
763    /// The crypto store must be opened before being set.
764    #[cfg(feature = "e2e-encryption")]
765    pub fn crypto_store(mut self, store: impl IntoCryptoStore) -> Self {
766        self.crypto_store = store.into_crypto_store();
767        self
768    }
769
770    /// Set a custom implementation of a `StateStore`.
771    pub fn state_store(mut self, store: impl IntoStateStore) -> Self {
772        self.state_store = store.into_state_store();
773        self
774    }
775
776    /// Set a custom implementation of an `EventCacheStore`.
777    pub fn event_cache_store<S>(mut self, event_cache_store: S) -> Self
778    where
779        S: event_cache_store::IntoEventCacheStore,
780    {
781        self.event_cache_store = event_cache_store::EventCacheStoreLock::new(
782            event_cache_store,
783            self.cross_process_store_locks_holder_name.clone(),
784        );
785        self
786    }
787}
788
789#[cfg(test)]
790mod tests {
791    use std::sync::Arc;
792
793    use assert_matches::assert_matches;
794    use matrix_sdk_test::async_test;
795    use ruma::{owned_device_id, owned_user_id, room_id, user_id};
796    use tokio::sync::broadcast;
797
798    use super::{BaseStateStore, MemoryStore, RoomLoadSettings};
799    use crate::{RoomInfo, RoomState, SessionMeta, StateChanges};
800
801    #[async_test]
802    async fn test_set_session_meta() {
803        let store = BaseStateStore::new(Arc::new(MemoryStore::new()));
804
805        let session_meta = SessionMeta {
806            user_id: owned_user_id!("@mnt_io:matrix.org"),
807            device_id: owned_device_id!("HELLOYOU"),
808        };
809
810        assert!(store.session_meta.get().is_none());
811
812        store.set_session_meta(session_meta.clone());
813
814        assert_eq!(store.session_meta.get(), Some(&session_meta));
815    }
816
817    #[async_test]
818    #[should_panic]
819    async fn test_set_session_meta_twice() {
820        let store = BaseStateStore::new(Arc::new(MemoryStore::new()));
821
822        let session_meta = SessionMeta {
823            user_id: owned_user_id!("@mnt_io:matrix.org"),
824            device_id: owned_device_id!("HELLOYOU"),
825        };
826
827        store.set_session_meta(session_meta.clone());
828        // Kaboom.
829        store.set_session_meta(session_meta);
830    }
831
832    #[async_test]
833    async fn test_derive_from_other() {
834        // The first store.
835        let other = BaseStateStore::new(Arc::new(MemoryStore::new()));
836
837        let session_meta = SessionMeta {
838            user_id: owned_user_id!("@mnt_io:matrix.org"),
839            device_id: owned_device_id!("HELLOYOU"),
840        };
841        let (room_info_notable_update_sender, _) = broadcast::channel(1);
842        let room_id_0 = room_id!("!r0");
843
844        other
845            .load_rooms(
846                &session_meta.user_id,
847                RoomLoadSettings::One(room_id_0.to_owned()),
848                &room_info_notable_update_sender,
849            )
850            .await
851            .unwrap();
852        other.set_session_meta(session_meta.clone());
853
854        // Derive another store.
855        let store = BaseStateStore::new(Arc::new(MemoryStore::new()));
856        store.derive_from_other(&other, &room_info_notable_update_sender).await.unwrap();
857
858        // `SessionMeta` is derived.
859        assert_eq!(store.session_meta.get(), Some(&session_meta));
860        // `RoomLoadSettings` is derived.
861        assert_matches!(*store.room_load_settings.read().await, RoomLoadSettings::One(ref room_id) => {
862            assert_eq!(room_id, room_id_0);
863        });
864    }
865
866    #[test]
867    fn test_room_load_settings_default() {
868        assert_matches!(RoomLoadSettings::default(), RoomLoadSettings::All);
869    }
870
871    #[async_test]
872    async fn test_load_all_rooms() {
873        let room_id_0 = room_id!("!r0");
874        let room_id_1 = room_id!("!r1");
875        let user_id = user_id!("@mnt_io:matrix.org");
876
877        let memory_state_store = Arc::new(MemoryStore::new());
878
879        // Initial state.
880        {
881            let store = BaseStateStore::new(memory_state_store.clone());
882            let mut changes = StateChanges::default();
883            changes.add_room(RoomInfo::new(room_id_0, RoomState::Joined));
884            changes.add_room(RoomInfo::new(room_id_1, RoomState::Joined));
885
886            store.inner.save_changes(&changes).await.unwrap();
887        }
888
889        // Check a `BaseStateStore` is able to load all rooms.
890        {
891            let store = BaseStateStore::new(memory_state_store.clone());
892            let (room_info_notable_update_sender, _) = broadcast::channel(2);
893
894            // Default value.
895            assert_matches!(*store.room_load_settings.read().await, RoomLoadSettings::All);
896
897            // Load rooms.
898            store
899                .load_rooms(user_id, RoomLoadSettings::All, &room_info_notable_update_sender)
900                .await
901                .unwrap();
902
903            // Check the last room load settings.
904            assert_matches!(*store.room_load_settings.read().await, RoomLoadSettings::All);
905
906            // Check the loaded rooms.
907            let mut rooms = store.rooms();
908            rooms.sort_by(|a, b| a.room_id().cmp(b.room_id()));
909
910            assert_eq!(rooms.len(), 2);
911
912            assert_eq!(rooms[0].room_id(), room_id_0);
913            assert_eq!(rooms[0].own_user_id(), user_id);
914
915            assert_eq!(rooms[1].room_id(), room_id_1);
916            assert_eq!(rooms[1].own_user_id(), user_id);
917        }
918    }
919
920    #[async_test]
921    async fn test_load_one_room() {
922        let room_id_0 = room_id!("!r0");
923        let room_id_1 = room_id!("!r1");
924        let user_id = user_id!("@mnt_io:matrix.org");
925
926        let memory_state_store = Arc::new(MemoryStore::new());
927
928        // Initial state.
929        {
930            let store = BaseStateStore::new(memory_state_store.clone());
931            let mut changes = StateChanges::default();
932            changes.add_room(RoomInfo::new(room_id_0, RoomState::Joined));
933            changes.add_room(RoomInfo::new(room_id_1, RoomState::Joined));
934
935            store.inner.save_changes(&changes).await.unwrap();
936        }
937
938        // Check a `BaseStateStore` is able to load one room.
939        {
940            let store = BaseStateStore::new(memory_state_store.clone());
941            let (room_info_notable_update_sender, _) = broadcast::channel(2);
942
943            // Default value.
944            assert_matches!(*store.room_load_settings.read().await, RoomLoadSettings::All);
945
946            // Load rooms.
947            store
948                .load_rooms(
949                    user_id,
950                    RoomLoadSettings::One(room_id_1.to_owned()),
951                    &room_info_notable_update_sender,
952                )
953                .await
954                .unwrap();
955
956            // Check the last room load settings.
957            assert_matches!(
958                *store.room_load_settings.read().await,
959                RoomLoadSettings::One(ref room_id) => {
960                    assert_eq!(room_id, room_id_1);
961                }
962            );
963
964            // Check the loaded rooms.
965            let rooms = store.rooms();
966            assert_eq!(rooms.len(), 1);
967
968            assert_eq!(rooms[0].room_id(), room_id_1);
969            assert_eq!(rooms[0].own_user_id(), user_id);
970        }
971    }
972}