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::{ChunkIdentifier, ChunkIdentifierGenerator, RawChunk, Update},
20    AsyncTraitDeps,
21};
22use ruma::{MxcUri, OwnedEventId, 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    #[doc(hidden)]
73    async fn load_all_chunks(
74        &self,
75        room_id: &RoomId,
76    ) -> Result<Vec<RawChunk<Event, Gap>>, Self::Error>;
77
78    /// Load the last chunk of the `LinkedChunk` holding all events of the room
79    /// identified by `room_id`.
80    ///
81    /// This is used to iteratively load events for the `EventCache`.
82    async fn load_last_chunk(
83        &self,
84        room_id: &RoomId,
85    ) -> Result<(Option<RawChunk<Event, Gap>>, ChunkIdentifierGenerator), Self::Error>;
86
87    /// Load the chunk before the chunk identified by `before_chunk_identifier`
88    /// of the `LinkedChunk` holding all events of the room identified by
89    /// `room_id`
90    ///
91    /// This is used to iteratively load events for the `EventCache`.
92    async fn load_previous_chunk(
93        &self,
94        room_id: &RoomId,
95        before_chunk_identifier: ChunkIdentifier,
96    ) -> Result<Option<RawChunk<Event, Gap>>, Self::Error>;
97
98    /// Clear persisted events for all the rooms.
99    ///
100    /// This will empty and remove all the linked chunks stored previously,
101    /// using the above [`Self::handle_linked_chunk_updates`] methods.
102    async fn clear_all_rooms_chunks(&self) -> Result<(), Self::Error>;
103
104    /// Given a set of event ID, remove the unique events and return the
105    /// duplicated events.
106    async fn filter_duplicated_events(
107        &self,
108        room_id: &RoomId,
109        events: Vec<OwnedEventId>,
110    ) -> Result<Vec<OwnedEventId>, Self::Error>;
111
112    /// Add a media file's content in the media store.
113    ///
114    /// # Arguments
115    ///
116    /// * `request` - The `MediaRequest` of the file.
117    ///
118    /// * `content` - The content of the file.
119    async fn add_media_content(
120        &self,
121        request: &MediaRequestParameters,
122        content: Vec<u8>,
123        ignore_policy: IgnoreMediaRetentionPolicy,
124    ) -> Result<(), Self::Error>;
125
126    /// Replaces the given media's content key with another one.
127    ///
128    /// This should be used whenever a temporary (local) MXID has been used, and
129    /// it must now be replaced with its actual remote counterpart (after
130    /// uploading some content, or creating an empty MXC URI).
131    ///
132    /// ⚠ No check is performed to ensure that the media formats are consistent,
133    /// i.e. it's possible to update with a thumbnail key a media that was
134    /// keyed as a file before. The caller is responsible of ensuring that
135    /// the replacement makes sense, according to their use case.
136    ///
137    /// This should not raise an error when the `from` parameter points to an
138    /// unknown media, and it should silently continue in this case.
139    ///
140    /// # Arguments
141    ///
142    /// * `from` - The previous `MediaRequest` of the file.
143    ///
144    /// * `to` - The new `MediaRequest` of the file.
145    async fn replace_media_key(
146        &self,
147        from: &MediaRequestParameters,
148        to: &MediaRequestParameters,
149    ) -> Result<(), Self::Error>;
150
151    /// Get a media file's content out of the media store.
152    ///
153    /// # Arguments
154    ///
155    /// * `request` - The `MediaRequest` of the file.
156    async fn get_media_content(
157        &self,
158        request: &MediaRequestParameters,
159    ) -> Result<Option<Vec<u8>>, Self::Error>;
160
161    /// Remove a media file's content from the media store.
162    ///
163    /// # Arguments
164    ///
165    /// * `request` - The `MediaRequest` of the file.
166    async fn remove_media_content(
167        &self,
168        request: &MediaRequestParameters,
169    ) -> Result<(), Self::Error>;
170
171    /// Get a media file's content associated to an `MxcUri` from the
172    /// media store.
173    ///
174    /// In theory, there could be several files stored using the same URI and a
175    /// different `MediaFormat`. This API is meant to be used with a media file
176    /// that has only been stored with a single format.
177    ///
178    /// If there are several media files for a given URI in different formats,
179    /// this API will only return one of them. Which one is left as an
180    /// implementation detail.
181    ///
182    /// # Arguments
183    ///
184    /// * `uri` - The `MxcUri` of the media file.
185    async fn get_media_content_for_uri(&self, uri: &MxcUri)
186        -> Result<Option<Vec<u8>>, Self::Error>;
187
188    /// Remove all the media files' content associated to an `MxcUri` from the
189    /// media store.
190    ///
191    /// This should not raise an error when the `uri` parameter points to an
192    /// unknown media, and it should return an Ok result in this case.
193    ///
194    /// # Arguments
195    ///
196    /// * `uri` - The `MxcUri` of the media files.
197    async fn remove_media_content_for_uri(&self, uri: &MxcUri) -> Result<(), Self::Error>;
198
199    /// Set the `MediaRetentionPolicy` to use for deciding whether to store or
200    /// keep media content.
201    ///
202    /// # Arguments
203    ///
204    /// * `policy` - The `MediaRetentionPolicy` to use.
205    async fn set_media_retention_policy(
206        &self,
207        policy: MediaRetentionPolicy,
208    ) -> Result<(), Self::Error>;
209
210    /// Get the current `MediaRetentionPolicy`.
211    fn media_retention_policy(&self) -> MediaRetentionPolicy;
212
213    /// Set whether the current [`MediaRetentionPolicy`] should be ignored for
214    /// the media.
215    ///
216    /// The change will be taken into account in the next cleanup.
217    ///
218    /// # Arguments
219    ///
220    /// * `request` - The `MediaRequestParameters` of the file.
221    ///
222    /// * `ignore_policy` - Whether the current `MediaRetentionPolicy` should be
223    ///   ignored.
224    async fn set_ignore_media_retention_policy(
225        &self,
226        request: &MediaRequestParameters,
227        ignore_policy: IgnoreMediaRetentionPolicy,
228    ) -> Result<(), Self::Error>;
229
230    /// Clean up the media cache with the current `MediaRetentionPolicy`.
231    ///
232    /// If there is already an ongoing cleanup, this is a noop.
233    async fn clean_up_media_cache(&self) -> Result<(), Self::Error>;
234}
235
236#[repr(transparent)]
237struct EraseEventCacheStoreError<T>(T);
238
239#[cfg(not(tarpaulin_include))]
240impl<T: fmt::Debug> fmt::Debug for EraseEventCacheStoreError<T> {
241    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242        self.0.fmt(f)
243    }
244}
245
246#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
247#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
248impl<T: EventCacheStore> EventCacheStore for EraseEventCacheStoreError<T> {
249    type Error = EventCacheStoreError;
250
251    async fn try_take_leased_lock(
252        &self,
253        lease_duration_ms: u32,
254        key: &str,
255        holder: &str,
256    ) -> Result<bool, Self::Error> {
257        self.0.try_take_leased_lock(lease_duration_ms, key, holder).await.map_err(Into::into)
258    }
259
260    async fn handle_linked_chunk_updates(
261        &self,
262        room_id: &RoomId,
263        updates: Vec<Update<Event, Gap>>,
264    ) -> Result<(), Self::Error> {
265        self.0.handle_linked_chunk_updates(room_id, updates).await.map_err(Into::into)
266    }
267
268    async fn load_all_chunks(
269        &self,
270        room_id: &RoomId,
271    ) -> Result<Vec<RawChunk<Event, Gap>>, Self::Error> {
272        self.0.load_all_chunks(room_id).await.map_err(Into::into)
273    }
274
275    async fn load_last_chunk(
276        &self,
277        room_id: &RoomId,
278    ) -> Result<(Option<RawChunk<Event, Gap>>, ChunkIdentifierGenerator), Self::Error> {
279        self.0.load_last_chunk(room_id).await.map_err(Into::into)
280    }
281
282    async fn load_previous_chunk(
283        &self,
284        room_id: &RoomId,
285        before_chunk_identifier: ChunkIdentifier,
286    ) -> Result<Option<RawChunk<Event, Gap>>, Self::Error> {
287        self.0.load_previous_chunk(room_id, before_chunk_identifier).await.map_err(Into::into)
288    }
289
290    async fn clear_all_rooms_chunks(&self) -> Result<(), Self::Error> {
291        self.0.clear_all_rooms_chunks().await.map_err(Into::into)
292    }
293
294    async fn filter_duplicated_events(
295        &self,
296        room_id: &RoomId,
297        events: Vec<OwnedEventId>,
298    ) -> Result<Vec<OwnedEventId>, Self::Error> {
299        self.0.filter_duplicated_events(room_id, events).await.map_err(Into::into)
300    }
301
302    async fn add_media_content(
303        &self,
304        request: &MediaRequestParameters,
305        content: Vec<u8>,
306        ignore_policy: IgnoreMediaRetentionPolicy,
307    ) -> Result<(), Self::Error> {
308        self.0.add_media_content(request, content, ignore_policy).await.map_err(Into::into)
309    }
310
311    async fn replace_media_key(
312        &self,
313        from: &MediaRequestParameters,
314        to: &MediaRequestParameters,
315    ) -> Result<(), Self::Error> {
316        self.0.replace_media_key(from, to).await.map_err(Into::into)
317    }
318
319    async fn get_media_content(
320        &self,
321        request: &MediaRequestParameters,
322    ) -> Result<Option<Vec<u8>>, Self::Error> {
323        self.0.get_media_content(request).await.map_err(Into::into)
324    }
325
326    async fn remove_media_content(
327        &self,
328        request: &MediaRequestParameters,
329    ) -> Result<(), Self::Error> {
330        self.0.remove_media_content(request).await.map_err(Into::into)
331    }
332
333    async fn get_media_content_for_uri(
334        &self,
335        uri: &MxcUri,
336    ) -> Result<Option<Vec<u8>>, Self::Error> {
337        self.0.get_media_content_for_uri(uri).await.map_err(Into::into)
338    }
339
340    async fn remove_media_content_for_uri(&self, uri: &MxcUri) -> Result<(), Self::Error> {
341        self.0.remove_media_content_for_uri(uri).await.map_err(Into::into)
342    }
343
344    async fn set_media_retention_policy(
345        &self,
346        policy: MediaRetentionPolicy,
347    ) -> Result<(), Self::Error> {
348        self.0.set_media_retention_policy(policy).await.map_err(Into::into)
349    }
350
351    fn media_retention_policy(&self) -> MediaRetentionPolicy {
352        self.0.media_retention_policy()
353    }
354
355    async fn set_ignore_media_retention_policy(
356        &self,
357        request: &MediaRequestParameters,
358        ignore_policy: IgnoreMediaRetentionPolicy,
359    ) -> Result<(), Self::Error> {
360        self.0.set_ignore_media_retention_policy(request, ignore_policy).await.map_err(Into::into)
361    }
362
363    async fn clean_up_media_cache(&self) -> Result<(), Self::Error> {
364        self.0.clean_up_media_cache().await.map_err(Into::into)
365    }
366}
367
368/// A type-erased [`EventCacheStore`].
369pub type DynEventCacheStore = dyn EventCacheStore<Error = EventCacheStoreError>;
370
371/// A type that can be type-erased into `Arc<dyn EventCacheStore>`.
372///
373/// This trait is not meant to be implemented directly outside
374/// `matrix-sdk-base`, but it is automatically implemented for everything that
375/// implements `EventCacheStore`.
376pub trait IntoEventCacheStore {
377    #[doc(hidden)]
378    fn into_event_cache_store(self) -> Arc<DynEventCacheStore>;
379}
380
381impl<T> IntoEventCacheStore for T
382where
383    T: EventCacheStore + Sized + 'static,
384{
385    fn into_event_cache_store(self) -> Arc<DynEventCacheStore> {
386        Arc::new(EraseEventCacheStoreError(self))
387    }
388}
389
390// Turns a given `Arc<T>` into `Arc<DynEventCacheStore>` by attaching the
391// `EventCacheStore` impl vtable of `EraseEventCacheStoreError<T>`.
392impl<T> IntoEventCacheStore for Arc<T>
393where
394    T: EventCacheStore + 'static,
395{
396    fn into_event_cache_store(self) -> Arc<DynEventCacheStore> {
397        let ptr: *const T = Arc::into_raw(self);
398        let ptr_erased = ptr as *const EraseEventCacheStoreError<T>;
399        // SAFETY: EraseEventCacheStoreError is repr(transparent) so T and
400        //         EraseEventCacheStoreError<T> have the same layout and ABI
401        unsafe { Arc::from_raw(ptr_erased) }
402    }
403}