diff --git a/crates/matrix-sdk-base/src/event_cache/store/media/media_retention_policy.rs b/crates/matrix-sdk-base/src/event_cache/store/media/media_retention_policy.rs index b64a77af7..dc3e9f5ff 100644 --- a/crates/matrix-sdk-base/src/event_cache/store/media/media_retention_policy.rs +++ b/crates/matrix-sdk-base/src/event_cache/store/media/media_retention_policy.rs @@ -79,6 +79,17 @@ pub struct MediaRetentionPolicy { /// Defaults to 60 days. #[serde(default, skip_serializing_if = "Option::is_none")] pub last_access_expiry: Option, + + /// The duration between two automatic media cache cleanups. + /// + /// If this is set, a cleanup will be triggered after the given duration + /// is elapsed, at the next call to the media cache API. If this is set to + /// zero, each call to the media cache API will trigger a cleanup. If this + /// is `None`, cleanups will only occur if they are triggered manually. + /// + /// Defaults to running cleanups daily. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cleanup_frequency: Option, } impl MediaRetentionPolicy { @@ -91,7 +102,12 @@ impl MediaRetentionPolicy { /// /// This means that all media will be cached and cleanups have no effect. pub fn empty() -> Self { - Self { max_cache_size: None, max_file_size: None, last_access_expiry: None } + Self { + max_cache_size: None, + max_file_size: None, + last_access_expiry: None, + cleanup_frequency: None, + } } /// Set the maximum authorized size of the overall media cache, in bytes. @@ -113,6 +129,12 @@ impl MediaRetentionPolicy { self } + /// Set the duration between two automatic media cache cleanups. + pub fn with_cleanup_frequency(mut self, duration: Option) -> Self { + self.cleanup_frequency = duration; + self + } + /// Whether this policy has limitations. /// /// If this policy has no limitations, a cleanup job would have no effect. @@ -178,6 +200,24 @@ impl MediaRetentionPolicy { .is_ok_and(|elapsed| elapsed >= max_duration) }) } + + /// Whether an automatic media cache cleanup should be triggered given the + /// time of the last cleanup. + /// + /// # Arguments + /// + /// * `current_time` - The current time. + /// + /// * `last_cleanup_time` - The time of the last media cache cleanup. + pub fn should_clean_up(&self, current_time: SystemTime, last_cleanup_time: SystemTime) -> bool { + self.cleanup_frequency.is_some_and(|max_duration| { + current_time + .duration_since(last_cleanup_time) + // If this returns an error, the last cleanup time is newer than the current time. + // This shouldn't happen but in this case no cleanup job is needed. + .is_ok_and(|elapsed| elapsed >= max_duration) + }) + } } impl Default for MediaRetentionPolicy { @@ -189,6 +229,8 @@ impl Default for MediaRetentionPolicy { max_file_size: Some(20 * 1024 * 1024), // 60 days. last_access_expiry: Some(Duration::from_secs(60 * 24 * 60 * 60)), + // 1 day. + cleanup_frequency: Some(Duration::from_secs(24 * 60 * 60)), } } } @@ -327,4 +369,36 @@ mod tests { assert!(policy.has_content_expired(epoch_plus_60, last_access_time)); assert!(policy.has_content_expired(epoch_plus_120, last_access_time)); } + + #[test] + fn test_media_retention_policy_cleanup_frequency() { + let epoch = SystemTime::UNIX_EPOCH; + let epoch_plus_60 = epoch + Duration::from_secs(60); + let epoch_plus_120 = epoch + Duration::from_secs(120); + + let mut policy = MediaRetentionPolicy::empty(); + assert!(!policy.should_clean_up(epoch_plus_60, epoch)); + assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_60)); + assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_120)); + + policy = policy.with_cleanup_frequency(Some(Duration::from_secs(0))); + assert!(policy.should_clean_up(epoch_plus_60, epoch)); + assert!(policy.should_clean_up(epoch_plus_60, epoch_plus_60)); + assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_120)); + + policy = policy.with_cleanup_frequency(Some(Duration::from_secs(30))); + assert!(policy.should_clean_up(epoch_plus_60, epoch)); + assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_60)); + assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_120)); + + policy = policy.with_cleanup_frequency(Some(Duration::from_secs(60))); + assert!(policy.should_clean_up(epoch_plus_60, epoch)); + assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_60)); + assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_120)); + + policy = policy.with_cleanup_frequency(Some(Duration::from_secs(90))); + assert!(!policy.should_clean_up(epoch_plus_60, epoch)); + assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_60)); + assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_120)); + } }