Skip to content
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6355200
Added function to parallelise uploads. Still need to address non-thre…
k0raph Jan 11, 2026
6aef506
Fixed notification manager updates jumping around and improved thread…
k0raph Jan 11, 2026
1a5bac6
fixed FileUploadHelper to make sure uploads are correctly reflecting …
k0raph Jan 11, 2026
df66baa
removed magic number
k0raph Jan 12, 2026
a13d265
Merge branch 'master' into parallelise-uploads
k0raph Jan 12, 2026
032f7cf
extracted various classes, favouring dependency injection to make tes…
k0raph Jan 18, 2026
61ea286
Merge branch 'parallelise-uploads' of github.com:k0raph/nextcloud-and…
k0raph Jan 18, 2026
cdbfd9f
extracted various classes, favouring dependency injection to make tes…
k0raph Jan 18, 2026
3c31dbe
added a prefer param to allow the user to configure the number of con…
k0raph Jan 18, 2026
99ae6f8
Merge pull request #1 from k0raph/user-configurable-max-concurrent-up…
k0raph Jan 18, 2026
ac3f5c1
Merge branch 'master' into parallelise-uploads
k0raph Jan 18, 2026
0100b1f
addressed codacy issues
k0raph Jan 19, 2026
4d641dc
Merge branch 'parallelise-uploads' of github.com:k0raph/nextcloud-and…
k0raph Jan 19, 2026
fea4862
Merge branch 'master' into parallelise-uploads
k0raph Jan 19, 2026
40188f2
ran spotless apply
k0raph Jan 20, 2026
a1f1232
Merge branch 'master' into parallelise-uploads
k0raph Jan 20, 2026
cf8eb86
Merge branch 'master' into parallelise-uploads
k0raph Jan 20, 2026
67bdf0b
Explorting alternative solution - parallelising workers - tests added
k0raph Jan 20, 2026
bda6728
Merge branch 'parallelise-uploads' of github.com:k0raph/nextcloud-and…
k0raph Jan 20, 2026
94872b2
fixed FileUploadWorker to correctly update progress of uploading file…
k0raph Jan 20, 2026
d92b579
ran spotless apply
k0raph Jan 20, 2026
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
@@ -0,0 +1,72 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2026 Raphael Vieira raphaelecv.projects@gmail.com
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
*/
package com.nextcloud.client.jobs.upload

import androidx.work.WorkManager
import com.owncloud.android.AbstractOnServerIT
import com.owncloud.android.files.services.NameCollisionPolicy
import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation
import com.owncloud.android.operations.UploadFileOperation
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test

class FileUploadWorkerIT : AbstractOnServerIT() {

private lateinit var workManager: WorkManager
private lateinit var fileUploadHelper: FileUploadHelper

@Before
fun setUp() {
workManager = WorkManager.getInstance(targetContext)
fileUploadHelper = FileUploadHelper.instance()
}

@Test
fun multipleFilesUploadBatch() {
val file1 = getDummyFile("empty.txt")
val file2 = getDummyFile("nonEmpty.txt")
val remotePath1 = "/batch_upload_1_${System.currentTimeMillis()}.txt"
val remotePath2 = "/batch_upload_2_${System.currentTimeMillis()}.txt"

fileUploadHelper.uploadNewFiles(
user,
arrayOf(file1.absolutePath, file2.absolutePath),
arrayOf(remotePath1, remotePath2),
FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
true,
UploadFileOperation.CREATED_BY_USER,
false,
false,
NameCollisionPolicy.DEFAULT
)

// waiting for the upload jobs to finish
val tag = "files_upload" + user.accountName
var isFinished = false
val startTime = System.currentTimeMillis()
val timeout = 60000L

while (!isFinished && System.currentTimeMillis() - startTime < timeout) {
val workInfos = workManager.getWorkInfosByTag(tag).get()
if (workInfos.isNotEmpty() && workInfos.all { it.state.isFinished }) {
isFinished = true
} else {
Thread.sleep(1000)
}
}

assertTrue("Batch upload jobs did not finish within timeout", isFinished)

// Verifying both files are uploaded to the server
val result1 = ReadFileRemoteOperation(remotePath1).execute(client)
assertTrue("File 1 should be on server", result1.isSuccess)

val result2 = ReadFileRemoteOperation(remotePath2).execute(client)
assertTrue("File 2 should be on server", result2.isSuccess)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@ import com.nextcloud.client.jobs.download.FileDownloadWorker
import com.nextcloud.client.jobs.metadata.MetadataWorker
import com.nextcloud.client.jobs.offlineOperations.OfflineOperationsWorker
import com.nextcloud.client.jobs.folderDownload.FolderDownloadWorker
import com.nextcloud.client.jobs.upload.FileUploadOperationFactory
import com.nextcloud.client.jobs.upload.FileUploadWorker
import com.nextcloud.client.jobs.upload.UploadNotificationManager
import com.nextcloud.client.logger.Logger
import com.nextcloud.client.network.ClientFactory
import com.nextcloud.client.network.ConnectivityService
import com.nextcloud.client.preferences.AppPreferences
import com.owncloud.android.datamodel.ArbitraryDataProvider
Expand All @@ -39,6 +42,7 @@ import com.owncloud.android.utils.theme.ViewThemeUtils
import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
import javax.inject.Provider
import kotlin.random.Random

/**
* This factory is responsible for creating all background jobs and for injecting worker dependencies.
Expand All @@ -65,6 +69,8 @@ class BackgroundJobFactory @Inject constructor(
private val localBroadcastManager: Provider<LocalBroadcastManager>,
private val generatePdfUseCase: GeneratePDFUseCase,
private val syncedFolderProvider: SyncedFolderProvider,
private val clientFactory: ClientFactory,
private val fileUploadOperationFactory: FileUploadOperationFactory,
private val database: NextcloudDatabase
) : WorkerFactory() {

Expand Down Expand Up @@ -237,7 +243,10 @@ class BackgroundJobFactory @Inject constructor(
localBroadcastManager.get(),
backgroundJobManager.get(),
preferences,
clientFactory,
fileUploadOperationFactory,
context,
UploadNotificationManager(context, viewThemeUtils.get(), Random.nextInt()),
params
)

Expand Down
18 changes: 18 additions & 0 deletions app/src/main/java/com/nextcloud/client/jobs/JobsModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ import android.content.ContextWrapper
import androidx.work.Configuration
import androidx.work.WorkManager
import com.nextcloud.client.core.Clock
import com.nextcloud.client.device.PowerManagementService
import com.nextcloud.client.jobs.upload.FileUploadOperationFactory
import com.nextcloud.client.jobs.upload.FileUploadOperationFactoryImpl
import com.nextcloud.client.network.ConnectivityService
import com.nextcloud.client.preferences.AppPreferences
import com.owncloud.android.datamodel.UploadsStorageManager
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
Expand Down Expand Up @@ -41,4 +46,17 @@ class JobsModule {
clock: Clock,
preferences: AppPreferences
): BackgroundJobManager = BackgroundJobManagerImpl(workManager, clock, preferences)

@Provides
fun fileUploadOperationFactory(
uploadsStorageManager: UploadsStorageManager,
connectivityService: ConnectivityService,
powerManagementService: PowerManagementService,
context: Context
): FileUploadOperationFactory = FileUploadOperationFactoryImpl(
uploadsStorageManager,
connectivityService,
powerManagementService,
context
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import com.nextcloud.client.database.entity.toUploadEntity
import com.nextcloud.client.device.BatteryStatus
import com.nextcloud.client.device.PowerManagementService
import com.nextcloud.client.jobs.BackgroundJobManager
import com.nextcloud.client.jobs.upload.FileUploadWorker.Companion.currentUploadFileOperation
import com.nextcloud.client.notifications.AppWideNotificationManager
import com.nextcloud.client.jobs.upload.FileUploadWorker.Companion.activeUploadFileOperations
import com.nextcloud.client.network.Connectivity
import com.nextcloud.client.network.ConnectivityService
import com.nextcloud.client.notifications.AppWideNotificationManager
import com.nextcloud.utils.extensions.getUploadIds
import com.owncloud.android.MainApp
import com.owncloud.android.R
Expand Down Expand Up @@ -372,17 +372,14 @@ class FileUploadHelper {

@Suppress("ReturnCount")
fun isUploadingNow(upload: OCUpload?): Boolean {
val currentUploadFileOperation = currentUploadFileOperation
if (currentUploadFileOperation == null || currentUploadFileOperation.user == null) return false
if (upload == null || upload.accountName != currentUploadFileOperation.user.accountName) return false

return if (currentUploadFileOperation.oldFile != null) {
// For file conflicts check old file remote path
upload.remotePath == currentUploadFileOperation.remotePath ||
upload.remotePath == currentUploadFileOperation.oldFile!!
.remotePath
} else {
upload.remotePath == currentUploadFileOperation.remotePath
upload ?: return false

return activeUploadFileOperations.values.any { operation ->
operation.user?.accountName == upload.accountName &&
(
upload.remotePath == operation.remotePath ||
upload.remotePath == operation.oldFile?.remotePath
)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2026 Raphael Vieira raphaelecv.projects@gmail.com
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
*/

package com.nextcloud.client.jobs.upload

import android.content.Context
import com.nextcloud.client.account.User
import com.nextcloud.client.device.PowerManagementService
import com.nextcloud.client.network.ConnectivityService
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.datamodel.UploadsStorageManager
import com.owncloud.android.db.OCUpload
import com.owncloud.android.operations.UploadFileOperation
import javax.inject.Inject

interface FileUploadOperationFactory {
fun create(upload: OCUpload, user: User, storageManager: FileDataStorageManager): UploadFileOperation
}

class FileUploadOperationFactoryImpl @Inject constructor(
private val uploadsStorageManager: UploadsStorageManager,
private val connectivityService: ConnectivityService,
private val powerManagementService: PowerManagementService,
private val context: Context
) : FileUploadOperationFactory {
override fun create(upload: OCUpload, user: User, storageManager: FileDataStorageManager): UploadFileOperation =
UploadFileOperation(
uploadsStorageManager,
connectivityService,
powerManagementService,
user,
null,
upload,
upload.nameCollisionPolicy,
upload.localAction,
context,
upload.isUseWifiOnly,
upload.isWhileChargingOnly,
storageManager
)
}
Loading