diff --git a/crates/matrix-sdk-indexeddb/src/event_cache_store/migrations.rs b/crates/matrix-sdk-indexeddb/src/event_cache_store/migrations.rs index 95a121c14..f7163827b 100644 --- a/crates/matrix-sdk-indexeddb/src/event_cache_store/migrations.rs +++ b/crates/matrix-sdk-indexeddb/src/event_cache_store/migrations.rs @@ -139,6 +139,8 @@ pub mod v1 { pub const MEDIA_CONTENT_SIZE_KEY_PATH: &str = "content_size"; pub const MEDIA_LAST_ACCESS: &str = "media_last_access"; pub const MEDIA_LAST_ACCESS_KEY_PATH: &str = "last_access"; + pub const MEDIA_RETENTION_METADATA: &str = "media_retention_metadata"; + pub const MEDIA_RETENTION_METADATA_KEY_PATH: &str = "retention_metadata"; } /// Create all object stores and indices for v1 database @@ -237,6 +239,8 @@ pub mod v1 { /// whether to ignore the [`MediaRetentionPolicy`][2] /// * Index - `last_access` - tracks the last time the associated media was /// accessed + /// * Index - `retention_metadata` - tracks all retention metadata - i.e., + /// joins `content_size` and `last_access` /// /// [1]: ruma::events::room::MediaSource /// [2]: matrix_sdk_base::event_cache::store::media::MediaRetentionPolicy @@ -247,6 +251,10 @@ pub mod v1 { media.create_index(keys::MEDIA_SOURCE, &keys::MEDIA_SOURCE_KEY_PATH.into())?; media.create_index(keys::MEDIA_CONTENT_SIZE, &keys::MEDIA_CONTENT_SIZE_KEY_PATH.into())?; media.create_index(keys::MEDIA_LAST_ACCESS, &keys::MEDIA_LAST_ACCESS_KEY_PATH.into())?; + media.create_index( + keys::MEDIA_RETENTION_METADATA, + &keys::MEDIA_RETENTION_METADATA_KEY_PATH.into(), + )?; Ok(()) } } diff --git a/crates/matrix-sdk-indexeddb/src/event_cache_store/serializer/types.rs b/crates/matrix-sdk-indexeddb/src/event_cache_store/serializer/types.rs index eb86ac4d3..3542980a6 100644 --- a/crates/matrix-sdk-indexeddb/src/event_cache_store/serializer/types.rs +++ b/crates/matrix-sdk-indexeddb/src/event_cache_store/serializer/types.rs @@ -154,6 +154,23 @@ static INDEXED_KEY_UPPER_EVENT_POSITION: LazyLock = LazyLock::new(|| P index: INDEXED_KEY_UPPER_EVENT_INDEX, }); +/// An [`IndexedMediaContentSize`] set to it's minimal value - i.e., `0`. +/// +/// This value is useful for constructing a key range over all keys which +/// contain [`IndexedMediaContentSize`] values when used in conjunction with +/// [`INDEXED_KEY_UPPER_MEDIA_CONTENT_SIZE`]. +const INDEXED_KEY_LOWER_MEDIA_CONTENT_SIZE: IndexedMediaContentSize = 0; + +/// An [`IndexedMediaContentSize`] set to [`js_sys::Number::MAX_SAFE_INTEGER`]. +/// Note that this restricts the size of [`IndexedMedia::content`], which +/// ultimately restricts the size of [`Media::content`]. +/// +/// This value is useful for constructing a key range over all keys which +/// contain [`IndexedMediaContentSize`] values when used in conjunction with +/// [`INDEXED_KEY_LOWER_MEDIA_CONTENT_SIZE`]. +const INDEXED_KEY_UPPER_MEDIA_CONTENT_SIZE: IndexedMediaContentSize = + js_sys::Number::MAX_SAFE_INTEGER as usize; + /// The minimum possible [`Duration`]. /// /// This value is useful for constructing a key range over all keys which @@ -939,6 +956,9 @@ pub struct IndexedMedia { /// The last time the media was accessed and whether to ignore the /// [`MediaRetentionPolicy`] pub last_access: IndexedMediaLastAccessKey, + /// The last the media was accessed, the size (in bytes) of the media + /// content, and whether to ignore the [`MediaRetentionPolicy`] + pub retention_metadata: IndexedMediaRetentionMetadataKey, /// The (possibly) encrypted metadata - i.e., [`MediaMetadata`][1] /// /// [1]: crate::event_cache_store::types::MediaMetadata @@ -985,6 +1005,10 @@ impl Indexed for Media { (self.metadata.ignore_policy, self.metadata.last_access), serializer, ), + retention_metadata: IndexedMediaRetentionMetadataKey::encode( + (self.metadata.ignore_policy, self.metadata.last_access, content.len()), + serializer, + ), metadata: serializer.maybe_encrypt_value(&self.metadata)?, content, }) @@ -1088,13 +1112,13 @@ impl<'a> IndexedPrefixKeyComponentBounds<'a, Media, IgnoreMediaRetentionPolicy> fn lower_key_components_with_prefix( prefix: IgnoreMediaRetentionPolicy, ) -> Self::KeyComponents<'a> { - (prefix, IndexedMediaContentSize::MIN) + (prefix, INDEXED_KEY_LOWER_MEDIA_CONTENT_SIZE) } fn upper_key_components_with_prefix( prefix: IgnoreMediaRetentionPolicy, ) -> Self::KeyComponents<'a> { - (prefix, IndexedMediaContentSize::MAX) + (prefix, INDEXED_KEY_UPPER_MEDIA_CONTENT_SIZE) } } @@ -1148,3 +1172,57 @@ impl<'a> IndexedPrefixKeyComponentBounds<'a, Media, IgnoreMediaRetentionPolicy> (prefix, INDEXED_KEY_UPPER_DURATION_SECONDS) } } + +/// The value associated with the +/// [`retention_metadata`](IndexedMedia::retention_metadata) index of the +/// [`MEDIA`][1] object store, which is constructed from: +/// +/// - The value of [`IgnoreMediaRetentionPolicy`] +/// - The last time the associated [`IndexedMedia`] was accessed (in seconds +/// since the Unix Epoch) +/// - The size in bytes of the associated [`IndexedMedia::content`] +/// +/// [1]: crate::event_cache_store::migrations::v1::create_media_object_store +#[derive(Debug, Serialize, Deserialize)] +pub struct IndexedMediaRetentionMetadataKey( + #[serde(with = "ignore_media_retention_policy")] IgnoreMediaRetentionPolicy, + IndexedSecondsSinceUnixEpoch, + IndexedMediaContentSize, +); + +impl IndexedKey for IndexedMediaRetentionMetadataKey { + type KeyComponents<'a> = (IgnoreMediaRetentionPolicy, Duration, IndexedMediaContentSize); + + fn encode( + (ignore_policy, last_access, content_size): Self::KeyComponents<'_>, + _: &IndexeddbSerializer, + ) -> Self { + Self(ignore_policy, last_access.as_secs(), content_size) + } +} + +impl IndexedKeyComponentBounds for IndexedMediaRetentionMetadataKey { + fn lower_key_components() -> Self::KeyComponents<'static> { + Self::lower_key_components_with_prefix(IgnoreMediaRetentionPolicy::No) + } + + fn upper_key_components() -> Self::KeyComponents<'static> { + Self::lower_key_components_with_prefix(IgnoreMediaRetentionPolicy::Yes) + } +} + +impl<'a> IndexedPrefixKeyComponentBounds<'a, Media, IgnoreMediaRetentionPolicy> + for IndexedMediaRetentionMetadataKey +{ + fn lower_key_components_with_prefix( + prefix: IgnoreMediaRetentionPolicy, + ) -> Self::KeyComponents<'a> { + (prefix, INDEXED_KEY_LOWER_DURATION, INDEXED_KEY_LOWER_MEDIA_CONTENT_SIZE) + } + + fn upper_key_components_with_prefix( + prefix: IgnoreMediaRetentionPolicy, + ) -> Self::KeyComponents<'a> { + (prefix, INDEXED_KEY_UPPER_DURATION_SECONDS, INDEXED_KEY_UPPER_MEDIA_CONTENT_SIZE) + } +}