Merge branch 'develop' into feature/fga/space_settings_iteration
This commit is contained in:
commit
ce079e84f5
600 changed files with 3591 additions and 2388 deletions
|
|
@ -75,7 +75,10 @@ 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.mxCallbackFlow
|
||||
import io.element.android.libraries.matrix.impl.verification.RustSessionVerificationService
|
||||
import io.element.android.libraries.matrix.impl.workmanager.PerformDatabaseVacuumWorkManagerRequest
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import io.element.android.libraries.workmanager.api.WorkManagerRequestType
|
||||
import io.element.android.libraries.workmanager.api.WorkManagerScheduler
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
|
@ -133,6 +136,7 @@ class RustMatrixClient(
|
|||
timelineEventTypeFilterFactory: TimelineEventTypeFilterFactory,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
private val analyticsService: AnalyticsService,
|
||||
private val workManagerScheduler: WorkManagerScheduler,
|
||||
) : MatrixClient {
|
||||
override val sessionId: UserId = UserId(innerClient.userId())
|
||||
override val deviceId: DeviceId = DeviceId(innerClient.deviceId())
|
||||
|
|
@ -276,6 +280,9 @@ class RustMatrixClient(
|
|||
// Force a refresh of the profile
|
||||
getUserProfile()
|
||||
}
|
||||
|
||||
// Schedule regular database vacuuming to ensure DB performance remains optimal
|
||||
scheduleDatabaseVacuum()
|
||||
}
|
||||
|
||||
override fun userIdServerName(): String {
|
||||
|
|
@ -726,6 +733,13 @@ class RustMatrixClient(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun performDatabaseVacuum(): Result<Unit> = withContext(sessionDispatcher) {
|
||||
runCatchingExceptions {
|
||||
Timber.d("Performing database vacuuming for session $sessionId...")
|
||||
innerClient.optimizeStores()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getCacheSize(
|
||||
includeCryptoDb: Boolean = false,
|
||||
): Long = withContext(sessionDispatcher) {
|
||||
|
|
@ -750,6 +764,15 @@ class RustMatrixClient(
|
|||
// Delete all the files for this session
|
||||
sessionPathsProvider.provides(sessionId)?.deleteRecursively()
|
||||
}
|
||||
|
||||
private fun scheduleDatabaseVacuum() {
|
||||
// If there's already a periodic work request, do not schedule another one
|
||||
if (workManagerScheduler.hasPendingWork(sessionId, WorkManagerRequestType.DB_VACUUM)) return
|
||||
|
||||
Timber.i("Scheduling periodic database vacuuming for session $sessionId")
|
||||
val request = PerformDatabaseVacuumWorkManagerRequest(sessionId)
|
||||
workManagerScheduler.submit(request)
|
||||
}
|
||||
}
|
||||
|
||||
private val defaultRoomCreationPowerLevels = PowerLevels(
|
||||
|
|
|
|||
|
|
@ -22,10 +22,12 @@ 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.room.TimelineEventTypeFilterFactory
|
||||
import io.element.android.libraries.matrix.impl.storage.SqliteStoreBuilderProvider
|
||||
import io.element.android.libraries.matrix.impl.util.anonymizedTokens
|
||||
import io.element.android.libraries.network.useragent.UserAgentProvider
|
||||
import io.element.android.libraries.sessionstorage.api.SessionData
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import io.element.android.libraries.workmanager.api.WorkManagerScheduler
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
@ -36,7 +38,6 @@ import org.matrix.rustcomponents.sdk.RequestConfig
|
|||
import org.matrix.rustcomponents.sdk.Session
|
||||
import org.matrix.rustcomponents.sdk.SlidingSyncVersion
|
||||
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
|
||||
|
|
@ -62,6 +63,8 @@ class RustMatrixClientFactory(
|
|||
private val featureFlagService: FeatureFlagService,
|
||||
private val timelineEventTypeFilterFactory: TimelineEventTypeFilterFactory,
|
||||
private val clientBuilderProvider: ClientBuilderProvider,
|
||||
private val sqliteStoreBuilderProvider: SqliteStoreBuilderProvider,
|
||||
private val workManagerScheduler: WorkManagerScheduler,
|
||||
) {
|
||||
private val sessionDelegate = RustClientSessionDelegate(sessionStore, appCoroutineScope, coroutineDispatchers)
|
||||
|
||||
|
|
@ -115,6 +118,7 @@ class RustMatrixClientFactory(
|
|||
timelineEventTypeFilterFactory = timelineEventTypeFilterFactory,
|
||||
featureFlagService = featureFlagService,
|
||||
analyticsService = analyticsService,
|
||||
workManagerScheduler = workManagerScheduler,
|
||||
).also {
|
||||
Timber.tag(it.toString()).d("Creating Client with access token '$anonymizedAccessToken' and refresh token '$anonymizedRefreshToken'")
|
||||
}
|
||||
|
|
@ -126,12 +130,11 @@ class RustMatrixClientFactory(
|
|||
slidingSyncType: ClientBuilderSlidingSync,
|
||||
): ClientBuilder {
|
||||
return clientBuilderProvider.provide()
|
||||
.sqliteStore(
|
||||
SqliteStoreBuilder(
|
||||
dataPath = sessionPaths.fileDirectory.absolutePath,
|
||||
cachePath = sessionPaths.cacheDirectory.absolutePath,
|
||||
).passphrase(passphrase)
|
||||
)
|
||||
.run {
|
||||
sqliteStoreBuilderProvider.provide(sessionPaths)
|
||||
.passphrase(passphrase)
|
||||
.setupClientBuilder(this)
|
||||
}
|
||||
.setSessionDelegate(sessionDelegate)
|
||||
.userAgent(userAgentProvider.provide())
|
||||
.addRootCertificates(userCertificatesProvider.provides())
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
|||
import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails
|
||||
import io.element.android.libraries.matrix.api.auth.OidcDetails
|
||||
import io.element.android.libraries.matrix.api.auth.OidcPrompt
|
||||
import io.element.android.libraries.matrix.api.auth.SessionRestorationException
|
||||
import io.element.android.libraries.matrix.api.auth.external.ExternalSession
|
||||
import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData
|
||||
import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep
|
||||
|
|
@ -91,10 +92,10 @@ class RustMatrixAuthenticationService(
|
|||
}
|
||||
rustMatrixClientFactory.create(sessionData)
|
||||
} else {
|
||||
error("Token is not valid")
|
||||
throw SessionRestorationException.InvalidToken()
|
||||
}
|
||||
} else {
|
||||
error("No session to restore with id $sessionId")
|
||||
throw SessionRestorationException.MissingSession(sessionId)
|
||||
}
|
||||
}.mapFailure { failure ->
|
||||
failure.mapClientException()
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ fun Throwable.mapRecoveryException(): RecoveryException {
|
|||
}
|
||||
}
|
||||
else -> RecoveryException.Client(
|
||||
ClientException.Other("Unknown error")
|
||||
ClientException.Other("Unknown error", this)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,15 +15,16 @@ fun Throwable.mapClientException(): ClientException {
|
|||
return when (this) {
|
||||
is RustClientException -> {
|
||||
when (this) {
|
||||
is RustClientException.Generic -> ClientException.Generic(msg, details)
|
||||
is RustClientException.Generic -> ClientException.Generic(message = msg, details = details, cause = this)
|
||||
is RustClientException.MatrixApi -> ClientException.MatrixApi(
|
||||
kind = kind.map(),
|
||||
code = code,
|
||||
message = msg,
|
||||
details = details,
|
||||
cause = this,
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> ClientException.Other(message ?: "Unknown error")
|
||||
else -> ClientException.Other(message ?: "Unknown error", this)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class RoomSyncSubscriber(
|
|||
}
|
||||
subscribedRoomIds.add(roomId)
|
||||
} catch (exception: Exception) {
|
||||
Timber.e("Failed to subscribe to room $roomId")
|
||||
Timber.e(exception, "Failed to subscribe to room $roomId")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,7 +85,9 @@ class RustBaseRoom(
|
|||
}.stateIn(roomCoroutineScope, started = SharingStarted.Lazily, initialValue = initialRoomInfo)
|
||||
|
||||
override fun predecessorRoom(): PredecessorRoom? {
|
||||
return innerRoom.predecessorRoom()?.map()
|
||||
return runCatchingExceptions { innerRoom.predecessorRoom()?.map() }
|
||||
.onFailure { Timber.e(it, "Could not get predecessor room") }
|
||||
.getOrNull()
|
||||
}
|
||||
|
||||
override suspend fun subscribeToSync() = roomSyncSubscriber.subscribe(roomId)
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ fun RoomListInterface.loadingStateFlow(): Flow<RoomListLoadingState> =
|
|||
try {
|
||||
send(result.state)
|
||||
} catch (exception: Exception) {
|
||||
Timber.d("loadingStateFlow() initialState failed.")
|
||||
Timber.d(exception, "loadingStateFlow() initialState failed.")
|
||||
}
|
||||
result.stateStream
|
||||
}.catch {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ class RoomSummaryFactory(
|
|||
) {
|
||||
suspend fun create(room: Room): RoomSummary {
|
||||
val roomInfo = room.roomInfo().let(roomInfoMapper::map)
|
||||
val latestEvent = room.newLatestEvent().use { event ->
|
||||
val latestEvent = room.latestEvent().use { event ->
|
||||
when (event) {
|
||||
is RustLatestEventValue.None -> LatestEventValue.None
|
||||
is RustLatestEventValue.Local -> LatestEventValue.Local(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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.matrix.impl.storage
|
||||
|
||||
import io.element.android.libraries.matrix.impl.paths.SessionPaths
|
||||
import org.matrix.rustcomponents.sdk.ClientBuilder
|
||||
import org.matrix.rustcomponents.sdk.SqliteStoreBuilder as SdkSqliteStoreBuilder
|
||||
|
||||
interface SqliteStoreBuilder {
|
||||
fun passphrase(passphrase: String?): SqliteStoreBuilder
|
||||
fun setupClientBuilder(clientBuilder: ClientBuilder): ClientBuilder
|
||||
}
|
||||
|
||||
class RustSqliteStoreBuilder(
|
||||
private val sessionPaths: SessionPaths,
|
||||
) : SqliteStoreBuilder {
|
||||
private var inner = SdkSqliteStoreBuilder(
|
||||
dataPath = sessionPaths.fileDirectory.absolutePath,
|
||||
cachePath = sessionPaths.cacheDirectory.absolutePath,
|
||||
)
|
||||
|
||||
override fun passphrase(passphrase: String?): SqliteStoreBuilder {
|
||||
inner = inner.passphrase(passphrase)
|
||||
return this
|
||||
}
|
||||
|
||||
override fun setupClientBuilder(clientBuilder: ClientBuilder): ClientBuilder {
|
||||
return clientBuilder.sqliteStore(this.inner)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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.matrix.impl.storage
|
||||
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.libraries.matrix.impl.paths.SessionPaths
|
||||
|
||||
interface SqliteStoreBuilderProvider {
|
||||
fun provide(sessionPaths: SessionPaths): SqliteStoreBuilder
|
||||
}
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class RustSqliteStoreBuilderProvider : SqliteStoreBuilderProvider {
|
||||
override fun provide(sessionPaths: SessionPaths): SqliteStoreBuilder {
|
||||
return RustSqliteStoreBuilder(sessionPaths)
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,8 @@ fun TraceLogPack.map(): RustTraceLogPack = when (this) {
|
|||
TraceLogPack.EVENT_CACHE -> RustTraceLogPack.EVENT_CACHE
|
||||
TraceLogPack.TIMELINE -> RustTraceLogPack.TIMELINE
|
||||
TraceLogPack.NOTIFICATION_CLIENT -> RustTraceLogPack.NOTIFICATION_CLIENT
|
||||
TraceLogPack.LATEST_EVENTS -> RustTraceLogPack.LATEST_EVENTS
|
||||
TraceLogPack.SYNC_PROFILING -> RustTraceLogPack.SYNC_PROFILING
|
||||
}
|
||||
|
||||
fun Collection<TraceLogPack>.map(): List<RustTraceLogPack> {
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@ import timber.log.Timber
|
|||
fun logError(throwable: Throwable) {
|
||||
when (throwable) {
|
||||
is ClientException.Generic -> {
|
||||
Timber.e("Error ${throwable.msg}", throwable)
|
||||
Timber.e(throwable, "Error ${throwable.msg}")
|
||||
}
|
||||
else -> {
|
||||
Timber.e("Error", throwable)
|
||||
Timber.e(throwable, "Error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ class RustWidgetDriver(
|
|||
override suspend fun send(message: String) {
|
||||
try {
|
||||
driverAndHandle.handle.send(message)
|
||||
} catch (e: IllegalStateException) {
|
||||
} catch (_: IllegalStateException) {
|
||||
// The handle is closed, ignore
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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.matrix.impl.workmanager
|
||||
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.Data
|
||||
import androidx.work.PeriodicWorkRequest
|
||||
import androidx.work.WorkRequest
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.impl.workmanager.VacuumDatabaseWorker.Companion.SESSION_ID_PARAM
|
||||
import io.element.android.libraries.workmanager.api.WorkManagerRequest
|
||||
import io.element.android.libraries.workmanager.api.WorkManagerRequestType
|
||||
import io.element.android.libraries.workmanager.api.workManagerTag
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class PerformDatabaseVacuumWorkManagerRequest(
|
||||
private val sessionId: SessionId,
|
||||
) : WorkManagerRequest {
|
||||
override fun build(): Result<List<WorkRequest>> {
|
||||
val data = Data.Builder().putString(SESSION_ID_PARAM, sessionId.value).build()
|
||||
val workRequest = PeriodicWorkRequest.Builder(
|
||||
workerClass = VacuumDatabaseWorker::class,
|
||||
// Run once a day
|
||||
repeatInterval = 1,
|
||||
repeatIntervalTimeUnit = TimeUnit.DAYS,
|
||||
)
|
||||
.addTag(workManagerTag(sessionId, WorkManagerRequestType.DB_VACUUM))
|
||||
.setInputData(data)
|
||||
// Only run when the device is idle to avoid impacting user experience
|
||||
.setConstraints(Constraints.Builder().setRequiresDeviceIdle(true).build())
|
||||
.build()
|
||||
|
||||
return Result.success(listOf(workRequest))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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.matrix.impl.workmanager
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.WorkerParameters
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.AssistedFactory
|
||||
import dev.zacsweers.metro.AssistedInject
|
||||
import dev.zacsweers.metro.ContributesIntoMap
|
||||
import dev.zacsweers.metro.binding
|
||||
import io.element.android.libraries.di.annotations.ApplicationContext
|
||||
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.workmanager.api.di.MetroWorkerFactory
|
||||
import io.element.android.libraries.workmanager.api.di.WorkerKey
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.analytics.api.recordTransaction
|
||||
import timber.log.Timber
|
||||
|
||||
@AssistedInject
|
||||
class VacuumDatabaseWorker(
|
||||
@Assisted workerParams: WorkerParameters,
|
||||
@ApplicationContext private val context: Context,
|
||||
private val matrixClientProvider: MatrixClientProvider,
|
||||
private val analyticsService: AnalyticsService,
|
||||
) : CoroutineWorker(context, workerParams) {
|
||||
companion object {
|
||||
const val SESSION_ID_PARAM = "session_id"
|
||||
}
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
Timber.d("Starting database vacuuming...")
|
||||
val sessionId = inputData.getString(SESSION_ID_PARAM)?.let(::SessionId) ?: return Result.failure()
|
||||
val client = matrixClientProvider.getOrRestore(sessionId).getOrNull() ?: return Result.failure()
|
||||
return analyticsService.recordTransaction("Vacuuming DBs", "vacuuming") { transaction ->
|
||||
client.performDatabaseVacuum()
|
||||
.fold(
|
||||
onSuccess = {
|
||||
Timber.d("Database vacuuming finished successfully")
|
||||
Result.success()
|
||||
},
|
||||
onFailure = { error ->
|
||||
transaction.attachError(error)
|
||||
Timber.e(error, "Database vacuuming failed")
|
||||
Result.failure()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ContributesIntoMap(AppScope::class, binding = binding<MetroWorkerFactory.WorkerInstanceFactory<*>>())
|
||||
@WorkerKey(VacuumDatabaseWorker::class)
|
||||
@AssistedFactory
|
||||
interface Factory : MetroWorkerFactory.WorkerInstanceFactory<VacuumDatabaseWorker>
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue