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")]
85 pub last_access_expiry: Option<Duration>,
86
87 #[serde(default, skip_serializing_if = "Option::is_none")]
96 pub cleanup_frequency: Option<Duration>,
97}
98
99impl MediaRetentionPolicy {
100 pub fn new() -> Self {
102 Self::default()
103 }
104
105 pub fn empty() -> Self {
109 Self {
110 max_cache_size: None,
111 max_file_size: None,
112 last_access_expiry: None,
113 cleanup_frequency: None,
114 }
115 }
116
117 pub fn with_max_cache_size(mut self, size: Option<u64>) -> Self {
119 self.max_cache_size = size;
120 self
121 }
122
123 pub fn with_max_file_size(mut self, size: Option<u64>) -> Self {
125 self.max_file_size = size;
126 self
127 }
128
129 pub fn with_last_access_expiry(mut self, duration: Option<Duration>) -> Self {
132 self.last_access_expiry = duration;
133 self
134 }
135
136 pub fn with_cleanup_frequency(mut self, duration: Option<Duration>) -> Self {
138 self.cleanup_frequency = duration;
139 self
140 }
141
142 pub fn has_limitations(&self) -> bool {
148 self.max_cache_size.is_some()
149 || self.max_file_size.is_some()
150 || self.last_access_expiry.is_some()
151 }
152
153 pub fn exceeds_max_cache_size(&self, size: u64) -> bool {
160 self.max_cache_size.is_some_and(|max_size| size > max_size)
161 }
162
163 pub fn computed_max_file_size(&self) -> Option<u64> {
168 match (self.max_cache_size, self.max_file_size) {
169 (None, None) => None,
170 (None, Some(size)) => Some(size),
171 (Some(size), None) => Some(size),
172 (Some(max_cache_size), Some(max_file_size)) => Some(max_cache_size.min(max_file_size)),
173 }
174 }
175
176 pub fn exceeds_max_file_size(&self, size: u64) -> bool {
183 self.computed_max_file_size().is_some_and(|max_size| size > max_size)
184 }
185
186 pub fn has_content_expired(
195 &self,
196 current_time: SystemTime,
197 last_access_time: SystemTime,
198 ) -> bool {
199 self.last_access_expiry.is_some_and(|max_duration| {
200 current_time
201 .duration_since(last_access_time)
202 .is_ok_and(|elapsed| elapsed >= max_duration)
205 })
206 }
207
208 pub fn should_clean_up(&self, current_time: SystemTime, last_cleanup_time: SystemTime) -> bool {
217 self.cleanup_frequency.is_some_and(|max_duration| {
218 current_time
219 .duration_since(last_cleanup_time)
220 .is_ok_and(|elapsed| elapsed >= max_duration)
223 })
224 }
225}
226
227impl Default for MediaRetentionPolicy {
228 fn default() -> Self {
229 Self {
230 max_cache_size: Some(400 * 1024 * 1024),
232 max_file_size: Some(20 * 1024 * 1024),
234 last_access_expiry: Some(Duration::from_secs(60 * 24 * 60 * 60)),
236 cleanup_frequency: Some(Duration::from_secs(24 * 60 * 60)),
238 }
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use ruma::time::{Duration, SystemTime};
245
246 use super::MediaRetentionPolicy;
247
248 #[test]
249 fn test_media_retention_policy_has_limitations() {
250 let mut policy = MediaRetentionPolicy::empty();
251 assert!(!policy.has_limitations());
252
253 policy = policy.with_last_access_expiry(Some(Duration::from_secs(60)));
254 assert!(policy.has_limitations());
255
256 policy = policy.with_last_access_expiry(None);
257 assert!(!policy.has_limitations());
258
259 policy = policy.with_max_cache_size(Some(1_024));
260 assert!(policy.has_limitations());
261
262 policy = policy.with_max_cache_size(None);
263 assert!(!policy.has_limitations());
264
265 policy = policy.with_max_file_size(Some(1_024));
266 assert!(policy.has_limitations());
267
268 policy = policy.with_max_file_size(None);
269 assert!(!policy.has_limitations());
270
271 assert!(MediaRetentionPolicy::new().has_limitations());
273 }
274
275 #[test]
276 fn test_media_retention_policy_max_cache_size() {
277 let file_size = 2_048;
278
279 let mut policy = MediaRetentionPolicy::empty();
280 assert!(!policy.exceeds_max_cache_size(file_size));
281 assert_eq!(policy.computed_max_file_size(), None);
282 assert!(!policy.exceeds_max_file_size(file_size));
283
284 policy = policy.with_max_cache_size(Some(4_096));
285 assert!(!policy.exceeds_max_cache_size(file_size));
286 assert_eq!(policy.computed_max_file_size(), Some(4_096));
287 assert!(!policy.exceeds_max_file_size(file_size));
288
289 policy = policy.with_max_cache_size(Some(2_048));
290 assert!(!policy.exceeds_max_cache_size(file_size));
291 assert_eq!(policy.computed_max_file_size(), Some(2_048));
292 assert!(!policy.exceeds_max_file_size(file_size));
293
294 policy = policy.with_max_cache_size(Some(1_024));
295 assert!(policy.exceeds_max_cache_size(file_size));
296 assert_eq!(policy.computed_max_file_size(), Some(1_024));
297 assert!(policy.exceeds_max_file_size(file_size));
298 }
299
300 #[test]
301 fn test_media_retention_policy_max_file_size() {
302 let file_size = 2_048;
303
304 let mut policy = MediaRetentionPolicy::empty();
305 assert_eq!(policy.computed_max_file_size(), None);
306 assert!(!policy.exceeds_max_file_size(file_size));
307
308 policy = policy.with_max_file_size(Some(4_096));
310 assert_eq!(policy.computed_max_file_size(), Some(4_096));
311 assert!(!policy.exceeds_max_file_size(file_size));
312
313 policy = policy.with_max_file_size(Some(2_048));
314 assert_eq!(policy.computed_max_file_size(), Some(2_048));
315 assert!(!policy.exceeds_max_file_size(file_size));
316
317 policy = policy.with_max_file_size(Some(1_024));
318 assert_eq!(policy.computed_max_file_size(), Some(1_024));
319 assert!(policy.exceeds_max_file_size(file_size));
320
321 policy = policy.with_max_cache_size(Some(2_048));
323 assert_eq!(policy.computed_max_file_size(), Some(1_024));
324 assert!(policy.exceeds_max_file_size(file_size));
325
326 policy = policy.with_max_file_size(Some(2_048));
327 assert_eq!(policy.computed_max_file_size(), Some(2_048));
328 assert!(!policy.exceeds_max_file_size(file_size));
329
330 policy = policy.with_max_file_size(Some(4_096));
331 assert_eq!(policy.computed_max_file_size(), Some(2_048));
332 assert!(!policy.exceeds_max_file_size(file_size));
333
334 policy = policy.with_max_cache_size(Some(1_024));
335 assert_eq!(policy.computed_max_file_size(), Some(1_024));
336 assert!(policy.exceeds_max_file_size(file_size));
337 }
338
339 #[test]
340 fn test_media_retention_policy_has_content_expired() {
341 let epoch = SystemTime::UNIX_EPOCH;
342 let last_access_time = epoch + Duration::from_secs(30);
343 let epoch_plus_60 = epoch + Duration::from_secs(60);
344 let epoch_plus_120 = epoch + Duration::from_secs(120);
345
346 let mut policy = MediaRetentionPolicy::empty();
347 assert!(!policy.has_content_expired(epoch, last_access_time));
348 assert!(!policy.has_content_expired(last_access_time, last_access_time));
349 assert!(!policy.has_content_expired(epoch_plus_60, last_access_time));
350 assert!(!policy.has_content_expired(epoch_plus_120, last_access_time));
351
352 policy = policy.with_last_access_expiry(Some(Duration::from_secs(120)));
353 assert!(!policy.has_content_expired(epoch, last_access_time));
354 assert!(!policy.has_content_expired(last_access_time, last_access_time));
355 assert!(!policy.has_content_expired(epoch_plus_60, last_access_time));
356 assert!(!policy.has_content_expired(epoch_plus_120, last_access_time));
357
358 policy = policy.with_last_access_expiry(Some(Duration::from_secs(60)));
359 assert!(!policy.has_content_expired(epoch, last_access_time));
360 assert!(!policy.has_content_expired(last_access_time, last_access_time));
361 assert!(!policy.has_content_expired(epoch_plus_60, last_access_time));
362 assert!(policy.has_content_expired(epoch_plus_120, last_access_time));
363
364 policy = policy.with_last_access_expiry(Some(Duration::from_secs(30)));
365 assert!(!policy.has_content_expired(epoch, last_access_time));
366 assert!(!policy.has_content_expired(last_access_time, last_access_time));
367 assert!(policy.has_content_expired(epoch_plus_60, last_access_time));
368 assert!(policy.has_content_expired(epoch_plus_120, last_access_time));
369
370 policy = policy.with_last_access_expiry(Some(Duration::from_secs(0)));
371 assert!(!policy.has_content_expired(epoch, last_access_time));
372 assert!(policy.has_content_expired(last_access_time, last_access_time));
373 assert!(policy.has_content_expired(epoch_plus_60, last_access_time));
374 assert!(policy.has_content_expired(epoch_plus_120, last_access_time));
375 }
376
377 #[test]
378 fn test_media_retention_policy_cleanup_frequency() {
379 let epoch = SystemTime::UNIX_EPOCH;
380 let epoch_plus_60 = epoch + Duration::from_secs(60);
381 let epoch_plus_120 = epoch + Duration::from_secs(120);
382
383 let mut policy = MediaRetentionPolicy::empty();
384 assert!(!policy.should_clean_up(epoch_plus_60, epoch));
385 assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_60));
386 assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_120));
387
388 policy = policy.with_cleanup_frequency(Some(Duration::from_secs(0)));
389 assert!(policy.should_clean_up(epoch_plus_60, epoch));
390 assert!(policy.should_clean_up(epoch_plus_60, epoch_plus_60));
391 assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_120));
392
393 policy = policy.with_cleanup_frequency(Some(Duration::from_secs(30)));
394 assert!(policy.should_clean_up(epoch_plus_60, epoch));
395 assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_60));
396 assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_120));
397
398 policy = policy.with_cleanup_frequency(Some(Duration::from_secs(60)));
399 assert!(policy.should_clean_up(epoch_plus_60, epoch));
400 assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_60));
401 assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_120));
402
403 policy = policy.with_cleanup_frequency(Some(Duration::from_secs(90)));
404 assert!(!policy.should_clean_up(epoch_plus_60, epoch));
405 assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_60));
406 assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_120));
407 }
408}