From 94a87744ac73502886b951d8f4f53af90daf1699 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 23 Aug 2022 09:50:35 +0200 Subject: [PATCH] Defer the treatment of updating the User profiles to a background Worker. --- .../sdk/internal/session/SessionComponent.kt | 3 + ...cResponsePostTreatmentAggregatorHandler.kt | 57 ++++++----- .../session/sync/handler/UpdateUserWorker.kt | 99 +++++++++++++++++++ .../internal/worker/MatrixWorkerFactory.kt | 3 + 4 files changed, 133 insertions(+), 29 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UpdateUserWorker.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt index a79f35bcb6..a7572035df 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt @@ -55,6 +55,7 @@ import org.matrix.android.sdk.internal.session.space.SpaceModule import org.matrix.android.sdk.internal.session.sync.SyncModule import org.matrix.android.sdk.internal.session.sync.SyncTask import org.matrix.android.sdk.internal.session.sync.SyncTokenStore +import org.matrix.android.sdk.internal.session.sync.handler.UpdateUserWorker import org.matrix.android.sdk.internal.session.sync.job.SyncWorker import org.matrix.android.sdk.internal.session.terms.TermsModule import org.matrix.android.sdk.internal.session.thirdparty.ThirdPartyModule @@ -128,6 +129,8 @@ internal interface SessionComponent { fun inject(worker: UpdateTrustWorker) + fun inject(worker: UpdateUserWorker) + fun inject(worker: DeactivateLiveLocationShareWorker) @Component.Factory diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt index b7806d154b..2d8339c901 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt @@ -16,30 +16,33 @@ package org.matrix.android.sdk.internal.session.sync.handler -import com.zhuinden.monarchy.Monarchy +import androidx.work.BackoffPolicy +import androidx.work.ExistingWorkPolicy import org.matrix.android.sdk.api.MatrixPatterns -import org.matrix.android.sdk.api.extensions.tryOrNull -import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultCrossSigningService -import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.session.profile.GetProfileInfoTask +import org.matrix.android.sdk.internal.crypto.crosssigning.UpdateTrustWorker +import org.matrix.android.sdk.internal.crypto.crosssigning.UpdateTrustWorkerDataRepository +import org.matrix.android.sdk.internal.di.SessionId +import org.matrix.android.sdk.internal.di.WorkManagerProvider import org.matrix.android.sdk.internal.session.sync.RoomSyncEphemeralTemporaryStore import org.matrix.android.sdk.internal.session.sync.SyncResponsePostTreatmentAggregator import org.matrix.android.sdk.internal.session.sync.model.accountdata.toMutable -import org.matrix.android.sdk.internal.session.user.UserEntityFactory import org.matrix.android.sdk.internal.session.user.accountdata.DirectChatsHelper import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask -import org.matrix.android.sdk.internal.util.awaitTransaction +import org.matrix.android.sdk.internal.util.logLimit +import org.matrix.android.sdk.internal.worker.WorkerParamsFactory import timber.log.Timber +import java.util.concurrent.TimeUnit import javax.inject.Inject internal class SyncResponsePostTreatmentAggregatorHandler @Inject constructor( private val directChatsHelper: DirectChatsHelper, private val ephemeralTemporaryStore: RoomSyncEphemeralTemporaryStore, private val updateUserAccountDataTask: UpdateUserAccountDataTask, - private val getProfileInfoTask: GetProfileInfoTask, private val crossSigningService: DefaultCrossSigningService, - @SessionDatabase private val monarchy: Monarchy, + private val updateTrustWorkerDataRepository: UpdateTrustWorkerDataRepository, + private val workManagerProvider: WorkManagerProvider, + @SessionId private val sessionId: String, ) { suspend fun handle(aggregator: SyncResponsePostTreatmentAggregator) { cleanupEphemeralFiles(aggregator.ephemeralFilesToDelete) @@ -83,30 +86,26 @@ internal class SyncResponsePostTreatmentAggregatorHandler @Inject constructor( } } - private suspend fun fetchAndUpdateUsers(userIdsToFetch: Collection) { - fetchUsers(userIdsToFetch) - .takeIf { it.isNotEmpty() } - ?.saveLocally() - } + private fun fetchAndUpdateUsers(userIdsToFetch: Collection) { + if (userIdsToFetch.isEmpty()) return + Timber.d("## Configure Worker to update users: ${userIdsToFetch.logLimit()}") + val workerParams = UpdateTrustWorker.Params( + sessionId = sessionId, + filename = updateTrustWorkerDataRepository.createParam(userIdsToFetch.toList()) + ) + val workerData = WorkerParamsFactory.toData(workerParams) - private suspend fun fetchUsers(userIdsToFetch: Collection) = userIdsToFetch.mapNotNull { - tryOrNull { - val profileJson = getProfileInfoTask.execute(GetProfileInfoTask.Params(it)) - User.fromJson(it, profileJson) - } + val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder() + .setInputData(workerData) + .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS) + .build() + + workManagerProvider.workManager + .beginUniqueWork("USER_UPDATE_QUEUE", ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest) + .enqueue() } private fun handleUserIdsWithDeviceUpdate(userIdsWithDeviceUpdate: Iterable) { crossSigningService.onUsersDeviceUpdate(userIdsWithDeviceUpdate.toList()) } - - private suspend fun List.saveLocally() { - val userEntities = map { user -> UserEntityFactory.create(user) } - Timber.d("## saveLocally()") - monarchy.awaitTransaction { - Timber.d("## saveLocally() - in transaction") - it.insertOrUpdate(userEntities) - } - Timber.d("## saveLocally() - END") - } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UpdateUserWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UpdateUserWorker.kt new file mode 100644 index 0000000000..646e09f30c --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UpdateUserWorker.kt @@ -0,0 +1,99 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.session.sync.handler + +import android.content.Context +import androidx.work.WorkerParameters +import com.zhuinden.monarchy.Monarchy +import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.session.user.model.User +import org.matrix.android.sdk.internal.SessionManager +import org.matrix.android.sdk.internal.crypto.crosssigning.UpdateTrustWorker +import org.matrix.android.sdk.internal.crypto.crosssigning.UpdateTrustWorkerDataRepository +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.session.SessionComponent +import org.matrix.android.sdk.internal.session.profile.GetProfileInfoTask +import org.matrix.android.sdk.internal.session.user.UserEntityFactory +import org.matrix.android.sdk.internal.util.awaitTransaction +import org.matrix.android.sdk.internal.util.logLimit +import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker +import timber.log.Timber +import javax.inject.Inject + +/** + * Note: We reuse the same type [UpdateTrustWorker.Params], since the inout data are the same. + */ +internal class UpdateUserWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) : + SessionSafeCoroutineWorker(context, params, sessionManager, UpdateTrustWorker.Params::class.java) { + + @SessionDatabase + @Inject lateinit var monarchy: Monarchy + @Inject lateinit var updateTrustWorkerDataRepository: UpdateTrustWorkerDataRepository + @Inject lateinit var getProfileInfoTask: GetProfileInfoTask + + override fun injectWith(injector: SessionComponent) { + injector.inject(this) + } + + override suspend fun doSafeWork(params: UpdateTrustWorker.Params): Result { + val userList = params.filename + ?.let { updateTrustWorkerDataRepository.getParam(it) } + ?.userIds + ?: params.updatedUserIds.orEmpty() + + // List should not be empty, but let's avoid go further in case of empty list + if (userList.isNotEmpty()) { + Timber.v("## UpdateUserWorker - updating users: ${userList.logLimit()}") + fetchAndUpdateUsers(userList) + } + + cleanup(params) + return Result.success() + } + + private suspend fun fetchAndUpdateUsers(userIdsToFetch: Collection) { + fetchUsers(userIdsToFetch) + .takeIf { it.isNotEmpty() } + ?.saveLocally() + } + + private suspend fun fetchUsers(userIdsToFetch: Collection) = userIdsToFetch.mapNotNull { + tryOrNull { + val profileJson = getProfileInfoTask.execute(GetProfileInfoTask.Params(it)) + User.fromJson(it, profileJson) + } + } + + private suspend fun List.saveLocally() { + val userEntities = map { user -> UserEntityFactory.create(user) } + Timber.d("## saveLocally()") + monarchy.awaitTransaction { + Timber.d("## saveLocally() - in transaction") + it.insertOrUpdate(userEntities) + } + Timber.d("## saveLocally() - END") + } + + private fun cleanup(params: UpdateTrustWorker.Params) { + params.filename + ?.let { updateTrustWorkerDataRepository.delete(it) } + } + + override fun buildErrorParams(params: UpdateTrustWorker.Params, message: String): UpdateTrustWorker.Params { + return params.copy(lastFailureMessage = params.lastFailureMessage ?: message) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt index 83f9532870..80bbbb7938 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt @@ -30,6 +30,7 @@ import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.Dea import org.matrix.android.sdk.internal.session.room.send.MultipleEventSendingDispatcherWorker import org.matrix.android.sdk.internal.session.room.send.RedactEventWorker import org.matrix.android.sdk.internal.session.room.send.SendEventWorker +import org.matrix.android.sdk.internal.session.sync.handler.UpdateUserWorker import org.matrix.android.sdk.internal.session.sync.job.SyncWorker import timber.log.Timber import javax.inject.Inject @@ -62,6 +63,8 @@ internal class MatrixWorkerFactory @Inject constructor(private val sessionManage SyncWorker(appContext, workerParameters, sessionManager) UpdateTrustWorker::class.java.name -> UpdateTrustWorker(appContext, workerParameters, sessionManager) + UpdateUserWorker::class.java.name -> + UpdateUserWorker(appContext, workerParameters, sessionManager) UploadContentWorker::class.java.name -> UploadContentWorker(appContext, workerParameters, sessionManager) DeactivateLiveLocationShareWorker::class.java.name ->