Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ interface BackgroundJobManager {
fun isAutoUploadScheduled(syncedFolderID: Long): Boolean
fun isStartFileUploadJobScheduled(accountName: String): Boolean
fun isAutoUploadIgnoringPowerSavingScheduled(syncedFolderID: Long): Boolean
fun schedulePeriodicAutoUpload()
fun schedulePeriodicAutoUpload(resetSchedule: Boolean = false)
fun cancelEnqueuedAutoUploads()
fun cancelFilesDownloadJob(accountName: String, fileId: Long)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -512,14 +512,19 @@ internal class BackgroundJobManagerImpl(
.forEach { workManager.cancelWorkById(it.id) }
}

override fun schedulePeriodicAutoUpload() {
override fun schedulePeriodicAutoUpload(resetSchedule: Boolean) {
val request = periodicRequestBuilder(
jobClass = AutoUploadRescanWorker::class,
jobName = JOB_PERIODIC_FILES_SYNC,
constraints = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
).build()

workManager.enqueueUniquePeriodicWork(JOB_PERIODIC_FILES_SYNC, ExistingPeriodicWorkPolicy.KEEP, request)
val policy = if (resetSchedule) {
ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE
} else {
ExistingPeriodicWorkPolicy.KEEP
}
workManager.enqueueUniquePeriodicWork(JOB_PERIODIC_FILES_SYNC, policy, request)
}

override fun startAutoUpload(syncedFolder: SyncedFolder, overridePowerSaving: Boolean) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,8 @@ class UploadListFragment :
activity,
preferences,
userAccountManager,
uploadsStorageManager
uploadsStorageManager,
backgroundJobManager
) { uploadListAdapter.notifyDataSetChanged() }

activity.addMenuProvider(menuProvider, viewLifecycleOwner, Lifecycle.State.RESUMED)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import android.view.MenuInflater
import android.view.MenuItem
import androidx.core.view.MenuProvider
import com.nextcloud.client.account.UserAccountManager
import com.nextcloud.client.jobs.BackgroundJobManager
import com.nextcloud.client.jobs.upload.AlbumFileUploadWorker
import com.nextcloud.client.jobs.upload.FileUploadHelper
import com.nextcloud.client.jobs.upload.FileUploadWorker
Expand All @@ -24,6 +25,7 @@ class UploadListMenuProvider(
private val preferences: AppPreferences,
private val userAccountManager: UserAccountManager,
private val uploadsStorageManager: UploadsStorageManager,
private val backgroundJobManager: BackgroundJobManager,
private val onGlobalPauseToggled: () -> Unit
) : MenuProvider {

Expand Down Expand Up @@ -68,6 +70,9 @@ class UploadListMenuProvider(
val ids = uploadsStorageManager.getCurrentUploadIds(user.accountName)
uploadHelper.cancelAndRestartUploadJob(user, ids)
}
if (!paused) {
backgroundJobManager.schedulePeriodicAutoUpload(resetSchedule = true)
}
onGlobalPauseToggled()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* 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.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequest
import androidx.work.WorkManager
import com.nextcloud.client.jobs.autoUpload.AutoUploadRescanWorker
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoMoreInteractions
import java.util.concurrent.TimeUnit

class AutoUploadScheduleTest {

private val workManager: WorkManager = mock()
private val manager: BackgroundJobManager = BackgroundJobManagerImpl(workManager, mock(), mock())

@Test
fun `startup preserves the existing periodic scan`() {
manager.schedulePeriodicAutoUpload()

verify(workManager).enqueueUniquePeriodicWork(
eq(BackgroundJobManagerImpl.JOB_PERIODIC_FILES_SYNC),
eq(ExistingPeriodicWorkPolicy.KEEP),
any()
)
verifyNoMoreInteractions(workManager)
}

@Test
fun `resume replaces only the periodic coordinator with an immediately eligible scan`() {
manager.schedulePeriodicAutoUpload(resetSchedule = true)

val requests = argumentCaptor<PeriodicWorkRequest>()
verify(workManager).enqueueUniquePeriodicWork(
eq(BackgroundJobManagerImpl.JOB_PERIODIC_FILES_SYNC),
eq(ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE),
requests.capture()
)
verifyNoMoreInteractions(workManager)

val request = requests.firstValue
val spec = request.workSpec
assertEquals(AutoUploadRescanWorker::class.java.name, spec.workerClassName)
assertEquals(NetworkType.CONNECTED, spec.constraints.requiredNetworkType)
assertEquals(TimeUnit.MINUTES.toMillis(15), spec.intervalDuration)
assertEquals(spec.intervalDuration, spec.flexDuration)
assertEquals(0L, spec.initialDelay)
spec.lastEnqueueTime = ENQUEUE_TIME
assertEquals(ENQUEUE_TIME, spec.calculateNextRunTime())
assertTrue(request.tags.contains(BackgroundJobManagerImpl.TAG_ALL))
assertTrue(
request.tags.contains(
BackgroundJobManagerImpl.formatNameTag(BackgroundJobManagerImpl.JOB_PERIODIC_FILES_SYNC)
)
)
}

companion object {
private const val ENQUEUE_TIME = 1_000_000L
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.fragment.uploadList

import android.content.Context
import android.view.MenuItem
import com.nextcloud.client.account.UserAccountManager
import com.nextcloud.client.jobs.BackgroundJobManager
import com.nextcloud.client.jobs.upload.AlbumFileUploadWorker
import com.nextcloud.client.jobs.upload.FileUploadHelper
import com.nextcloud.client.jobs.upload.FileUploadWorker
import com.nextcloud.client.preferences.AppPreferences
import com.owncloud.android.R
import io.mockk.Runs
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.unmockkObject
import io.mockk.verify
import io.mockk.verifyOrder
import org.junit.After
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test

class UploadListMenuProviderTest {

private val preferences = mockk<AppPreferences>(relaxed = true)
private val manager = mockk<BackgroundJobManager>(relaxed = true)
private val accountManager = mockk<UserAccountManager>()
private val onPauseToggled = mockk<() -> Unit>(relaxed = true)
private val menuItem = mockk<MenuItem>(relaxed = true)
private var paused = false
private val provider = UploadListMenuProvider(
mockk<Context>(relaxed = true),
preferences,
accountManager,
mockk(),
manager,
onPauseToggled
)

@Before
fun setUp() {
mockkObject(FileUploadHelper, FileUploadWorker, AlbumFileUploadWorker)
every { FileUploadHelper.instance() } returns mockk(relaxed = true)
every { FileUploadWorker.pauseActiveUploads() } just Runs
every { AlbumFileUploadWorker.pauseActiveUploads() } just Runs
every { accountManager.allUsers } returns emptyList()
every { preferences.isGlobalUploadPaused() } answers { paused }
every { preferences.setGlobalUploadPaused(any()) } answers { paused = firstArg() }
every { menuItem.itemId } returns R.id.action_toggle_global_pause
}

@After
fun tearDown() {
unmockkObject(FileUploadHelper, FileUploadWorker, AlbumFileUploadWorker)
}

@Test
fun `resume clears the pause before resetting the automatic scan`() {
paused = true

assertTrue(provider.onMenuItemSelected(menuItem))

assertFalse(paused)
verifyOrder {
preferences.setGlobalUploadPaused(false)
manager.schedulePeriodicAutoUpload(resetSchedule = true)
onPauseToggled()
}
verify(exactly = 1) { manager.schedulePeriodicAutoUpload(true) }
}

@Test
fun `pause stops active transfers without restarting automatic scans`() {
assertTrue(provider.onMenuItemSelected(menuItem))

assertTrue(paused)
verify { FileUploadWorker.pauseActiveUploads() }
verify { AlbumFileUploadWorker.pauseActiveUploads() }
verify(exactly = 0) { manager.schedulePeriodicAutoUpload(any()) }
}

@Test
fun `unrelated menu actions leave the scan schedule alone`() {
every { menuItem.itemId } returns R.id.action_search

assertFalse(provider.onMenuItemSelected(menuItem))

verify(exactly = 0) { preferences.setGlobalUploadPaused(any()) }
verify(exactly = 0) { manager.schedulePeriodicAutoUpload(any()) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,22 @@ class FilesSyncHelperTest {
assertEquals(1, startedFolderCount)
verify(backgroundJobManager).startAutoUpload(enabled, false)
}

@Test
fun `rescan preserves scheduled folder work and starts only idle enabled folders`() {
val scheduled = syncedFolder(id = 1, enabled = true)
val idle = syncedFolder(id = 2, enabled = true)
val disabled = syncedFolder(id = 3, enabled = false)
whenever(backgroundJobManager.isAutoUploadScheduled(scheduled.id)).thenReturn(true)

val count = FilesSyncHelper.startAutoUploadForEnabledSyncedFoldersIfNotActive(
provider(scheduled, idle, disabled),
backgroundJobManager
)

assertEquals(1, count)
verify(backgroundJobManager, never()).startAutoUpload(scheduled, false)
verify(backgroundJobManager, never()).startAutoUpload(disabled, false)
verify(backgroundJobManager).startAutoUpload(idle, false)
}
}