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