From 53ff78a26bb02c32533747d95ee0d6ce12d7e847 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 6 Feb 2026 11:54:16 +0100 Subject: [PATCH 01/12] fix(auto-upload): file detection Signed-off-by: alperozturk96 --- .../client/database/dao/FileSystemDao.kt | 4 + .../client/database/dao/UploadDao.kt | 4 +- .../client/jobs/BackgroundJobFactory.kt | 2 +- .../jobs/autoUpload/AutoUploadWorker.kt | 103 +----------------- .../jobs/autoUpload/FileSystemRepository.kt | 54 ++++++--- .../jobs/autoUpload/SyncFolderHelper.kt | 102 +++++++++++++++++ .../client/jobs/upload/FileUploadHelper.kt | 2 +- 7 files changed, 153 insertions(+), 118 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/client/jobs/autoUpload/SyncFolderHelper.kt diff --git a/app/src/main/java/com/nextcloud/client/database/dao/FileSystemDao.kt b/app/src/main/java/com/nextcloud/client/database/dao/FileSystemDao.kt index 91a03044afde..af363b743283 100644 --- a/app/src/main/java/com/nextcloud/client/database/dao/FileSystemDao.kt +++ b/app/src/main/java/com/nextcloud/client/database/dao/FileSystemDao.kt @@ -8,6 +8,7 @@ package com.nextcloud.client.database.dao import androidx.room.Dao +import androidx.room.Delete import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query @@ -19,6 +20,9 @@ interface FileSystemDao { @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertOrReplace(filesystemEntity: FilesystemEntity) + @Delete + fun delete(entity: FilesystemEntity) + @Query( """ DELETE FROM ${ProviderMeta.ProviderTableMeta.FILESYSTEM_TABLE_NAME} diff --git a/app/src/main/java/com/nextcloud/client/database/dao/UploadDao.kt b/app/src/main/java/com/nextcloud/client/database/dao/UploadDao.kt index 7f09f4aa0543..2e1d86d726d5 100644 --- a/app/src/main/java/com/nextcloud/client/database/dao/UploadDao.kt +++ b/app/src/main/java/com/nextcloud/client/database/dao/UploadDao.kt @@ -41,7 +41,7 @@ interface UploadDao { "WHERE ${ProviderTableMeta.UPLOADS_ACCOUNT_NAME} = :accountName " + "AND ${ProviderTableMeta.UPLOADS_REMOTE_PATH} = :remotePath" ) - fun deleteByAccountAndRemotePath(remotePath: String, accountName: String) + fun deleteByRemotePathAndAccountName(remotePath: String, accountName: String) @Query( "SELECT * FROM " + ProviderTableMeta.UPLOADS_TABLE_NAME + @@ -51,7 +51,7 @@ interface UploadDao { ) fun getUploadById(id: Long, accountName: String): UploadEntity? - @Insert(onConflict = OnConflictStrategy.Companion.REPLACE) + @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertOrReplace(entity: UploadEntity): Long @Query( diff --git a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt index 4b1dd1d659b2..8b6012c0e8db 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt @@ -179,7 +179,7 @@ class BackgroundJobFactory @Inject constructor( powerManagementService = powerManagementService, syncedFolderProvider = syncedFolderProvider, backgroundJobManager = backgroundJobManager.get(), - repository = FileSystemRepository(dao = database.fileSystemDao(), context), + repository = FileSystemRepository(dao = database.fileSystemDao(), uploadsStorageManager, context), viewThemeUtils = viewThemeUtils.get(), localBroadcastManager = localBroadcastManager.get() ) diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt index af38b417a2b5..f2af22abf14e 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt @@ -9,8 +9,6 @@ package com.nextcloud.client.jobs.autoUpload import android.app.Notification import android.content.Context -import android.content.res.Resources -import androidx.exifinterface.media.ExifInterface import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.work.CoroutineWorker import androidx.work.ForegroundInfo @@ -25,13 +23,11 @@ import com.nextcloud.client.jobs.upload.FileUploadBroadcastManager import com.nextcloud.client.jobs.upload.FileUploadWorker import com.nextcloud.client.jobs.utils.UploadErrorNotificationManager import com.nextcloud.client.network.ConnectivityService -import com.nextcloud.client.preferences.SubFolderRule import com.nextcloud.utils.extensions.isNonRetryable import com.nextcloud.utils.extensions.updateStatus import com.owncloud.android.R import com.owncloud.android.datamodel.ArbitraryDataProviderImpl import com.owncloud.android.datamodel.FileDataStorageManager -import com.owncloud.android.datamodel.MediaFolderType import com.owncloud.android.datamodel.SyncedFolder import com.owncloud.android.datamodel.SyncedFolderProvider import com.owncloud.android.datamodel.UploadsStorageManager @@ -44,16 +40,10 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.operations.UploadFileOperation import com.owncloud.android.ui.activity.SettingsActivity -import com.owncloud.android.utils.FileStorageUtils -import com.owncloud.android.utils.MimeType import com.owncloud.android.utils.theme.ViewThemeUtils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.io.File -import java.text.ParsePosition -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.TimeZone @Suppress("LongParameterList", "TooManyFunctions", "TooGenericExceptionCaught") class AutoUploadWorker( @@ -79,6 +69,7 @@ class AutoUploadWorker( } private val helper = AutoUploadHelper() + private val syncFolderHelper = SyncFolderHelper(context) private val fileUploadBroadcastManager = FileUploadBroadcastManager(localBroadcastManager) private lateinit var syncedFolder: SyncedFolder private val notificationManager = AutoUploadNotificationManager(context, viewThemeUtils, NOTIFICATION_ID) @@ -233,13 +224,6 @@ class AutoUploadWorker( Log_OC.d(TAG, "Exception collectFileChangesFromContentObserverWork: $e") } - private fun prepareDateFormat(): SimpleDateFormat { - val currentLocale = context.resources.configuration.locales[0] - return SimpleDateFormat("yyyy:MM:dd HH:mm:ss", currentLocale).apply { - timeZone = TimeZone.getTimeZone(TimeZone.getDefault().id) - } - } - private fun getUserOrReturn(syncedFolder: SyncedFolder): User? { val optionalUser = userAccountManager.getUser(syncedFolder.account) if (!optionalUser.isPresent) { @@ -274,13 +258,10 @@ class AutoUploadWorker( @Suppress("LongMethod", "DEPRECATION", "TooGenericExceptionCaught") private suspend fun uploadFiles(syncedFolder: SyncedFolder) = withContext(Dispatchers.IO) { - val dateFormat = prepareDateFormat() val user = getUserOrReturn(syncedFolder) ?: return@withContext val ocAccount = OwnCloudAccount(user.toPlatformAccount(), context) val client = OwnCloudClientManagerFactory.getDefaultSingleton() .getClientFor(ocAccount, context) - val lightVersion = context.resources.getBoolean(R.bool.syncedFolder_light) - val currentLocale = context.resources.configuration.locales[0] trySetForeground() updateNotification() @@ -299,14 +280,7 @@ class AutoUploadWorker( filePathsWithIds.forEachIndexed { batchIndex, (path, id) -> val file = File(path) val localPath = file.absolutePath - val remotePath = getRemotePath( - file, - syncedFolder, - dateFormat, - lightVersion, - context.resources, - currentLocale - ) + val remotePath = syncFolderHelper.getAutoUploadRemotePath(syncedFolder, file) try { val entityResult = getEntityResult(user, localPath, remotePath) @@ -471,79 +445,6 @@ class AutoUploadWorker( FileDataStorageManager(user, context.contentResolver) ) - private fun getRemotePath( - file: File, - syncedFolder: SyncedFolder, - sFormatter: SimpleDateFormat, - lightVersion: Boolean, - resources: Resources, - currentLocale: Locale - ): String { - val lastModificationTime = calculateLastModificationTime(file, syncedFolder, sFormatter) - - val (remoteFolder, useSubfolders, subFolderRule) = if (lightVersion) { - Triple( - resources.getString(R.string.syncedFolder_remote_folder), - resources.getBoolean(R.bool.syncedFolder_light_use_subfolders), - SubFolderRule.YEAR_MONTH - ) - } else { - Triple( - syncedFolder.remotePath, - syncedFolder.isSubfolderByDate, - syncedFolder.subfolderRule - ) - } - - return FileStorageUtils.getInstantUploadFilePath( - file, - currentLocale, - remoteFolder, - syncedFolder.localPath, - lastModificationTime, - useSubfolders, - subFolderRule - ) - } - - private fun hasExif(file: File): Boolean { - val mimeType = FileStorageUtils.getMimeTypeFromName(file.absolutePath) - return MimeType.JPEG.equals(mimeType, ignoreCase = true) || MimeType.TIFF.equals(mimeType, ignoreCase = true) - } - - @Suppress("NestedBlockDepth") - private fun calculateLastModificationTime( - file: File, - syncedFolder: SyncedFolder, - formatter: SimpleDateFormat - ): Long { - var lastModificationTime = file.lastModified() - if (MediaFolderType.IMAGE == syncedFolder.type && hasExif(file)) { - Log_OC.d(TAG, "calculateLastModificationTime exif found") - - @Suppress("TooGenericExceptionCaught") - try { - val exifInterface = ExifInterface(file.absolutePath) - val exifDate = exifInterface.getAttribute(ExifInterface.TAG_DATETIME) - if (!exifDate.isNullOrBlank()) { - val pos = ParsePosition(0) - val dateTime = formatter.parse(exifDate, pos) - if (dateTime != null) { - lastModificationTime = dateTime.time - Log_OC.w(TAG, "calculateLastModificationTime calculatedTime is: $lastModificationTime") - } else { - Log_OC.w(TAG, "calculateLastModificationTime dateTime is empty") - } - } else { - Log_OC.w(TAG, "calculateLastModificationTime exifDate is empty") - } - } catch (e: Exception) { - Log_OC.d(TAG, "Failed to get the proper time " + e.localizedMessage) - } - } - return lastModificationTime - } - private fun sendUploadFinishEvent(operation: UploadFileOperation, result: RemoteOperationResult<*>) { fileUploadBroadcastManager.sendFinished( operation, diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/FileSystemRepository.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/FileSystemRepository.kt index f9c12b36b9a1..b50da8c76b2c 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/FileSystemRepository.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/FileSystemRepository.kt @@ -15,19 +15,37 @@ import com.nextcloud.client.database.entity.FilesystemEntity import com.nextcloud.utils.extensions.shouldSkipFile import com.nextcloud.utils.extensions.toFile import com.owncloud.android.datamodel.SyncedFolder +import com.owncloud.android.datamodel.UploadsStorageManager import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.utils.SyncedFolderUtils import java.io.File import java.util.zip.CRC32 @Suppress("TooGenericExceptionCaught", "NestedBlockDepth", "MagicNumber", "ReturnCount") -class FileSystemRepository(private val dao: FileSystemDao, private val context: Context) { +class FileSystemRepository( + private val dao: FileSystemDao, + private val uploadsStorageManager: UploadsStorageManager, + private val context: Context +) { + private val syncFolderHelper = SyncFolderHelper(context) companion object { private const val TAG = "FilesystemRepository" const val BATCH_SIZE = 50 } + fun deleteAutoUploadEntityAndUploadEntity(syncedFolder: SyncedFolder, localPath: String, entity: FilesystemEntity) { + Log_OC.d(TAG, "deleting auto upload entity and upload entity") + + val file = File(localPath) + val remotePath = syncFolderHelper.getAutoUploadRemotePath(syncedFolder, file) + uploadsStorageManager.uploadDao.deleteByRemotePathAndAccountName( + remotePath = remotePath, + accountName = syncedFolder.account + ) + dao.delete(entity) + } + suspend fun deleteByLocalPathAndId(path: String, id: Int) { dao.deleteByLocalPathAndId(path, id) } @@ -39,20 +57,23 @@ class FileSystemRepository(private val dao: FileSystemDao, private val context: val entities = dao.getAutoUploadFilesEntities(syncedFolderId, BATCH_SIZE, lastId) val filtered = mutableListOf>() - entities.forEach { - it.localPath?.let { path -> + entities.forEach { entity -> + entity.localPath?.let { path -> val file = File(path) if (!file.exists()) { Log_OC.w(TAG, "Ignoring file for upload (doesn't exist): $path") + deleteAutoUploadEntityAndUploadEntity(syncedFolder, path, entity) } else if (!SyncedFolderUtils.isQualifiedFolder(file.parent)) { Log_OC.w(TAG, "Ignoring file for upload (unqualified folder): $path") + deleteAutoUploadEntityAndUploadEntity(syncedFolder, path, entity) } else if (!SyncedFolderUtils.isFileNameQualifiedForAutoUpload(file.name)) { Log_OC.w(TAG, "Ignoring file for upload (unqualified file): $path") + deleteAutoUploadEntityAndUploadEntity(syncedFolder, path, entity) } else { Log_OC.d(TAG, "Adding path to upload: $path") - if (it.id != null) { - filtered.add(path to it.id) + if (entity.id != null) { + filtered.add(path to entity.id) } else { Log_OC.w(TAG, "cant adding path to upload, id is null") } @@ -160,22 +181,29 @@ class FileSystemRepository(private val dao: FileSystemDao, private val context: } val entity = dao.getFileByPathAndFolder(localPath, syncedFolder.id.toString()) - val fileSentForUpload = (entity != null && entity.fileSentForUpload == 1) - if (fileSentForUpload) { - Log_OC.d(TAG, "File was sent for upload, checking if it changed...") - } val fileModified = (lastModified ?: file.lastModified()) - if (syncedFolder.shouldSkipFile(file, fileModified, creationTime, fileSentForUpload)) { + if (fileModified <= 0L) { + Log_OC.d(TAG, "file is deleted, skipping: $localPath") + entity?.let { + deleteAutoUploadEntityAndUploadEntity(syncedFolder, localPath, entity) + } return } - if (fileSentForUpload) { - Log_OC.d(TAG, "File was sent for upload before but has changed, will re-upload: $localPath") + val hasNotChanged = entity?.fileModified == fileModified + val fileSentForUpload = entity?.fileSentForUpload == 1 + + if (hasNotChanged && fileSentForUpload) { + Log_OC.d(TAG, "File hasn't changed since last scan. skipping: $localPath") + return } - val crc = getFileChecksum(file) + if (syncedFolder.shouldSkipFile(file, fileModified, creationTime, fileSentForUpload)) { + return + } + val crc = getFileChecksum(file) val newEntity = FilesystemEntity( id = entity?.id, localPath = localPath, diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/SyncFolderHelper.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/SyncFolderHelper.kt new file mode 100644 index 000000000000..6100487f6210 --- /dev/null +++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/SyncFolderHelper.kt @@ -0,0 +1,102 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.client.jobs.autoUpload + +import android.content.Context +import androidx.exifinterface.media.ExifInterface +import com.nextcloud.client.preferences.SubFolderRule +import com.owncloud.android.R +import com.owncloud.android.datamodel.MediaFolderType +import com.owncloud.android.datamodel.SyncedFolder +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.utils.FileStorageUtils +import com.owncloud.android.utils.MimeType +import java.io.File +import java.text.ParsePosition +import java.text.SimpleDateFormat +import java.util.TimeZone + +class SyncFolderHelper(context: Context) { + + private val resources = context.resources + private val isLightVersion = resources.getBoolean(R.bool.syncedFolder_light) + + companion object { + private const val TAG = "SyncFolderHelper" + } + + fun getAutoUploadRemotePath(syncedFolder: SyncedFolder, file: File): String { + val lastModificationTime = calculateLastModificationTime(file, syncedFolder) + + val remoteFolder: String + val useSubfolders: Boolean + val subFolderRule: SubFolderRule + + if (isLightVersion) { + remoteFolder = resources.getString(R.string.syncedFolder_remote_folder) + useSubfolders = resources.getBoolean(R.bool.syncedFolder_light_use_subfolders) + subFolderRule = SubFolderRule.YEAR_MONTH + } else { + remoteFolder = syncedFolder.remotePath + useSubfolders = syncedFolder.isSubfolderByDate + subFolderRule = syncedFolder.subfolderRule + } + + return FileStorageUtils.getInstantUploadFilePath( + file, + resources.configuration.locales[0], + remoteFolder, + syncedFolder.localPath, + lastModificationTime, + useSubfolders, + subFolderRule + ) + } + + @Suppress("NestedBlockDepth") + private fun calculateLastModificationTime(file: File, syncedFolder: SyncedFolder): Long { + val currentLocale = resources.configuration.locales[0] + val formatter = SimpleDateFormat("yyyy:MM:dd HH:mm:ss", currentLocale).apply { + timeZone = TimeZone.getTimeZone(TimeZone.getDefault().id) + } + var lastModificationTime = file.lastModified() + if (MediaFolderType.IMAGE == syncedFolder.type && hasExif(file)) { + Log_OC.d(TAG, "calculateLastModificationTime exif found") + + @Suppress("TooGenericExceptionCaught") + try { + val exifInterface = ExifInterface(file.absolutePath) + val exifDate = exifInterface.getAttribute(ExifInterface.TAG_DATETIME) + if (!exifDate.isNullOrBlank()) { + val pos = ParsePosition(0) + val dateTime = formatter.parse(exifDate, pos) + if (dateTime != null) { + lastModificationTime = dateTime.time + Log_OC.w( + TAG, + "calculateLastModificationTime calculatedTime is: $lastModificationTime" + ) + } else { + Log_OC.w(TAG, "calculateLastModificationTime dateTime is empty") + } + } else { + Log_OC.w(TAG, "calculateLastModificationTime exifDate is empty") + } + } catch (e: Exception) { + Log_OC.d(TAG, "Failed to get the proper time " + e.localizedMessage) + } + } + return lastModificationTime + } + + private fun hasExif(file: File): Boolean { + val mimeType = FileStorageUtils.getMimeTypeFromName(file.absolutePath) + return mimeType.equals(MimeType.JPEG, ignoreCase = true) || + mimeType.equals(MimeType.TIFF, ignoreCase = true) + } +} diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt index 809d24c8e66d..5ef42d5aa2ea 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt @@ -261,7 +261,7 @@ class FileUploadHelper { } fun removeFileUpload(remotePath: String, accountName: String) { - uploadsStorageManager.uploadDao.deleteByAccountAndRemotePath(remotePath, accountName) + uploadsStorageManager.uploadDao.deleteByRemotePathAndAccountName(remotePath, accountName) } fun updateUploadStatus(remotePath: String, accountName: String, status: UploadStatus) { From 0735ab6a3b57636dfeb5999cc107073080aa33db Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 6 Feb 2026 11:55:22 +0100 Subject: [PATCH 02/12] fix(auto-upload): file detection Signed-off-by: alperozturk96 --- .../nextcloud/client/jobs/autoUpload/SyncFolderHelper.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/SyncFolderHelper.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/SyncFolderHelper.kt index 6100487f6210..bf0d549774b8 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/SyncFolderHelper.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/SyncFolderHelper.kt @@ -47,7 +47,7 @@ class SyncFolderHelper(context: Context) { subFolderRule = syncedFolder.subfolderRule } - return FileStorageUtils.getInstantUploadFilePath( + val result = FileStorageUtils.getInstantUploadFilePath( file, resources.configuration.locales[0], remoteFolder, @@ -56,6 +56,10 @@ class SyncFolderHelper(context: Context) { useSubfolders, subFolderRule ) + + Log_OC.d(TAG, "auto upload remote path: $result") + + return result } @Suppress("NestedBlockDepth") From 320729e593be7179978cca747378869966df099b Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 6 Feb 2026 11:56:36 +0100 Subject: [PATCH 03/12] fix(auto-upload): file detection Signed-off-by: alperozturk96 --- .../nextcloud/client/jobs/autoUpload/SyncFolderHelper.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/SyncFolderHelper.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/SyncFolderHelper.kt index bf0d549774b8..990ad440990c 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/SyncFolderHelper.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/SyncFolderHelper.kt @@ -21,16 +21,15 @@ import java.text.ParsePosition import java.text.SimpleDateFormat import java.util.TimeZone -class SyncFolderHelper(context: Context) { - - private val resources = context.resources - private val isLightVersion = resources.getBoolean(R.bool.syncedFolder_light) +class SyncFolderHelper(private val context: Context) { companion object { private const val TAG = "SyncFolderHelper" } fun getAutoUploadRemotePath(syncedFolder: SyncedFolder, file: File): String { + val resources = context.resources + val isLightVersion = resources.getBoolean(R.bool.syncedFolder_light) val lastModificationTime = calculateLastModificationTime(file, syncedFolder) val remoteFolder: String @@ -64,6 +63,7 @@ class SyncFolderHelper(context: Context) { @Suppress("NestedBlockDepth") private fun calculateLastModificationTime(file: File, syncedFolder: SyncedFolder): Long { + val resources = context.resources val currentLocale = resources.configuration.locales[0] val formatter = SimpleDateFormat("yyyy:MM:dd HH:mm:ss", currentLocale).apply { timeZone = TimeZone.getTimeZone(TimeZone.getDefault().id) From c9ccac35ca2eb9ecb2f4ee48105606425ec3e29c Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 6 Feb 2026 13:28:47 +0100 Subject: [PATCH 04/12] fix(auto-upload): file lock unlock Signed-off-by: alperozturk96 --- .../client/jobs/upload/FileUploadHelper.kt | 9 ++- .../operations/UploadFileOperation.java | 81 +++++++++++++++---- 2 files changed, 73 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt index 5ef42d5aa2ea..9478850e1be0 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt @@ -19,9 +19,9 @@ 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.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 @@ -479,7 +479,12 @@ class FileUploadHelper { } @Suppress("MagicNumber") - fun isSameFileOnRemote(user: User, localFile: File, remotePath: String, context: Context): Boolean { + fun isSameFileOnRemote(user: User?, localFile: File?, remotePath: String?, context: Context?): Boolean { + if (user == null || localFile == null || remotePath == null || context == null) { + Log_OC.e(TAG,"cannot compare remote and local file") + return false + } + // Compare remote file to local file val localLastModifiedTimestamp = localFile.lastModified() / 1000 // remote file timestamp in milli not micro sec val localCreationTimestamp = FileUtil.getCreationTimestamp(localFile) diff --git a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java index fa8b2a13c72a..652b4bb24c7f 100644 --- a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java @@ -1011,6 +1011,7 @@ private RemoteOperationResult normalUpload(OwnCloudClient client) { File expectedFile = null; FileLock fileLock = null; FileChannel channel = null; + FileInputStream fileInputStream = null; long size; @@ -1043,9 +1044,19 @@ private RemoteOperationResult normalUpload(OwnCloudClient client) { final Long creationTimestamp = FileUtil.getCreationTimestamp(originalFile); try { - channel = new RandomAccessFile(mFile.getStoragePath(), "rw").getChannel(); - fileLock = channel.tryLock(); + fileInputStream = new FileInputStream(mFile.getStoragePath()); + channel = fileInputStream.getChannel(); + try { + // request a shared lock instead of exclusive one, since we are just reading file + fileLock = channel.tryLock(0L, Long.MAX_VALUE, true); + Log_OC.d(TAG ,"file locked"); + } catch (OverlappingFileLockException e) { + // if another thread has the lock, current thread can still read the file. + Log_OC.e(TAG, "shared lock overlap detected; proceeding safely."); + } } catch (FileNotFoundException e) { + Log_OC.e(TAG, "file not found exception: normal upload"); + // this basically means that the file is on SD card // try to copy file to temporary dir if it doesn't exist String temporalPath = FileStorageUtils.getInternalTemporalPath(user.getAccountName(), mContext) + @@ -1058,8 +1069,13 @@ private RemoteOperationResult normalUpload(OwnCloudClient client) { if (result.isSuccess()) { if (temporalFile.length() == originalFile.length()) { - channel = new RandomAccessFile(temporalFile.getAbsolutePath(), "rw").getChannel(); - fileLock = channel.tryLock(); + try { + fileInputStream = new FileInputStream(temporalFile.getAbsolutePath()); + channel = fileInputStream.getChannel(); + fileLock = channel.tryLock(0L, Long.MAX_VALUE, true); + } catch (OverlappingFileLockException ex) { + Log_OC.e(TAG, "shared lock overlap detected; proceeding safely."); + } } else { result = new RemoteOperationResult<>(ResultCode.LOCK_FAILED); } @@ -1067,7 +1083,11 @@ private RemoteOperationResult normalUpload(OwnCloudClient client) { } try { - size = channel.size(); + if (channel != null && channel.isOpen()) { + size = channel.size(); + } else { + size = new File(mFile.getStoragePath()).length(); + } } catch (Exception exception) { Log_OC.e(TAG, "normalUpload, size cannot be determined from channel: " + exception); size = new File(mFile.getStoragePath()).length(); @@ -1121,28 +1141,42 @@ private RemoteOperationResult normalUpload(OwnCloudClient client) { } catch (FileNotFoundException e) { Log_OC.d(TAG, mOriginalStoragePath + " not exists anymore"); result = new RemoteOperationResult<>(ResultCode.LOCAL_FILE_NOT_FOUND); - } catch (OverlappingFileLockException e) { - Log_OC.d(TAG, "Overlapping file lock exception"); - result = new RemoteOperationResult<>(ResultCode.LOCK_FAILED); } catch (Exception e) { result = new RemoteOperationResult<>(e); } finally { mUploadStarted.set(false); - if (fileLock != null) { + if (fileLock != null && fileLock.isValid()) { try { fileLock.release(); - } catch (IOException e) { - Log_OC.e(TAG, "Failed to unlock file with path " + mOriginalStoragePath); + Log_OC.d(TAG ,"file lock released"); + } catch (IOException ignored) { + Log_OC.e(TAG, "failed to unlock file with path " + mOriginalStoragePath); } + } else { + Log_OC.e(TAG, "file lock is null"); } if (channel != null) { try { channel.close(); - } catch (IOException e) { - Log_OC.w(TAG, "Failed to close file channel"); + Log_OC.d(TAG ,"file channel closed"); + } catch (IOException ignored) { + Log_OC.e(TAG, "failed to close file channel"); } + } else { + Log_OC.e(TAG, "channel is null"); + } + + if (fileInputStream != null) { + try { + fileInputStream.close(); + Log_OC.d(TAG ,"file input stream closed"); + } catch (IOException ignored) { + Log_OC.e(TAG, "failed to close file input stream"); + } + } else { + Log_OC.e(TAG, "file input stream is null"); } if (temporalFile != null && !originalFile.equals(temporalFile)) { @@ -1248,7 +1282,24 @@ private RemoteOperationResult checkNameCollision(OCFile parentFile, break; case ASK_USER: Log_OC.d(TAG, "Name collision; asking the user what to do"); - return new RemoteOperationResult(ResultCode.SYNC_CONFLICT); + + // check if its real SYNC_CONFLICT + boolean isSameFileOnRemote = false; + if (mFile != null) { + String localPath = mFile.getStoragePath(); + + if (localPath != null) { + File localFile = new File(localPath); + isSameFileOnRemote = FileUploadHelper.Companion.instance() + .isSameFileOnRemote(user, localFile, mRemotePath, mContext); + } + } + + if (isSameFileOnRemote) { + return new RemoteOperationResult<>(ResultCode.OK); + } else { + return new RemoteOperationResult<>(ResultCode.SYNC_CONFLICT); + } } } @@ -1474,7 +1525,7 @@ private static boolean existsFile(OwnCloudClient client, return false; } else { ExistenceCheckRemoteOperation existsOperation = new ExistenceCheckRemoteOperation(remotePath, false); - RemoteOperationResult result = existsOperation.execute(client); + final var result = existsOperation.execute(client); return result.isSuccess(); } } From d4b6f10c17ceeb683792529e07caf2e3af918de9 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 6 Feb 2026 13:32:50 +0100 Subject: [PATCH 05/12] fix codacy Signed-off-by: alperozturk96 --- .../com/nextcloud/client/jobs/autoUpload/SyncFolderHelper.kt | 2 +- .../java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/SyncFolderHelper.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/SyncFolderHelper.kt index 990ad440990c..75896f372d0b 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/SyncFolderHelper.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/SyncFolderHelper.kt @@ -46,7 +46,7 @@ class SyncFolderHelper(private val context: Context) { subFolderRule = syncedFolder.subfolderRule } - val result = FileStorageUtils.getInstantUploadFilePath( + val result = FileStorageUtils.getInstantUploadFilePath( file, resources.configuration.locales[0], remoteFolder, diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt index 9478850e1be0..5bb40b30969d 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt @@ -478,10 +478,10 @@ class FileUploadHelper { } } - @Suppress("MagicNumber") + @Suppress("MagicNumber", "ReturnCount", "ComplexCondition") fun isSameFileOnRemote(user: User?, localFile: File?, remotePath: String?, context: Context?): Boolean { if (user == null || localFile == null || remotePath == null || context == null) { - Log_OC.e(TAG,"cannot compare remote and local file") + Log_OC.e(TAG, "cannot compare remote and local file") return false } From 7434d38c3a14b4ca1ebe8f8c3b9c6c38da6d7d26 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 6 Feb 2026 14:26:45 +0100 Subject: [PATCH 06/12] fix test Signed-off-by: alperozturk96 --- .../client/jobs/autoUpload/FileSystemRepository.kt | 10 +++++----- .../android/operations/UploadFileOperation.java | 12 ++++++------ .../owncloud/android/utils/AutoUploadHelperTest.kt | 4 +++- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/FileSystemRepository.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/FileSystemRepository.kt index b50da8c76b2c..d4804134f466 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/FileSystemRepository.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/FileSystemRepository.kt @@ -34,7 +34,7 @@ class FileSystemRepository( const val BATCH_SIZE = 50 } - fun deleteAutoUploadEntityAndUploadEntity(syncedFolder: SyncedFolder, localPath: String, entity: FilesystemEntity) { + fun deleteAutoUploadAndUploadEntity(syncedFolder: SyncedFolder, localPath: String, entity: FilesystemEntity) { Log_OC.d(TAG, "deleting auto upload entity and upload entity") val file = File(localPath) @@ -62,13 +62,13 @@ class FileSystemRepository( val file = File(path) if (!file.exists()) { Log_OC.w(TAG, "Ignoring file for upload (doesn't exist): $path") - deleteAutoUploadEntityAndUploadEntity(syncedFolder, path, entity) + deleteAutoUploadAndUploadEntity(syncedFolder, path, entity) } else if (!SyncedFolderUtils.isQualifiedFolder(file.parent)) { Log_OC.w(TAG, "Ignoring file for upload (unqualified folder): $path") - deleteAutoUploadEntityAndUploadEntity(syncedFolder, path, entity) + deleteAutoUploadAndUploadEntity(syncedFolder, path, entity) } else if (!SyncedFolderUtils.isFileNameQualifiedForAutoUpload(file.name)) { Log_OC.w(TAG, "Ignoring file for upload (unqualified file): $path") - deleteAutoUploadEntityAndUploadEntity(syncedFolder, path, entity) + deleteAutoUploadAndUploadEntity(syncedFolder, path, entity) } else { Log_OC.d(TAG, "Adding path to upload: $path") @@ -186,7 +186,7 @@ class FileSystemRepository( if (fileModified <= 0L) { Log_OC.d(TAG, "file is deleted, skipping: $localPath") entity?.let { - deleteAutoUploadEntityAndUploadEntity(syncedFolder, localPath, entity) + deleteAutoUploadAndUploadEntity(syncedFolder, localPath, entity) } return } diff --git a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java index 652b4bb24c7f..1f4a6b611fa7 100644 --- a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java @@ -1049,7 +1049,7 @@ private RemoteOperationResult normalUpload(OwnCloudClient client) { try { // request a shared lock instead of exclusive one, since we are just reading file fileLock = channel.tryLock(0L, Long.MAX_VALUE, true); - Log_OC.d(TAG ,"file locked"); + Log_OC.d(TAG ,"🔒" + "file locked"); } catch (OverlappingFileLockException e) { // if another thread has the lock, current thread can still read the file. Log_OC.e(TAG, "shared lock overlap detected; proceeding safely."); @@ -1089,7 +1089,7 @@ private RemoteOperationResult normalUpload(OwnCloudClient client) { size = new File(mFile.getStoragePath()).length(); } } catch (Exception exception) { - Log_OC.e(TAG, "normalUpload, size cannot be determined from channel: " + exception); + Log_OC.e(TAG, "size cannot be determined from channel: " + exception); size = new File(mFile.getStoragePath()).length(); } @@ -1149,7 +1149,7 @@ private RemoteOperationResult normalUpload(OwnCloudClient client) { if (fileLock != null && fileLock.isValid()) { try { fileLock.release(); - Log_OC.d(TAG ,"file lock released"); + Log_OC.d(TAG ,"🔓" + "file lock released"); } catch (IOException ignored) { Log_OC.e(TAG, "failed to unlock file with path " + mOriginalStoragePath); } @@ -1160,7 +1160,7 @@ private RemoteOperationResult normalUpload(OwnCloudClient client) { if (channel != null) { try { channel.close(); - Log_OC.d(TAG ,"file channel closed"); + Log_OC.d(TAG ,"📢" + "file channel closed"); } catch (IOException ignored) { Log_OC.e(TAG, "failed to close file channel"); } @@ -1171,7 +1171,7 @@ private RemoteOperationResult normalUpload(OwnCloudClient client) { if (fileInputStream != null) { try { fileInputStream.close(); - Log_OC.d(TAG ,"file input stream closed"); + Log_OC.d(TAG ,"📝" + "file input stream closed"); } catch (IOException ignored) { Log_OC.e(TAG, "failed to close file input stream"); } @@ -1181,7 +1181,7 @@ private RemoteOperationResult normalUpload(OwnCloudClient client) { if (temporalFile != null && !originalFile.equals(temporalFile)) { boolean isTempFileDeleted = temporalFile.delete(); - Log_OC.d(TAG, "normalUpload, temp folder deletion: " + isTempFileDeleted); + Log_OC.d(TAG, "temp folder deletion: " + isTempFileDeleted); } if (result == null) { diff --git a/app/src/test/java/com/owncloud/android/utils/AutoUploadHelperTest.kt b/app/src/test/java/com/owncloud/android/utils/AutoUploadHelperTest.kt index a7aff50a8cc3..5eb368139729 100644 --- a/app/src/test/java/com/owncloud/android/utils/AutoUploadHelperTest.kt +++ b/app/src/test/java/com/owncloud/android/utils/AutoUploadHelperTest.kt @@ -15,6 +15,7 @@ import com.nextcloud.client.preferences.SubFolderRule import com.nextcloud.utils.extensions.shouldSkipFile import com.owncloud.android.datamodel.MediaFolderType import com.owncloud.android.datamodel.SyncedFolder +import com.owncloud.android.datamodel.UploadsStorageManager import io.mockk.clearAllMocks import io.mockk.mockk import org.junit.After @@ -36,6 +37,7 @@ class AutoUploadHelperTest { private val mockContext: Context = mockk(relaxed = true) private lateinit var repo: FileSystemRepository + private val mockUploadsStorageManager: UploadsStorageManager = mockk(relaxed = true) @Before fun setup() { @@ -43,7 +45,7 @@ class AutoUploadHelperTest { tempDir.mkdirs() assertTrue("Failed to create temp directory", tempDir.exists()) - repo = FileSystemRepository(mockDao, mockContext) + repo = FileSystemRepository(mockDao, mockUploadsStorageManager, mockContext) } @After From 8fa70410d2461c561afcd45e01771aec0549a3cb Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 10 Feb 2026 08:51:41 +0100 Subject: [PATCH 07/12] only update last scan time after uploading files Signed-off-by: alperozturk96 --- .../nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt index f2af22abf14e..c4f9e26034d8 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt @@ -93,7 +93,11 @@ class AutoUploadWorker( collectFileChangesFromContentObserverWork(contentUris) uploadFiles(syncedFolder) - Log_OC.d(TAG, "✅ ${syncedFolder.remotePath} finished checking files.") + // only update last scan time after uploading files + syncedFolder.lastScanTimestampMs = System.currentTimeMillis() + syncedFolderProvider.updateSyncFolder(syncedFolder) + + Log_OC.d(TAG, "✅ ${syncedFolder.remotePath} completed") Result.success() } catch (e: Exception) { Log_OC.e(TAG, "❌ failed: ${e.message}") @@ -217,8 +221,6 @@ class AutoUploadWorker( helper.insertEntries(syncedFolder, repository) } } - syncedFolder.lastScanTimestampMs = System.currentTimeMillis() - syncedFolderProvider.updateSyncFolder(syncedFolder) } } catch (e: Exception) { Log_OC.d(TAG, "Exception collectFileChangesFromContentObserverWork: $e") From dd8d79d1d914cb33b641ca058a1f9f30d811ce5f Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 11 Feb 2026 08:23:42 +0100 Subject: [PATCH 08/12] only update database within upload file operation to prevent unnecessary db updates Signed-off-by: alperozturk96 --- .../jobs/autoUpload/AutoUploadWorker.kt | 6 ---- .../client/jobs/upload/FileUploadWorker.kt | 6 +--- .../client/jobs/upload/UploadTask.kt | 1 - .../UploadStorageManagerExtensions.kt | 28 ------------------- .../datamodel/UploadsStorageManager.java | 1 - .../operations/UploadFileOperation.java | 6 ++++ 6 files changed, 7 insertions(+), 41 deletions(-) delete mode 100644 app/src/main/java/com/nextcloud/utils/extensions/UploadStorageManagerExtensions.kt diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt index c4f9e26034d8..72bd41b6e7c2 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt @@ -24,7 +24,6 @@ import com.nextcloud.client.jobs.upload.FileUploadWorker import com.nextcloud.client.jobs.utils.UploadErrorNotificationManager import com.nextcloud.client.network.ConnectivityService import com.nextcloud.utils.extensions.isNonRetryable -import com.nextcloud.utils.extensions.updateStatus import com.owncloud.android.R import com.owncloud.android.datamodel.ArbitraryDataProviderImpl import com.owncloud.android.datamodel.FileDataStorageManager @@ -313,7 +312,6 @@ class AutoUploadWorker( val result = operation.execute(client) fileUploadBroadcastManager.sendStarted(operation, context) - uploadsStorageManager.updateStatus(uploadEntity, result.isSuccess) UploadErrorNotificationManager.handleResult( context, @@ -343,10 +341,6 @@ class AutoUploadWorker( sendUploadFinishEvent(operation, result) } } catch (e: Exception) { - uploadsStorageManager.updateStatus( - uploadEntity, - UploadsStorageManager.UploadStatus.UPLOAD_FAILED - ) Log_OC.e( TAG, "Exception during upload file, localPath: $localPath, remotePath: $remotePath," + diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt index ea0bac258db8..ddea5679e41e 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt @@ -24,7 +24,6 @@ import com.nextcloud.client.network.ConnectivityService import com.nextcloud.client.preferences.AppPreferences import com.nextcloud.utils.ForegroundServiceHelper import com.nextcloud.utils.extensions.getPercent -import com.nextcloud.utils.extensions.updateStatus import com.owncloud.android.R import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.datamodel.ForegroundServiceType @@ -258,8 +257,6 @@ class FileUploadWorker( val result = withContext(Dispatchers.IO) { upload(operation, user, client) } - val entity = uploadsStorageManager.uploadDao.getUploadById(upload.uploadId, accountName) - uploadsStorageManager.updateStatus(entity, result.isSuccess) currentUploadFileOperation = null if (result.code == ResultCode.QUOTA_EXCEEDED) { @@ -346,10 +343,9 @@ class FileUploadWorker( fileUploadBroadcastManager.sendStarted(operation, context) } catch (e: Exception) { Log_OC.e(TAG, "Error uploading", e) - result = RemoteOperationResult(e) + result = RemoteOperationResult(e) } finally { if (!isStopped) { - uploadsStorageManager.updateDatabaseUploadResult(result, operation) UploadErrorNotificationManager.handleResult( context, notificationManager, diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/UploadTask.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/UploadTask.kt index 21aa3619b85d..efe129f8d765 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/UploadTask.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/UploadTask.kt @@ -78,7 +78,6 @@ class UploadTask( val client = clientProvider() uploadsStorageManager.updateDatabaseUploadStart(op) val result = op.execute(client) - uploadsStorageManager.updateDatabaseUploadResult(result, op) return Result(file, result.isSuccess) } } diff --git a/app/src/main/java/com/nextcloud/utils/extensions/UploadStorageManagerExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/UploadStorageManagerExtensions.kt deleted file mode 100644 index e86a9f0f8ec3..000000000000 --- a/app/src/main/java/com/nextcloud/utils/extensions/UploadStorageManagerExtensions.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2025 Alper Ozturk - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -package com.nextcloud.utils.extensions - -import com.nextcloud.client.database.entity.UploadEntity -import com.owncloud.android.datamodel.UploadsStorageManager - -fun UploadsStorageManager.updateStatus(entity: UploadEntity?, status: UploadsStorageManager.UploadStatus) { - entity ?: return - uploadDao.insertOrReplace(entity.withStatus(status)) -} - -fun UploadsStorageManager.updateStatus(entity: UploadEntity?, success: Boolean) { - entity ?: return - val newStatus = if (success) { - UploadsStorageManager.UploadStatus.UPLOAD_SUCCEEDED - } else { - UploadsStorageManager.UploadStatus.UPLOAD_FAILED - } - uploadDao.insertOrReplace(entity.withStatus(newStatus)) -} - -private fun UploadEntity.withStatus(newStatus: UploadsStorageManager.UploadStatus) = this.copy(status = newStatus.value) diff --git a/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java b/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java index b339655b438c..130045768810 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java @@ -554,7 +554,6 @@ public void clearSuccessfulUploads() { * Updates the persistent upload database with upload result. */ public void updateDatabaseUploadResult(RemoteOperationResult uploadResult, UploadFileOperation upload) { - // result: success or fail notification Log_OC.d(TAG, "updateDatabaseUploadResult uploadResult: " + uploadResult + " upload: " + upload); if (uploadResult.isCancelled()) { diff --git a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java index 1f4a6b611fa7..e1a8acbb3a22 100644 --- a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java @@ -577,6 +577,9 @@ private RemoteOperationResult encryptedUpload(OwnCloudClient client, OCFile pare result = new RemoteOperationResult<>(e); } finally { result = cleanupE2EUpload(fileLock, channel, e2eFiles, result, object, client, token); + + // update upload status + uploadsStorageManager.updateDatabaseUploadResult(result, this); } completeE2EUpload(result, e2eFiles, client); @@ -1189,6 +1192,9 @@ private RemoteOperationResult normalUpload(OwnCloudClient client) { } logResult(result, mOriginalStoragePath, mRemotePath); + + // update upload status + uploadsStorageManager.updateDatabaseUploadResult(result, this); } if (result.isSuccess()) { From 1915b8f812df8d6ee551c5eddcc65d5de152469b Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 11 Feb 2026 09:12:44 +0100 Subject: [PATCH 09/12] fix updateDatabaseUploadResult Signed-off-by: alperozturk96 --- .../datamodel/UploadsStorageManager.java | 83 ++++---- .../operations/UploadFileOperation.java | 201 ++++++------------ 2 files changed, 104 insertions(+), 180 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java b/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java index 130045768810..41f8ea969674 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java @@ -550,58 +550,49 @@ public void clearSuccessfulUploads() { } } - /** - * Updates the persistent upload database with upload result. - */ public void updateDatabaseUploadResult(RemoteOperationResult uploadResult, UploadFileOperation upload) { Log_OC.d(TAG, "updateDatabaseUploadResult uploadResult: " + uploadResult + " upload: " + upload); if (uploadResult.isCancelled()) { - removeUpload( - upload.getUser().getAccountName(), - upload.getRemotePath() - ); - } else { - String localPath = (FileUploadWorker.LOCAL_BEHAVIOUR_MOVE == upload.getLocalBehaviour()) - ? upload.getStoragePath() : null; - - if (uploadResult.isSuccess()) { - updateUploadStatus( - upload.getOCUploadId(), - UploadStatus.UPLOAD_SUCCEEDED, - UploadResult.UPLOADED, - upload.getRemotePath(), - localPath - ); - } else if (uploadResult.getCode() == RemoteOperationResult.ResultCode.SYNC_CONFLICT && - new FileUploadHelper().isSameFileOnRemote( - upload.getUser(), new File(upload.getStoragePath()), upload.getRemotePath(), upload.getContext())) { - - updateUploadStatus( - upload.getOCUploadId(), - UploadStatus.UPLOAD_SUCCEEDED, - UploadResult.SAME_FILE_CONFLICT, - upload.getRemotePath(), - localPath - ); - } else if (uploadResult.getCode() == RemoteOperationResult.ResultCode.LOCAL_FILE_NOT_FOUND) { - updateUploadStatus( - upload.getOCUploadId(), - UploadStatus.UPLOAD_SUCCEEDED, - UploadResult.FILE_NOT_FOUND, - upload.getRemotePath(), - localPath - ); - } else if (uploadResult.getCode() != RemoteOperationResult.ResultCode.USER_CANCELLED){ - updateUploadStatus( - upload.getOCUploadId(), - UploadStatus.UPLOAD_FAILED, - UploadResult.fromOperationResult(uploadResult), - upload.getRemotePath(), - localPath - ); + Log_OC.w(TAG, "upload is cancelled, removing upload"); + removeUpload(upload.getUser().getAccountName(), upload.getRemotePath()); + return; + } + + String localPath = (upload.getLocalBehaviour() == FileUploadWorker.LOCAL_BEHAVIOUR_MOVE) + ? upload.getStoragePath() : null; + + Log_OC.d(TAG, "local path of upload: " + localPath); + + UploadStatus status = UploadStatus.UPLOAD_FAILED; + UploadResult result = UploadResult.fromOperationResult(uploadResult); + RemoteOperationResult.ResultCode code = uploadResult.getCode(); + + if (uploadResult.isSuccess()) { + status = UploadStatus.UPLOAD_SUCCEEDED; + result = UploadResult.UPLOADED; + } else if (code == RemoteOperationResult.ResultCode.SYNC_CONFLICT) { + boolean isSame = new FileUploadHelper().isSameFileOnRemote( + upload.getUser(), new File(upload.getStoragePath()), upload.getRemotePath(), upload.getContext()); + + if (isSame) { + result = UploadResult.SAME_FILE_CONFLICT; + status = UploadStatus.UPLOAD_SUCCEEDED; + } else { + result = UploadResult.SYNC_CONFLICT; } + } else if (code == RemoteOperationResult.ResultCode.LOCAL_FILE_NOT_FOUND) { + result = UploadResult.FILE_NOT_FOUND; } + + Log_OC.d(TAG, String.format( + "Upload Finished [%s] | RemoteCode: %s | internalResult: %s | FinalStatus: %s | Path: %s", + uploadResult.isSuccess() ? "✅" : "❌", + code, + result.name(), + status, + upload.getRemotePath())); + updateUploadStatus(upload.getOCUploadId(), status, result, upload.getRemotePath(), localPath); } /** diff --git a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java index e1a8acbb3a22..a0cbcef60658 100644 --- a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java @@ -83,7 +83,9 @@ import java.nio.channels.FileLock; import java.nio.channels.OverlappingFileLockException; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @@ -1008,60 +1010,33 @@ private RemoteOperationResult checkConditions(File originalFile) { } private RemoteOperationResult normalUpload(OwnCloudClient client) { - RemoteOperationResult result = null; + RemoteOperationResult result = null; File temporalFile = null; File originalFile = new File(mOriginalStoragePath); File expectedFile = null; - FileLock fileLock = null; - FileChannel channel = null; - FileInputStream fileInputStream = null; - - long size; try { - // check conditions result = checkConditions(originalFile); + if (result != null) return result; - if (result != null) { - return result; - } - - // check name collision final var collisionResult = checkNameCollision(null, client, null, false); - if (collisionResult != null) { - result = collisionResult; - return collisionResult; - } + if (collisionResult != null) return collisionResult; String expectedPath = FileStorageUtils.getDefaultSavePathFor(user.getAccountName(), mFile); expectedFile = new File(expectedPath); result = copyFile(originalFile, expectedPath); - if (!result.isSuccess()) { - return result; - } + if (!result.isSuccess()) return result; // Get the last modification date of the file from the file system long lastModifiedTimestamp = originalFile.lastModified() / 1000; - final Long creationTimestamp = FileUtil.getCreationTimestamp(originalFile); - try { - fileInputStream = new FileInputStream(mFile.getStoragePath()); - channel = fileInputStream.getChannel(); - try { - // request a shared lock instead of exclusive one, since we are just reading file - fileLock = channel.tryLock(0L, Long.MAX_VALUE, true); - Log_OC.d(TAG ,"🔒" + "file locked"); - } catch (OverlappingFileLockException e) { - // if another thread has the lock, current thread can still read the file. - Log_OC.e(TAG, "shared lock overlap detected; proceeding safely."); - } - } catch (FileNotFoundException e) { - Log_OC.e(TAG, "file not found exception: normal upload"); + Path filePath = Paths.get(mFile.getStoragePath()); - // this basically means that the file is on SD card - // try to copy file to temporary dir if it doesn't exist + // file does not exists in storage + if (!Files.exists(filePath)) { + Log_OC.e(TAG, "file not found exception: normal upload, probably file in sd card"); String temporalPath = FileStorageUtils.getInternalTemporalPath(user.getAccountName(), mContext) + mFile.getRemotePath(); mFile.setStoragePath(temporalPath); @@ -1070,121 +1045,86 @@ private RemoteOperationResult normalUpload(OwnCloudClient client) { Files.deleteIfExists(Paths.get(temporalPath)); result = copy(originalFile, temporalFile); - if (result.isSuccess()) { - if (temporalFile.length() == originalFile.length()) { - try { - fileInputStream = new FileInputStream(temporalFile.getAbsolutePath()); - channel = fileInputStream.getChannel(); - fileLock = channel.tryLock(0L, Long.MAX_VALUE, true); - } catch (OverlappingFileLockException ex) { - Log_OC.e(TAG, "shared lock overlap detected; proceeding safely."); - } - } else { - result = new RemoteOperationResult<>(ResultCode.LOCK_FAILED); - } + if (!result.isSuccess()) return result; + + if (temporalFile.length() != originalFile.length()) { + return new RemoteOperationResult<>(ResultCode.LOCK_FAILED); } + filePath = temporalFile.toPath(); } - try { - if (channel != null && channel.isOpen()) { + // file exists in storage + try (FileChannel channel = FileChannel.open(filePath, StandardOpenOption.READ)) { + FileLock fileLock = null; + try { + // request a shared lock instead of exclusive one, since we are just reading file + fileLock = channel.tryLock(0L, Long.MAX_VALUE, true); + Log_OC.d(TAG ,"🔒" + "file locked"); + } catch (OverlappingFileLockException e) { + Log_OC.e(TAG, "shared lock overlap detected; proceeding safely."); + } + + // determine size + long size; + try { size = channel.size(); + } catch (IOException e) { + size = Files.size(filePath); + } + updateSize(size); + + // decide whether chunked or not + if (size > ChunkedFileUploadRemoteOperation.CHUNK_SIZE_MOBILE) { + boolean onWifiConnection = connectivityService.getConnectivity().isWifi(); + mUploadOperation = new ChunkedFileUploadRemoteOperation( + mFile.getStoragePath(), mFile.getRemotePath(), mFile.getMimeType(), + mFile.getEtagInConflict(), lastModifiedTimestamp, creationTimestamp, + onWifiConnection, mDisableRetries); } else { - size = new File(mFile.getStoragePath()).length(); + mUploadOperation = new UploadFileRemoteOperation( + mFile.getStoragePath(), mFile.getRemotePath(), mFile.getMimeType(), + mFile.getEtagInConflict(), lastModifiedTimestamp, creationTimestamp, + mDisableRetries); } - } catch (Exception exception) { - Log_OC.e(TAG, "size cannot be determined from channel: " + exception); - size = new File(mFile.getStoragePath()).length(); - } - - updateSize(size); - // perform the upload - if (size > ChunkedFileUploadRemoteOperation.CHUNK_SIZE_MOBILE) { - boolean onWifiConnection = connectivityService.getConnectivity().isWifi(); - - mUploadOperation = new ChunkedFileUploadRemoteOperation(mFile.getStoragePath(), - mFile.getRemotePath(), - mFile.getMimeType(), - mFile.getEtagInConflict(), - lastModifiedTimestamp, - creationTimestamp, - onWifiConnection, - mDisableRetries); - } else { - mUploadOperation = new UploadFileRemoteOperation(mFile.getStoragePath(), - mFile.getRemotePath(), - mFile.getMimeType(), - mFile.getEtagInConflict(), - lastModifiedTimestamp, - creationTimestamp, - mDisableRetries); - } - - /** - * Adds the onTransferProgress in FileUploadWorker - * {@link FileUploadWorker#onTransferProgress(long, long, long, String)()} - */ - for (OnDatatransferProgressListener mDataTransferListener : mDataTransferListeners) { - mUploadOperation.addDataTransferProgressListener(mDataTransferListener); - } + /** + * Adds the onTransferProgress in FileUploadWorker + * {@link FileUploadWorker#onTransferProgress(long, long, long, String)()} + */ + for (OnDatatransferProgressListener mDataTransferListener : mDataTransferListeners) { + mUploadOperation.addDataTransferProgressListener(mDataTransferListener); + } - if (mCancellationRequested.get()) { - throw new OperationCancelledException(); - } + if (mCancellationRequested.get()) { + throw new OperationCancelledException(); + } - if (result.isSuccess() && mUploadOperation != null) { + // Execute result = mUploadOperation.execute(client); - /// move local temporal file or original file to its corresponding + // move local temporal file or original file to its corresponding // location in the Nextcloud local folder if (!result.isSuccess() && result.getHttpCode() == HttpStatus.SC_PRECONDITION_FAILED) { result = new RemoteOperationResult<>(ResultCode.SYNC_CONFLICT); } + + if (fileLock != null && fileLock.isValid()) { + fileLock.release(); + Log_OC.d(TAG ,"🔓" + "file lock released"); + } } } catch (FileNotFoundException e) { - Log_OC.d(TAG, mOriginalStoragePath + " not exists anymore"); + Log_OC.e(TAG, mOriginalStoragePath + " not exists anymore"); result = new RemoteOperationResult<>(ResultCode.LOCAL_FILE_NOT_FOUND); } catch (Exception e) { + Log_OC.e(TAG, "normal upload exception: ", e); result = new RemoteOperationResult<>(e); } finally { mUploadStarted.set(false); - if (fileLock != null && fileLock.isValid()) { - try { - fileLock.release(); - Log_OC.d(TAG ,"🔓" + "file lock released"); - } catch (IOException ignored) { - Log_OC.e(TAG, "failed to unlock file with path " + mOriginalStoragePath); - } - } else { - Log_OC.e(TAG, "file lock is null"); - } - - if (channel != null) { - try { - channel.close(); - Log_OC.d(TAG ,"📢" + "file channel closed"); - } catch (IOException ignored) { - Log_OC.e(TAG, "failed to close file channel"); - } - } else { - Log_OC.e(TAG, "channel is null"); - } - - if (fileInputStream != null) { - try { - fileInputStream.close(); - Log_OC.d(TAG ,"📝" + "file input stream closed"); - } catch (IOException ignored) { - Log_OC.e(TAG, "failed to close file input stream"); - } - } else { - Log_OC.e(TAG, "file input stream is null"); - } - - if (temporalFile != null && !originalFile.equals(temporalFile)) { - boolean isTempFileDeleted = temporalFile.delete(); - Log_OC.d(TAG, "temp folder deletion: " + isTempFileDeleted); + // Clean up temporal file if it exists + if (temporalFile != null && !temporalFile.delete() && temporalFile.exists()) { + Log_OC.e(TAG, "Could not delete temporal file"); } if (result == null) { @@ -1192,8 +1132,6 @@ private RemoteOperationResult normalUpload(OwnCloudClient client) { } logResult(result, mOriginalStoragePath, mRemotePath); - - // update upload status uploadsStorageManager.updateDatabaseUploadResult(result, this); } @@ -1203,11 +1141,6 @@ private RemoteOperationResult normalUpload(OwnCloudClient client) { getStorageManager().saveConflict(mFile, mFile.getEtagInConflict()); } - // delete temporal file - if (temporalFile != null && temporalFile.exists() && !temporalFile.delete()) { - Log_OC.e(TAG, "Could not delete temporal file " + temporalFile.getAbsolutePath()); - } - return result; } From beeff1d6ad83e05daf8249abf5869a9ce3a99f87 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 11 Feb 2026 09:32:15 +0100 Subject: [PATCH 10/12] assign result in failed scenarious Signed-off-by: alperozturk96 --- .../android/operations/UploadFileOperation.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java index a0cbcef60658..c496b0bbc90c 100644 --- a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java @@ -1017,16 +1017,23 @@ private RemoteOperationResult normalUpload(OwnCloudClient client) { try { result = checkConditions(originalFile); - if (result != null) return result; + if (result != null) { + return result; + } final var collisionResult = checkNameCollision(null, client, null, false); - if (collisionResult != null) return collisionResult; + if (collisionResult != null) { + result = collisionResult; + return collisionResult; + } String expectedPath = FileStorageUtils.getDefaultSavePathFor(user.getAccountName(), mFile); expectedFile = new File(expectedPath); result = copyFile(originalFile, expectedPath); - if (!result.isSuccess()) return result; + if (!result.isSuccess()) { + return result; + } // Get the last modification date of the file from the file system long lastModifiedTimestamp = originalFile.lastModified() / 1000; @@ -1048,7 +1055,7 @@ private RemoteOperationResult normalUpload(OwnCloudClient client) { if (!result.isSuccess()) return result; if (temporalFile.length() != originalFile.length()) { - return new RemoteOperationResult<>(ResultCode.LOCK_FAILED); + result = new RemoteOperationResult<>(ResultCode.LOCK_FAILED); } filePath = temporalFile.toPath(); } From d3fc6dc870cd9a8ca53ad8da0665ac31fff24d1f Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 11 Feb 2026 11:25:46 +0100 Subject: [PATCH 11/12] cover edge cases Signed-off-by: alperozturk96 --- .../jobs/autoUpload/AutoUploadWorker.kt | 5 ++++ .../jobs/autoUpload/FileSystemRepository.kt | 8 ------ .../client/jobs/upload/FileUploadWorker.kt | 12 +++++++- .../UploadStorageManagerExtensions.kt | 28 +++++++++++++++++++ .../java/com/owncloud/android/MainApp.java | 2 +- .../datamodel/UploadsStorageManager.java | 2 ++ .../operations/UploadFileOperation.java | 21 ++++++++++---- 7 files changed, 63 insertions(+), 15 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/utils/extensions/UploadStorageManagerExtensions.kt diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt index 72bd41b6e7c2..01d78e1d9959 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt @@ -24,6 +24,7 @@ import com.nextcloud.client.jobs.upload.FileUploadWorker import com.nextcloud.client.jobs.utils.UploadErrorNotificationManager import com.nextcloud.client.network.ConnectivityService import com.nextcloud.utils.extensions.isNonRetryable +import com.nextcloud.utils.extensions.updateStatus import com.owncloud.android.R import com.owncloud.android.datamodel.ArbitraryDataProviderImpl import com.owncloud.android.datamodel.FileDataStorageManager @@ -341,6 +342,10 @@ class AutoUploadWorker( sendUploadFinishEvent(operation, result) } } catch (e: Exception) { + uploadsStorageManager.updateStatus( + uploadEntity, + UploadsStorageManager.UploadStatus.UPLOAD_FAILED + ) Log_OC.e( TAG, "Exception during upload file, localPath: $localPath, remotePath: $remotePath," + diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/FileSystemRepository.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/FileSystemRepository.kt index d4804134f466..17c3bddb4806 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/FileSystemRepository.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/FileSystemRepository.kt @@ -183,14 +183,6 @@ class FileSystemRepository( val entity = dao.getFileByPathAndFolder(localPath, syncedFolder.id.toString()) val fileModified = (lastModified ?: file.lastModified()) - if (fileModified <= 0L) { - Log_OC.d(TAG, "file is deleted, skipping: $localPath") - entity?.let { - deleteAutoUploadAndUploadEntity(syncedFolder, localPath, entity) - } - return - } - val hasNotChanged = entity?.fileModified == fileModified val fileSentForUpload = entity?.fileSentForUpload == 1 diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt index ddea5679e41e..e64ca25ca296 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt @@ -24,6 +24,7 @@ import com.nextcloud.client.network.ConnectivityService import com.nextcloud.client.preferences.AppPreferences import com.nextcloud.utils.ForegroundServiceHelper import com.nextcloud.utils.extensions.getPercent +import com.nextcloud.utils.extensions.updateStatus import com.owncloud.android.R import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.datamodel.ForegroundServiceType @@ -255,7 +256,7 @@ class FileUploadWorker( ) val result = withContext(Dispatchers.IO) { - upload(operation, user, client) + upload(upload, operation, user, client) } currentUploadFileOperation = null @@ -327,6 +328,7 @@ class FileUploadWorker( @Suppress("TooGenericExceptionCaught", "DEPRECATION") private suspend fun upload( + upload: OCUpload, operation: UploadFileOperation, user: User, client: OwnCloudClient @@ -343,6 +345,14 @@ class FileUploadWorker( fileUploadBroadcastManager.sendStarted(operation, context) } catch (e: Exception) { Log_OC.e(TAG, "Error uploading", e) + uploadsStorageManager.run { + uploadDao.getUploadById(upload.uploadId, user.accountName)?.let { entity -> + updateStatus( + entity, + UploadsStorageManager.UploadStatus.UPLOAD_FAILED + ) + } + } result = RemoteOperationResult(e) } finally { if (!isStopped) { diff --git a/app/src/main/java/com/nextcloud/utils/extensions/UploadStorageManagerExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/UploadStorageManagerExtensions.kt new file mode 100644 index 000000000000..e86a9f0f8ec3 --- /dev/null +++ b/app/src/main/java/com/nextcloud/utils/extensions/UploadStorageManagerExtensions.kt @@ -0,0 +1,28 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.utils.extensions + +import com.nextcloud.client.database.entity.UploadEntity +import com.owncloud.android.datamodel.UploadsStorageManager + +fun UploadsStorageManager.updateStatus(entity: UploadEntity?, status: UploadsStorageManager.UploadStatus) { + entity ?: return + uploadDao.insertOrReplace(entity.withStatus(status)) +} + +fun UploadsStorageManager.updateStatus(entity: UploadEntity?, success: Boolean) { + entity ?: return + val newStatus = if (success) { + UploadsStorageManager.UploadStatus.UPLOAD_SUCCEEDED + } else { + UploadsStorageManager.UploadStatus.UPLOAD_FAILED + } + uploadDao.insertOrReplace(entity.withStatus(newStatus)) +} + +private fun UploadEntity.withStatus(newStatus: UploadsStorageManager.UploadStatus) = this.copy(status = newStatus.value) diff --git a/app/src/main/java/com/owncloud/android/MainApp.java b/app/src/main/java/com/owncloud/android/MainApp.java index 1b979f23a433..f46ed83f14e8 100644 --- a/app/src/main/java/com/owncloud/android/MainApp.java +++ b/app/src/main/java/com/owncloud/android/MainApp.java @@ -392,7 +392,7 @@ public void disableDocumentsStorageProvider() { FilesSyncHelper.startAutoUploadForEnabledSyncedFolders(syncedFolderProvider, backgroundJobManager, new String[]{}, - true); + false); preferences.setLastAutoUploadOnStartTime(System.currentTimeMillis()); } } else if (event == Lifecycle.Event.ON_STOP) { diff --git a/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java b/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java index 41f8ea969674..492c20d943a8 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java @@ -582,6 +582,8 @@ public void updateDatabaseUploadResult(RemoteOperationResult uploadResult, Uploa result = UploadResult.SYNC_CONFLICT; } } else if (code == RemoteOperationResult.ResultCode.LOCAL_FILE_NOT_FOUND) { + // upload status is SUCCEEDED because user cannot take action about it, it will always fail + status = UploadStatus.UPLOAD_SUCCEEDED; result = UploadResult.FILE_NOT_FOUND; } diff --git a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java index c496b0bbc90c..fc8513a5e7bd 100644 --- a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java @@ -1106,8 +1106,10 @@ private RemoteOperationResult normalUpload(OwnCloudClient client) { throw new OperationCancelledException(); } - // Execute - result = mUploadOperation.execute(client); + // execute + if (result.isSuccess() && mUploadOperation != null) { + result = mUploadOperation.execute(client); + } // move local temporal file or original file to its corresponding // location in the Nextcloud local folder @@ -1129,11 +1131,20 @@ private RemoteOperationResult normalUpload(OwnCloudClient client) { } finally { mUploadStarted.set(false); - // Clean up temporal file if it exists - if (temporalFile != null && !temporalFile.delete() && temporalFile.exists()) { - Log_OC.e(TAG, "Could not delete temporal file"); + // clean up temporal file if it exists + try { + if (temporalFile != null) { + if (temporalFile.exists() && !temporalFile.delete()) { + Log_OC.e(TAG, "Could not delete temporal file"); + } + } else { + Log_OC.e(TAG, "temporal file is null, cannot delete it"); + } + } catch (Exception e) { + Log_OC.e(TAG, "an exception occurred during deletion of temporal file: ", e); } + if (result == null) { result = new RemoteOperationResult<>(ResultCode.UNKNOWN_ERROR); } From 2ee55df1e9f5eaa434140bea73a48d67f7f89ae5 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 11 Feb 2026 11:55:14 +0100 Subject: [PATCH 12/12] correct logs Signed-off-by: alperozturk96 --- .../com/owncloud/android/datamodel/UploadsStorageManager.java | 2 ++ .../com/owncloud/android/operations/UploadFileOperation.java | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java b/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java index 492c20d943a8..244f87cecfe2 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java @@ -562,6 +562,8 @@ public void updateDatabaseUploadResult(RemoteOperationResult uploadResult, Uploa String localPath = (upload.getLocalBehaviour() == FileUploadWorker.LOCAL_BEHAVIOUR_MOVE) ? upload.getStoragePath() : null; + + Log_OC.d(TAG, "local behaviour: " + upload.getLocalBehaviour()); Log_OC.d(TAG, "local path of upload: " + localPath); UploadStatus status = UploadStatus.UPLOAD_FAILED; diff --git a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java index fc8513a5e7bd..efbf642eb0c1 100644 --- a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java @@ -1138,13 +1138,12 @@ private RemoteOperationResult normalUpload(OwnCloudClient client) { Log_OC.e(TAG, "Could not delete temporal file"); } } else { - Log_OC.e(TAG, "temporal file is null, cannot delete it"); + Log_OC.d(TAG, "temporal file is null - internal storage is used instead of sd-card"); } } catch (Exception e) { Log_OC.e(TAG, "an exception occurred during deletion of temporal file: ", e); } - if (result == null) { result = new RemoteOperationResult<>(ResultCode.UNKNOWN_ERROR); }