Add a periodic DB vacuuming task
This commit is contained in:
committed by
Jorge Martin Espinosa
parent
9c72310cb4
commit
482d7e0648
@@ -12,7 +12,7 @@ import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import timber.log.Timber
|
||||
|
||||
interface VacuumStoresUseCase {
|
||||
fun interface VacuumStoresUseCase {
|
||||
suspend operator fun invoke()
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ class DefaultVacuumStoresUseCase(
|
||||
private val matrixClient: MatrixClient,
|
||||
) : VacuumStoresUseCase {
|
||||
override suspend fun invoke() {
|
||||
matrixClient.vacuumStores()
|
||||
matrixClient.performDatabaseVacuum()
|
||||
.onFailure { Timber.e(it, "Failed to vacuum stores") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||
import io.element.android.features.preferences.impl.developer.tracing.LogLevelItem
|
||||
import io.element.android.features.preferences.impl.tasks.FakeClearCacheUseCase
|
||||
import io.element.android.features.preferences.impl.tasks.FakeComputeCacheSizeUseCase
|
||||
import io.element.android.features.preferences.impl.tasks.VacuumStoresUseCase
|
||||
import io.element.android.features.rageshake.api.preferences.aRageshakePreferencesState
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
@@ -212,6 +213,23 @@ class DeveloperSettingsPresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - VacuumStores action invokes the VacuumStoresUseCase`() = runTest {
|
||||
var vacuumCalled = false
|
||||
val presenter = createDeveloperSettingsPresenter(
|
||||
vacuumStoresUseCase = VacuumStoresUseCase {
|
||||
vacuumCalled = true
|
||||
}
|
||||
)
|
||||
presenter.test {
|
||||
val state = awaitItem()
|
||||
assertThat(vacuumCalled).isFalse()
|
||||
state.eventSink(DeveloperSettingsEvents.VacuumStores)
|
||||
skipItems(1)
|
||||
assertThat(vacuumCalled).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createDeveloperSettingsPresenter(
|
||||
sessionId: SessionId = A_SESSION_ID,
|
||||
featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(
|
||||
@@ -230,6 +248,7 @@ class DeveloperSettingsPresenterTest {
|
||||
preferencesStore: InMemoryAppPreferencesStore = InMemoryAppPreferencesStore(),
|
||||
buildMeta: BuildMeta = aBuildMeta(),
|
||||
enterpriseService: EnterpriseService = FakeEnterpriseService(),
|
||||
vacuumStoresUseCase: VacuumStoresUseCase = VacuumStoresUseCase {},
|
||||
): DeveloperSettingsPresenter {
|
||||
return DeveloperSettingsPresenter(
|
||||
sessionId = sessionId,
|
||||
@@ -240,6 +259,7 @@ class DeveloperSettingsPresenterTest {
|
||||
appPreferencesStore = preferencesStore,
|
||||
buildMeta = buildMeta,
|
||||
enterpriseService = enterpriseService,
|
||||
vacuumStoresUseCase = vacuumStoresUseCase,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ interface MatrixClient {
|
||||
*/
|
||||
suspend fun markRoomAsFullyRead(roomId: RoomId, eventId: EventId): Result<Unit>
|
||||
|
||||
suspend fun vacuumStores(): Result<Unit>
|
||||
suspend fun performDatabaseVacuum(): Result<Unit>
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,6 +34,7 @@ dependencies {
|
||||
implementation(projects.libraries.featureflag.api)
|
||||
implementation(projects.libraries.network)
|
||||
implementation(projects.libraries.preferences.api)
|
||||
implementation(projects.libraries.workmanager.api)
|
||||
implementation(projects.services.analytics.api)
|
||||
implementation(projects.services.toolbox.api)
|
||||
api(projects.libraries.matrix.api)
|
||||
@@ -49,6 +50,7 @@ dependencies {
|
||||
testImplementation(projects.libraries.preferences.test)
|
||||
testImplementation(projects.libraries.previewutils)
|
||||
testImplementation(projects.libraries.sessionStorage.test)
|
||||
testImplementation(projects.libraries.workmanager.test)
|
||||
testImplementation(projects.services.analytics.test)
|
||||
testImplementation(projects.services.toolbox.test)
|
||||
}
|
||||
|
||||
@@ -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,8 +733,9 @@ class RustMatrixClient(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun vacuumStores(): Result<Unit> = withContext(sessionDispatcher) {
|
||||
override suspend fun performDatabaseVacuum(): Result<Unit> = withContext(sessionDispatcher) {
|
||||
runCatchingExceptions {
|
||||
Timber.d("Performing database vacuuming for session $sessionId...")
|
||||
innerClient.optimizeStores()
|
||||
}
|
||||
}
|
||||
@@ -756,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(
|
||||
|
||||
@@ -27,6 +27,7 @@ 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
|
||||
@@ -63,6 +64,7 @@ class RustMatrixClientFactory(
|
||||
private val timelineEventTypeFilterFactory: TimelineEventTypeFilterFactory,
|
||||
private val clientBuilderProvider: ClientBuilderProvider,
|
||||
private val sqliteStoreBuilderProvider: SqliteStoreBuilderProvider,
|
||||
private val workManagerScheduler: WorkManagerScheduler,
|
||||
) {
|
||||
private val sessionDelegate = RustClientSessionDelegate(sessionStore, appCoroutineScope, coroutineDispatchers)
|
||||
|
||||
@@ -116,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'")
|
||||
}
|
||||
|
||||
@@ -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,38 @@
|
||||
/*
|
||||
* 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.Assisted
|
||||
import dev.zacsweers.metro.AssistedInject
|
||||
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
|
||||
|
||||
@AssistedInject
|
||||
class VacuumDatabaseWorker(
|
||||
@Assisted workerParams: WorkerParameters,
|
||||
@ApplicationContext private val context: Context,
|
||||
private val matrixClientProvider: MatrixClientProvider,
|
||||
) : CoroutineWorker(context, workerParams) {
|
||||
companion object {
|
||||
const val SESSION_ID_PARAM = "session_id"
|
||||
}
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
val sessionId = inputData.getString(SESSION_ID_PARAM)?.let(::SessionId) ?: return Result.failure()
|
||||
val client = matrixClientProvider.getOrRestore(sessionId).getOrNull() ?: return Result.failure()
|
||||
return client.performDatabaseVacuum()
|
||||
.fold(
|
||||
onSuccess = { Result.success() },
|
||||
onFailure = { Result.failure() }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,11 @@ import io.element.android.libraries.network.useragent.SimpleUserAgentProvider
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
|
||||
import io.element.android.libraries.sessionstorage.test.aSessionData
|
||||
import io.element.android.libraries.workmanager.api.WorkManagerRequest
|
||||
import io.element.android.libraries.workmanager.test.FakeWorkManagerScheduler
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.services.toolbox.test.systemclock.FakeSystemClock
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
@@ -30,9 +33,14 @@ import java.io.File
|
||||
class RustMatrixClientFactoryTest {
|
||||
@Test
|
||||
fun test() = runTest {
|
||||
val sut = createRustMatrixClientFactory()
|
||||
val scheduleVacuumLambda = lambdaRecorder<WorkManagerRequest, Unit> {}
|
||||
val workManagerScheduler = FakeWorkManagerScheduler(submitLambda = scheduleVacuumLambda)
|
||||
val sut = createRustMatrixClientFactory(workManagerScheduler = workManagerScheduler)
|
||||
|
||||
val result = sut.create(aSessionData())
|
||||
|
||||
assertThat(result.sessionId).isEqualTo(SessionId("@alice:server.org"))
|
||||
scheduleVacuumLambda.assertions().isCalledOnce()
|
||||
result.destroy()
|
||||
}
|
||||
}
|
||||
@@ -43,6 +51,7 @@ fun TestScope.createRustMatrixClientFactory(
|
||||
updateUserProfileResult = { _, _, _ -> },
|
||||
),
|
||||
clientBuilderProvider: ClientBuilderProvider = FakeClientBuilderProvider(),
|
||||
workManagerScheduler: FakeWorkManagerScheduler = FakeWorkManagerScheduler(),
|
||||
) = RustMatrixClientFactory(
|
||||
cacheDirectory = cacheDirectory,
|
||||
appCoroutineScope = backgroundScope,
|
||||
@@ -57,4 +66,5 @@ fun TestScope.createRustMatrixClientFactory(
|
||||
timelineEventTypeFilterFactory = FakeTimelineEventTypeFilterFactory(),
|
||||
clientBuilderProvider = clientBuilderProvider,
|
||||
sqliteStoreBuilderProvider = FakeSqliteStoreBuilderProvider(),
|
||||
workManagerScheduler = workManagerScheduler,
|
||||
)
|
||||
|
||||
@@ -22,6 +22,7 @@ import io.element.android.libraries.matrix.test.A_USER_NAME
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
|
||||
import io.element.android.libraries.sessionstorage.test.aSessionData
|
||||
import io.element.android.libraries.workmanager.test.FakeWorkManagerScheduler
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.services.toolbox.test.systemclock.FakeSystemClock
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
@@ -116,5 +117,6 @@ class RustMatrixClientTest {
|
||||
timelineEventTypeFilterFactory = FakeTimelineEventTypeFilterFactory(),
|
||||
featureFlagService = FakeFeatureFlagService(),
|
||||
analyticsService = FakeAnalyticsService(),
|
||||
workManagerScheduler = FakeWorkManagerScheduler(submitLambda = {}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -104,6 +104,7 @@ class FakeMatrixClient(
|
||||
private val getRecentEmojisLambda: () -> Result<List<String>> = { Result.success(emptyList()) },
|
||||
private val addRecentEmojiLambda: (String) -> Result<Unit> = { Result.success(Unit) },
|
||||
private val markRoomAsFullyReadResult: (RoomId, EventId) -> Result<Unit> = { _, _ -> lambdaError() },
|
||||
private val performDatabaseVacuumLambda: () -> Result<Unit> = { lambdaError() },
|
||||
) : MatrixClient {
|
||||
var setDisplayNameCalled: Boolean = false
|
||||
private set
|
||||
@@ -351,4 +352,8 @@ class FakeMatrixClient(
|
||||
override suspend fun markRoomAsFullyRead(roomId: RoomId, eventId: EventId): Result<Unit> {
|
||||
return markRoomAsFullyReadResult(roomId, eventId)
|
||||
}
|
||||
|
||||
override suspend fun performDatabaseVacuum(): Result<Unit> {
|
||||
return performDatabaseVacuumLambda()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.push.api.push.NotificationEventRequest
|
||||
import io.element.android.libraries.push.impl.notifications.model.ResolvedPushEvent
|
||||
import io.element.android.libraries.push.impl.workmanager.SyncNotificationWorkManagerRequest
|
||||
import io.element.android.libraries.push.impl.workmanager.WorkerDataConverter
|
||||
import io.element.android.libraries.push.impl.workmanager.SyncNotificationsWorkerDataConverter
|
||||
import io.element.android.libraries.workmanager.api.WorkManagerScheduler
|
||||
import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -50,7 +50,7 @@ class DefaultNotificationResolverQueue(
|
||||
private val appCoroutineScope: CoroutineScope,
|
||||
private val workManagerScheduler: WorkManagerScheduler,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
private val workerDataConverter: WorkerDataConverter,
|
||||
private val workerDataConverter: SyncNotificationsWorkerDataConverter,
|
||||
private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider,
|
||||
) : NotificationResolverQueue {
|
||||
companion object {
|
||||
|
||||
@@ -49,7 +49,7 @@ class FetchNotificationsWorker(
|
||||
private val workManagerScheduler: WorkManagerScheduler,
|
||||
private val syncOnNotifiableEvent: SyncOnNotifiableEvent,
|
||||
private val coroutineDispatchers: CoroutineDispatchers,
|
||||
private val workerDataConverter: WorkerDataConverter,
|
||||
private val workerDataConverter: SyncNotificationsWorkerDataConverter,
|
||||
private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider,
|
||||
) : CoroutineWorker(context, workerParams) {
|
||||
override suspend fun doWork(): Result = withContext(coroutineDispatchers.io) {
|
||||
|
||||
@@ -26,7 +26,7 @@ import java.security.InvalidParameterException
|
||||
class SyncNotificationWorkManagerRequest(
|
||||
private val sessionId: SessionId,
|
||||
private val notificationEventRequests: List<NotificationEventRequest>,
|
||||
private val workerDataConverter: WorkerDataConverter,
|
||||
private val workerDataConverter: SyncNotificationsWorkerDataConverter,
|
||||
private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider,
|
||||
) : WorkManagerRequest {
|
||||
override fun build(): Result<List<WorkRequest>> {
|
||||
|
||||
@@ -21,7 +21,7 @@ import io.element.android.libraries.push.api.push.NotificationEventRequest
|
||||
import timber.log.Timber
|
||||
|
||||
@Inject
|
||||
class WorkerDataConverter(
|
||||
class SyncNotificationsWorkerDataConverter(
|
||||
private val json: JsonProvider,
|
||||
) {
|
||||
fun serialize(notificationEventRequests: List<NotificationEventRequest>): Result<List<Data>> {
|
||||
@@ -47,7 +47,7 @@ import io.element.android.libraries.push.impl.notifications.model.NotifiableEven
|
||||
import io.element.android.libraries.push.impl.notifications.model.ResolvedPushEvent
|
||||
import io.element.android.libraries.push.impl.test.DefaultTestPush
|
||||
import io.element.android.libraries.push.impl.troubleshoot.DiagnosticPushHandler
|
||||
import io.element.android.libraries.push.impl.workmanager.WorkerDataConverter
|
||||
import io.element.android.libraries.push.impl.workmanager.SyncNotificationsWorkerDataConverter
|
||||
import io.element.android.libraries.pushproviders.api.PushData
|
||||
import io.element.android.libraries.pushstore.api.UserPushStore
|
||||
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
|
||||
@@ -718,7 +718,7 @@ class DefaultPushHandlerTest {
|
||||
appCoroutineScope = backgroundScope,
|
||||
workManagerScheduler = workManagerScheduler,
|
||||
featureFlagService = featureFlagService,
|
||||
workerDataConverter = WorkerDataConverter(DefaultJsonProvider()),
|
||||
workerDataConverter = SyncNotificationsWorkerDataConverter(DefaultJsonProvider()),
|
||||
buildVersionSdkIntProvider = FakeBuildVersionSdkIntProvider(33),
|
||||
),
|
||||
appCoroutineScope = backgroundScope,
|
||||
|
||||
@@ -177,7 +177,7 @@ class FetchNotificationWorkerTest {
|
||||
workManagerScheduler = workManagerScheduler,
|
||||
syncOnNotifiableEvent = syncOnNotifiableEvent,
|
||||
coroutineDispatchers = testCoroutineDispatchers(),
|
||||
workerDataConverter = WorkerDataConverter(DefaultJsonProvider()),
|
||||
workerDataConverter = SyncNotificationsWorkerDataConverter(DefaultJsonProvider()),
|
||||
buildVersionSdkIntProvider = FakeBuildVersionSdkIntProvider(33),
|
||||
)
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ class SyncNotificationWorkManagerRequestTest {
|
||||
val request = createSyncNotificationWorkManagerRequest(
|
||||
sessionId = A_SESSION_ID,
|
||||
notificationEventRequests = listOf(aNotificationEventRequest()),
|
||||
workerDataConverter = WorkerDataConverter({ error("error during serialization") })
|
||||
workerDataConverter = SyncNotificationsWorkerDataConverter({ error("error during serialization") })
|
||||
)
|
||||
val result = request.build()
|
||||
assertThat(result.isFailure).isTrue()
|
||||
@@ -88,7 +88,7 @@ class SyncNotificationWorkManagerRequestTest {
|
||||
private fun createSyncNotificationWorkManagerRequest(
|
||||
sessionId: SessionId,
|
||||
notificationEventRequests: List<NotificationEventRequest>,
|
||||
workerDataConverter: WorkerDataConverter = WorkerDataConverter(DefaultJsonProvider()),
|
||||
workerDataConverter: SyncNotificationsWorkerDataConverter = SyncNotificationsWorkerDataConverter(DefaultJsonProvider()),
|
||||
sdkVersion: Int = 33,
|
||||
) = SyncNotificationWorkManagerRequest(
|
||||
sessionId = sessionId,
|
||||
|
||||
@@ -57,10 +57,10 @@ class WorkerDataConverterTest {
|
||||
providerInfo = "info$it",
|
||||
)
|
||||
}
|
||||
val sut = WorkerDataConverter(DefaultJsonProvider())
|
||||
val sut = SyncNotificationsWorkerDataConverter(DefaultJsonProvider())
|
||||
val serialized = sut.serialize(data)
|
||||
assertThat(serialized.getOrNull()?.size).isGreaterThan(1)
|
||||
assertThat(serialized.getOrNull()?.size).isEqualTo(100 / WorkerDataConverter.CHUNK_SIZE)
|
||||
assertThat(serialized.getOrNull()?.size).isEqualTo(100 / SyncNotificationsWorkerDataConverter.CHUNK_SIZE)
|
||||
// All the items are present
|
||||
val deserialized = serialized.getOrNull()?.flatMap { sut.deserialize(it)!! }
|
||||
assertThat(deserialized).containsExactlyElementsIn(data)
|
||||
@@ -76,10 +76,10 @@ class WorkerDataConverterTest {
|
||||
providerInfo = "info$it",
|
||||
)
|
||||
}
|
||||
val sut = WorkerDataConverter(DefaultJsonProvider())
|
||||
val sut = SyncNotificationsWorkerDataConverter(DefaultJsonProvider())
|
||||
val serialized = sut.serialize(data)
|
||||
assertThat(serialized.getOrNull()?.size).isGreaterThan(1)
|
||||
assertThat(serialized.getOrNull()?.size).isEqualTo(100 / WorkerDataConverter.CHUNK_SIZE + 1)
|
||||
assertThat(serialized.getOrNull()?.size).isEqualTo(100 / SyncNotificationsWorkerDataConverter.CHUNK_SIZE + 1)
|
||||
// All the items are present
|
||||
val deserialized = serialized.getOrNull()?.flatMap { sut.deserialize(it)!! }
|
||||
assertThat(deserialized).containsExactlyElementsIn(data)
|
||||
@@ -112,7 +112,7 @@ class WorkerDataConverterTest {
|
||||
)
|
||||
}
|
||||
val data = (data1 + data2 + data3).shuffled()
|
||||
val sut = WorkerDataConverter(DefaultJsonProvider())
|
||||
val sut = SyncNotificationsWorkerDataConverter(DefaultJsonProvider())
|
||||
val serialized = sut.serialize(data)
|
||||
assertThat(serialized.getOrNull()?.size).isEqualTo(2)
|
||||
// All the items are present
|
||||
@@ -133,7 +133,7 @@ class WorkerDataConverterTest {
|
||||
}
|
||||
|
||||
private fun testIdentity(data: List<NotificationEventRequest>) {
|
||||
val sut = WorkerDataConverter(DefaultJsonProvider())
|
||||
val sut = SyncNotificationsWorkerDataConverter(DefaultJsonProvider())
|
||||
val serialized = sut.serialize(data).getOrThrow()
|
||||
val result = sut.deserialize(serialized.first())
|
||||
assertThat(result).isEqualTo(data)
|
||||
|
||||
@@ -12,16 +12,19 @@ import io.element.android.libraries.matrix.api.core.SessionId
|
||||
|
||||
interface WorkManagerScheduler {
|
||||
fun submit(workManagerRequest: WorkManagerRequest)
|
||||
fun hasPendingWork(sessionId: SessionId, requestType: WorkManagerRequestType): Boolean
|
||||
fun cancel(sessionId: SessionId)
|
||||
}
|
||||
|
||||
fun workManagerTag(sessionId: SessionId, requestType: WorkManagerRequestType): String {
|
||||
val prefix = when (requestType) {
|
||||
WorkManagerRequestType.NOTIFICATION_SYNC -> "notifications"
|
||||
WorkManagerRequestType.DB_VACUUM -> "db_vacuum"
|
||||
}
|
||||
return "$prefix-$sessionId"
|
||||
}
|
||||
|
||||
enum class WorkManagerRequestType {
|
||||
NOTIFICATION_SYNC,
|
||||
DB_VACUUM,
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ package io.element.android.libraries.workmanager.impl
|
||||
import androidx.work.WorkManager
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.libraries.core.bool.orFalse
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.sessionstorage.api.observer.SessionListener
|
||||
@@ -51,6 +52,10 @@ class DefaultWorkManagerScheduler(
|
||||
)
|
||||
}
|
||||
|
||||
override fun hasPendingWork(sessionId: SessionId, requestType: WorkManagerRequestType): Boolean {
|
||||
return workManager.getWorkInfosByTag(workManagerTag(sessionId, requestType)).get()?.isNotEmpty().orFalse()
|
||||
}
|
||||
|
||||
override fun cancel(sessionId: SessionId) {
|
||||
Timber.d("Cancelling work for sessionId: $sessionId")
|
||||
for (requestType in WorkManagerRequestType.entries) {
|
||||
|
||||
@@ -10,17 +10,23 @@ package io.element.android.libraries.workmanager.test
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.workmanager.api.WorkManagerRequest
|
||||
import io.element.android.libraries.workmanager.api.WorkManagerRequestType
|
||||
import io.element.android.libraries.workmanager.api.WorkManagerScheduler
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakeWorkManagerScheduler(
|
||||
private val submitLambda: (WorkManagerRequest) -> Unit = { lambdaError() },
|
||||
private val hasPendingWorkLambda: (SessionId, WorkManagerRequestType) -> Boolean = { _, _ -> false },
|
||||
private val cancelLambda: (SessionId) -> Unit = { lambdaError() },
|
||||
) : WorkManagerScheduler {
|
||||
override fun submit(workManagerRequest: WorkManagerRequest) {
|
||||
submitLambda(workManagerRequest)
|
||||
}
|
||||
|
||||
override fun hasPendingWork(sessionId: SessionId, requestType: WorkManagerRequestType): Boolean {
|
||||
return hasPendingWorkLambda(sessionId, requestType)
|
||||
}
|
||||
|
||||
override fun cancel(sessionId: SessionId) {
|
||||
cancelLambda(sessionId)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user