diff --git a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt index 122b5a8ddd59..e0064c84d399 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt @@ -503,13 +503,17 @@ internal class BackgroundJobManagerImpl( override fun isAutoUploadIgnoringPowerSavingScheduled(syncedFolderID: Long): Boolean = workManager.isWorkScheduled(autoUploadIgnorePowerSavingTag(syncedFolderID)) + // An enqueued run that never starts still counts as scheduled and, with ExistingWorkPolicy.KEEP, swallows every + // later request for its folder. Explicit "sync now" runs stay, because restarting them would drop the override. override fun cancelEnqueuedAutoUploads() { workManager.getWorkInfosByTag(formatClassTag(AutoUploadWorker::class)) .get() - .filter { it.state == WorkInfo.State.ENQUEUED && it.periodicityInfo != null } + .filter { it.state == WorkInfo.State.ENQUEUED && !it.isIgnoringPowerSaving() } .forEach { workManager.cancelWorkById(it.id) } } + private fun WorkInfo.isIgnoringPowerSaving(): Boolean = tags.any { it.endsWith("_$TAG_SUFFIX_IGNORE_POWER_SAVING") } + override fun schedulePeriodicAutoUpload() { val request = periodicRequestBuilder( jobClass = AutoUploadRescanWorker::class, diff --git a/app/src/test/java/com/nextcloud/client/jobs/BackgroundJobManagerImplTest.kt b/app/src/test/java/com/nextcloud/client/jobs/BackgroundJobManagerImplTest.kt new file mode 100644 index 000000000000..cc5e003dadbf --- /dev/null +++ b/app/src/test/java/com/nextcloud/client/jobs/BackgroundJobManagerImplTest.kt @@ -0,0 +1,68 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.client.jobs + +import androidx.work.WorkInfo +import androidx.work.WorkManager +import com.google.common.util.concurrent.Futures +import com.nextcloud.client.core.Clock +import com.nextcloud.client.jobs.autoUpload.AutoUploadWorker +import com.nextcloud.client.preferences.AppPreferences +import org.junit.Test +import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.stub +import org.mockito.kotlin.verify +import java.util.UUID + +class BackgroundJobManagerImplTest { + + private val classTag = BackgroundJobManagerImpl.formatClassTag(AutoUploadWorker::class) + private val workManager: WorkManager = mock() + private val manager = BackgroundJobManagerImpl(workManager, mock(), mock()) + + private fun autoUploadWork(state: WorkInfo.State, vararg extraTags: String): WorkInfo = + WorkInfo(UUID.randomUUID(), state, setOf(classTag, *extraTags)) + + private fun givenAutoUploadWorks(vararg works: WorkInfo) { + workManager.stub { on { getWorkInfosByTag(classTag) } doReturn Futures.immediateFuture(works.toList()) } + } + + @Test + fun `rescan restarts an enqueued folder run that never started`() { + val stuck = autoUploadWork(WorkInfo.State.ENQUEUED, "name:immediate_files_sync_1") + givenAutoUploadWorks(stuck) + + manager.cancelEnqueuedAutoUploads() + + verify(workManager).cancelWorkById(stuck.id) + } + + @Test + fun `rescan leaves running and finished folder runs alone`() { + val running = autoUploadWork(WorkInfo.State.RUNNING) + val succeeded = autoUploadWork(WorkInfo.State.SUCCEEDED) + givenAutoUploadWorks(running, succeeded) + + manager.cancelEnqueuedAutoUploads() + + verify(workManager, never()).cancelWorkById(any()) + } + + @Test + fun `rescan keeps an enqueued sync now run that ignores power saving`() { + val syncNow = autoUploadWork(WorkInfo.State.ENQUEUED, "immediate_files_sync_1_ignore_power_saving") + givenAutoUploadWorks(syncNow) + + manager.cancelEnqueuedAutoUploads() + + verify(workManager, never()).cancelWorkById(any()) + } +}