matrix_sdk_base/media/store/
media_retention_policy.rs1use ruma::time::{Duration, SystemTime};
30use serde::{Deserialize, Serialize};
31
32#[cfg(doc)]
33use crate::media::store::MediaStore;
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
39#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
40#[non_exhaustive]
41pub struct MediaRetentionPolicy {
42 #[serde(default, skip_serializing_if = "Option::is_none")]
57 pub max_cache_size: Option<u64>,
58
59 #[serde(default, skip_serializing_if = "Option::is_none")]
75 pub max_file_size: Option<u64>,
76
77 #[serde(default, skip_serializing_if = "Option::is_none")]
84 pub last_access_expiry: Option<Duration>,
85
86 #[serde(default, skip_serializing_if = "Option::is_none")]
95 pub cleanup_frequency: Option<Duration>,
96}
97
98impl MediaRetentionPolicy {
99 pub fn new() -> Self {
101 Self::default()
102 }
103
104 pub fn empty() -> Self {
108 Self {
109 max_cache_size: None,
110 max_file_size: None,
111 last_access_expiry: None,
112 cleanup_frequency: None,
113 }
114 }
115
116 pub fn with_max_cache_size(mut self, size: Option<u64>) -> Self {
118 self.max_cache_size = size;
119 self
120 }
121
122 pub fn with_max_file_size(mut self, size: Option<u64>) -> Self {
124 self.max_file_size = size;
125 self
126 }
127
128 pub fn with_last_access_expiry(mut self, duration: Option<Duration>) -> Self {
131 self.last_access_expiry = duration;
132 self
133 }
134
135 pub fn with_cleanup_frequency(mut self, duration: Option<Duration>) -> Self {
137 self.cleanup_frequency = duration;
138 self
139 }
140
141 pub fn has_limitations(&self) -> bool {
147 self.max_cache_size.is_some()
148 || self.max_file_size.is_some()
149 || self.last_access_expiry.is_some()
150 }
151
152 pub fn exceeds_max_cache_size(&self, size: u64) -> bool {
159 self.max_cache_size.is_some_and(|max_size| size > max_size)
160 }
161
162 pub fn computed_max_file_size(&self) -> Option<u64> {
167 match (self.max_cache_size, self.max_file_size) {
168 (None, None) => None,
169 (None, Some(size)) => Some(size),
170 (Some(size), None) => Some(size),
171 (Some(max_cache_size), Some(max_file_size)) => Some(max_cache_size.min(max_file_size)),
172 }
173 }
174
175 pub fn exceeds_max_file_size(&self, size: u64) -> bool {
182 self.computed_max_file_size().is_some_and(|max_size| size > max_size)
183 }
184
185 pub fn has_content_expired(
193 &self,
194 current_time: SystemTime,
195 last_access_time: SystemTime,
196 ) -> bool {
197 self.last_access_expiry.is_some_and(|max_duration| {
198 current_time
199 .duration_since(last_access_time)
200 .is_ok_and(|elapsed| elapsed >= max_duration)
204 })
205 }
206
207 pub fn should_clean_up(&self, current_time: SystemTime, last_cleanup_time: SystemTime) -> bool {
215 self.cleanup_frequency.is_some_and(|max_duration| {
216 current_time
217 .duration_since(last_cleanup_time)
218 .is_ok_and(|elapsed| elapsed >= max_duration)
222 })
223 }
224}
225
226impl Default for MediaRetentionPolicy {
227 fn default() -> Self {
228 Self {
229 max_cache_size: Some(400 * 1024 * 1024),
231 max_file_size: Some(20 * 1024 * 1024),
233 last_access_expiry: Some(Duration::from_secs(60 * 24 * 60 * 60)),
235 cleanup_frequency: Some(Duration::from_secs(24 * 60 * 60)),
237 }
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use ruma::time::{Duration, SystemTime};
244
245 use super::MediaRetentionPolicy;
246
247 #[test]
248 fn test_media_retention_policy_has_limitations() {
249 let mut policy = MediaRetentionPolicy::empty();
250 assert!(!policy.has_limitations());
251
252 policy = policy.with_last_access_expiry(Some(Duration::from_secs(60)));
253 assert!(policy.has_limitations());
254
255 policy = policy.with_last_access_expiry(None);
256 assert!(!policy.has_limitations());
257
258 policy = policy.with_max_cache_size(Some(1_024));
259 assert!(policy.has_limitations());
260
261 policy = policy.with_max_cache_size(None);
262 assert!(!policy.has_limitations());
263
264 policy = policy.with_max_file_size(Some(1_024));
265 assert!(policy.has_limitations());
266
267 policy = policy.with_max_file_size(None);
268 assert!(!policy.has_limitations());
269
270 assert!(MediaRetentionPolicy::new().has_limitations());
272 }
273
274 #[test]
275 fn test_media_retention_policy_max_cache_size() {
276 let file_size = 2_048;
277
278 let mut policy = MediaRetentionPolicy::empty();
279 assert!(!policy.exceeds_max_cache_size(file_size));
280 assert_eq!(policy.computed_max_file_size(), None);
281 assert!(!policy.exceeds_max_file_size(file_size));
282
283 policy = policy.with_max_cache_size(Some(4_096));
284 assert!(!policy.exceeds_max_cache_size(file_size));
285 assert_eq!(policy.computed_max_file_size(), Some(4_096));
286 assert!(!policy.exceeds_max_file_size(file_size));
287
288 policy = policy.with_max_cache_size(Some(2_048));
289 assert!(!policy.exceeds_max_cache_size(file_size));
290 assert_eq!(policy.computed_max_file_size(), Some(2_048));
291 assert!(!policy.exceeds_max_file_size(file_size));
292
293 policy = policy.with_max_cache_size(Some(1_024));
294 assert!(policy.exceeds_max_cache_size(file_size));
295 assert_eq!(policy.computed_max_file_size(), Some(1_024));
296 assert!(policy.exceeds_max_file_size(file_size));
297 }
298
299 #[test]
300 fn test_media_retention_policy_max_file_size() {
301 let file_size = 2_048;
302
303 let mut policy = MediaRetentionPolicy::empty();
304 assert_eq!(policy.computed_max_file_size(), None);
305 assert!(!policy.exceeds_max_file_size(file_size));
306
307 policy = policy.with_max_file_size(Some(4_096));
309 assert_eq!(policy.computed_max_file_size(), Some(4_096));
310 assert!(!policy.exceeds_max_file_size(file_size));
311
312 policy = policy.with_max_file_size(Some(2_048));
313 assert_eq!(policy.computed_max_file_size(), Some(2_048));
314 assert!(!policy.exceeds_max_file_size(file_size));
315
316 policy = policy.with_max_file_size(Some(1_024));
317 assert_eq!(policy.computed_max_file_size(), Some(1_024));
318 assert!(policy.exceeds_max_file_size(file_size));
319
320 policy = policy.with_max_cache_size(Some(2_048));
322 assert_eq!(policy.computed_max_file_size(), Some(1_024));
323 assert!(policy.exceeds_max_file_size(file_size));
324
325 policy = policy.with_max_file_size(Some(2_048));
326 assert_eq!(policy.computed_max_file_size(), Some(2_048));
327 assert!(!policy.exceeds_max_file_size(file_size));
328
329 policy = policy.with_max_file_size(Some(4_096));
330 assert_eq!(policy.computed_max_file_size(), Some(2_048));
331 assert!(!policy.exceeds_max_file_size(file_size));
332
333 policy = policy.with_max_cache_size(Some(1_024));
334 assert_eq!(policy.computed_max_file_size(), Some(1_024));
335 assert!(policy.exceeds_max_file_size(file_size));
336 }
337
338 #[test]
339 fn test_media_retention_policy_has_content_expired() {
340 let epoch = SystemTime::UNIX_EPOCH;
341 let last_access_time = epoch + Duration::from_secs(30);
342 let epoch_plus_60 = epoch + Duration::from_secs(60);
343 let epoch_plus_120 = epoch + Duration::from_secs(120);
344
345 let mut policy = MediaRetentionPolicy::empty();
346 assert!(!policy.has_content_expired(epoch, last_access_time));
347 assert!(!policy.has_content_expired(last_access_time, last_access_time));
348 assert!(!policy.has_content_expired(epoch_plus_60, last_access_time));
349 assert!(!policy.has_content_expired(epoch_plus_120, last_access_time));
350
351 policy = policy.with_last_access_expiry(Some(Duration::from_secs(120)));
352 assert!(!policy.has_content_expired(epoch, last_access_time));
353 assert!(!policy.has_content_expired(last_access_time, last_access_time));
354 assert!(!policy.has_content_expired(epoch_plus_60, last_access_time));
355 assert!(!policy.has_content_expired(epoch_plus_120, last_access_time));
356
357 policy = policy.with_last_access_expiry(Some(Duration::from_secs(60)));
358 assert!(!policy.has_content_expired(epoch, last_access_time));
359 assert!(!policy.has_content_expired(last_access_time, last_access_time));
360 assert!(!policy.has_content_expired(epoch_plus_60, last_access_time));
361 assert!(policy.has_content_expired(epoch_plus_120, last_access_time));
362
363 policy = policy.with_last_access_expiry(Some(Duration::from_secs(30)));
364 assert!(!policy.has_content_expired(epoch, last_access_time));
365 assert!(!policy.has_content_expired(last_access_time, last_access_time));
366 assert!(policy.has_content_expired(epoch_plus_60, last_access_time));
367 assert!(policy.has_content_expired(epoch_plus_120, last_access_time));
368
369 policy = policy.with_last_access_expiry(Some(Duration::from_secs(0)));
370 assert!(!policy.has_content_expired(epoch, last_access_time));
371 assert!(policy.has_content_expired(last_access_time, last_access_time));
372 assert!(policy.has_content_expired(epoch_plus_60, last_access_time));
373 assert!(policy.has_content_expired(epoch_plus_120, last_access_time));
374 }
375
376 #[test]
377 fn test_media_retention_policy_cleanup_frequency() {
378 let epoch = SystemTime::UNIX_EPOCH;
379 let epoch_plus_60 = epoch + Duration::from_secs(60);
380 let epoch_plus_120 = epoch + Duration::from_secs(120);
381
382 let mut policy = MediaRetentionPolicy::empty();
383 assert!(!policy.should_clean_up(epoch_plus_60, epoch));
384 assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_60));
385 assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_120));
386
387 policy = policy.with_cleanup_frequency(Some(Duration::from_secs(0)));
388 assert!(policy.should_clean_up(epoch_plus_60, epoch));
389 assert!(policy.should_clean_up(epoch_plus_60, epoch_plus_60));
390 assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_120));
391
392 policy = policy.with_cleanup_frequency(Some(Duration::from_secs(30)));
393 assert!(policy.should_clean_up(epoch_plus_60, epoch));
394 assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_60));
395 assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_120));
396
397 policy = policy.with_cleanup_frequency(Some(Duration::from_secs(60)));
398 assert!(policy.should_clean_up(epoch_plus_60, epoch));
399 assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_60));
400 assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_120));
401
402 policy = policy.with_cleanup_frequency(Some(Duration::from_secs(90)));
403 assert!(!policy.should_clean_up(epoch_plus_60, epoch));
404 assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_60));
405 assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_120));
406 }
407}