From 6ff186b744d876eefac544c8eb53894e50ea015b Mon Sep 17 00:00:00 2001 From: Michael Goldenberg Date: Fri, 17 Oct 2025 09:59:51 -0400 Subject: [PATCH] refactor(indexeddb): add indexed types and keys for media metadata Signed-off-by: Michael Goldenberg --- .../media_store/serializer/indexed_types.rs | 320 +++++++++++++++++- 1 file changed, 315 insertions(+), 5 deletions(-) diff --git a/crates/matrix-sdk-indexeddb/src/media_store/serializer/indexed_types.rs b/crates/matrix-sdk-indexeddb/src/media_store/serializer/indexed_types.rs index 45e062ea8..0a6702773 100644 --- a/crates/matrix-sdk-indexeddb/src/media_store/serializer/indexed_types.rs +++ b/crates/matrix-sdk-indexeddb/src/media_store/serializer/indexed_types.rs @@ -48,7 +48,7 @@ use crate::{ }, foreign::{ignore_media_retention_policy, unix_time}, }, - types::{Lease, Media, MediaCleanupTime, MediaContent, UnixTime}, + types::{Lease, Media, MediaCleanupTime, MediaContent, MediaMetadata, UnixTime}, }, serializer::{ indexed_type::constants::{INDEXED_KEY_LOWER_U64, INDEXED_KEY_UPPER_U64}, @@ -74,10 +74,8 @@ pub type IndexedMediaRetentionPolicyContent = MaybeEncrypted; /// cleaned - i.e., as a [`UnixTime`] pub type IndexedMediaCleanupTimeContent = MaybeEncrypted; -/// A (possibly) encrypted representation of a [`MediaMetadata`][1] -/// -/// [1]: crate::media_store::types::MediaMetadata -pub type IndexedMediaMetadata = MaybeEncrypted; +/// A (possibly) encrypted representation of a [`MediaMetadata`] +pub type IndexedMediaMetadataContent = MaybeEncrypted; /// A representation of the size in bytes of the [`IndexedMediaContent`] which /// is suitable for use in an IndexedDB key @@ -579,6 +577,318 @@ impl<'a> IndexedPrefixKeyComponentBounds<'a, Media, IgnoreMediaRetentionPolicy> } } +/// Represents the [`MEDIA_METADATA`][1] object store. +/// +/// [1]: crate::media_store::migrations::v1::create_media_metadata_object_store +#[derive(Debug, Serialize, Deserialize)] +pub struct IndexedMediaMetadata { + /// The primary key of the object store + pub id: IndexedMediaMetadataIdKey, + /// The (possibly) hashed [`MxcUri`] of the media derived from + /// [`MediaRequestParameters::uri`] + pub uri: IndexedMediaMetadataUriKey, + /// The size (in bytes) of the media content and whether to ignore the + /// [`MediaRetentionPolicy`] + pub content_size: IndexedMediaMetadataContentSizeKey, + /// The last time the media was accessed and whether to ignore the + /// [`MediaRetentionPolicy`] + pub last_access: IndexedMediaMetadataLastAccessKey, + /// The last the media was accessed, the size (in bytes) of the media + /// content, and whether to ignore the [`MediaRetentionPolicy`] + pub retention: IndexedMediaMetadataRetentionKey, + /// The (possibly) encrypted content - i.e., [`MediaMetadata`] + pub content: IndexedMediaMetadataContent, +} + +impl Indexed for MediaMetadata { + const OBJECT_STORE: &'static str = keys::MEDIA_METADATA; + + type IndexedType = IndexedMediaMetadata; + type Error = IndexedMediaError; + + fn to_indexed( + &self, + serializer: &SafeEncodeSerializer, + ) -> Result { + Ok(Self::IndexedType { + id: >::encode( + &self.request_parameters, + serializer, + ), + uri: >::encode( + self.request_parameters.uri(), + serializer, + ), + content_size: IndexedMediaMetadataContentSizeKey::encode( + (self.ignore_policy, self.content_size), + serializer, + ), + last_access: IndexedMediaMetadataLastAccessKey::encode( + (self.ignore_policy, self.last_access), + serializer, + ), + retention: IndexedMediaMetadataRetentionKey::encode( + (self.ignore_policy, self.last_access, self.content_size), + serializer, + ), + content: serializer.maybe_encrypt_value(self)?, + }) + } + + fn from_indexed( + indexed: Self::IndexedType, + serializer: &SafeEncodeSerializer, + ) -> Result { + Ok(serializer.maybe_decrypt_value(indexed.content)?) + } +} + +/// The primary key of the [`MEDIA_METADATA`][1] object store, which is +/// constructed from: +/// +/// - The (possibly) hashed value returned by +/// [`MediaRequestParameters::unique_key`] +/// +/// [1]: crate::media_store::migrations::v1::create_media_metadata_object_store +#[derive(Debug, Serialize, Deserialize)] +pub struct IndexedMediaMetadataIdKey(String); + +impl IndexedKey for IndexedMediaMetadataIdKey { + type KeyComponents<'a> = &'a MediaRequestParameters; + + fn encode(components: Self::KeyComponents<'_>, serializer: &SafeEncodeSerializer) -> Self { + Self(serializer.encode_key_as_string(keys::MEDIA_METADATA, components.unique_key())) + } +} + +/// The value associated with the [`ur`](IndexedMediaMetadata::uri) index of the +/// [`MEDIA_METADATA`][1] object store, which is constructed from: +/// +/// - The (possibly) hashed [`MxcUri`] returned by +/// [`MediaRequestParameters::uri`] +/// +/// [1]: crate::media_store::migrations::v1::create_media_metadata_object_store +#[derive(Debug, Serialize, Deserialize)] +pub struct IndexedMediaMetadataUriKey(String); + +impl IndexedKey for IndexedMediaMetadataUriKey { + const INDEX: Option<&'static str> = Some(keys::MEDIA_METADATA_URI); + + type KeyComponents<'a> = &'a MxcUri; + + fn encode(components: Self::KeyComponents<'_>, serializer: &SafeEncodeSerializer) -> Self { + Self(serializer.encode_key_as_string(keys::MEDIA_METADATA_URI, components)) + } +} + +/// The value associated with the +/// [`content_size`](IndexedMediaMetadata::content_size) index of the +/// [`MEDIA_METADATA`][1] object store, which is constructed from: +/// +/// - The value of [`IgnoreMediaRetentionPolicy`] +/// - The size in bytes of the associated [`IndexedMediaMetadata::content`] +/// +/// [1]: crate::media_store::migrations::v1::create_media_metadata_object_store +#[derive(Debug, Serialize, Deserialize)] +pub struct IndexedMediaMetadataContentSizeKey( + #[serde(with = "ignore_media_retention_policy")] IgnoreMediaRetentionPolicy, + IndexedMediaContentSize, +); + +impl IndexedMediaMetadataContentSizeKey { + /// Returns the [`IgnoreMediaRetentionPolicy`] value of the associated + /// [`IndexedMediaMetadata`] + pub fn ignore_policy(&self) -> IgnoreMediaRetentionPolicy { + self.0 + } + + /// Returns the size in bytes of the associated [`IndexedMediaContent`] + pub fn content_size(&self) -> usize { + self.1 + } +} + +impl IndexedKey for IndexedMediaMetadataContentSizeKey { + const INDEX: Option<&'static str> = Some(keys::MEDIA_METADATA_CONTENT_SIZE); + + type KeyComponents<'a> = (IgnoreMediaRetentionPolicy, IndexedMediaContentSize); + + fn encode( + (ignore_policy, content_size): Self::KeyComponents<'_>, + _: &SafeEncodeSerializer, + ) -> Self { + Self(ignore_policy, content_size) + } +} + +impl IndexedKeyComponentBounds for IndexedMediaMetadataContentSizeKey { + 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, MediaMetadata, IgnoreMediaRetentionPolicy> + for IndexedMediaMetadataContentSizeKey +{ + fn lower_key_components_with_prefix( + prefix: IgnoreMediaRetentionPolicy, + ) -> Self::KeyComponents<'a> { + (prefix, INDEXED_KEY_LOWER_MEDIA_CONTENT_SIZE) + } + + fn upper_key_components_with_prefix( + prefix: IgnoreMediaRetentionPolicy, + ) -> Self::KeyComponents<'a> { + (prefix, INDEXED_KEY_UPPER_MEDIA_CONTENT_SIZE) + } +} + +/// The value associated with the +/// [`last_access`](IndexedMediaMetadata::last_access) index of the +/// [`MEDIA_METADATA`][1] object store, which is constructed from: +/// +/// - The value of [`IgnoreMediaRetentionPolicy`] +/// - The last time the associated [`IndexedMediaContent`] was accessed, +/// represented as a [`UnixTime`] +/// +/// [1]: crate::media_store::migrations::v1::create_media_metadata_object_store +#[derive(Debug, Serialize, Deserialize)] +pub struct IndexedMediaMetadataLastAccessKey( + #[serde(with = "ignore_media_retention_policy")] IgnoreMediaRetentionPolicy, + #[serde(with = "unix_time")] UnixTime, +); + +impl IndexedMediaMetadataLastAccessKey { + /// Returns the [`IgnoreMediaRetentionPolicy`] value of the associated + /// [`IndexedMedia`] + pub fn ignore_policy(&self) -> IgnoreMediaRetentionPolicy { + self.0 + } + + /// Returns the last time the associated [`IndexedMediaContent`] was + /// accessed as a [`UnixTime`] + pub fn last_access(&self) -> UnixTime { + self.1 + } +} + +impl IndexedKey for IndexedMediaMetadataLastAccessKey { + const INDEX: Option<&'static str> = Some(keys::MEDIA_METADATA_LAST_ACCESS); + + type KeyComponents<'a> = (IgnoreMediaRetentionPolicy, UnixTime); + + fn encode( + (ignore_policy, last_access): Self::KeyComponents<'_>, + _: &SafeEncodeSerializer, + ) -> Self { + Self(ignore_policy, last_access) + } +} + +impl IndexedKeyComponentBounds for IndexedMediaMetadataLastAccessKey { + 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, MediaMetadata, IgnoreMediaRetentionPolicy> + for IndexedMediaMetadataLastAccessKey +{ + fn lower_key_components_with_prefix( + prefix: IgnoreMediaRetentionPolicy, + ) -> Self::KeyComponents<'a> { + (prefix, INDEXED_KEY_LOWER_UNIX_TIME) + } + + fn upper_key_components_with_prefix( + prefix: IgnoreMediaRetentionPolicy, + ) -> Self::KeyComponents<'a> { + (prefix, INDEXED_KEY_UPPER_UNIX_TIME) + } +} + +/// The value associated with the [`retention`](IndexedMediaMetadata::retention) +/// index of the [`MEDIA_METADATA`][1] object store, which is constructed from: +/// +/// - The value of [`IgnoreMediaRetentionPolicy`] +/// - The last time the associated [`IndexedMediaContent`] was accessed, +/// represented as a [`UnixTime`] +/// - The size in bytes of the associated [`IndexedMediaContent`] +/// +/// [1]: crate::media_store::migrations::v1::create_media_metadata_object_store +#[derive(Debug, Serialize, Deserialize)] +pub struct IndexedMediaMetadataRetentionKey( + #[serde(with = "ignore_media_retention_policy")] IgnoreMediaRetentionPolicy, + #[serde(with = "unix_time")] UnixTime, + IndexedMediaContentSize, +); + +impl IndexedMediaMetadataRetentionKey { + /// Returns the [`IgnoreMediaRetentionPolicy`] value of the associated + /// [`IndexedMedia`] + pub fn ignore_policy(&self) -> IgnoreMediaRetentionPolicy { + self.0 + } + + /// Returns the last time the associated [`IndexedMediaContent`] was + /// accessed as a [`UnixTime`] + pub fn last_access(&self) -> UnixTime { + self.1 + } + + /// Returns the size in bytes of the associated [`IndexedMediaContent`] + pub fn content_size(&self) -> usize { + self.2 + } +} + +impl IndexedKey for IndexedMediaMetadataRetentionKey { + const INDEX: Option<&'static str> = Some(keys::MEDIA_METADATA_RETENTION); + + type KeyComponents<'a> = (IgnoreMediaRetentionPolicy, UnixTime, IndexedMediaContentSize); + + fn encode( + (ignore_policy, last_access, content_size): Self::KeyComponents<'_>, + _: &SafeEncodeSerializer, + ) -> Self { + Self(ignore_policy, last_access, content_size) + } +} + +impl IndexedKeyComponentBounds for IndexedMediaMetadataRetentionKey { + 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, MediaMetadata, IgnoreMediaRetentionPolicy> + for IndexedMediaMetadataRetentionKey +{ + fn lower_key_components_with_prefix( + prefix: IgnoreMediaRetentionPolicy, + ) -> Self::KeyComponents<'a> { + (prefix, INDEXED_KEY_LOWER_UNIX_TIME, INDEXED_KEY_LOWER_MEDIA_CONTENT_SIZE) + } + + fn upper_key_components_with_prefix( + prefix: IgnoreMediaRetentionPolicy, + ) -> Self::KeyComponents<'a> { + (prefix, INDEXED_KEY_UPPER_UNIX_TIME, INDEXED_KEY_UPPER_MEDIA_CONTENT_SIZE) + } +} + /// Represents the [`MEDIA_CONTENT`][1] object store. /// /// [1]: crate::media_store::migrations::v1::create_media_content_object_store