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    AsyncTraitDeps,
20    linked_chunk::{
21        ChunkIdentifier, ChunkIdentifierGenerator, ChunkMetadata, LinkedChunkId, Position,
22        RawChunk, Update,
23    },
24};
25use ruma::{EventId, MxcUri, OwnedEventId, RoomId, events::relation::RelationType};
26
27use super::{
28    EventCacheStoreError,
29    media::{IgnoreMediaRetentionPolicy, MediaRetentionPolicy},
30};
31use crate::{
32    event_cache::{Event, Gap},
33    media::MediaRequestParameters,
34};
35
36/// A default capacity for linked chunks, when manipulating in conjunction with
37/// an `EventCacheStore` implementation.
38// TODO: move back?
39pub const DEFAULT_CHUNK_CAPACITY: usize = 128;
40
41/// An abstract trait that can be used to implement different store backends
42/// for the event cache of the SDK.
43#[cfg_attr(target_family = "wasm", async_trait(?Send))]
44#[cfg_attr(not(target_family = "wasm"), async_trait)]
45pub trait EventCacheStore: AsyncTraitDeps {
46    /// The error type used by this event cache store.
47    type Error: fmt::Debug + Into<EventCacheStoreError>;
48
49    /// Try to take a lock using the given store.
50    async fn try_take_leased_lock(
51        &self,
52        lease_duration_ms: u32,
53        key: &str,
54        holder: &str,
55    ) -> Result<bool, Self::Error>;
56
57    /// An [`Update`] reflects an operation that has happened inside a linked
58    /// chunk. The linked chunk is used by the event cache to store the events
59    /// in-memory. This method aims at forwarding this update inside this store.
60    async fn handle_linked_chunk_updates(
61        &self,
62        linked_chunk_id: LinkedChunkId<'_>,
63        updates: Vec<Update<Event, Gap>>,
64    ) -> Result<(), Self::Error>;
65
66    /// Remove all data tied to a given room from the cache.
67    async fn remove_room(&self, room_id: &RoomId) -> Result<(), Self::Error> {
68        // Right now, this means removing all the linked chunk. If implementations
69        // override this behavior, they should *also* include this code.
70        self.handle_linked_chunk_updates(LinkedChunkId::Room(room_id), vec![Update::Clear]).await
71    }
72
73    /// Return all the raw components of a linked chunk, so the caller may
74    /// reconstruct the linked chunk later.
75    #[doc(hidden)]
76    async fn load_all_chunks(
77        &self,
78        linked_chunk_id: LinkedChunkId<'_>,
79    ) -> Result<Vec<RawChunk<Event, Gap>>, Self::Error>;
80
81    /// Load all of the chunks' metadata for the given [`LinkedChunkId`].
82    ///
83    /// Chunks are unordered, and there's no guarantee that the chunks would
84    /// form a valid linked chunk after reconstruction.
85    async fn load_all_chunks_metadata(
86        &self,
87        linked_chunk_id: LinkedChunkId<'_>,
88    ) -> Result<Vec<ChunkMetadata>, Self::Error>;
89
90    /// Load the last chunk of the `LinkedChunk` holding all events of the room
91    /// identified by `room_id`.
92    ///
93    /// This is used to iteratively load events for the `EventCache`.
94    async fn load_last_chunk(
95        &self,
96        linked_chunk_id: LinkedChunkId<'_>,
97    ) -> Result<(Option<RawChunk<Event, Gap>>, ChunkIdentifierGenerator), Self::Error>;
98
99    /// Load the chunk before the chunk identified by `before_chunk_identifier`
100    /// of the `LinkedChunk` holding all events of the room identified by
101    /// `room_id`
102    ///
103    /// This is used to iteratively load events for the `EventCache`.
104    async fn load_previous_chunk(
105        &self,
106        linked_chunk_id: LinkedChunkId<'_>,
107        before_chunk_identifier: ChunkIdentifier,
108    ) -> Result<Option<RawChunk<Event, Gap>>, Self::Error>;
109
110    /// Clear persisted events for all the rooms.
111    ///
112    /// This will empty and remove all the linked chunks stored previously,
113    /// using the above [`Self::handle_linked_chunk_updates`] methods. It
114    /// must *also* delete all the events' content, if they were stored in a
115    /// separate table.
116    ///
117    /// ⚠ This is meant only for super specific use cases, where there shouldn't
118    /// be any live in-memory linked chunks. In general, prefer using
119    /// `EventCache::clear_all_rooms()` from the common SDK crate.
120    async fn clear_all_linked_chunks(&self) -> Result<(), Self::Error>;
121
122    /// Given a set of event IDs, return the duplicated events along with their
123    /// position if there are any.
124    async fn filter_duplicated_events(
125        &self,
126        linked_chunk_id: LinkedChunkId<'_>,
127        events: Vec<OwnedEventId>,
128    ) -> Result<Vec<(OwnedEventId, Position)>, Self::Error>;
129
130    /// Find an event by its ID in a room.
131    ///
132    /// This method must return events saved either in any linked chunks, *or*
133    /// events saved "out-of-band" with the [`Self::save_event`] method.
134    async fn find_event(
135        &self,
136        room_id: &RoomId,
137        event_id: &EventId,
138    ) -> Result<Option<Event>, Self::Error>;
139
140    /// Find all the events (alongside their position in the room's linked
141    /// chunk, if available) that relate to a given event.
142    ///
143    /// The only events which don't have a position are those which have been
144    /// saved out-of-band using [`Self::save_event`].
145    ///
146    /// Note: it doesn't process relations recursively: for instance, if
147    /// requesting only thread events, it will NOT return the aggregated
148    /// events affecting the returned events. It is the responsibility of
149    /// the caller to do so, if needed.
150    ///
151    /// An additional filter can be provided to only retrieve related events for
152    /// a certain relationship.
153    ///
154    /// This method must return events saved either in any linked chunks, *or*
155    /// events saved "out-of-band" with the [`Self::save_event`] method.
156    async fn find_event_relations(
157        &self,
158        room_id: &RoomId,
159        event_id: &EventId,
160        filter: Option<&[RelationType]>,
161    ) -> Result<Vec<(Event, Option<Position>)>, Self::Error>;
162
163    /// Save an event, that might or might not be part of an existing linked
164    /// chunk.
165    ///
166    /// If the event has no event id, it will not be saved, and the function
167    /// must return an Ok result early.
168    ///
169    /// If the event was already stored with the same id, it must be replaced,
170    /// without causing an error.
171    async fn save_event(&self, room_id: &RoomId, event: Event) -> Result<(), Self::Error>;
172
173    /// Add a media file's content in the media store.
174    ///
175    /// # Arguments
176    ///
177    /// * `request` - The `MediaRequest` of the file.
178    ///
179    /// * `content` - The content of the file.
180    async fn add_media_content(
181        &self,
182        request: &MediaRequestParameters,
183        content: Vec<u8>,
184        ignore_policy: IgnoreMediaRetentionPolicy,
185    ) -> Result<(), Self::Error>;
186
187    /// Replaces the given media's content key with another one.
188    ///
189    /// This should be used whenever a temporary (local) MXID has been used, and
190    /// it must now be replaced with its actual remote counterpart (after
191    /// uploading some content, or creating an empty MXC URI).
192    ///
193    /// ⚠ No check is performed to ensure that the media formats are consistent,
194    /// i.e. it's possible to update with a thumbnail key a media that was
195    /// keyed as a file before. The caller is responsible of ensuring that
196    /// the replacement makes sense, according to their use case.
197    ///
198    /// This should not raise an error when the `from` parameter points to an
199    /// unknown media, and it should silently continue in this case.
200    ///
201    /// # Arguments
202    ///
203    /// * `from` - The previous `MediaRequest` of the file.
204    ///
205    /// * `to` - The new `MediaRequest` of the file.
206    async fn replace_media_key(
207        &self,
208        from: &MediaRequestParameters,
209        to: &MediaRequestParameters,
210    ) -> Result<(), Self::Error>;
211
212    /// Get a media file's content out of the media store.
213    ///
214    /// # Arguments
215    ///
216    /// * `request` - The `MediaRequest` of the file.
217    async fn get_media_content(
218        &self,
219        request: &MediaRequestParameters,
220    ) -> Result<Option<Vec<u8>>, Self::Error>;
221
222    /// Remove a media file's content from the media store.
223    ///
224    /// # Arguments
225    ///
226    /// * `request` - The `MediaRequest` of the file.
227    async fn remove_media_content(
228        &self,
229        request: &MediaRequestParameters,
230    ) -> Result<(), Self::Error>;
231
232    /// Get a media file's content associated to an `MxcUri` from the
233    /// media store.
234    ///
235    /// In theory, there could be several files stored using the same URI and a
236    /// different `MediaFormat`. This API is meant to be used with a media file
237    /// that has only been stored with a single format.
238    ///
239    /// If there are several media files for a given URI in different formats,
240    /// this API will only return one of them. Which one is left as an
241    /// implementation detail.
242    ///
243    /// # Arguments
244    ///
245    /// * `uri` - The `MxcUri` of the media file.
246    async fn get_media_content_for_uri(&self, uri: &MxcUri)
247    -> Result<Option<Vec<u8>>, Self::Error>;
248
249    /// Remove all the media files' content associated to an `MxcUri` from the
250    /// media store.
251    ///
252    /// This should not raise an error when the `uri` parameter points to an
253    /// unknown media, and it should return an Ok result in this case.
254    ///
255    /// # Arguments
256    ///
257    /// * `uri` - The `MxcUri` of the media files.
258    async fn remove_media_content_for_uri(&self, uri: &MxcUri) -> Result<(), Self::Error>;
259
260    /// Set the `MediaRetentionPolicy` to use for deciding whether to store or
261    /// keep media content.
262    ///
263    /// # Arguments
264    ///
265    /// * `policy` - The `MediaRetentionPolicy` to use.
266    async fn set_media_retention_policy(
267        &self,
268        policy: MediaRetentionPolicy,
269    ) -> Result<(), Self::Error>;
270
271    /// Get the current `MediaRetentionPolicy`.
272    fn media_retention_policy(&self) -> MediaRetentionPolicy;
273
274    /// Set whether the current [`MediaRetentionPolicy`] should be ignored for
275    /// the media.
276    ///
277    /// The change will be taken into account in the next cleanup.
278    ///
279    /// # Arguments
280    ///
281    /// * `request` - The `MediaRequestParameters` of the file.
282    ///
283    /// * `ignore_policy` - Whether the current `MediaRetentionPolicy` should be
284    ///   ignored.
285    async fn set_ignore_media_retention_policy(
286        &self,
287        request: &MediaRequestParameters,
288        ignore_policy: IgnoreMediaRetentionPolicy,
289    ) -> Result<(), Self::Error>;
290
291    /// Clean up the media cache with the current `MediaRetentionPolicy`.
292    ///
293    /// If there is already an ongoing cleanup, this is a noop.
294    async fn clean_up_media_cache(&self) -> Result<(), Self::Error>;
295}
296
297#[repr(transparent)]
298struct EraseEventCacheStoreError<T>(T);
299
300#[cfg(not(tarpaulin_include))]
301impl<T: fmt::Debug> fmt::Debug for EraseEventCacheStoreError<T> {
302    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
303        self.0.fmt(f)
304    }
305}
306
307#[cfg_attr(target_family = "wasm", async_trait(?Send))]
308#[cfg_attr(not(target_family = "wasm"), async_trait)]
309impl<T: EventCacheStore> EventCacheStore for EraseEventCacheStoreError<T> {
310    type Error = EventCacheStoreError;
311
312    async fn try_take_leased_lock(
313        &self,
314        lease_duration_ms: u32,
315        key: &str,
316        holder: &str,
317    ) -> Result<bool, Self::Error> {
318        self.0.try_take_leased_lock(lease_duration_ms, key, holder).await.map_err(Into::into)
319    }
320
321    async fn handle_linked_chunk_updates(
322        &self,
323        linked_chunk_id: LinkedChunkId<'_>,
324        updates: Vec<Update<Event, Gap>>,
325    ) -> Result<(), Self::Error> {
326        self.0.handle_linked_chunk_updates(linked_chunk_id, updates).await.map_err(Into::into)
327    }
328
329    async fn load_all_chunks(
330        &self,
331        linked_chunk_id: LinkedChunkId<'_>,
332    ) -> Result<Vec<RawChunk<Event, Gap>>, Self::Error> {
333        self.0.load_all_chunks(linked_chunk_id).await.map_err(Into::into)
334    }
335
336    async fn load_all_chunks_metadata(
337        &self,
338        linked_chunk_id: LinkedChunkId<'_>,
339    ) -> Result<Vec<ChunkMetadata>, Self::Error> {
340        self.0.load_all_chunks_metadata(linked_chunk_id).await.map_err(Into::into)
341    }
342
343    async fn load_last_chunk(
344        &self,
345        linked_chunk_id: LinkedChunkId<'_>,
346    ) -> Result<(Option<RawChunk<Event, Gap>>, ChunkIdentifierGenerator), Self::Error> {
347        self.0.load_last_chunk(linked_chunk_id).await.map_err(Into::into)
348    }
349
350    async fn load_previous_chunk(
351        &self,
352        linked_chunk_id: LinkedChunkId<'_>,
353        before_chunk_identifier: ChunkIdentifier,
354    ) -> Result<Option<RawChunk<Event, Gap>>, Self::Error> {
355        self.0
356            .load_previous_chunk(linked_chunk_id, before_chunk_identifier)
357            .await
358            .map_err(Into::into)
359    }
360
361    async fn clear_all_linked_chunks(&self) -> Result<(), Self::Error> {
362        self.0.clear_all_linked_chunks().await.map_err(Into::into)
363    }
364
365    async fn filter_duplicated_events(
366        &self,
367        linked_chunk_id: LinkedChunkId<'_>,
368        events: Vec<OwnedEventId>,
369    ) -> Result<Vec<(OwnedEventId, Position)>, Self::Error> {
370        self.0.filter_duplicated_events(linked_chunk_id, events).await.map_err(Into::into)
371    }
372
373    async fn find_event(
374        &self,
375        room_id: &RoomId,
376        event_id: &EventId,
377    ) -> Result<Option<Event>, Self::Error> {
378        self.0.find_event(room_id, event_id).await.map_err(Into::into)
379    }
380
381    async fn find_event_relations(
382        &self,
383        room_id: &RoomId,
384        event_id: &EventId,
385        filter: Option<&[RelationType]>,
386    ) -> Result<Vec<(Event, Option<Position>)>, Self::Error> {
387        self.0.find_event_relations(room_id, event_id, filter).await.map_err(Into::into)
388    }
389
390    async fn save_event(&self, room_id: &RoomId, event: Event) -> Result<(), Self::Error> {
391        self.0.save_event(room_id, event).await.map_err(Into::into)
392    }
393
394    async fn add_media_content(
395        &self,
396        request: &MediaRequestParameters,
397        content: Vec<u8>,
398        ignore_policy: IgnoreMediaRetentionPolicy,
399    ) -> Result<(), Self::Error> {
400        self.0.add_media_content(request, content, ignore_policy).await.map_err(Into::into)
401    }
402
403    async fn replace_media_key(
404        &self,
405        from: &MediaRequestParameters,
406        to: &MediaRequestParameters,
407    ) -> Result<(), Self::Error> {
408        self.0.replace_media_key(from, to).await.map_err(Into::into)
409    }
410
411    async fn get_media_content(
412        &self,
413        request: &MediaRequestParameters,
414    ) -> Result<Option<Vec<u8>>, Self::Error> {
415        self.0.get_media_content(request).await.map_err(Into::into)
416    }
417
418    async fn remove_media_content(
419        &self,
420        request: &MediaRequestParameters,
421    ) -> Result<(), Self::Error> {
422        self.0.remove_media_content(request).await.map_err(Into::into)
423    }
424
425    async fn get_media_content_for_uri(
426        &self,
427        uri: &MxcUri,
428    ) -> Result<Option<Vec<u8>>, Self::Error> {
429        self.0.get_media_content_for_uri(uri).await.map_err(Into::into)
430    }
431
432    async fn remove_media_content_for_uri(&self, uri: &MxcUri) -> Result<(), Self::Error> {
433        self.0.remove_media_content_for_uri(uri).await.map_err(Into::into)
434    }
435
436    async fn set_media_retention_policy(
437        &self,
438        policy: MediaRetentionPolicy,
439    ) -> Result<(), Self::Error> {
440        self.0.set_media_retention_policy(policy).await.map_err(Into::into)
441    }
442
443    fn media_retention_policy(&self) -> MediaRetentionPolicy {
444        self.0.media_retention_policy()
445    }
446
447    async fn set_ignore_media_retention_policy(
448        &self,
449        request: &MediaRequestParameters,
450        ignore_policy: IgnoreMediaRetentionPolicy,
451    ) -> Result<(), Self::Error> {
452        self.0.set_ignore_media_retention_policy(request, ignore_policy).await.map_err(Into::into)
453    }
454
455    async fn clean_up_media_cache(&self) -> Result<(), Self::Error> {
456        self.0.clean_up_media_cache().await.map_err(Into::into)
457    }
458}
459
460/// A type-erased [`EventCacheStore`].
461pub type DynEventCacheStore = dyn EventCacheStore<Error = EventCacheStoreError>;
462
463/// A type that can be type-erased into `Arc<dyn EventCacheStore>`.
464///
465/// This trait is not meant to be implemented directly outside
466/// `matrix-sdk-base`, but it is automatically implemented for everything that
467/// implements `EventCacheStore`.
468pub trait IntoEventCacheStore {
469    #[doc(hidden)]
470    fn into_event_cache_store(self) -> Arc<DynEventCacheStore>;
471}
472
473impl IntoEventCacheStore for Arc<DynEventCacheStore> {
474    fn into_event_cache_store(self) -> Arc<DynEventCacheStore> {
475        self
476    }
477}
478
479impl<T> IntoEventCacheStore for T
480where
481    T: EventCacheStore + Sized + 'static,
482{
483    fn into_event_cache_store(self) -> Arc<DynEventCacheStore> {
484        Arc::new(EraseEventCacheStoreError(self))
485    }
486}
487
488// Turns a given `Arc<T>` into `Arc<DynEventCacheStore>` by attaching the
489// `EventCacheStore` impl vtable of `EraseEventCacheStoreError<T>`.
490impl<T> IntoEventCacheStore for Arc<T>
491where
492    T: EventCacheStore + 'static,
493{
494    fn into_event_cache_store(self) -> Arc<DynEventCacheStore> {
495        let ptr: *const T = Arc::into_raw(self);
496        let ptr_erased = ptr as *const EraseEventCacheStoreError<T>;
497        // SAFETY: EraseEventCacheStoreError is repr(transparent) so T and
498        //         EraseEventCacheStoreError<T> have the same layout and ABI
499        unsafe { Arc::from_raw(ptr_erased) }
500    }
501}