matrix_sdk_base/media/store/
memory_store.rs1use 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#[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#[derive(Debug)]
59struct MediaContent {
60 uri: OwnedMxcUri,
62
63 key: String,
65
66 data: Vec<u8>,
68
69 ignore_policy: bool,
71
72 last_access: SystemTime,
74}
75
76const NUMBER_OF_MEDIAS: NonZeroUsize = NonZeroUsize::new(20).unwrap();
77
78impl Default for MemoryMediaStore {
79 fn default() -> Self {
80 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 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 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 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 return Ok(());
285 }
286
287 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 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 let data = content.data.clone();
335
336 content.last_access = current_time;
338
339 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 return Ok(());
353 }
354
355 let mut inner = self.inner.write().unwrap();
356
357 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 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 if let Some(max_cache_size) = policy.max_cache_size {
375 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 return (cache_size, items_to_remove);
384 }
385
386 let remove_item = if items_to_remove.is_empty() {
387 if let Some(sum) = cache_size.checked_add(content.data.len() as u64) {
389 cache_size = sum;
390 cache_size > max_cache_size
393 } else {
394 true
398 }
399 } else {
400 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 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}