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