From 2b5c94cf9a53ceed0c67861461beea243507ef56 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Tue, 18 Nov 2025 08:53:45 +0100 Subject: [PATCH] Add media retention policy (#5749) * Add media retention policy. Add `ByteSize` class to help with conversions between byte units. * Use bit shifting instead of multiplication Improve the tests too --- .../android/libraries/core/data/ByteSize.kt | 27 ++++++++++ .../libraries/core/data/ByteSizeTest.kt | 49 +++++++++++++++++++ .../matrix/impl/RustMatrixClientFactory.kt | 18 +++++++ 3 files changed, 94 insertions(+) create mode 100644 libraries/core/src/main/kotlin/io/element/android/libraries/core/data/ByteSize.kt create mode 100644 libraries/core/src/test/kotlin/io/element/android/libraries/core/data/ByteSizeTest.kt diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/data/ByteSize.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/data/ByteSize.kt new file mode 100644 index 0000000000..fe72866d7e --- /dev/null +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/data/ByteSize.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.core.data + +enum class ByteUnit(val bitShift: Int) { + BYTES(0), + KB(10), + MB(20), + GB(30) +} + +class ByteSize internal constructor(val value: Long, val unit: ByteUnit) { + fun to(dest: ByteUnit): Long { + if (unit == dest) return value + return value shl unit.bitShift shr dest.bitShift + } +} + +val Number.gigaBytes get() = ByteSize(toLong(), ByteUnit.GB) +val Number.megaBytes get() = ByteSize(toLong(), ByteUnit.MB) +val Number.kiloBytes get() = ByteSize(toLong(), ByteUnit.KB) +val Number.bytes get() = ByteSize(toLong(), ByteUnit.BYTES) diff --git a/libraries/core/src/test/kotlin/io/element/android/libraries/core/data/ByteSizeTest.kt b/libraries/core/src/test/kotlin/io/element/android/libraries/core/data/ByteSizeTest.kt new file mode 100644 index 0000000000..0d1ec87b63 --- /dev/null +++ b/libraries/core/src/test/kotlin/io/element/android/libraries/core/data/ByteSizeTest.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.core.data + +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +class ByteSizeTest { + @Test + fun testSizeConversions() { + // Check bytes to other units + val bytes = 10_000_000.bytes + assertThat(bytes.to(ByteUnit.BYTES)).isEqualTo(bytes.value) + assertThat(bytes.to(ByteUnit.KB)).isEqualTo(bytes.value / 1024L) + assertThat(bytes.to(ByteUnit.MB)).isEqualTo(bytes.value / 1024L / 1024L) + assertThat(bytes.to(ByteUnit.GB)).isEqualTo(bytes.value / 1024L / 1024L / 1024L) + + // Now check for values too small to be converted + assertThat(100.bytes.to(ByteUnit.KB)).isEqualTo(0) + assertThat(100.bytes.to(ByteUnit.MB)).isEqualTo(0) + assertThat(100.bytes.to(ByteUnit.GB)).isEqualTo(0) + + // Check for KBs + val kiloBytes = 10_000.kiloBytes + assertThat(kiloBytes.to(ByteUnit.BYTES)).isEqualTo(kiloBytes.value * 1024L) + assertThat(kiloBytes.to(ByteUnit.KB)).isEqualTo(kiloBytes.value) + assertThat(kiloBytes.to(ByteUnit.MB)).isEqualTo(kiloBytes.value / 1024L) + assertThat(kiloBytes.to(ByteUnit.GB)).isEqualTo(kiloBytes.value / 1024L / 1024L) + + // Check for MBs + val megaBytes = 10_000.megaBytes + assertThat(megaBytes.to(ByteUnit.BYTES)).isEqualTo(megaBytes.value * 1024L * 1024L) + assertThat(megaBytes.to(ByteUnit.KB)).isEqualTo(megaBytes.value * 1024L) + assertThat(megaBytes.to(ByteUnit.MB)).isEqualTo(megaBytes.value) + assertThat(megaBytes.to(ByteUnit.GB)).isEqualTo(megaBytes.value / 1024L) + + // Check for GBs + val gigaBytes = 10.gigaBytes + assertThat(gigaBytes.to(ByteUnit.BYTES)).isEqualTo(gigaBytes.value * 1024L * 1024L * 1024L) + assertThat(gigaBytes.to(ByteUnit.KB)).isEqualTo(gigaBytes.value * 1024L * 1024L) + assertThat(gigaBytes.to(ByteUnit.MB)).isEqualTo(gigaBytes.value * 1024L) + assertThat(gigaBytes.to(ByteUnit.GB)).isEqualTo(gigaBytes.value) + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt index f6ee30a223..f0b2b54b43 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt @@ -10,6 +10,8 @@ package io.element.android.libraries.matrix.impl import dev.zacsweers.metro.Inject import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.core.data.ByteUnit +import io.element.android.libraries.core.data.megaBytes import io.element.android.libraries.di.CacheDirectory import io.element.android.libraries.di.annotations.AppCoroutineScope import io.element.android.libraries.featureflag.api.FeatureFlagService @@ -37,10 +39,13 @@ import org.matrix.rustcomponents.sdk.SlidingSyncVersionBuilder import org.matrix.rustcomponents.sdk.SqliteStoreBuilder import org.matrix.rustcomponents.sdk.use import timber.log.Timber +import uniffi.matrix_sdk_base.MediaRetentionPolicy import uniffi.matrix_sdk_crypto.CollectStrategy import uniffi.matrix_sdk_crypto.DecryptionSettings import uniffi.matrix_sdk_crypto.TrustRequirement import java.io.File +import kotlin.time.Duration.Companion.days +import kotlin.time.toJavaDuration @Inject class RustMatrixClientFactory( @@ -70,6 +75,19 @@ class RustMatrixClientFactory( .username(sessionData.userId) .use { it.build() } + client.setMediaRetentionPolicy( + MediaRetentionPolicy( + // Make this 500MB instead of 400MB + maxCacheSize = 500.megaBytes.to(ByteUnit.BYTES).toULong(), + // This is the default value, but let's make it explicit + maxFileSize = 20.megaBytes.to(ByteUnit.BYTES).toULong(), + // Use 30 days instead of 60 + lastAccessExpiry = 30.days.toJavaDuration(), + // This is the default value, but let's make it explicit + cleanupFrequency = 1.days.toJavaDuration(), + ) + ) + client.restoreSession(sessionData.toSession()) create(client)