1// Copyright 2024 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.
1415//! The event cache stores holds events and downloaded media when the cache was
16//! activated to save bandwidth at the cost of increased storage space usage.
17//!
18//! Implementing the `EventCacheStore` trait, you can plug any storage backend
19//! into the event cache for the actual storage. By default this brings an
20//! in-memory store.
2122use std::{fmt, ops::Deref, str::Utf8Error, sync::Arc};
2324#[cfg(any(test, feature = "testing"))]
25#[macro_use]
26pub mod integration_tests;
27pub mod media;
28mod memory_store;
29mod traits;
3031use matrix_sdk_common::store_locks::{
32 BackingStore, CrossProcessStoreLock, CrossProcessStoreLockGuard, LockStoreError,
33};
34pub use matrix_sdk_store_encryption::Error as StoreEncryptionError;
3536#[cfg(any(test, feature = "testing"))]
37pub use self::integration_tests::EventCacheStoreIntegrationTests;
38pub use self::{
39 memory_store::MemoryStore,
40 traits::{DynEventCacheStore, EventCacheStore, IntoEventCacheStore, DEFAULT_CHUNK_CAPACITY},
41};
4243/// The high-level public type to represent an `EventCacheStore` lock.
44#[derive(Clone)]
45pub struct EventCacheStoreLock {
46/// The inner cross process lock that is used to lock the `EventCacheStore`.
47cross_process_lock: Arc<CrossProcessStoreLock<LockableEventCacheStore>>,
4849/// The store itself.
50 ///
51 /// That's the only place where the store exists.
52store: Arc<DynEventCacheStore>,
53}
5455#[cfg(not(tarpaulin_include))]
56impl fmt::Debug for EventCacheStoreLock {
57fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
58 formatter.debug_struct("EventCacheStoreLock").finish_non_exhaustive()
59 }
60}
6162impl EventCacheStoreLock {
63/// Create a new lock around the [`EventCacheStore`].
64 ///
65 /// The `holder` argument represents the holder inside the
66 /// [`CrossProcessStoreLock::new`].
67pub fn new<S>(store: S, holder: String) -> Self
68where
69S: IntoEventCacheStore,
70 {
71let store = store.into_event_cache_store();
7273Self {
74 cross_process_lock: Arc::new(CrossProcessStoreLock::new(
75 LockableEventCacheStore(store.clone()),
76"default".to_owned(),
77 holder,
78 )),
79 store,
80 }
81 }
8283/// Acquire a spin lock (see [`CrossProcessStoreLock::spin_lock`]).
84pub async fn lock(&self) -> Result<EventCacheStoreLockGuard<'_>, LockStoreError> {
85let cross_process_lock_guard = self.cross_process_lock.spin_lock(None).await?;
8687Ok(EventCacheStoreLockGuard { cross_process_lock_guard, store: self.store.deref() })
88 }
89}
9091/// An RAII implementation of a “scoped lock” of an [`EventCacheStoreLock`].
92/// When this structure is dropped (falls out of scope), the lock will be
93/// unlocked.
94pub struct EventCacheStoreLockGuard<'a> {
95/// The cross process lock guard.
96#[allow(unused)]
97cross_process_lock_guard: CrossProcessStoreLockGuard,
9899/// A reference to the store.
100store: &'a DynEventCacheStore,
101}
102103#[cfg(not(tarpaulin_include))]
104impl fmt::Debug for EventCacheStoreLockGuard<'_> {
105fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
106 formatter.debug_struct("EventCacheStoreLockGuard").finish_non_exhaustive()
107 }
108}
109110impl Deref for EventCacheStoreLockGuard<'_> {
111type Target = DynEventCacheStore;
112113fn deref(&self) -> &Self::Target {
114self.store
115 }
116}
117118/// Event cache store specific error type.
119#[derive(Debug, thiserror::Error)]
120pub enum EventCacheStoreError {
121/// An error happened in the underlying database backend.
122#[error(transparent)]
123Backend(Box<dyn std::error::Error + Send + Sync>),
124125/// The store is locked with a passphrase and an incorrect passphrase
126 /// was given.
127#[error("The event cache store failed to be unlocked")]
128Locked,
129130/// An unencrypted store was tried to be unlocked with a passphrase.
131#[error("The event cache store is not encrypted but tried to be opened with a passphrase")]
132Unencrypted,
133134/// The store failed to encrypt or decrypt some data.
135#[error("Error encrypting or decrypting data from the event cache store: {0}")]
136Encryption(#[from] StoreEncryptionError),
137138/// The store failed to encode or decode some data.
139#[error("Error encoding or decoding data from the event cache store: {0}")]
140Codec(#[from] Utf8Error),
141142/// The store failed to serialize or deserialize some data.
143#[error("Error serializing or deserializing data from the event cache store: {0}")]
144Serialization(#[from] serde_json::Error),
145146/// The database format has changed in a backwards incompatible way.
147#[error(
148"The database format of the event cache store changed in an incompatible way, \
149 current version: {0}, latest version: {1}"
150)]
151UnsupportedDatabaseVersion(usize, usize),
152153/// The store contains invalid data.
154#[error("The store contains invalid data: {details}")]
155InvalidData {
156/// Details why the data contained in the store was invalid.
157details: String,
158 },
159}
160161impl EventCacheStoreError {
162/// Create a new [`Backend`][Self::Backend] error.
163 ///
164 /// Shorthand for `EventCacheStoreError::Backend(Box::new(error))`.
165#[inline]
166pub fn backend<E>(error: E) -> Self
167where
168E: std::error::Error + Send + Sync + 'static,
169 {
170Self::Backend(Box::new(error))
171 }
172}
173174/// An `EventCacheStore` specific result type.
175pub type Result<T, E = EventCacheStoreError> = std::result::Result<T, E>;
176177/// A type that wraps the [`EventCacheStore`] but implements [`BackingStore`] to
178/// make it usable inside the cross process lock.
179#[derive(Clone, Debug)]
180struct LockableEventCacheStore(Arc<DynEventCacheStore>);
181182#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
183#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
184impl BackingStore for LockableEventCacheStore {
185type LockError = EventCacheStoreError;
186187async fn try_lock(
188&self,
189 lease_duration_ms: u32,
190 key: &str,
191 holder: &str,
192 ) -> std::result::Result<bool, Self::LockError> {
193self.0.try_take_leased_lock(lease_duration_ms, key, holder).await
194}
195}