matrix_sdk_base/media/store/
memory_store.rs1use std::{
16 collections::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 close(&self) -> Result<(), Self::Error> {
226 Ok(())
227 }
228
229 async fn reopen(&self) -> Result<(), Self::Error> {
230 Ok(())
231 }
232}
233
234#[cfg_attr(target_family = "wasm", async_trait(?Send))]
235#[cfg_attr(not(target_family = "wasm"), async_trait)]
236impl MediaStoreInner for MemoryMediaStore {
237 type Error = MediaStoreError;
238
239 async fn media_retention_policy_inner(
240 &self,
241 ) -> Result<Option<MediaRetentionPolicy>, Self::Error> {
242 Ok(self.inner.read().unwrap().media_retention_policy)
243 }
244
245 async fn set_media_retention_policy_inner(
246 &self,
247 policy: MediaRetentionPolicy,
248 ) -> Result<(), Self::Error> {
249 self.inner.write().unwrap().media_retention_policy = Some(policy);
250 Ok(())
251 }
252
253 async fn add_media_content_inner(
254 &self,
255 request: &MediaRequestParameters,
256 data: Vec<u8>,
257 last_access: SystemTime,
258 policy: MediaRetentionPolicy,
259 ignore_policy: IgnoreMediaRetentionPolicy,
260 ) -> Result<(), Self::Error> {
261 self.remove_media_content(request).await?;
263
264 let ignore_policy = ignore_policy.is_yes();
265
266 if !ignore_policy && policy.exceeds_max_file_size(data.len() as u64) {
267 return Ok(());
269 }
270
271 let mut inner = self.inner.write().unwrap();
273 inner.media.push(MediaContent {
274 uri: request.uri().to_owned(),
275 key: request.unique_key(),
276 data,
277 ignore_policy,
278 last_access,
279 });
280
281 Ok(())
282 }
283
284 async fn set_ignore_media_retention_policy_inner(
285 &self,
286 request: &MediaRequestParameters,
287 ignore_policy: IgnoreMediaRetentionPolicy,
288 ) -> Result<(), Self::Error> {
289 let mut inner = self.inner.write().unwrap();
290 let expected_key = request.unique_key();
291
292 if let Some(media_content) = inner.media.iter_mut().find(|media| media.key == expected_key)
293 {
294 media_content.ignore_policy = ignore_policy.is_yes();
295 }
296
297 Ok(())
298 }
299
300 async fn get_media_content_inner(
301 &self,
302 request: &MediaRequestParameters,
303 current_time: SystemTime,
304 ) -> Result<Option<Vec<u8>>, Self::Error> {
305 let mut inner = self.inner.write().unwrap();
306 let expected_key = request.unique_key();
307
308 let Some(index) = inner.media.iter().position(|media| media.key == expected_key) else {
311 return Ok(None);
312 };
313 let Some(mut content) = inner.media.remove(index) else {
314 return Ok(None);
315 };
316
317 let data = content.data.clone();
319
320 content.last_access = current_time;
322
323 inner.media.push(content);
325
326 Ok(Some(data))
327 }
328
329 async fn clean_inner(
330 &self,
331 policy: MediaRetentionPolicy,
332 current_time: SystemTime,
333 ) -> Result<(), Self::Error> {
334 if !policy.has_limitations() {
335 return Ok(());
337 }
338
339 let mut inner = self.inner.write().unwrap();
340
341 if policy.computed_max_file_size().is_some() {
343 inner.media.retain(|content| {
344 content.ignore_policy || !policy.exceeds_max_file_size(content.data.len() as u64)
345 });
346 }
347
348 if policy.last_access_expiry.is_some() {
350 inner.media.retain(|content| {
351 content.ignore_policy
352 || !policy.has_content_expired(current_time, content.last_access)
353 });
354 }
355
356 if let Some(max_cache_size) = policy.max_cache_size {
359 let (_, items_to_remove) = inner.media.iter().enumerate().rev().fold(
363 (0u64, Vec::with_capacity(NUMBER_OF_MEDIAS.into())),
364 |(mut cache_size, mut items_to_remove), (index, content)| {
365 if content.ignore_policy {
366 return (cache_size, items_to_remove);
368 }
369
370 let remove_item = if items_to_remove.is_empty() {
371 if let Some(sum) = cache_size.checked_add(content.data.len() as u64) {
373 cache_size = sum;
374 cache_size > max_cache_size
377 } else {
378 true
382 }
383 } else {
384 true
387 };
388
389 if remove_item {
390 items_to_remove.push(index);
391 }
392
393 (cache_size, items_to_remove)
394 },
395 );
396
397 for index in items_to_remove {
400 inner.media.remove(index);
401 }
402 }
403
404 inner.last_media_cleanup_time = current_time;
405
406 Ok(())
407 }
408
409 async fn last_media_cleanup_time_inner(&self) -> Result<Option<SystemTime>, Self::Error> {
410 Ok(Some(self.inner.read().unwrap().last_media_cleanup_time))
411 }
412}
413
414#[cfg(test)]
415mod tests {
416 use super::{MemoryMediaStore, Result};
417 use crate::{
418 media_store_inner_integration_tests, media_store_integration_tests,
419 media_store_integration_tests_time,
420 };
421
422 async fn get_media_store() -> Result<MemoryMediaStore> {
423 Ok(MemoryMediaStore::new())
424 }
425
426 media_store_inner_integration_tests!();
427 media_store_integration_tests!();
428 media_store_integration_tests_time!();
429}