diff --git a/crates/matrix-sdk-base/src/event_cache/store/media/integration_tests.rs b/crates/matrix-sdk-base/src/event_cache/store/media/integration_tests.rs index 647c0096d..986c3bdaf 100644 --- a/crates/matrix-sdk-base/src/event_cache/store/media/integration_tests.rs +++ b/crates/matrix-sdk-base/src/event_cache/store/media/integration_tests.rs @@ -53,6 +53,9 @@ pub trait EventCacheStoreMediaIntegrationTests { /// Test [`IgnoreMediaRetentionPolicy`] with the media content's retention /// policy expiry. async fn test_media_ignore_expiry(&self); + + /// Test last media cleanup time storage. + async fn test_store_last_media_cleanup_time(&self); } #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] @@ -941,6 +944,25 @@ where let stored = self.get_media_content_inner(&request_5, time).await.unwrap(); assert!(stored.is_none()); } + + async fn test_store_last_media_cleanup_time(&self) { + let initial = self.last_media_cleanup_time_inner().await.unwrap(); + let new_time = initial.unwrap_or_else(SystemTime::now) + Duration::from_secs(60); + + // With an empty policy. + let policy = MediaRetentionPolicy::empty(); + self.clean_up_media_cache_inner(policy, new_time).await.unwrap(); + + let stored = self.last_media_cleanup_time_inner().await.unwrap(); + assert_eq!(stored, initial); + + // With the default policy. + let policy = MediaRetentionPolicy::default(); + self.clean_up_media_cache_inner(policy, new_time).await.unwrap(); + + let stored = self.last_media_cleanup_time_inner().await.unwrap(); + assert_eq!(stored, Some(new_time)); + } } /// Macro building to allow your [`EventCacheStoreMedia`] implementation to run @@ -1031,5 +1053,11 @@ macro_rules! event_cache_store_media_integration_tests { let event_cache_store_media = get_event_cache_store().await.unwrap(); event_cache_store_media.test_media_ignore_expiry().await; } + + #[async_test] + async fn test_store_last_media_cleanup_time() { + let event_cache_store_media = get_event_cache_store().await.unwrap(); + event_cache_store_media.test_store_last_media_cleanup_time().await; + } }; } diff --git a/crates/matrix-sdk-base/src/event_cache/store/media/media_service.rs b/crates/matrix-sdk-base/src/event_cache/store/media/media_service.rs index 722c0c5ec..3568b3fe0 100644 --- a/crates/matrix-sdk-base/src/event_cache/store/media/media_service.rs +++ b/crates/matrix-sdk-base/src/event_cache/store/media/media_service.rs @@ -340,12 +340,15 @@ pub trait EventCacheStoreMedia: AsyncTraitDeps { /// `cleanup_frequency` will be ignored. /// /// * `current_time` - The current time, to be used to check for expired - /// content. + /// content and to be stored as the time of the last media cache cleanup. async fn clean_up_media_cache_inner( &self, policy: MediaRetentionPolicy, current_time: SystemTime, ) -> Result<(), Self::Error>; + + /// The time of the last media cache cleanup. + async fn last_media_cleanup_time_inner(&self) -> Result, Self::Error>; } /// Whether the [`MediaRetentionPolicy`] should be ignored for the current @@ -617,6 +620,10 @@ mod tests { Ok(()) } + + async fn last_media_cleanup_time_inner(&self) -> Result, Self::Error> { + Ok(self.inner().cleanup_time) + } } #[derive(Debug)] @@ -712,12 +719,12 @@ mod tests { assert!(media_content.ignore_policy); // Try a cleanup. With the empty policy the store should not be accessed. - assert_eq!(store.inner().cleanup_time, None); + assert_eq!(store.last_media_cleanup_time_inner().await.unwrap(), None); store.reset_accessed(); service.clean_up_media_cache(&store).await.unwrap(); assert!(!store.accessed()); - assert_eq!(store.inner().cleanup_time, None); + assert_eq!(store.last_media_cleanup_time_inner().await.unwrap(), None); } #[async_test] @@ -871,7 +878,7 @@ mod tests { assert_eq!(media.last_access, now); // Try a cleanup, the store should be accessed. - assert_eq!(store.inner().cleanup_time, None); + assert_eq!(store.last_media_cleanup_time_inner().await.unwrap(), None); let now = now + Duration::from_secs(60); service.time_provider.set_now(now); @@ -879,6 +886,6 @@ mod tests { service.clean_up_media_cache(&store).await.unwrap(); assert!(store.accessed()); - assert_eq!(store.inner().cleanup_time, Some(now)); + assert_eq!(store.last_media_cleanup_time_inner().await.unwrap(), Some(now)); } } diff --git a/crates/matrix-sdk-base/src/event_cache/store/memory_store.rs b/crates/matrix-sdk-base/src/event_cache/store/memory_store.rs index f3a4a0260..b1a6b08a5 100644 --- a/crates/matrix-sdk-base/src/event_cache/store/memory_store.rs +++ b/crates/matrix-sdk-base/src/event_cache/store/memory_store.rs @@ -50,6 +50,7 @@ struct MemoryStoreInner { leases: HashMap, events: RelationalLinkedChunk, media_retention_policy: Option, + last_media_cleanup_time: SystemTime, } /// A media content in the `MemoryStore`. @@ -82,6 +83,8 @@ impl Default for MemoryStore { leases: Default::default(), events: RelationalLinkedChunk::new(), media_retention_policy: None, + // Given that the store is empty, we won't need to clean it up right away. + last_media_cleanup_time: SystemTime::now(), }), // No need to call `restore()` since nothing is persisted. media_service: MediaService::new(), @@ -431,8 +434,14 @@ impl EventCacheStoreMedia for MemoryStore { } } + inner.last_media_cleanup_time = current_time; + Ok(()) } + + async fn last_media_cleanup_time_inner(&self) -> Result, Self::Error> { + Ok(Some(self.inner.read().unwrap().last_media_cleanup_time)) + } } #[cfg(test)] diff --git a/crates/matrix-sdk-sqlite/src/event_cache_store.rs b/crates/matrix-sdk-sqlite/src/event_cache_store.rs index b442d7752..211c73e56 100644 --- a/crates/matrix-sdk-sqlite/src/event_cache_store.rs +++ b/crates/matrix-sdk-sqlite/src/event_cache_store.rs @@ -52,6 +52,7 @@ use crate::{ mod keys { // Entries in Key-value store pub const MEDIA_RETENTION_POLICY: &str = "media_retention_policy"; + pub const LAST_MEDIA_CLEANUP_TIME: &str = "last_media_cleanup_time"; // Tables pub const LINKED_CHUNKS: &str = "linked_chunks"; @@ -948,6 +949,8 @@ impl EventCacheStoreMedia for SqliteEventCacheStore { } } + txn.set_serialized_kv(keys::LAST_MEDIA_CLEANUP_TIME, current_time)?; + Ok(removed) }) .await?; @@ -969,6 +972,11 @@ impl EventCacheStoreMedia for SqliteEventCacheStore { Ok(()) } + + async fn last_media_cleanup_time_inner(&self) -> Result, Self::Error> { + let conn = self.acquire().await?; + conn.get_serialized_kv(keys::LAST_MEDIA_CLEANUP_TIME).await + } } /// Like `deadpool::managed::Object::with_transaction`, but starts the