Fix ANRs when calculating DB sizes for Sentry (#7017)

* Fix ANRs when calculating DB sizes for Sentry

Instead of using the SDK's `Client.getStoreSizes()` method, manually check the sizes using the file system.

For this, the `SessionPaths` need to be synchronously accessible from a `MatrixClient`. I removed `SessionPathsProvider` too since it's not needed anymore.
This commit is contained in:
Jorge Martin Espinosa
2026-06-16 16:49:09 +02:00
committed by GitHub
parent 8e9c0bedf2
commit ac93044f49
19 changed files with 88 additions and 101 deletions
@@ -27,6 +27,7 @@ import io.element.android.libraries.matrix.api.media.MediaPreviewService
import io.element.android.libraries.matrix.api.notification.NotificationService import io.element.android.libraries.matrix.api.notification.NotificationService
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
import io.element.android.libraries.matrix.api.oauth.AccountManagementAction import io.element.android.libraries.matrix.api.oauth.AccountManagementAction
import io.element.android.libraries.matrix.api.paths.SessionPaths
import io.element.android.libraries.matrix.api.pusher.PushersService import io.element.android.libraries.matrix.api.pusher.PushersService
import io.element.android.libraries.matrix.api.room.BaseRoom import io.element.android.libraries.matrix.api.room.BaseRoom
import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.JoinedRoom
@@ -53,6 +54,7 @@ import java.util.Optional
interface MatrixClient { interface MatrixClient {
val sessionId: SessionId val sessionId: SessionId
val deviceId: DeviceId val deviceId: DeviceId
val sessionPaths: SessionPaths
val userProfile: StateFlow<MatrixUser> val userProfile: StateFlow<MatrixUser>
val roomListService: RoomListService val roomListService: RoomListService
val spaceService: SpaceService val spaceService: SpaceService
@@ -10,5 +10,5 @@ package io.element.android.libraries.matrix.api.analytics
import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.SessionId
fun interface GetDatabaseSizesUseCase { fun interface GetDatabaseSizesUseCase {
suspend operator fun invoke(sessionId: SessionId): Result<SdkStoreSizes> operator fun invoke(sessionId: SessionId): Result<SdkStoreSizes>
} }
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2026 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.matrix.api.paths
import java.io.File
data class SessionPaths(
val fileDirectory: File,
val cacheDirectory: File,
) {
fun deleteRecursively() {
fileDirectory.deleteRecursively()
cacheDirectory.deleteRecursively()
}
}
@@ -32,6 +32,7 @@ import io.element.android.libraries.matrix.api.linknewdevice.LinkDesktopHandler
import io.element.android.libraries.matrix.api.linknewdevice.LinkMobileHandler import io.element.android.libraries.matrix.api.linknewdevice.LinkMobileHandler
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
import io.element.android.libraries.matrix.api.oauth.AccountManagementAction import io.element.android.libraries.matrix.api.oauth.AccountManagementAction
import io.element.android.libraries.matrix.api.paths.SessionPaths
import io.element.android.libraries.matrix.api.room.BaseRoom import io.element.android.libraries.matrix.api.room.BaseRoom
import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.JoinedRoom
@@ -81,7 +82,6 @@ import io.element.android.libraries.matrix.impl.spaces.RustSpaceService
import io.element.android.libraries.matrix.impl.sync.RustSyncService import io.element.android.libraries.matrix.impl.sync.RustSyncService
import io.element.android.libraries.matrix.impl.sync.map import io.element.android.libraries.matrix.impl.sync.map
import io.element.android.libraries.matrix.impl.usersearch.UserSearchResultMapper import io.element.android.libraries.matrix.impl.usersearch.UserSearchResultMapper
import io.element.android.libraries.matrix.impl.util.SessionPathsProvider
import io.element.android.libraries.matrix.impl.util.cancelAndDestroy import io.element.android.libraries.matrix.impl.util.cancelAndDestroy
import io.element.android.libraries.matrix.impl.util.mxCallbackFlow import io.element.android.libraries.matrix.impl.util.mxCallbackFlow
import io.element.android.libraries.matrix.impl.verification.RustSessionVerificationService import io.element.android.libraries.matrix.impl.verification.RustSessionVerificationService
@@ -137,6 +137,7 @@ import org.matrix.rustcomponents.sdk.RoomPreset as RustRoomPreset
import org.matrix.rustcomponents.sdk.SyncService as ClientSyncService import org.matrix.rustcomponents.sdk.SyncService as ClientSyncService
class RustMatrixClient( class RustMatrixClient(
override val sessionPaths: SessionPaths,
private val innerClient: Client, private val innerClient: Client,
private val sessionStore: SessionStore, private val sessionStore: SessionStore,
private val sessionDelegate: RustClientSessionDelegate, private val sessionDelegate: RustClientSessionDelegate,
@@ -187,8 +188,6 @@ class RustMatrixClient(
sessionDispatcher = sessionDispatcher, sessionDispatcher = sessionDispatcher,
) )
private val sessionPathsProvider = SessionPathsProvider(sessionStore)
private val roomSyncSubscriber: RoomSyncSubscriber = RoomSyncSubscriber(innerRoomListService, dispatchers) private val roomSyncSubscriber: RoomSyncSubscriber = RoomSyncSubscriber(innerRoomListService, dispatchers)
override val roomListService: RoomListService = RustRoomListService( override val roomListService: RoomListService = RustRoomListService(
@@ -824,17 +823,16 @@ class RustMatrixClient(
private suspend fun getCacheSize( private suspend fun getCacheSize(
includeCryptoDb: Boolean = false, includeCryptoDb: Boolean = false,
): Long = withContext(sessionDispatcher) { ): Long = withContext(sessionDispatcher) {
val sessionDirectory = sessionPathsProvider.provides(sessionId) ?: return@withContext 0L val cacheSize = sessionPaths.cacheDirectory.getSizeOfFiles()
val cacheSize = sessionDirectory.cacheDirectory.getSizeOfFiles()
if (includeCryptoDb) { if (includeCryptoDb) {
cacheSize + sessionDirectory.fileDirectory.getSizeOfFiles() cacheSize + sessionPaths.fileDirectory.getSizeOfFiles()
} else { } else {
cacheSize + listOf( cacheSize + listOf(
"matrix-sdk-state.sqlite3", "matrix-sdk-state.sqlite3",
"matrix-sdk-state.sqlite3-shm", "matrix-sdk-state.sqlite3-shm",
"matrix-sdk-state.sqlite3-wal", "matrix-sdk-state.sqlite3-wal",
).map { fileName -> ).map { fileName ->
File(sessionDirectory.fileDirectory, fileName) File(sessionPaths.fileDirectory, fileName)
}.sumOf { file -> }.sumOf { file ->
file.length() file.length()
} }
@@ -843,7 +841,7 @@ class RustMatrixClient(
private suspend fun deleteSessionDirectory() = withContext(sessionDispatcher) { private suspend fun deleteSessionDirectory() = withContext(sessionDispatcher) {
// Delete all the files for this session // Delete all the files for this session
sessionPathsProvider.provides(sessionId)?.deleteRecursively() sessionPaths.deleteRecursively()
} }
private fun scheduleDatabaseVacuum() { private fun scheduleDatabaseVacuum() {
@@ -17,9 +17,9 @@ import io.element.android.libraries.di.CacheDirectory
import io.element.android.libraries.di.annotations.AppCoroutineScope import io.element.android.libraries.di.annotations.AppCoroutineScope
import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.matrix.api.paths.SessionPaths
import io.element.android.libraries.matrix.impl.analytics.UtdTracker import io.element.android.libraries.matrix.impl.analytics.UtdTracker
import io.element.android.libraries.matrix.impl.certificates.UserCertificatesProvider import io.element.android.libraries.matrix.impl.certificates.UserCertificatesProvider
import io.element.android.libraries.matrix.impl.paths.SessionPaths
import io.element.android.libraries.matrix.impl.paths.getSessionPaths import io.element.android.libraries.matrix.impl.paths.getSessionPaths
import io.element.android.libraries.matrix.impl.proxy.ProxyProvider import io.element.android.libraries.matrix.impl.proxy.ProxyProvider
import io.element.android.libraries.matrix.impl.room.TimelineEventFilterFactory import io.element.android.libraries.matrix.impl.room.TimelineEventFilterFactory
@@ -102,10 +102,10 @@ class RustMatrixClientFactory(
client.restoreSession(sessionData.toSession()) client.restoreSession(sessionData.toSession())
create(client) create(client, sessionData)
} }
suspend fun create(client: Client): RustMatrixClient { suspend fun create(client: Client, sessionData: SessionData): RustMatrixClient {
val (anonymizedAccessToken, anonymizedRefreshToken) = client.session().anonymizedTokens() val (anonymizedAccessToken, anonymizedRefreshToken) = client.session().anonymizedTokens()
// Must be called before creating the sync service, timelines etc. // Must be called before creating the sync service, timelines etc.
@@ -121,6 +121,7 @@ class RustMatrixClientFactory(
.finish() .finish()
return RustMatrixClient( return RustMatrixClient(
sessionPaths = sessionData.getSessionPaths(),
innerClient = client, innerClient = client,
sessionStore = sessionStore, sessionStore = sessionStore,
appCoroutineScope = appCoroutineScope, appCoroutineScope = appCoroutineScope,
@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2025 Element Creations Ltd. * Copyright (c) 2026 Element Creations Ltd.
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details. * Please see LICENSE files in the repository root for full details.
@@ -9,19 +9,53 @@ package io.element.android.libraries.matrix.impl.analytics
import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding import dev.zacsweers.metro.ContributesBinding
import io.element.android.libraries.core.data.bytes
import io.element.android.libraries.matrix.api.MatrixClientProvider import io.element.android.libraries.matrix.api.MatrixClientProvider
import io.element.android.libraries.matrix.api.analytics.GetDatabaseSizesUseCase import io.element.android.libraries.matrix.api.analytics.GetDatabaseSizesUseCase
import io.element.android.libraries.matrix.api.analytics.SdkStoreSizes import io.element.android.libraries.matrix.api.analytics.SdkStoreSizes
import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.SessionId
import java.io.File
import kotlin.math.max
/**
* An implementation of [GetDatabaseSizesUseCase] that manually calculates the database sizes by checking the file system.
* This is not as accurate as the SDK's internal method, but is used as a fallback for Sentry's non-suspendable hook where
* calling the SDK's suspendable method blocks the main thread and causes ANRs.
*/
@ContributesBinding(AppScope::class) @ContributesBinding(AppScope::class)
class DefaultGetDatabaseSizesUseCase( class DefaultGetDatabaseSizesUseCase(
private val clientProvider: Lazy<MatrixClientProvider>, private val clientProvider: Lazy<MatrixClientProvider>,
) : GetDatabaseSizesUseCase { ) : GetDatabaseSizesUseCase {
override suspend fun invoke(sessionId: SessionId): Result<SdkStoreSizes> { override fun invoke(sessionId: SessionId): Result<SdkStoreSizes> {
val client = clientProvider.value.getOrNull(sessionId) val client = clientProvider.value.getOrNull(sessionId)
?: return Result.failure(IllegalArgumentException("No MatrixClient for session $sessionId")) ?: return Result.failure(IllegalArgumentException("No MatrixClient for session $sessionId"))
return client.getDatabaseSizes() val fileDir = client.sessionPaths.fileDirectory
val cacheDir = client.sessionPaths.cacheDirectory
val cryptoSize = getDatabaseSize(fileDir, "matrix-sdk-crypto")
val stateSize = getDatabaseSize(fileDir, "matrix-sdk-state")
val eventCacheSize = getDatabaseSize(cacheDir, "matrix-sdk-event-cache")
val mediaCacheSize = getDatabaseSize(cacheDir, "matrix-sdk-media")
return Result.success(SdkStoreSizes(
stateStore = stateSize.bytes,
cryptoStore = cryptoSize.bytes,
eventCacheStore = eventCacheSize.bytes,
mediaStore = mediaCacheSize.bytes,
))
}
private fun getDatabaseSize(baseDirectory: File, databaseFileName: String): Long {
// Check the sizes of both the main database file and the -wal file, and return the max of the two:
// The -wal file can contain additional data not yet merged into the main file, becoming larger than it.
val databaseFile = File(baseDirectory, "$databaseFileName.sqlite3")
val walFile = File(baseDirectory, "$databaseFileName.sqlite3-wal")
val databaseSize = if (databaseFile.exists()) databaseFile.length() else 0L
val walSize = if (walFile.exists()) walFile.length() else 0L
return max(databaseSize, walSize)
} }
} }
@@ -29,6 +29,7 @@ import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginDat
import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep
import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.paths.SessionPaths
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
import io.element.android.libraries.matrix.impl.ClientBuilderSlidingSync import io.element.android.libraries.matrix.impl.ClientBuilderSlidingSync
import io.element.android.libraries.matrix.impl.RustMatrixClientFactory import io.element.android.libraries.matrix.impl.RustMatrixClientFactory
@@ -38,7 +39,6 @@ import io.element.android.libraries.matrix.impl.auth.qrlogin.toStep
import io.element.android.libraries.matrix.impl.exception.mapClientException import io.element.android.libraries.matrix.impl.exception.mapClientException
import io.element.android.libraries.matrix.impl.keys.SecretGenerator import io.element.android.libraries.matrix.impl.keys.SecretGenerator
import io.element.android.libraries.matrix.impl.mapper.toSessionData import io.element.android.libraries.matrix.impl.mapper.toSessionData
import io.element.android.libraries.matrix.impl.paths.SessionPaths
import io.element.android.libraries.matrix.impl.paths.SessionPathsFactory import io.element.android.libraries.matrix.impl.paths.SessionPathsFactory
import io.element.android.libraries.matrix.impl.toSession import io.element.android.libraries.matrix.impl.toSession
import io.element.android.libraries.sessionstorage.api.LoginType import io.element.android.libraries.sessionstorage.api.LoginType
@@ -160,7 +160,7 @@ class RustMatrixAuthenticationService(
passphrase = pendingKey.formattedAsString(), passphrase = pendingKey.formattedAsString(),
sessionPaths = currentSessionPaths, sessionPaths = currentSessionPaths,
) )
val matrixClient = rustMatrixClientFactory.create(client) val matrixClient = rustMatrixClientFactory.create(client, sessionData)
newMatrixClientObservers.forEach { it.invoke(matrixClient) } newMatrixClientObservers.forEach { it.invoke(matrixClient) }
sessionStore.addSession(sessionData) sessionStore.addSession(sessionData)
@@ -235,7 +235,7 @@ class RustMatrixAuthenticationService(
// We restore the client using the just retrieved session data // We restore the client using the just retrieved session data
client.restoreSession(sessionData.toSession()) client.restoreSession(sessionData.toSession())
val matrixClient = rustMatrixClientFactory.create(client) val matrixClient = rustMatrixClientFactory.create(client, sessionData)
// We wait for the verification state to be known // We wait for the verification state to be known
matrixClient.waitForKnownVerificationState() matrixClient.waitForKnownVerificationState()
@@ -325,7 +325,7 @@ class RustMatrixAuthenticationService(
passphrase = pendingKey.formattedAsString(), passphrase = pendingKey.formattedAsString(),
sessionPaths = currentSessionPaths, sessionPaths = currentSessionPaths,
) )
val matrixClient = rustMatrixClientFactory.create(client) val matrixClient = rustMatrixClientFactory.create(client, sessionData)
matrixClient.waitForKnownVerificationState() matrixClient.waitForKnownVerificationState()
newMatrixClientObservers.forEach { it.invoke(matrixClient) } newMatrixClientObservers.forEach { it.invoke(matrixClient) }
@@ -390,7 +390,7 @@ class RustMatrixAuthenticationService(
passphrase = pendingKey.formattedAsString(), passphrase = pendingKey.formattedAsString(),
sessionPaths = emptySessionPaths, sessionPaths = emptySessionPaths,
) )
val matrixClient = rustMatrixClientFactory.create(client) val matrixClient = rustMatrixClientFactory.create(client, sessionData)
newMatrixClientObservers.forEach { it.invoke(matrixClient) } newMatrixClientObservers.forEach { it.invoke(matrixClient) }
sessionStore.addSession(sessionData) sessionStore.addSession(sessionData)
@@ -9,7 +9,7 @@
package io.element.android.libraries.matrix.impl.mapper package io.element.android.libraries.matrix.impl.mapper
import io.element.android.libraries.matrix.api.auth.external.ExternalSession import io.element.android.libraries.matrix.api.auth.external.ExternalSession
import io.element.android.libraries.matrix.impl.paths.SessionPaths import io.element.android.libraries.matrix.api.paths.SessionPaths
import io.element.android.libraries.sessionstorage.api.LoginType import io.element.android.libraries.sessionstorage.api.LoginType
import io.element.android.libraries.sessionstorage.api.SessionData import io.element.android.libraries.sessionstorage.api.SessionData
import org.matrix.rustcomponents.sdk.Session import org.matrix.rustcomponents.sdk.Session
@@ -8,19 +8,10 @@
package io.element.android.libraries.matrix.impl.paths package io.element.android.libraries.matrix.impl.paths
import io.element.android.libraries.matrix.api.paths.SessionPaths
import io.element.android.libraries.sessionstorage.api.SessionData import io.element.android.libraries.sessionstorage.api.SessionData
import java.io.File import java.io.File
data class SessionPaths(
val fileDirectory: File,
val cacheDirectory: File,
) {
fun deleteRecursively() {
fileDirectory.deleteRecursively()
cacheDirectory.deleteRecursively()
}
}
internal fun SessionData.getSessionPaths(): SessionPaths { internal fun SessionData.getSessionPaths(): SessionPaths {
return SessionPaths( return SessionPaths(
fileDirectory = File(sessionPath), fileDirectory = File(sessionPath),
@@ -11,6 +11,7 @@ package io.element.android.libraries.matrix.impl.paths
import dev.zacsweers.metro.Inject import dev.zacsweers.metro.Inject
import io.element.android.libraries.di.BaseDirectory import io.element.android.libraries.di.BaseDirectory
import io.element.android.libraries.di.CacheDirectory import io.element.android.libraries.di.CacheDirectory
import io.element.android.libraries.matrix.api.paths.SessionPaths
import java.io.File import java.io.File
import java.util.UUID import java.util.UUID
@@ -10,7 +10,7 @@ package io.element.android.libraries.matrix.impl.storage
import io.element.android.libraries.androidutils.crypto.ClientSecret import io.element.android.libraries.androidutils.crypto.ClientSecret
import io.element.android.libraries.core.data.ByteUnit import io.element.android.libraries.core.data.ByteUnit
import io.element.android.libraries.core.data.megaBytes import io.element.android.libraries.core.data.megaBytes
import io.element.android.libraries.matrix.impl.paths.SessionPaths import io.element.android.libraries.matrix.api.paths.SessionPaths
import org.matrix.rustcomponents.sdk.ClientBuilder import org.matrix.rustcomponents.sdk.ClientBuilder
import org.matrix.rustcomponents.sdk.SqliteStoreBuilder as SdkSqliteStoreBuilder import org.matrix.rustcomponents.sdk.SqliteStoreBuilder as SdkSqliteStoreBuilder
@@ -9,7 +9,7 @@ package io.element.android.libraries.matrix.impl.storage
import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding import dev.zacsweers.metro.ContributesBinding
import io.element.android.libraries.matrix.impl.paths.SessionPaths import io.element.android.libraries.matrix.api.paths.SessionPaths
interface SqliteStoreBuilderProvider { interface SqliteStoreBuilderProvider {
fun provide(sessionPaths: SessionPaths): SqliteStoreBuilder fun provide(sessionPaths: SessionPaths): SqliteStoreBuilder
@@ -1,23 +0,0 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector 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.matrix.impl.util
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.impl.paths.SessionPaths
import io.element.android.libraries.matrix.impl.paths.getSessionPaths
import io.element.android.libraries.sessionstorage.api.SessionStore
class SessionPathsProvider(
private val sessionStore: SessionStore,
) {
suspend fun provides(sessionId: SessionId): SessionPaths? {
val sessionData = sessionStore.getSession(sessionId.value) ?: return null
return sessionData.getSessionPaths()
}
}
@@ -13,6 +13,7 @@ package io.element.android.libraries.matrix.impl
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.core.data.bytes import io.element.android.libraries.core.data.bytes
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.matrix.api.paths.SessionPaths
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiClient import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiClient
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiSyncService import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiSyncService
import io.element.android.libraries.matrix.impl.room.FakeTimelineEventFilterFactory import io.element.android.libraries.matrix.impl.room.FakeTimelineEventFilterFactory
@@ -141,6 +142,7 @@ class RustMatrixClientTest {
), ),
) = RustMatrixClient( ) = RustMatrixClient(
innerClient = client, innerClient = client,
sessionPaths = SessionPaths(fileDirectory = File("files"), cacheDirectory = File("cache")),
sessionStore = sessionStore, sessionStore = sessionStore,
appCoroutineScope = backgroundScope, appCoroutineScope = backgroundScope,
sessionDelegate = aRustClientSessionDelegate( sessionDelegate = aRustClientSessionDelegate(
@@ -10,8 +10,8 @@ package io.element.android.libraries.matrix.impl.mapper
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.auth.external.ExternalSession import io.element.android.libraries.matrix.api.auth.external.ExternalSession
import io.element.android.libraries.matrix.api.paths.SessionPaths
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustSession import io.element.android.libraries.matrix.impl.fixtures.factories.aRustSession
import io.element.android.libraries.matrix.impl.paths.SessionPaths
import io.element.android.libraries.matrix.test.A_DEVICE_ID import io.element.android.libraries.matrix.test.A_DEVICE_ID
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL import io.element.android.libraries.matrix.test.A_HOMESERVER_URL
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL_2 import io.element.android.libraries.matrix.test.A_HOMESERVER_URL_2
@@ -7,7 +7,7 @@
package io.element.android.libraries.matrix.impl.storage package io.element.android.libraries.matrix.impl.storage
import io.element.android.libraries.matrix.impl.paths.SessionPaths import io.element.android.libraries.matrix.api.paths.SessionPaths
class FakeSqliteStoreBuilderProvider : SqliteStoreBuilderProvider { class FakeSqliteStoreBuilderProvider : SqliteStoreBuilderProvider {
override fun provide(sessionPaths: SessionPaths): SqliteStoreBuilder { override fun provide(sessionPaths: SessionPaths): SqliteStoreBuilder {
@@ -1,41 +0,0 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector 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.matrix.impl.util
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
import io.element.android.libraries.sessionstorage.test.aSessionData
import kotlinx.coroutines.test.runTest
import org.junit.Test
class SessionPathsProviderTest {
@Test
fun `if session is not found, provides returns null`() = runTest {
val sut = SessionPathsProvider(InMemorySessionStore())
val result = sut.provides(A_SESSION_ID)
assertThat(result).isNull()
}
@Test
fun `if session is found, provides returns the data`() = runTest {
val store = InMemorySessionStore(
initialList = listOf(
aSessionData(
sessionPath = "/a/path/to/a/session",
cachePath = "/a/path/to/a/cache",
)
)
)
val sut = SessionPathsProvider(store)
val result = sut.provides(A_SESSION_ID)!!
assertThat(result.fileDirectory.absolutePath).isEqualTo("/a/path/to/a/session")
assertThat(result.cacheDirectory.absolutePath).isEqualTo("/a/path/to/a/cache")
}
}
@@ -27,6 +27,7 @@ import io.element.android.libraries.matrix.api.media.MediaPreviewService
import io.element.android.libraries.matrix.api.notification.NotificationService import io.element.android.libraries.matrix.api.notification.NotificationService
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
import io.element.android.libraries.matrix.api.oauth.AccountManagementAction import io.element.android.libraries.matrix.api.oauth.AccountManagementAction
import io.element.android.libraries.matrix.api.paths.SessionPaths
import io.element.android.libraries.matrix.api.pusher.PushersService import io.element.android.libraries.matrix.api.pusher.PushersService
import io.element.android.libraries.matrix.api.room.BaseRoom import io.element.android.libraries.matrix.api.room.BaseRoom
import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.JoinedRoom
@@ -66,10 +67,12 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.TestScope
import java.io.File
import java.util.Optional import java.util.Optional
class FakeMatrixClient( class FakeMatrixClient(
override val sessionId: SessionId = A_SESSION_ID, override val sessionId: SessionId = A_SESSION_ID,
override val sessionPaths: SessionPaths = SessionPaths(fileDirectory = File("files"), cacheDirectory = File("cache")),
override val deviceId: DeviceId = A_DEVICE_ID, override val deviceId: DeviceId = A_DEVICE_ID,
override val sessionCoroutineScope: CoroutineScope = TestScope(), override val sessionCoroutineScope: CoroutineScope = TestScope(),
private val userDisplayName: String? = A_USER_NAME, private val userDisplayName: String? = A_USER_NAME,
@@ -33,7 +33,6 @@ import io.sentry.Sentry
import io.sentry.SentryOptions import io.sentry.SentryOptions
import io.sentry.android.core.SentryAndroid import io.sentry.android.core.SentryAndroid
import io.sentry.protocol.SentryTransaction import io.sentry.protocol.SentryTransaction
import kotlinx.coroutines.runBlocking
import timber.log.Timber import timber.log.Timber
@ContributesIntoSet(AppScope::class) @ContributesIntoSet(AppScope::class)
@@ -132,7 +131,7 @@ class SentryAnalyticsProvider(
if (sessionId != null) { if (sessionId != null) {
// This runs in a separate thread, so although using `runBlocking` is not great, at least it shouldn't freeze the app // This runs in a separate thread, so although using `runBlocking` is not great, at least it shouldn't freeze the app
// Also, the method is fairly quick, so the blocking shouldn't take longer than a few ms // Also, the method is fairly quick, so the blocking shouldn't take longer than a few ms
val databaseSizes = runBlocking { getDatabaseSizesUseCase(sessionId) }.getOrNull() val databaseSizes = getDatabaseSizesUseCase(sessionId).getOrNull()
databaseSizes?.stateStore?.let { transaction.setExtra(AnalyticsUserData.STATE_STORE_SIZE, it.into(ByteUnit.MB)) } databaseSizes?.stateStore?.let { transaction.setExtra(AnalyticsUserData.STATE_STORE_SIZE, it.into(ByteUnit.MB)) }
databaseSizes?.eventCacheStore?.let { transaction.setExtra(AnalyticsUserData.EVENT_CACHE_SIZE, it.into(ByteUnit.MB)) } databaseSizes?.eventCacheStore?.let { transaction.setExtra(AnalyticsUserData.EVENT_CACHE_SIZE, it.into(ByteUnit.MB)) }