Merge pull request #3370 from element-hq/feature/bma/cacheFile

Provide distinct cache directory to the Rust SDK.
This commit is contained in:
Benoit Marty 2024-09-03 10:27:47 +02:00 committed by GitHub
commit 3efb371d34
19 changed files with 207 additions and 45 deletions

View file

@ -0,0 +1,61 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.migration.impl.migrations
import com.squareup.anvil.annotations.ContributesMultibinding
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.CacheDirectory
import io.element.android.libraries.sessionstorage.api.SessionStore
import java.io.File
import javax.inject.Inject
/**
* Create the cache directory for the existing sessions.
*/
@ContributesMultibinding(AppScope::class)
class AppMigration06 @Inject constructor(
private val sessionStore: SessionStore,
@CacheDirectory private val cacheDirectory: File,
) : AppMigration {
override val order: Int = 6
override suspend fun migrate() {
val allSessions = sessionStore.getAllSessions()
for (session in allSessions) {
if (session.cachePath.isEmpty()) {
val sessionFile = File(session.sessionPath)
val sessionFolder = sessionFile.name
val cachePath = File(cacheDirectory, sessionFolder)
sessionStore.updateData(session.copy(cachePath = cachePath.absolutePath))
// Move existing cache files
listOf(
"matrix-sdk-event-cache.sqlite3",
"matrix-sdk-event-cache.sqlite3-shm",
"matrix-sdk-event-cache.sqlite3-wal",
).map { fileName ->
File(sessionFile, fileName)
}.takeIf { files ->
files.all { it.exists() }
}?.forEach { cacheFile ->
val targetFile = File(cachePath, cacheFile.name)
cacheFile.copyTo(targetFile)
cacheFile.delete()
}
}
}
}
}

View file

@ -289,6 +289,7 @@ class DefaultBugReporterTest {
slidingSyncProxy = null, slidingSyncProxy = null,
passphrase = null, passphrase = null,
sessionPath = "session", sessionPath = "session",
cachePath = "cache",
) )
@Test @Test
fun `test sendBugReport error`() = runTest { fun `test sendBugReport error`() = runTest {

View file

@ -52,5 +52,6 @@ fun aSessionData(
loginType = LoginType.UNKNOWN, loginType = LoginType.UNKNOWN,
passphrase = null, passphrase = null,
sessionPath = "/a/path/to/a/session", sessionPath = "/a/path/to/a/session",
cachePath = "/a/path/to/a/cache",
) )
} }

View file

@ -56,6 +56,7 @@ import io.element.android.libraries.matrix.impl.media.RustMediaLoader
import io.element.android.libraries.matrix.impl.notification.RustNotificationService import io.element.android.libraries.matrix.impl.notification.RustNotificationService
import io.element.android.libraries.matrix.impl.notificationsettings.RustNotificationSettingsService import io.element.android.libraries.matrix.impl.notificationsettings.RustNotificationSettingsService
import io.element.android.libraries.matrix.impl.oidc.toRustAction import io.element.android.libraries.matrix.impl.oidc.toRustAction
import io.element.android.libraries.matrix.impl.paths.getSessionPaths
import io.element.android.libraries.matrix.impl.pushers.RustPushersService import io.element.android.libraries.matrix.impl.pushers.RustPushersService
import io.element.android.libraries.matrix.impl.room.RoomContentForwarder import io.element.android.libraries.matrix.impl.room.RoomContentForwarder
import io.element.android.libraries.matrix.impl.room.RoomSyncSubscriber import io.element.android.libraries.matrix.impl.room.RoomSyncSubscriber
@ -67,7 +68,7 @@ import io.element.android.libraries.matrix.impl.roomlist.RustRoomListService
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.usersearch.UserProfileMapper import io.element.android.libraries.matrix.impl.usersearch.UserProfileMapper
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.SessionDirectoryProvider import io.element.android.libraries.matrix.impl.util.SessionPathsProvider
import io.element.android.libraries.matrix.impl.util.anonymizedTokens import io.element.android.libraries.matrix.impl.util.anonymizedTokens
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
@ -161,7 +162,7 @@ class RustMatrixClient(
sessionDispatcher = sessionDispatcher, sessionDispatcher = sessionDispatcher,
) )
private val sessionDirectoryProvider = SessionDirectoryProvider(sessionStore) private val sessionPathsProvider = SessionPathsProvider(sessionStore)
private val isLoggingOut = AtomicBoolean(false) private val isLoggingOut = AtomicBoolean(false)
@ -186,7 +187,7 @@ class RustMatrixClient(
isTokenValid = false, isTokenValid = false,
loginType = existingData.loginType, loginType = existingData.loginType,
passphrase = existingData.passphrase, passphrase = existingData.passphrase,
sessionPath = existingData.sessionPath, sessionPaths = existingData.getSessionPaths(),
) )
sessionStore.updateData(newData) sessionStore.updateData(newData)
clientLog.d("Removed session data with access token: '$anonymizedAccessToken'.") clientLog.d("Removed session data with access token: '$anonymizedAccessToken'.")
@ -217,7 +218,7 @@ class RustMatrixClient(
isTokenValid = true, isTokenValid = true,
loginType = existingData.loginType, loginType = existingData.loginType,
passphrase = existingData.passphrase, passphrase = existingData.passphrase,
sessionPath = existingData.sessionPath, sessionPaths = existingData.getSessionPaths(),
) )
sessionStore.updateData(newData) sessionStore.updateData(newData)
clientLog.d("Saved new session data with access token: '$anonymizedAccessToken'.") clientLog.d("Saved new session data with access token: '$anonymizedAccessToken'.")
@ -617,16 +618,17 @@ class RustMatrixClient(
private suspend fun File.getCacheSize( private suspend fun File.getCacheSize(
includeCryptoDb: Boolean = false, includeCryptoDb: Boolean = false,
): Long = withContext(sessionDispatcher) { ): Long = withContext(sessionDispatcher) {
val sessionDirectory = sessionDirectoryProvider.provides(sessionId) ?: return@withContext 0L val sessionDirectory = sessionPathsProvider.provides(sessionId) ?: return@withContext 0L
val cacheSize = sessionDirectory.cacheDirectory.getSizeOfFiles()
if (includeCryptoDb) { if (includeCryptoDb) {
sessionDirectory.getSizeOfFiles() cacheSize + sessionDirectory.fileDirectory.getSizeOfFiles()
} else { } else {
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, fileName) File(sessionDirectory.fileDirectory, fileName)
}.sumOf { file -> }.sumOf { file ->
file.length() file.length()
} }
@ -636,14 +638,16 @@ class RustMatrixClient(
private suspend fun deleteSessionDirectory( private suspend fun deleteSessionDirectory(
deleteCryptoDb: Boolean = false, deleteCryptoDb: Boolean = false,
): Boolean = withContext(sessionDispatcher) { ): Boolean = withContext(sessionDispatcher) {
val sessionDirectory = sessionDirectoryProvider.provides(sessionId) ?: return@withContext false val sessionPaths = sessionPathsProvider.provides(sessionId) ?: return@withContext false
// Always delete the cache directory
sessionPaths.cacheDirectory.deleteRecursively()
if (deleteCryptoDb) { if (deleteCryptoDb) {
// Delete the folder and all its content // Delete the folder and all its content
sessionDirectory.deleteRecursively() sessionPaths.fileDirectory.deleteRecursively()
} else { } else {
// Delete only the state.db file // Do not delete the crypto database files.
sessionDirectory.listFiles().orEmpty() sessionPaths.fileDirectory.listFiles().orEmpty()
.filter { it.name.contains("matrix-sdk-state") } .filterNot { it.name.contains("matrix-sdk-crypto") }
.forEach { file -> .forEach { file ->
Timber.w("Deleting file ${file.name}...") Timber.w("Deleting file ${file.name}...")
file.safeDelete() file.safeDelete()

View file

@ -20,6 +20,8 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.di.CacheDirectory import io.element.android.libraries.di.CacheDirectory
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.proxy.ProxyProvider import io.element.android.libraries.matrix.impl.proxy.ProxyProvider
import io.element.android.libraries.matrix.impl.util.anonymizedTokens import io.element.android.libraries.matrix.impl.util.anonymizedTokens
import io.element.android.libraries.network.useragent.UserAgentProvider import io.element.android.libraries.network.useragent.UserAgentProvider
@ -52,7 +54,7 @@ class RustMatrixClientFactory @Inject constructor(
) { ) {
suspend fun create(sessionData: SessionData): RustMatrixClient = withContext(coroutineDispatchers.io) { suspend fun create(sessionData: SessionData): RustMatrixClient = withContext(coroutineDispatchers.io) {
val client = getBaseClientBuilder( val client = getBaseClientBuilder(
sessionPath = sessionData.sessionPath, sessionPaths = sessionData.getSessionPaths(),
passphrase = sessionData.passphrase, passphrase = sessionData.passphrase,
slidingSync = if (appPreferencesStore.isSimplifiedSlidingSyncEnabledFlow().first()) { slidingSync = if (appPreferencesStore.isSimplifiedSlidingSyncEnabledFlow().first()) {
ClientBuilderSlidingSync.Simplified ClientBuilderSlidingSync.Simplified
@ -87,14 +89,16 @@ class RustMatrixClientFactory @Inject constructor(
} }
internal fun getBaseClientBuilder( internal fun getBaseClientBuilder(
sessionPath: String, sessionPaths: SessionPaths,
passphrase: String?, passphrase: String?,
slidingSyncProxy: String? = null, slidingSyncProxy: String? = null,
slidingSync: ClientBuilderSlidingSync, slidingSync: ClientBuilderSlidingSync,
): ClientBuilder { ): ClientBuilder {
return ClientBuilder() return ClientBuilder()
// TODO SDK claims it's valid to use the same path for data and cache, but would be better to use different paths .sessionPaths(
.sessionPaths(dataPath = sessionPath, cachePath = sessionPath) dataPath = sessionPaths.fileDirectory.absolutePath,
cachePath = sessionPaths.cacheDirectory.absolutePath,
)
.passphrase(passphrase) .passphrase(passphrase)
.slidingSyncProxy(slidingSyncProxy) .slidingSyncProxy(slidingSyncProxy)
.userAgent(userAgentProvider.provide()) .userAgent(userAgentProvider.provide())

View file

@ -37,6 +37,8 @@ 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.PassphraseGenerator import io.element.android.libraries.matrix.impl.keys.PassphraseGenerator
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.sessionstorage.api.LoggedInState import io.element.android.libraries.sessionstorage.api.LoggedInState
import io.element.android.libraries.sessionstorage.api.LoginType import io.element.android.libraries.sessionstorage.api.LoginType
import io.element.android.libraries.sessionstorage.api.SessionStore import io.element.android.libraries.sessionstorage.api.SessionStore
@ -53,14 +55,12 @@ import org.matrix.rustcomponents.sdk.QrLoginProgressListener
import org.matrix.rustcomponents.sdk.use import org.matrix.rustcomponents.sdk.use
import timber.log.Timber import timber.log.Timber
import uniffi.matrix_sdk.OidcAuthorizationData import uniffi.matrix_sdk.OidcAuthorizationData
import java.io.File
import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
@ContributesBinding(AppScope::class) @ContributesBinding(AppScope::class)
@SingleIn(AppScope::class) @SingleIn(AppScope::class)
class RustMatrixAuthenticationService @Inject constructor( class RustMatrixAuthenticationService @Inject constructor(
private val baseDirectory: File, private val sessionPathsFactory: SessionPathsFactory,
private val coroutineDispatchers: CoroutineDispatchers, private val coroutineDispatchers: CoroutineDispatchers,
private val sessionStore: SessionStore, private val sessionStore: SessionStore,
private val rustMatrixClientFactory: RustMatrixClientFactory, private val rustMatrixClientFactory: RustMatrixClientFactory,
@ -73,14 +73,14 @@ class RustMatrixAuthenticationService @Inject constructor(
// Need to keep a copy of the current session path to eventually delete it. // Need to keep a copy of the current session path to eventually delete it.
// Ideally it would be possible to get the sessionPath from the Client to avoid doing this. // Ideally it would be possible to get the sessionPath from the Client to avoid doing this.
private var sessionPath: File? = null private var sessionPaths: SessionPaths? = null
private var currentClient: Client? = null private var currentClient: Client? = null
private var currentHomeserver = MutableStateFlow<MatrixHomeServerDetails?>(null) private var currentHomeserver = MutableStateFlow<MatrixHomeServerDetails?>(null)
private fun rotateSessionPath(): File { private fun rotateSessionPath(): SessionPaths {
sessionPath?.deleteRecursively() sessionPaths?.deleteRecursively()
return File(baseDirectory, UUID.randomUUID().toString()) return sessionPathsFactory.create()
.also { sessionPath = it } .also { sessionPaths = it }
} }
override fun loggedInStateFlow(): Flow<LoggedInState> { override fun loggedInStateFlow(): Flow<LoggedInState> {
@ -145,14 +145,14 @@ class RustMatrixAuthenticationService @Inject constructor(
withContext(coroutineDispatchers.io) { withContext(coroutineDispatchers.io) {
runCatching { runCatching {
val client = currentClient ?: error("You need to call `setHomeserver()` first") val client = currentClient ?: error("You need to call `setHomeserver()` first")
val currentSessionPath = sessionPath ?: error("You need to call `setHomeserver()` first") val currentSessionPaths = sessionPaths ?: error("You need to call `setHomeserver()` first")
client.login(username, password, "Element X Android", null) client.login(username, password, "Element X Android", null)
val sessionData = client.session() val sessionData = client.session()
.toSessionData( .toSessionData(
isTokenValid = true, isTokenValid = true,
loginType = LoginType.PASSWORD, loginType = LoginType.PASSWORD,
passphrase = pendingPassphrase, passphrase = pendingPassphrase,
sessionPath = currentSessionPath.absolutePath, sessionPaths = currentSessionPaths,
) )
clear() clear()
sessionStore.storeData(sessionData) sessionStore.storeData(sessionData)
@ -196,14 +196,14 @@ class RustMatrixAuthenticationService @Inject constructor(
return withContext(coroutineDispatchers.io) { return withContext(coroutineDispatchers.io) {
runCatching { runCatching {
val client = currentClient ?: error("You need to call `setHomeserver()` first") val client = currentClient ?: error("You need to call `setHomeserver()` first")
val currentSessionPath = sessionPath ?: error("You need to call `setHomeserver()` first") val currentSessionPaths = sessionPaths ?: error("You need to call `setHomeserver()` first")
val urlForOidcLogin = pendingOidcAuthorizationData ?: error("You need to call `getOidcUrl()` first") val urlForOidcLogin = pendingOidcAuthorizationData ?: error("You need to call `getOidcUrl()` first")
client.loginWithOidcCallback(urlForOidcLogin, callbackUrl) client.loginWithOidcCallback(urlForOidcLogin, callbackUrl)
val sessionData = client.session().toSessionData( val sessionData = client.session().toSessionData(
isTokenValid = true, isTokenValid = true,
loginType = LoginType.OIDC, loginType = LoginType.OIDC,
passphrase = pendingPassphrase, passphrase = pendingPassphrase,
sessionPath = currentSessionPath.absolutePath, sessionPaths = currentSessionPaths,
) )
clear() clear()
pendingOidcAuthorizationData?.close() pendingOidcAuthorizationData?.close()
@ -218,10 +218,10 @@ class RustMatrixAuthenticationService @Inject constructor(
override suspend fun loginWithQrCode(qrCodeData: MatrixQrCodeLoginData, progress: (QrCodeLoginStep) -> Unit) = override suspend fun loginWithQrCode(qrCodeData: MatrixQrCodeLoginData, progress: (QrCodeLoginStep) -> Unit) =
withContext(coroutineDispatchers.io) { withContext(coroutineDispatchers.io) {
val emptySessionPath = rotateSessionPath() val emptySessionPaths = rotateSessionPath()
runCatching { runCatching {
val client = rustMatrixClientFactory.getBaseClientBuilder( val client = rustMatrixClientFactory.getBaseClientBuilder(
sessionPath = emptySessionPath.absolutePath, sessionPaths = emptySessionPaths,
passphrase = pendingPassphrase, passphrase = pendingPassphrase,
slidingSyncProxy = AuthenticationConfig.SLIDING_SYNC_PROXY_URL, slidingSyncProxy = AuthenticationConfig.SLIDING_SYNC_PROXY_URL,
slidingSync = ClientBuilderSlidingSync.Discovered, slidingSync = ClientBuilderSlidingSync.Discovered,
@ -242,7 +242,7 @@ class RustMatrixAuthenticationService @Inject constructor(
isTokenValid = true, isTokenValid = true,
loginType = LoginType.QR, loginType = LoginType.QR,
passphrase = pendingPassphrase, passphrase = pendingPassphrase,
sessionPath = emptySessionPath.absolutePath, sessionPaths = emptySessionPaths,
) )
sessionStore.storeData(sessionData) sessionStore.storeData(sessionData)
SessionId(sessionData.userId) SessionId(sessionData.userId)
@ -262,10 +262,10 @@ class RustMatrixAuthenticationService @Inject constructor(
} }
private fun getBaseClientBuilder( private fun getBaseClientBuilder(
sessionPath: File, sessionPaths: SessionPaths,
) = rustMatrixClientFactory ) = rustMatrixClientFactory
.getBaseClientBuilder( .getBaseClientBuilder(
sessionPath = sessionPath.absolutePath, sessionPaths = sessionPaths,
passphrase = pendingPassphrase, passphrase = pendingPassphrase,
slidingSyncProxy = AuthenticationConfig.SLIDING_SYNC_PROXY_URL, slidingSyncProxy = AuthenticationConfig.SLIDING_SYNC_PROXY_URL,
slidingSync = ClientBuilderSlidingSync.Discovered, slidingSync = ClientBuilderSlidingSync.Discovered,

View file

@ -16,6 +16,7 @@
package io.element.android.libraries.matrix.impl.mapper package io.element.android.libraries.matrix.impl.mapper
import io.element.android.libraries.matrix.impl.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
@ -25,7 +26,7 @@ internal fun Session.toSessionData(
isTokenValid: Boolean, isTokenValid: Boolean,
loginType: LoginType, loginType: LoginType,
passphrase: String?, passphrase: String?,
sessionPath: String, sessionPaths: SessionPaths,
homeserverUrl: String? = null, homeserverUrl: String? = null,
) = SessionData( ) = SessionData(
userId = userId, userId = userId,
@ -39,5 +40,6 @@ internal fun Session.toSessionData(
isTokenValid = isTokenValid, isTokenValid = isTokenValid,
loginType = loginType, loginType = loginType,
passphrase = passphrase, passphrase = passphrase,
sessionPath = sessionPath, sessionPath = sessionPaths.fileDirectory.absolutePath,
cachePath = sessionPaths.cacheDirectory.absolutePath,
) )

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.impl.paths
import io.element.android.libraries.sessionstorage.api.SessionData
import java.io.File
data class SessionPaths(
val fileDirectory: File,
val cacheDirectory: File,
) {
fun deleteRecursively() {
fileDirectory.deleteRecursively()
cacheDirectory.deleteRecursively()
}
}
internal fun SessionData.getSessionPaths(): SessionPaths {
return SessionPaths(
fileDirectory = File(sessionPath),
cacheDirectory = File(cachePath),
)
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.impl.paths
import io.element.android.libraries.di.CacheDirectory
import java.io.File
import java.util.UUID
import javax.inject.Inject
class SessionPathsFactory @Inject constructor(
private val baseDirectory: File,
@CacheDirectory private val cacheDirectory: File,
) {
fun create(): SessionPaths {
val subPath = UUID.randomUUID().toString()
return SessionPaths(
fileDirectory = File(baseDirectory, subPath),
cacheDirectory = File(cacheDirectory, subPath),
)
}
}

View file

@ -17,15 +17,15 @@
package io.element.android.libraries.matrix.impl.util package io.element.android.libraries.matrix.impl.util
import io.element.android.libraries.matrix.api.core.SessionId 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 import io.element.android.libraries.sessionstorage.api.SessionStore
import java.io.File
import javax.inject.Inject
class SessionDirectoryProvider @Inject constructor( class SessionPathsProvider(
private val sessionStore: SessionStore, private val sessionStore: SessionStore,
) { ) {
suspend fun provides(sessionId: SessionId): File? { suspend fun provides(sessionId: SessionId): SessionPaths? {
val path = sessionStore.getSession(sessionId.value)?.sessionPath ?: return null val sessionData = sessionStore.getSession(sessionId.value) ?: return null
return File(path) return sessionData.getSessionPaths()
} }
} }

View file

@ -44,6 +44,8 @@ data class SessionData(
val loginType: LoginType, val loginType: LoginType,
/** The optional passphrase used to encrypt data in the SDK local store. */ /** The optional passphrase used to encrypt data in the SDK local store. */
val passphrase: String?, val passphrase: String?,
/** The path to the session data stored in the filesystem. */ /** The paths to the session data stored in the filesystem. */
val sessionPath: String, val sessionPath: String,
/** The path to the cache data stored for the session in the filesystem. */
val cachePath: String,
) )

View file

@ -35,6 +35,7 @@ internal fun SessionData.toDbModel(): DbSessionData {
loginType = loginType.name, loginType = loginType.name,
passphrase = passphrase, passphrase = passphrase,
sessionPath = sessionPath, sessionPath = sessionPath,
cachePath = cachePath,
) )
} }
@ -52,5 +53,6 @@ internal fun DbSessionData.toApiModel(): SessionData {
loginType = LoginType.fromName(loginType ?: LoginType.UNKNOWN.name), loginType = LoginType.fromName(loginType ?: LoginType.UNKNOWN.name),
passphrase = passphrase, passphrase = passphrase,
sessionPath = sessionPath, sessionPath = sessionPath,
cachePath = cachePath,
) )
} }

View file

@ -25,7 +25,9 @@ CREATE TABLE SessionData (
-- added in version 5 -- added in version 5
passphrase TEXT, passphrase TEXT,
-- added in version 6 -- added in version 6
sessionPath TEXT NOT NULL DEFAULT "" sessionPath TEXT NOT NULL DEFAULT "",
-- added in version 9
cachePath TEXT NOT NULL DEFAULT ""
); );

View file

@ -0,0 +1,4 @@
-- Migrate DB from version 8
-- Add cachePath so we can track the anonymized path for the session cache dir
ALTER TABLE SessionData ADD COLUMN cachePath TEXT NOT NULL DEFAULT "";

View file

@ -145,6 +145,7 @@ class DatabaseSessionStoreTest {
loginType = null, loginType = null,
passphrase = "aPassphrase", passphrase = "aPassphrase",
sessionPath = "sessionPath", sessionPath = "sessionPath",
cachePath = "cachePath",
) )
val secondSessionData = SessionData( val secondSessionData = SessionData(
userId = "userId", userId = "userId",
@ -159,6 +160,7 @@ class DatabaseSessionStoreTest {
loginType = null, loginType = null,
passphrase = "aPassphraseAltered", passphrase = "aPassphraseAltered",
sessionPath = "sessionPath", sessionPath = "sessionPath",
cachePath = "cachePath",
) )
assertThat(firstSessionData.userId).isEqualTo(secondSessionData.userId) assertThat(firstSessionData.userId).isEqualTo(secondSessionData.userId)
assertThat(firstSessionData.loginTimestamp).isNotEqualTo(secondSessionData.loginTimestamp) assertThat(firstSessionData.loginTimestamp).isNotEqualTo(secondSessionData.loginTimestamp)
@ -196,6 +198,7 @@ class DatabaseSessionStoreTest {
loginType = null, loginType = null,
passphrase = "aPassphrase", passphrase = "aPassphrase",
sessionPath = "sessionPath", sessionPath = "sessionPath",
cachePath = "cachePath",
) )
val secondSessionData = SessionData( val secondSessionData = SessionData(
userId = "userIdUnknown", userId = "userIdUnknown",
@ -210,6 +213,7 @@ class DatabaseSessionStoreTest {
loginType = null, loginType = null,
passphrase = "aPassphraseAltered", passphrase = "aPassphraseAltered",
sessionPath = "sessionPath", sessionPath = "sessionPath",
cachePath = "cachePath",
) )
assertThat(firstSessionData.userId).isNotEqualTo(secondSessionData.userId) assertThat(firstSessionData.userId).isNotEqualTo(secondSessionData.userId)

View file

@ -32,4 +32,5 @@ internal fun aSessionData() = SessionData(
loginType = LoginType.UNKNOWN.name, loginType = LoginType.UNKNOWN.name,
passphrase = null, passphrase = null,
sessionPath = "sessionPath", sessionPath = "sessionPath",
cachePath = "cachePath",
) )

View file

@ -37,5 +37,6 @@ fun aSessionData(
loginType = LoginType.UNKNOWN, loginType = LoginType.UNKNOWN,
passphrase = null, passphrase = null,
sessionPath = "/a/path/to/a/session", sessionPath = "/a/path/to/a/session",
cachePath = "/a/path/to/a/cache",
) )
} }

View file

@ -32,6 +32,7 @@ import io.element.android.libraries.matrix.impl.RustMatrixClientFactory
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.auth.OidcConfigurationProvider import io.element.android.libraries.matrix.impl.auth.OidcConfigurationProvider
import io.element.android.libraries.matrix.impl.auth.RustMatrixAuthenticationService import io.element.android.libraries.matrix.impl.auth.RustMatrixAuthenticationService
import io.element.android.libraries.matrix.impl.paths.SessionPathsFactory
import io.element.android.libraries.network.useragent.SimpleUserAgentProvider import io.element.android.libraries.network.useragent.SimpleUserAgentProvider
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
import io.element.android.libraries.sessionstorage.api.LoggedInState import io.element.android.libraries.sessionstorage.api.LoggedInState
@ -49,7 +50,7 @@ class MainActivity : ComponentActivity() {
val userCertificatesProvider = NoOpUserCertificatesProvider() val userCertificatesProvider = NoOpUserCertificatesProvider()
val proxyProvider = NoOpProxyProvider() val proxyProvider = NoOpProxyProvider()
RustMatrixAuthenticationService( RustMatrixAuthenticationService(
baseDirectory = baseDirectory, sessionPathsFactory = SessionPathsFactory(baseDirectory, applicationContext.cacheDir),
coroutineDispatchers = Singleton.coroutineDispatchers, coroutineDispatchers = Singleton.coroutineDispatchers,
sessionStore = sessionStore, sessionStore = sessionStore,
rustMatrixClientFactory = RustMatrixClientFactory( rustMatrixClientFactory = RustMatrixClientFactory(