From 21ab7a2111f824b52e975f913e72f6a7ee2bea5e Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 20 Mar 2026 09:57:17 +0100 Subject: [PATCH] draft auto upload fixes Signed-off-by: alperozturk96 --- .../client/database/dao/FileSystemDao.kt | 8 +++ .../client/database/dao/UploadDao.kt | 19 ++++++ .../client/jobs/BackgroundJobFactory.kt | 2 + .../jobs/autoUpload/FileSystemRepository.kt | 15 ++++ .../client/jobs/upload/FileUploadWorker.kt | 28 ++++++++ .../ui/activity/FileDisplayActivity.kt | 14 ++++ .../ui/activity/SyncedFoldersActivity.kt | 68 +++++++++++++------ .../android/ui/adapter/SyncedFolderAdapter.kt | 2 +- 8 files changed, 133 insertions(+), 23 deletions(-) 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 9d9be7179d3c..a662474b0020 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 @@ -67,6 +67,14 @@ interface FileSystemDao { ) fun getFileByPathAndFolder(localPath: String, syncedFolderId: String): FilesystemEntity? + @Query(""" + SELECT COUNT(*) > 0 FROM ${ProviderMeta.ProviderTableMeta.FILESYSTEM_TABLE_NAME} + WHERE ${ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_LOCAL_PATH} = :localPath + AND ${ProviderMeta.ProviderTableMeta.FILESYSTEM_SYNCED_FOLDER_ID} IS NOT NULL + LIMIT 1 +""") + suspend fun isBelongToAnyAutoFolder(localPath: String): Boolean + @Query( """ SELECT COUNT(*) > 0 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 2e1d86d726d5..659fe87e8977 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 @@ -16,6 +16,25 @@ import com.owncloud.android.db.ProviderMeta.ProviderTableMeta @Dao interface UploadDao { + @Query( + """ + SELECT COUNT(*) > 0 FROM ${ProviderTableMeta.UPLOADS_TABLE_NAME} + WHERE ${ProviderTableMeta.UPLOADS_ACCOUNT_NAME} = :accountName + AND ${ProviderTableMeta.UPLOADS_REMOTE_PATH} LIKE :remoteFolderPath || '%' + LIMIT 1 + """ + ) + suspend fun isBelongToAutoUploadFolder(accountName: String, remoteFolderPath: String): Boolean + + @Query( + """ + DELETE FROM ${ProviderTableMeta.UPLOADS_TABLE_NAME} + WHERE ${ProviderTableMeta.UPLOADS_ACCOUNT_NAME} = :accountName + AND ${ProviderTableMeta.UPLOADS_REMOTE_PATH} LIKE :remotePath || '%' + """ + ) + suspend fun deleteAllForAutoUploadFolder(accountName: String, remotePath: String) + @Query( "SELECT _id FROM " + ProviderTableMeta.UPLOADS_TABLE_NAME + " WHERE " + ProviderTableMeta.UPLOADS_STATUS + " = :status AND " + 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 a5a1a7f8ab35..5e573b1c4cd0 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt @@ -244,6 +244,8 @@ class BackgroundJobFactory @Inject constructor( localBroadcastManager.get(), backgroundJobManager.get(), preferences, + FileSystemRepository(dao = database.fileSystemDao(), uploadsStorageManager, context), + syncedFolderProvider, context, params ) 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 59203b7cc619..27a3b5eb1e19 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 @@ -19,6 +19,8 @@ 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 kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import java.io.File import java.util.zip.CRC32 @@ -35,6 +37,9 @@ class FileSystemRepository( const val BATCH_SIZE = 50 } + suspend fun isBelongToAnyAutoFolder(localPath: String): Boolean = + dao.isBelongToAnyAutoFolder(localPath) + fun deleteAutoUploadAndUploadEntity(syncedFolder: SyncedFolder, localPath: String, entity: FilesystemEntity) { Log_OC.d(TAG, "deleting auto upload entity and upload entity") @@ -182,6 +187,16 @@ class FileSystemRepository( return } + if (!SyncedFolderUtils.isQualifiedFolder(file.parent)) { + Log_OC.d(TAG, "Skipping unqualified folder: $localPath") + return + } + + if (!SyncedFolderUtils.isFileNameQualifiedForAutoUpload(file.name)) { + Log_OC.d(TAG, "Skipping unqualified file: $localPath") + return + } + if (checkFileType && !syncedFolder.containsTypedFile(file, localPath)) { Log_OC.w(TAG, "synced folder not contains typed file: $localPath") return 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 d2b9b4d7d077..eee8027040d9 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 @@ -19,6 +19,8 @@ import com.nextcloud.client.account.UserAccountManager import com.nextcloud.client.device.PowerManagementService import com.nextcloud.client.jobs.BackgroundJobManager import com.nextcloud.client.jobs.BackgroundJobManagerImpl +import com.nextcloud.client.jobs.autoUpload.FileSystemRepository +import com.nextcloud.client.jobs.autoUpload.SyncFolderHelper import com.nextcloud.client.jobs.utils.UploadErrorNotificationManager import com.nextcloud.client.network.ConnectivityService import com.nextcloud.client.preferences.AppPreferences @@ -28,6 +30,7 @@ import com.nextcloud.utils.extensions.updateStatus import com.owncloud.android.R import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.datamodel.ForegroundServiceType +import com.owncloud.android.datamodel.SyncedFolderProvider import com.owncloud.android.datamodel.ThumbnailsCacheManager import com.owncloud.android.datamodel.UploadsStorageManager import com.owncloud.android.db.OCUpload @@ -57,6 +60,8 @@ class FileUploadWorker( val localBroadcastManager: LocalBroadcastManager, private val backgroundJobManager: BackgroundJobManager, val preferences: AppPreferences, + val filesystemRepository: FileSystemRepository, + val syncedFolderProvider: SyncedFolderProvider, val context: Context, params: WorkerParameters ) : CoroutineWorker(context, params), @@ -219,10 +224,20 @@ class FileUploadWorker( val uploads = uploadsStorageManager.getUploadsByIds(uploadIds, accountName) val ocAccount = OwnCloudAccount(user.toPlatformAccount(), context) val client = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount, context) + val syncFolderHelper = SyncFolderHelper(context) for ((index, upload) in uploads.withIndex()) { ensureActive() + if (isBelongToAnySyncedFolder(upload, syncFolderHelper)) { + Log_OC.d(TAG, "skipping upload, will be handled by AutoUploadWorker: ${upload.localPath}") + uploadsStorageManager.uploadDao.deleteByRemotePathAndAccountName( + remotePath = upload.remotePath, + accountName = accountName + ) + continue + } + if (preferences.isGlobalUploadPaused) { Log_OC.d(TAG, "Upload is paused, skip uploading files!") notificationManager.notifyPaused( @@ -266,6 +281,19 @@ class FileUploadWorker( return@withContext Result.success() } + suspend fun isBelongToAnySyncedFolder( + upload: OCUpload, + syncFolderHelper: SyncFolderHelper + ): Boolean { + if (!filesystemRepository.isBelongToAnyAutoFolder(upload.localPath)) return false + + return syncedFolderProvider.syncedFolders.any { folder -> + val file = File(upload.localPath) + val expectedRemotePath = syncFolderHelper.getAutoUploadRemotePath(folder, file) + expectedRemotePath == upload.remotePath + } + } + private fun sendUploadFinishEvent( totalUploadSize: Int, currentUploadIndex: Int, diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index 2f4bfa23c424..f02928b6f3ab 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -89,6 +89,7 @@ import com.owncloud.android.R import com.owncloud.android.databinding.FilesBinding import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.datamodel.OCFile +import com.owncloud.android.datamodel.SyncedFolderObserver import com.owncloud.android.datamodel.SyncedFolderProvider import com.owncloud.android.datamodel.VirtualFolderType import com.owncloud.android.files.services.NameCollisionPolicy @@ -2172,6 +2173,19 @@ class FileDisplayActivity : } supportInvalidateOptionsMenu() fetchRecommendedFilesIfNeeded(ignoreETag = true, currentDir) + + user.ifPresent { + val isAutoUploadFolder = SyncedFolderObserver.isAutoUploadFolder(operation.file, it) + if (isAutoUploadFolder) { + lifecycleScope.launch(Dispatchers.IO) { + Log_OC.d(TAG, "auto upload folder is deleted, clearing oc-upload entities") + fileUploadHelper.uploadsStorageManager.uploadDao.deleteAllForAutoUploadFolder( + accountName = it.accountName, + remotePath = operation.file.remotePath + ) + } + } + } } else { if (result.isSslRecoverableException) { mLastSslUntrustedServerResult = result diff --git a/app/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.kt index 4f32e0d8f9b8..f1bc7d4ab584 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.kt @@ -569,26 +569,6 @@ class SyncedFoldersActivity : return result } - override fun onSyncStatusToggleClick(section: Int, syncedFolderDisplayItem: SyncedFolderDisplayItem?) { - if (syncedFolderDisplayItem == null) return - - if (syncedFolderDisplayItem.id > SyncedFolder.UNPERSISTED_ID) { - syncedFolderProvider.updateSyncedFolderEnabled( - syncedFolderDisplayItem.id, - syncedFolderDisplayItem.isEnabled - ) - } else { - val storedId = syncedFolderProvider.storeSyncedFolder(syncedFolderDisplayItem) - if (storedId != -1L) { - syncedFolderDisplayItem.id = storedId - } - } - if (syncedFolderDisplayItem.isEnabled) { - backgroundJobManager.startAutoUpload(syncedFolderDisplayItem, overridePowerSaving = false) - showBatteryOptimizationDialogIfNeeded() - } - } - override fun onSyncFolderSettingsClick(section: Int, syncedFolderDisplayItem: SyncedFolderDisplayItem?) { check(Looper.getMainLooper().isCurrentThread) { "This must be called on the main thread!" } @@ -776,13 +756,57 @@ class SyncedFoldersActivity : dialogFragment = null } + override fun onSyncStatusToggleClick( + section: Int, + item: SyncedFolderDisplayItem? + ) { + item ?: return + + // Ensure the item is persisted + if (item.id <= SyncedFolder.UNPERSISTED_ID) { + syncedFolderProvider.storeSyncedFolder(item) + .takeIf { it != -1L } + ?.let { item.id = it } + } else { + syncedFolderProvider.updateSyncedFolderEnabled(item.id, item.isEnabled) + } + + if (item.isEnabled) { + Log_OC.d(TAG, "auto-upload configuration sync status is enabled: " + item.remotePath) + backgroundJobManager.startAutoUpload(item, overridePowerSaving = false) + showBatteryOptimizationDialogIfNeeded() + return + } + + Log_OC.d(TAG, "auto-upload configuration sync status is disabled: " + item.remotePath) + + lifecycleScope.launch(Dispatchers.IO) { + fileUploadHelper.uploadsStorageManager.uploadDao + .deleteAllForAutoUploadFolder( + accountName = userAccountManager.user.accountName, + remotePath = item.remotePath + ) + } + } + override fun onDeleteSyncedFolderPreference(syncedFolder: SyncedFolderParcelable?) { if (syncedFolder == null) { return } - syncedFolderProvider.deleteSyncedFolder(syncedFolder.id) - adapter.removeItem(syncedFolder.section) + Log_OC.d(TAG, "deleting auto upload configuration: " + syncedFolder.remotePath) + + lifecycleScope.launch(Dispatchers.IO) { + fileUploadHelper.uploadsStorageManager.uploadDao + .deleteAllForAutoUploadFolder( + accountName = userAccountManager.user.accountName, + remotePath = syncedFolder.remotePath + ) + syncedFolderProvider.deleteSyncedFolder(syncedFolder.id) + withContext(Dispatchers.Main) { + adapter.removeItem(syncedFolder.section) + } + } } /** diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/SyncedFolderAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/SyncedFolderAdapter.kt index a08fe38d189f..050674feae5b 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/SyncedFolderAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/SyncedFolderAdapter.kt @@ -513,7 +513,7 @@ class SyncedFolderAdapter( get() = syncFolderItems.size - filteredSyncFolderItems.size interface ClickListener { - fun onSyncStatusToggleClick(section: Int, syncedFolderDisplayItem: SyncedFolderDisplayItem?) + fun onSyncStatusToggleClick(section: Int, item: SyncedFolderDisplayItem?) fun onSyncFolderSettingsClick(section: Int, syncedFolderDisplayItem: SyncedFolderDisplayItem?) fun onVisibilityToggleClick(section: Int, item: SyncedFolderDisplayItem?) fun showSubFolderWarningDialog()