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::{
20        ChunkIdentifier, ChunkIdentifierGenerator, ChunkMetadata, LinkedChunkId, Position,
21        RawChunk, Update,
22    },
23    AsyncTraitDeps,
24};
25use ruma::{events::relation::RelationType, EventId, MxcUri, OwnedEventId, RoomId};
26
27use super::{
28    media::{IgnoreMediaRetentionPolicy, MediaRetentionPolicy},
29    EventCacheStoreError,
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    async fn find_event(
132        &self,
133        room_id: &RoomId,
134        event_id: &EventId,
135    ) -> Result<Option<Event>, Self::Error>;
136
137    /// Find all the events (alongside their position in the room's linked
138    /// chunk, if available) that relate to a given event.
139    ///
140    /// The only events which don't have a position are those which have been
141    /// saved out-of-band using [`Self::save_event`].
142    ///
143    /// Note: it doesn't process relations recursively: for instance, if
144    /// requesting only thread events, it will NOT return the aggregated
145    /// events affecting the returned events. It is the responsibility of
146    /// the caller to do so, if needed.
147    ///
148    /// An additional filter can be provided to only retrieve related events for
149    /// a certain relationship.
150    async fn find_event_relations(
151        &self,
152        room_id: &RoomId,
153        event_id: &EventId,
154        filter: Option<&[RelationType]>,
155    ) -> Result<Vec<(Event, Option<Position>)>, Self::Error>;
156
157    /// Save an event, that might or might not be part of an existing linked
158    /// chunk.
159    ///
160    /// If the event has no event id, it will not be saved, and the function
161    /// must return an Ok result early.
162    ///
163    /// If the event was already stored with the same id, it must be replaced,
164    /// without causing an error.
165    async fn save_event(&self, room_id: &RoomId, event: Event) -> Result<(), Self::Error>;
166
167    /// Add a media file's content in the media store.
168    ///
169    /// # Arguments
170    ///
171    /// * `request` - The `MediaRequest` of the file.
172    ///
173    /// * `content` - The content of the file.
174    async fn add_media_content(
175        &self,
176        request: &MediaRequestParameters,
177        content: Vec<u8>,
178        ignore_policy: IgnoreMediaRetentionPolicy,
179    ) -> Result<(), Self::Error>;
180
181    /// Replaces the given media's content key with another one.
182    ///
183    /// This should be used whenever a temporary (local) MXID has been used, and
184    /// it must now be replaced with its actual remote counterpart (after
185    /// uploading some content, or creating an empty MXC URI).
186    ///
187    /// ⚠ No check is performed to ensure that the media formats are consistent,
188    /// i.e. it's possible to update with a thumbnail key a media that was
189    /// keyed as a file before. The caller is responsible of ensuring that
190    /// the replacement makes sense, according to their use case.
191    ///
192    /// This should not raise an error when the `from` parameter points to an
193    /// unknown media, and it should silently continue in this case.
194    ///
195    /// # Arguments
196    ///
197    /// * `from` - The previous `MediaRequest` of the file.
198    ///
199    /// * `to` - The new `MediaRequest` of the file.
200    async fn replace_media_key(
201        &self,
202        from: &MediaRequestParameters,
203        to: &MediaRequestParameters,
204    ) -> Result<(), Self::Error>;
205
206    /// Get a media file's content out of the media store.
207    ///
208    /// # Arguments
209    ///
210    /// * `request` - The `MediaRequest` of the file.
211    async fn get_media_content(
212        &self,
213        request: &MediaRequestParameters,
214    ) -> Result<Option<Vec<u8>>, Self::Error>;
215
216    /// Remove a media file's content from the media store.
217    ///
218    /// # Arguments
219    ///
220    /// * `request` - The `MediaRequest` of the file.
221    async fn remove_media_content(
222        &self,
223        request: &MediaRequestParameters,
224    ) -> Result<(), Self::Error>;
225
226    /// Get a media file's content associated to an `MxcUri` from the
227    /// media store.
228    ///
229    /// In theory, there could be several files stored using the same URI and a
230    /// different `MediaFormat`. This API is meant to be used with a media file
231    /// that has only been stored with a single format.
232    ///
233    /// If there are several media files for a given URI in different formats,
234    /// this API will only return one of them. Which one is left as an
235    /// implementation detail.
236    ///
237    /// # Arguments
238    ///
239    /// * `uri` - The `MxcUri` of the media file.
240    async fn get_media_content_for_uri(&self, uri: &MxcUri)
241        -> Result<Option<Vec<u8>>, Self::Error>;
242
243    /// Remove all the media files' content associated to an `MxcUri` from the
244    /// media store.
245    ///
246    /// This should not raise an error when the `uri` parameter points to an
247    /// unknown media, and it should return an Ok result in this case.
248    ///
249    /// # Arguments
250    ///
251    /// * `uri` - The `MxcUri` of the media files.
252    async fn remove_media_content_for_uri(&self, uri: &MxcUri) -> Result<(), Self::Error>;
253
254    /// Set the `MediaRetentionPolicy` to use for deciding whether to store or
255    /// keep media content.
256    ///
257    /// # Arguments
258    ///
259    /// * `policy` - The `MediaRetentionPolicy` to use.
260    async fn set_media_retention_policy(
261        &self,
262        policy: MediaRetentionPolicy,
263    ) -> Result<(), Self::Error>;
264
265    /// Get the current `MediaRetentionPolicy`.
266    fn media_retention_policy(&self) -> MediaRetentionPolicy;
267
268    /// Set whether the current [`MediaRetentionPolicy`] should be ignored for
269    /// the media.
270    ///
271    /// The change will be taken into account in the next cleanup.
272    ///
273    /// # Arguments
274    ///
275    /// * `request` - The `MediaRequestParameters` of the file.
276    ///
277    /// * `ignore_policy` - Whether the current `MediaRetentionPolicy` should be
278    ///   ignored.
279    async fn set_ignore_media_retention_policy(
280        &self,
281        request: &MediaRequestParameters,
282        ignore_policy: IgnoreMediaRetentionPolicy,
283    ) -> Result<(), Self::Error>;
284
285    /// Clean up the media cache with the current `MediaRetentionPolicy`.
286    ///
287    /// If there is already an ongoing cleanup, this is a noop.
288    async fn clean_up_media_cache(&self) -> Result<(), Self::Error>;
289}
290
291#[repr(transparent)]
292struct EraseEventCacheStoreError<T>(T);
293
294#[cfg(not(tarpaulin_include))]
295impl<T: fmt::Debug> fmt::Debug for EraseEventCacheStoreError<T> {
296    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
297        self.0.fmt(f)
298    }
299}
300
301#[cfg_attr(target_family = "wasm", async_trait(?Send))]
302#[cfg_attr(not(target_family = "wasm"), async_trait)]
303impl<T: EventCacheStore> EventCacheStore for EraseEventCacheStoreError<T> {
304    type Error = EventCacheStoreError;
305
306    async fn try_take_leased_lock(
307        &self,
308        lease_duration_ms: u32,
309        key: &str,
310        holder: &str,
311    ) -> Result<bool, Self::Error> {
312        self.0.try_take_leased_lock(lease_duration_ms, key, holder).await.map_err(Into::into)
313    }
314
315    async fn handle_linked_chunk_updates(
316        &self,
317        linked_chunk_id: LinkedChunkId<'_>,
318        updates: Vec<Update<Event, Gap>>,
319    ) -> Result<(), Self::Error> {
320        self.0.handle_linked_chunk_updates(linked_chunk_id, updates).await.map_err(Into::into)
321    }
322
323    async fn load_all_chunks(
324        &self,
325        linked_chunk_id: LinkedChunkId<'_>,
326    ) -> Result<Vec<RawChunk<Event, Gap>>, Self::Error> {
327        self.0.load_all_chunks(linked_chunk_id).await.map_err(Into::into)
328    }
329
330    async fn load_all_chunks_metadata(
331        &self,
332        linked_chunk_id: LinkedChunkId<'_>,
333    ) -> Result<Vec<ChunkMetadata>, Self::Error> {
334        self.0.load_all_chunks_metadata(linked_chunk_id).await.map_err(Into::into)
335    }
336
337    async fn load_last_chunk(
338        &self,
339        linked_chunk_id: LinkedChunkId<'_>,
340    ) -> Result<(Option<RawChunk<Event, Gap>>, ChunkIdentifierGenerator), Self::Error> {
341        self.0.load_last_chunk(linked_chunk_id).await.map_err(Into::into)
342    }
343
344    async fn load_previous_chunk(
345        &self,
346        linked_chunk_id: LinkedChunkId<'_>,
347        before_chunk_identifier: ChunkIdentifier,
348    ) -> Result<Option<RawChunk<Event, Gap>>, Self::Error> {
349        self.0
350            .load_previous_chunk(linked_chunk_id, before_chunk_identifier)
351            .await
352            .map_err(Into::into)
353    }
354
355    async fn clear_all_linked_chunks(&self) -> Result<(), Self::Error> {
356        self.0.clear_all_linked_chunks().await.map_err(Into::into)
357    }
358
359    async fn filter_duplicated_events(
360        &self,
361        linked_chunk_id: LinkedChunkId<'_>,
362        events: Vec<OwnedEventId>,
363    ) -> Result<Vec<(OwnedEventId, Position)>, Self::Error> {
364        self.0.filter_duplicated_events(linked_chunk_id, events).await.map_err(Into::into)
365    }
366
367    async fn find_event(
368        &self,
369        room_id: &RoomId,
370        event_id: &EventId,
371    ) -> Result<Option<Event>, Self::Error> {
372        self.0.find_event(room_id, event_id).await.map_err(Into::into)
373    }
374
375    async fn find_event_relations(
376        &self,
377        room_id: &RoomId,
378        event_id: &EventId,
379        filter: Option<&[RelationType]>,
380    ) -> Result<Vec<(Event, Option<Position>)>, Self::Error> {
381        self.0.find_event_relations(room_id, event_id, filter).await.map_err(Into::into)
382    }
383
384    async fn save_event(&self, room_id: &RoomId, event: Event) -> Result<(), Self::Error> {
385        self.0.save_event(room_id, event).await.map_err(Into::into)
386    }
387
388    async fn add_media_content(
389        &self,
390        request: &MediaRequestParameters,
391        content: Vec<u8>,
392        ignore_policy: IgnoreMediaRetentionPolicy,
393    ) -> Result<(), Self::Error> {
394        self.0.add_media_content(request, content, ignore_policy).await.map_err(Into::into)
395    }
396
397    async fn replace_media_key(
398        &self,
399        from: &MediaRequestParameters,
400        to: &MediaRequestParameters,
401    ) -> Result<(), Self::Error> {
402        self.0.replace_media_key(from, to).await.map_err(Into::into)
403    }
404
405    async fn get_media_content(
406        &self,
407        request: &MediaRequestParameters,
408    ) -> Result<Option<Vec<u8>>, Self::Error> {
409        self.0.get_media_content(request).await.map_err(Into::into)
410    }
411
412    async fn remove_media_content(
413        &self,
414        request: &MediaRequestParameters,
415    ) -> Result<(), Self::Error> {
416        self.0.remove_media_content(request).await.map_err(Into::into)
417    }
418
419    async fn get_media_content_for_uri(
420        &self,
421        uri: &MxcUri,
422    ) -> Result<Option<Vec<u8>>, Self::Error> {
423        self.0.get_media_content_for_uri(uri).await.map_err(Into::into)
424    }
425
426    async fn remove_media_content_for_uri(&self, uri: &MxcUri) -> Result<(), Self::Error> {
427        self.0.remove_media_content_for_uri(uri).await.map_err(Into::into)
428    }
429
430    async fn set_media_retention_policy(
431        &self,
432        policy: MediaRetentionPolicy,
433    ) -> Result<(), Self::Error> {
434        self.0.set_media_retention_policy(policy).await.map_err(Into::into)
435    }
436
437    fn media_retention_policy(&self) -> MediaRetentionPolicy {
438        self.0.media_retention_policy()
439    }
440
441    async fn set_ignore_media_retention_policy(
442        &self,
443        request: &MediaRequestParameters,
444        ignore_policy: IgnoreMediaRetentionPolicy,
445    ) -> Result<(), Self::Error> {
446        self.0.set_ignore_media_retention_policy(request, ignore_policy).await.map_err(Into::into)
447    }
448
449    async fn clean_up_media_cache(&self) -> Result<(), Self::Error> {
450        self.0.clean_up_media_cache().await.map_err(Into::into)
451    }
452}
453
454/// A type-erased [`EventCacheStore`].
455pub type DynEventCacheStore = dyn EventCacheStore<Error = EventCacheStoreError>;
456
457/// A type that can be type-erased into `Arc<dyn EventCacheStore>`.
458///
459/// This trait is not meant to be implemented directly outside
460/// `matrix-sdk-base`, but it is automatically implemented for everything that
461/// implements `EventCacheStore`.
462pub trait IntoEventCacheStore {
463    #[doc(hidden)]
464    fn into_event_cache_store(self) -> Arc<DynEventCacheStore>;
465}
466
467impl<T> IntoEventCacheStore for T
468where
469    T: EventCacheStore + Sized + 'static,
470{
471    fn into_event_cache_store(self) -> Arc<DynEventCacheStore> {
472        Arc::new(EraseEventCacheStoreError(self))
473    }
474}
475
476// Turns a given `Arc<T>` into `Arc<DynEventCacheStore>` by attaching the
477// `EventCacheStore` impl vtable of `EraseEventCacheStoreError<T>`.
478impl<T> IntoEventCacheStore for Arc<T>
479where
480    T: EventCacheStore + 'static,
481{
482    fn into_event_cache_store(self) -> Arc<DynEventCacheStore> {
483        let ptr: *const T = Arc::into_raw(self);
484        let ptr_erased = ptr as *const EraseEventCacheStoreError<T>;
485        // SAFETY: EraseEventCacheStoreError is repr(transparent) so T and
486        //         EraseEventCacheStoreError<T> have the same layout and ABI
487        unsafe { Arc::from_raw(ptr_erased) }
488    }
489}