matrix_sdk_base/event_cache/store/
traits.rs

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.
14
15use std::{fmt, sync::Arc};
16
17use async_trait::async_trait;
18use matrix_sdk_common::{
19    linked_chunk::{RawChunk, Update},
20    AsyncTraitDeps,
21};
22use ruma::{MxcUri, RoomId};
23
24use super::{
25    media::{IgnoreMediaRetentionPolicy, MediaRetentionPolicy},
26    EventCacheStoreError,
27};
28use crate::{
29    event_cache::{Event, Gap},
30    media::MediaRequestParameters,
31};
32
33/// A default capacity for linked chunks, when manipulating in conjunction with
34/// an `EventCacheStore` implementation.
35// TODO: move back?
36pub const DEFAULT_CHUNK_CAPACITY: usize = 128;
37
38/// An abstract trait that can be used to implement different store backends
39/// for the event cache of the SDK.
40#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
41#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
42pub trait EventCacheStore: AsyncTraitDeps {
43    /// The error type used by this event cache store.
44    type Error: fmt::Debug + Into<EventCacheStoreError>;
45
46    /// Try to take a lock using the given store.
47    async fn try_take_leased_lock(
48        &self,
49        lease_duration_ms: u32,
50        key: &str,
51        holder: &str,
52    ) -> Result<bool, Self::Error>;
53
54    /// An [`Update`] reflects an operation that has happened inside a linked
55    /// chunk. The linked chunk is used by the event cache to store the events
56    /// in-memory. This method aims at forwarding this update inside this store.
57    async fn handle_linked_chunk_updates(
58        &self,
59        room_id: &RoomId,
60        updates: Vec<Update<Event, Gap>>,
61    ) -> Result<(), Self::Error>;
62
63    /// Remove all data tied to a given room from the cache.
64    async fn remove_room(&self, room_id: &RoomId) -> Result<(), Self::Error> {
65        // Right now, this means removing all the linked chunk. If implementations
66        // override this behavior, they should *also* include this code.
67        self.handle_linked_chunk_updates(room_id, vec![Update::Clear]).await
68    }
69
70    /// Return all the raw components of a linked chunk, so the caller may
71    /// reconstruct the linked chunk later.
72    async fn reload_linked_chunk(
73        &self,
74        room_id: &RoomId,
75    ) -> Result<Vec<RawChunk<Event, Gap>>, Self::Error>;
76
77    /// Clear persisted events for all the rooms.
78    ///
79    /// This will empty and remove all the linked chunks stored previously,
80    /// using the above [`Self::handle_linked_chunk_updates`] methods.
81    async fn clear_all_rooms_chunks(&self) -> Result<(), Self::Error>;
82
83    /// Add a media file's content in the media store.
84    ///
85    /// # Arguments
86    ///
87    /// * `request` - The `MediaRequest` of the file.
88    ///
89    /// * `content` - The content of the file.
90    async fn add_media_content(
91        &self,
92        request: &MediaRequestParameters,
93        content: Vec<u8>,
94        ignore_policy: IgnoreMediaRetentionPolicy,
95    ) -> Result<(), Self::Error>;
96
97    /// Replaces the given media's content key with another one.
98    ///
99    /// This should be used whenever a temporary (local) MXID has been used, and
100    /// it must now be replaced with its actual remote counterpart (after
101    /// uploading some content, or creating an empty MXC URI).
102    ///
103    /// ⚠ No check is performed to ensure that the media formats are consistent,
104    /// i.e. it's possible to update with a thumbnail key a media that was
105    /// keyed as a file before. The caller is responsible of ensuring that
106    /// the replacement makes sense, according to their use case.
107    ///
108    /// This should not raise an error when the `from` parameter points to an
109    /// unknown media, and it should silently continue in this case.
110    ///
111    /// # Arguments
112    ///
113    /// * `from` - The previous `MediaRequest` of the file.
114    ///
115    /// * `to` - The new `MediaRequest` of the file.
116    async fn replace_media_key(
117        &self,
118        from: &MediaRequestParameters,
119        to: &MediaRequestParameters,
120    ) -> Result<(), Self::Error>;
121
122    /// Get a media file's content out of the media store.
123    ///
124    /// # Arguments
125    ///
126    /// * `request` - The `MediaRequest` of the file.
127    async fn get_media_content(
128        &self,
129        request: &MediaRequestParameters,
130    ) -> Result<Option<Vec<u8>>, Self::Error>;
131
132    /// Remove a media file's content from the media store.
133    ///
134    /// # Arguments
135    ///
136    /// * `request` - The `MediaRequest` of the file.
137    async fn remove_media_content(
138        &self,
139        request: &MediaRequestParameters,
140    ) -> Result<(), Self::Error>;
141
142    /// Get a media file's content associated to an `MxcUri` from the
143    /// media store.
144    ///
145    /// In theory, there could be several files stored using the same URI and a
146    /// different `MediaFormat`. This API is meant to be used with a media file
147    /// that has only been stored with a single format.
148    ///
149    /// If there are several media files for a given URI in different formats,
150    /// this API will only return one of them. Which one is left as an
151    /// implementation detail.
152    ///
153    /// # Arguments
154    ///
155    /// * `uri` - The `MxcUri` of the media file.
156    async fn get_media_content_for_uri(&self, uri: &MxcUri)
157        -> Result<Option<Vec<u8>>, Self::Error>;
158
159    /// Remove all the media files' content associated to an `MxcUri` from the
160    /// media store.
161    ///
162    /// This should not raise an error when the `uri` parameter points to an
163    /// unknown media, and it should return an Ok result in this case.
164    ///
165    /// # Arguments
166    ///
167    /// * `uri` - The `MxcUri` of the media files.
168    async fn remove_media_content_for_uri(&self, uri: &MxcUri) -> Result<(), Self::Error>;
169
170    /// Set the `MediaRetentionPolicy` to use for deciding whether to store or
171    /// keep media content.
172    ///
173    /// # Arguments
174    ///
175    /// * `policy` - The `MediaRetentionPolicy` to use.
176    async fn set_media_retention_policy(
177        &self,
178        policy: MediaRetentionPolicy,
179    ) -> Result<(), Self::Error>;
180
181    /// Get the current `MediaRetentionPolicy`.
182    fn media_retention_policy(&self) -> MediaRetentionPolicy;
183
184    /// Set whether the current [`MediaRetentionPolicy`] should be ignored for
185    /// the media.
186    ///
187    /// The change will be taken into account in the next cleanup.
188    ///
189    /// # Arguments
190    ///
191    /// * `request` - The `MediaRequestParameters` of the file.
192    ///
193    /// * `ignore_policy` - Whether the current `MediaRetentionPolicy` should be
194    ///   ignored.
195    async fn set_ignore_media_retention_policy(
196        &self,
197        request: &MediaRequestParameters,
198        ignore_policy: IgnoreMediaRetentionPolicy,
199    ) -> Result<(), Self::Error>;
200
201    /// Clean up the media cache with the current `MediaRetentionPolicy`.
202    ///
203    /// If there is already an ongoing cleanup, this is a noop.
204    async fn clean_up_media_cache(&self) -> Result<(), Self::Error>;
205}
206
207#[repr(transparent)]
208struct EraseEventCacheStoreError<T>(T);
209
210#[cfg(not(tarpaulin_include))]
211impl<T: fmt::Debug> fmt::Debug for EraseEventCacheStoreError<T> {
212    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213        self.0.fmt(f)
214    }
215}
216
217#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
218#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
219impl<T: EventCacheStore> EventCacheStore for EraseEventCacheStoreError<T> {
220    type Error = EventCacheStoreError;
221
222    async fn try_take_leased_lock(
223        &self,
224        lease_duration_ms: u32,
225        key: &str,
226        holder: &str,
227    ) -> Result<bool, Self::Error> {
228        self.0.try_take_leased_lock(lease_duration_ms, key, holder).await.map_err(Into::into)
229    }
230
231    async fn handle_linked_chunk_updates(
232        &self,
233        room_id: &RoomId,
234        updates: Vec<Update<Event, Gap>>,
235    ) -> Result<(), Self::Error> {
236        self.0.handle_linked_chunk_updates(room_id, updates).await.map_err(Into::into)
237    }
238
239    async fn reload_linked_chunk(
240        &self,
241        room_id: &RoomId,
242    ) -> Result<Vec<RawChunk<Event, Gap>>, Self::Error> {
243        self.0.reload_linked_chunk(room_id).await.map_err(Into::into)
244    }
245
246    async fn clear_all_rooms_chunks(&self) -> Result<(), Self::Error> {
247        self.0.clear_all_rooms_chunks().await.map_err(Into::into)
248    }
249
250    async fn add_media_content(
251        &self,
252        request: &MediaRequestParameters,
253        content: Vec<u8>,
254        ignore_policy: IgnoreMediaRetentionPolicy,
255    ) -> Result<(), Self::Error> {
256        self.0.add_media_content(request, content, ignore_policy).await.map_err(Into::into)
257    }
258
259    async fn replace_media_key(
260        &self,
261        from: &MediaRequestParameters,
262        to: &MediaRequestParameters,
263    ) -> Result<(), Self::Error> {
264        self.0.replace_media_key(from, to).await.map_err(Into::into)
265    }
266
267    async fn get_media_content(
268        &self,
269        request: &MediaRequestParameters,
270    ) -> Result<Option<Vec<u8>>, Self::Error> {
271        self.0.get_media_content(request).await.map_err(Into::into)
272    }
273
274    async fn remove_media_content(
275        &self,
276        request: &MediaRequestParameters,
277    ) -> Result<(), Self::Error> {
278        self.0.remove_media_content(request).await.map_err(Into::into)
279    }
280
281    async fn get_media_content_for_uri(
282        &self,
283        uri: &MxcUri,
284    ) -> Result<Option<Vec<u8>>, Self::Error> {
285        self.0.get_media_content_for_uri(uri).await.map_err(Into::into)
286    }
287
288    async fn remove_media_content_for_uri(&self, uri: &MxcUri) -> Result<(), Self::Error> {
289        self.0.remove_media_content_for_uri(uri).await.map_err(Into::into)
290    }
291
292    async fn set_media_retention_policy(
293        &self,
294        policy: MediaRetentionPolicy,
295    ) -> Result<(), Self::Error> {
296        self.0.set_media_retention_policy(policy).await.map_err(Into::into)
297    }
298
299    fn media_retention_policy(&self) -> MediaRetentionPolicy {
300        self.0.media_retention_policy()
301    }
302
303    async fn set_ignore_media_retention_policy(
304        &self,
305        request: &MediaRequestParameters,
306        ignore_policy: IgnoreMediaRetentionPolicy,
307    ) -> Result<(), Self::Error> {
308        self.0.set_ignore_media_retention_policy(request, ignore_policy).await.map_err(Into::into)
309    }
310
311    async fn clean_up_media_cache(&self) -> Result<(), Self::Error> {
312        self.0.clean_up_media_cache().await.map_err(Into::into)
313    }
314}
315
316/// A type-erased [`EventCacheStore`].
317pub type DynEventCacheStore = dyn EventCacheStore<Error = EventCacheStoreError>;
318
319/// A type that can be type-erased into `Arc<dyn EventCacheStore>`.
320///
321/// This trait is not meant to be implemented directly outside
322/// `matrix-sdk-base`, but it is automatically implemented for everything that
323/// implements `EventCacheStore`.
324pub trait IntoEventCacheStore {
325    #[doc(hidden)]
326    fn into_event_cache_store(self) -> Arc<DynEventCacheStore>;
327}
328
329impl<T> IntoEventCacheStore for T
330where
331    T: EventCacheStore + Sized + 'static,
332{
333    fn into_event_cache_store(self) -> Arc<DynEventCacheStore> {
334        Arc::new(EraseEventCacheStoreError(self))
335    }
336}
337
338// Turns a given `Arc<T>` into `Arc<DynEventCacheStore>` by attaching the
339// `EventCacheStore` impl vtable of `EraseEventCacheStoreError<T>`.
340impl<T> IntoEventCacheStore for Arc<T>
341where
342    T: EventCacheStore + 'static,
343{
344    fn into_event_cache_store(self) -> Arc<DynEventCacheStore> {
345        let ptr: *const T = Arc::into_raw(self);
346        let ptr_erased = ptr as *const EraseEventCacheStoreError<T>;
347        // SAFETY: EraseEventCacheStoreError is repr(transparent) so T and
348        //         EraseEventCacheStoreError<T> have the same layout and ABI
349        unsafe { Arc::from_raw(ptr_erased) }
350    }
351}