Skip to main content

matrix_sdk_base/media/store/
memory_store.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::{
16    collections::{BTreeMap, HashMap},
17    num::NonZeroUsize,
18    sync::{Arc, RwLock as StdRwLock},
19};
20
21use async_trait::async_trait;
22use matrix_sdk_common::{
23    cross_process_lock::{
24        CrossProcessLockGeneration,
25        memory_store_helper::{Lease, try_take_leased_lock},
26    },
27    ring_buffer::RingBuffer,
28};
29use ruma::{MxcUri, OwnedMxcUri, time::SystemTime};
30
31use super::Result;
32use crate::media::{
33    MediaRequestParameters, UniqueKey as _,
34    store::{
35        IgnoreMediaRetentionPolicy, MediaRetentionPolicy, MediaService, MediaStore,
36        MediaStoreError, MediaStoreInner,
37    },
38};
39
40/// In-memory, non-persistent implementation of the `MediaStore`.
41///
42/// Default if no other is configured at startup.
43#[derive(Debug, Clone)]
44pub struct MemoryMediaStore {
45    inner: Arc<StdRwLock<MemoryMediaStoreInner>>,
46    media_service: MediaService,
47}
48
49#[derive(Debug)]
50struct MemoryMediaStoreInner {
51    media: RingBuffer<MediaContent>,
52    leases: HashMap<String, Lease>,
53    media_retention_policy: Option<MediaRetentionPolicy>,
54    last_media_cleanup_time: SystemTime,
55}
56
57/// A media content in the `MemoryStore`.
58#[derive(Debug)]
59struct MediaContent {
60    /// The URI of the content.
61    uri: OwnedMxcUri,
62
63    /// The unique key of the content.
64    key: String,
65
66    /// The bytes of the content.
67    data: Vec<u8>,
68
69    /// Whether we should ignore the [`MediaRetentionPolicy`] for this content.
70    ignore_policy: bool,
71
72    /// The time of the last access of the content.
73    last_access: SystemTime,
74}
75
76const NUMBER_OF_MEDIAS: NonZeroUsize = NonZeroUsize::new(20).unwrap();
77
78impl Default for MemoryMediaStore {
79    fn default() -> Self {
80        // Given that the store is empty, we won't need to clean it up right
81        // away.
82        let last_media_cleanup_time = SystemTime::now();
83        let media_service = MediaService::new();
84        media_service.restore(None, Some(last_media_cleanup_time));
85
86        Self {
87            inner: Arc::new(StdRwLock::new(MemoryMediaStoreInner {
88                media: RingBuffer::new(NUMBER_OF_MEDIAS),
89                leases: Default::default(),
90                media_retention_policy: None,
91                last_media_cleanup_time,
92            })),
93            media_service,
94        }
95    }
96}
97
98impl MemoryMediaStore {
99    /// Create a new empty MemoryMediaStore
100    pub fn new() -> Self {
101        Self::default()
102    }
103}
104
105#[cfg_attr(target_family = "wasm", async_trait(?Send))]
106#[cfg_attr(not(target_family = "wasm"), async_trait)]
107impl MediaStore for MemoryMediaStore {
108    type Error = MediaStoreError;
109
110    async fn try_take_leased_lock(
111        &self,
112        lease_duration_ms: u32,
113        key: &str,
114        holder: &str,
115    ) -> Result<Option<CrossProcessLockGeneration>, Self::Error> {
116        let mut inner = self.inner.write().unwrap();
117
118        Ok(try_take_leased_lock(&mut inner.leases, lease_duration_ms, key, holder))
119    }
120
121    async fn add_media_content(
122        &self,
123        request: &MediaRequestParameters,
124        data: Vec<u8>,
125        ignore_policy: IgnoreMediaRetentionPolicy,
126    ) -> Result<(), Self::Error> {
127        self.media_service.add_media_content(self, request, data, ignore_policy).await
128    }
129
130    async fn replace_media_key(
131        &self,
132        from: &MediaRequestParameters,
133        to: &MediaRequestParameters,
134    ) -> Result<(), Self::Error> {
135        let expected_key = from.unique_key();
136
137        let mut inner = self.inner.write().unwrap();
138
139        if let Some(media_content) =
140            inner.media.iter_mut().find(|media_content| media_content.key == expected_key)
141        {
142            media_content.uri = to.uri().to_owned();
143            media_content.key = to.unique_key();
144        }
145
146        Ok(())
147    }
148
149    async fn get_media_content(
150        &self,
151        request: &MediaRequestParameters,
152    ) -> Result<Option<Vec<u8>>, Self::Error> {
153        self.media_service.get_media_content(self, request).await
154    }
155
156    async fn remove_media_content(
157        &self,
158        request: &MediaRequestParameters,
159    ) -> Result<(), Self::Error> {
160        let expected_key = request.unique_key();
161
162        let mut inner = self.inner.write().unwrap();
163
164        let Some(index) =
165            inner.media.iter().position(|media_content| media_content.key == expected_key)
166        else {
167            return Ok(());
168        };
169
170        inner.media.remove(index);
171
172        Ok(())
173    }
174
175    async fn remove_media_content_for_uri(&self, uri: &MxcUri) -> Result<(), Self::Error> {
176        let mut inner = self.inner.write().unwrap();
177
178        let positions = inner
179            .media
180            .iter()
181            .enumerate()
182            .filter_map(|(position, media_content)| (media_content.uri == uri).then_some(position))
183            .collect::<Vec<_>>();
184
185        // Iterate in reverse-order so that positions stay valid after first
186        // removals.
187        for position in positions.into_iter().rev() {
188            inner.media.remove(position);
189        }
190
191        Ok(())
192    }
193
194    async fn set_media_retention_policy(
195        &self,
196        policy: MediaRetentionPolicy,
197    ) -> Result<(), Self::Error> {
198        self.media_service.set_media_retention_policy(self, policy).await
199    }
200
201    fn media_retention_policy(&self) -> MediaRetentionPolicy {
202        self.media_service.media_retention_policy()
203    }
204
205    async fn set_ignore_media_retention_policy(
206        &self,
207        request: &MediaRequestParameters,
208        ignore_policy: IgnoreMediaRetentionPolicy,
209    ) -> Result<(), Self::Error> {
210        self.media_service.set_ignore_media_retention_policy(self, request, ignore_policy).await
211    }
212
213    async fn clean(&self) -> Result<(), Self::Error> {
214        self.media_service.clean(self).await
215    }
216
217    async fn optimize(&self) -> Result<(), Self::Error> {
218        Ok(())
219    }
220
221    async fn get_size(&self) -> Result<Option<usize>, Self::Error> {
222        Ok(None)
223    }
224
225    async fn media_sizes(
226        &self,
227        uris: &[OwnedMxcUri],
228    ) -> Result<BTreeMap<OwnedMxcUri, u64>, Self::Error> {
229        let inner = self.inner.read().unwrap();
230        let mut sizes = BTreeMap::new();
231
232        for media in inner.media.iter() {
233            if uris.contains(&media.uri) {
234                *sizes.entry(media.uri.clone()).or_default() += media.data.len() as u64;
235            }
236        }
237
238        Ok(sizes)
239    }
240
241    async fn close(&self) -> Result<(), Self::Error> {
242        Ok(())
243    }
244
245    async fn reopen(&self) -> Result<(), Self::Error> {
246        Ok(())
247    }
248}
249
250#[cfg_attr(target_family = "wasm", async_trait(?Send))]
251#[cfg_attr(not(target_family = "wasm"), async_trait)]
252impl MediaStoreInner for MemoryMediaStore {
253    type Error = MediaStoreError;
254
255    async fn media_retention_policy_inner(
256        &self,
257    ) -> Result<Option<MediaRetentionPolicy>, Self::Error> {
258        Ok(self.inner.read().unwrap().media_retention_policy)
259    }
260
261    async fn set_media_retention_policy_inner(
262        &self,
263        policy: MediaRetentionPolicy,
264    ) -> Result<(), Self::Error> {
265        self.inner.write().unwrap().media_retention_policy = Some(policy);
266        Ok(())
267    }
268
269    async fn add_media_content_inner(
270        &self,
271        request: &MediaRequestParameters,
272        data: Vec<u8>,
273        last_access: SystemTime,
274        policy: MediaRetentionPolicy,
275        ignore_policy: IgnoreMediaRetentionPolicy,
276    ) -> Result<(), Self::Error> {
277        // Avoid duplication. Let's try to remove it first.
278        self.remove_media_content(request).await?;
279
280        let ignore_policy = ignore_policy.is_yes();
281
282        if !ignore_policy && policy.exceeds_max_file_size(data.len() as u64) {
283            // Do not store it.
284            return Ok(());
285        }
286
287        // Now, let's add it.
288        let mut inner = self.inner.write().unwrap();
289        inner.media.push(MediaContent {
290            uri: request.uri().to_owned(),
291            key: request.unique_key(),
292            data,
293            ignore_policy,
294            last_access,
295        });
296
297        Ok(())
298    }
299
300    async fn set_ignore_media_retention_policy_inner(
301        &self,
302        request: &MediaRequestParameters,
303        ignore_policy: IgnoreMediaRetentionPolicy,
304    ) -> Result<(), Self::Error> {
305        let mut inner = self.inner.write().unwrap();
306        let expected_key = request.unique_key();
307
308        if let Some(media_content) = inner.media.iter_mut().find(|media| media.key == expected_key)
309        {
310            media_content.ignore_policy = ignore_policy.is_yes();
311        }
312
313        Ok(())
314    }
315
316    async fn get_media_content_inner(
317        &self,
318        request: &MediaRequestParameters,
319        current_time: SystemTime,
320    ) -> Result<Option<Vec<u8>>, Self::Error> {
321        let mut inner = self.inner.write().unwrap();
322        let expected_key = request.unique_key();
323
324        // First get the content out of the buffer, we are going to put it back
325        // at the end.
326        let Some(index) = inner.media.iter().position(|media| media.key == expected_key) else {
327            return Ok(None);
328        };
329        let Some(mut content) = inner.media.remove(index) else {
330            return Ok(None);
331        };
332
333        // Clone the data.
334        let data = content.data.clone();
335
336        // Update the last access time.
337        content.last_access = current_time;
338
339        // Put it back in the buffer.
340        inner.media.push(content);
341
342        Ok(Some(data))
343    }
344
345    async fn clean_inner(
346        &self,
347        policy: MediaRetentionPolicy,
348        current_time: SystemTime,
349    ) -> Result<(), Self::Error> {
350        if !policy.has_limitations() {
351            // We can safely skip all the checks.
352            return Ok(());
353        }
354
355        let mut inner = self.inner.write().unwrap();
356
357        // First, check media content that exceed the max filesize.
358        if policy.computed_max_file_size().is_some() {
359            inner.media.retain(|content| {
360                content.ignore_policy || !policy.exceeds_max_file_size(content.data.len() as u64)
361            });
362        }
363
364        // Then, clean up expired media content.
365        if policy.last_access_expiry.is_some() {
366            inner.media.retain(|content| {
367                content.ignore_policy
368                    || !policy.has_content_expired(current_time, content.last_access)
369            });
370        }
371
372        // Finally, if the cache size is too big, remove old items until it
373        // fits.
374        if let Some(max_cache_size) = policy.max_cache_size {
375            // Reverse the iterator because in case the cache size is
376            // overflowing, we want to count the number of old items to remove.
377            // Items are sorted by last access and old items are at the start.
378            let (_, items_to_remove) = inner.media.iter().enumerate().rev().fold(
379                (0u64, Vec::with_capacity(NUMBER_OF_MEDIAS.into())),
380                |(mut cache_size, mut items_to_remove), (index, content)| {
381                    if content.ignore_policy {
382                        // Do not count it.
383                        return (cache_size, items_to_remove);
384                    }
385
386                    let remove_item = if items_to_remove.is_empty() {
387                        // We have not reached the max cache size yet.
388                        if let Some(sum) = cache_size.checked_add(content.data.len() as u64) {
389                            cache_size = sum;
390                            // Start removing items if we have exceeded the max
391                            // cache size.
392                            cache_size > max_cache_size
393                        } else {
394                            // The cache size is overflowing, remove the
395                            // remaining items, since the max cache size cannot
396                            // be bigger than usize::MAX.
397                            true
398                        }
399                    } else {
400                        // We have reached the max cache size already, just
401                        // remove it.
402                        true
403                    };
404
405                    if remove_item {
406                        items_to_remove.push(index);
407                    }
408
409                    (cache_size, items_to_remove)
410                },
411            );
412
413            // The indexes are already in reverse order so we can just iterate
414            // in that order to remove them starting by the end.
415            for index in items_to_remove {
416                inner.media.remove(index);
417            }
418        }
419
420        inner.last_media_cleanup_time = current_time;
421
422        Ok(())
423    }
424
425    async fn last_media_cleanup_time_inner(&self) -> Result<Option<SystemTime>, Self::Error> {
426        Ok(Some(self.inner.read().unwrap().last_media_cleanup_time))
427    }
428}
429
430#[cfg(test)]
431mod tests {
432    use super::{MemoryMediaStore, Result};
433    use crate::{
434        media_store_inner_integration_tests, media_store_integration_tests,
435        media_store_integration_tests_time,
436    };
437
438    async fn get_media_store() -> Result<MemoryMediaStore> {
439        Ok(MemoryMediaStore::new())
440    }
441
442    media_store_inner_integration_tests!();
443    media_store_integration_tests!();
444    media_store_integration_tests_time!();
445}