Skip to content

Commit a1d9236

Browse files
Merge pull request #16712 from nextcloud/fix/auto-upload-clear-upload-db-when-auto-upload-delete-or-disabled
fix(auto-upload): clean stale upload entities
2 parents 3ab3403 + 4c1bce2 commit a1d9236

File tree

18 files changed

+1622
-61
lines changed

18 files changed

+1622
-61
lines changed

app/schemas/com.nextcloud.client.database.NextcloudDatabase/99.json

Lines changed: 1308 additions & 0 deletions
Large diffs are not rendered by default.

app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,9 @@ import com.owncloud.android.db.ProviderMeta
9393
AutoMigration(from = 93, to = 94, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class),
9494
AutoMigration(from = 94, to = 95, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class),
9595
AutoMigration(from = 95, to = 96),
96-
AutoMigration(from = 96, to = 97, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class)
96+
AutoMigration(from = 96, to = 97, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class),
9797
// manual migration used for 97 to 98
98+
AutoMigration(from = 98, to = 99)
9899
],
99100
exportSchema = true
100101
)

app/src/main/java/com/nextcloud/client/database/dao/FileSystemDao.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,25 @@ import com.owncloud.android.db.ProviderMeta
1717

1818
@Dao
1919
interface FileSystemDao {
20+
@Query(
21+
"""
22+
UPDATE ${ProviderMeta.ProviderTableMeta.FILESYSTEM_TABLE_NAME}
23+
SET ${ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_REMOTE_PATH} = :remotePath
24+
WHERE ${ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_LOCAL_PATH} = :localPath
25+
AND ${ProviderMeta.ProviderTableMeta.FILESYSTEM_SYNCED_FOLDER_ID} = :syncedFolderId
26+
"""
27+
)
28+
suspend fun updateRemotePath(remotePath: String, localPath: String, syncedFolderId: String)
29+
30+
@Query(
31+
"""
32+
SELECT *
33+
FROM ${ProviderMeta.ProviderTableMeta.FILESYSTEM_TABLE_NAME}
34+
WHERE ${ProviderMeta.ProviderTableMeta.FILESYSTEM_SYNCED_FOLDER_ID} = :syncedFolderId
35+
"""
36+
)
37+
suspend fun getBySyncedFolderId(syncedFolderId: String): List<FilesystemEntity>
38+
2039
@Query(
2140
"""
2241
SELECT COUNT(*) > 0 FROM ${ProviderMeta.ProviderTableMeta.FILESYSTEM_TABLE_NAME}

app/src/main/java/com/nextcloud/client/database/dao/SyncedFolderDao.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,14 @@ interface SyncedFolderDao {
2727

2828
@Query("SELECT * FROM ${ProviderMeta.ProviderTableMeta.SYNCED_FOLDERS_TABLE_NAME}")
2929
fun getAllAsFlow(): Flow<List<SyncedFolderEntity>>
30+
31+
@Query(
32+
"""
33+
SELECT * FROM ${ProviderMeta.ProviderTableMeta.SYNCED_FOLDERS_TABLE_NAME}
34+
WHERE ${ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_REMOTE_PATH} = :remotePath
35+
AND ${ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT} = :account
36+
LIMIT 1
37+
"""
38+
)
39+
suspend fun findByRemotePathAndAccount(remotePath: String, account: String): SyncedFolderEntity?
3040
}

app/src/main/java/com/nextcloud/client/database/dao/UploadDao.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@ interface UploadDao {
4343
)
4444
fun deleteByRemotePathAndAccountName(remotePath: String, accountName: String)
4545

46+
@Query(
47+
"""
48+
DELETE FROM ${ProviderTableMeta.UPLOADS_TABLE_NAME}
49+
WHERE ${ProviderTableMeta.UPLOADS_LOCAL_PATH} = :localPath
50+
AND ${ProviderTableMeta.UPLOADS_REMOTE_PATH} = :remotePath
51+
"""
52+
)
53+
suspend fun deleteByLocalRemotePath(localPath: String, remotePath: String)
54+
4655
@Query(
4756
"SELECT * FROM " + ProviderTableMeta.UPLOADS_TABLE_NAME +
4857
" WHERE " + ProviderTableMeta._ID + " = :id AND " +

app/src/main/java/com/nextcloud/client/database/entity/FilesystemEntity.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ data class FilesystemEntity(
1919
val id: Int?,
2020
@ColumnInfo(name = ProviderTableMeta.FILESYSTEM_FILE_LOCAL_PATH)
2121
val localPath: String?,
22+
@ColumnInfo(name = ProviderTableMeta.FILESYSTEM_FILE_REMOTE_PATH)
23+
val remotePath: String?,
2224
@ColumnInfo(name = ProviderTableMeta.FILESYSTEM_FILE_IS_FOLDER)
2325
val fileIsFolder: Int?,
2426
@ColumnInfo(name = ProviderTableMeta.FILESYSTEM_FILE_FOUND_RECENTLY)

app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,7 @@ class AutoUploadWorker(
293293
try {
294294
// Insert/update to IN_PROGRESS state before starting upload
295295
val generatedId = uploadsStorageManager.uploadDao.insertOrReplace(uploadEntity)
296+
repository.updateRemotePath(upload, syncedFolder)
296297
uploadEntity = uploadEntity.copy(id = generatedId.toInt())
297298
upload.uploadId = generatedId
298299

app/src/main/java/com/nextcloud/client/jobs/autoUpload/FileSystemRepository.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import com.nextcloud.client.database.entity.FilesystemEntity
1616
import com.nextcloud.utils.extensions.shouldSkipFile
1717
import com.owncloud.android.datamodel.SyncedFolder
1818
import com.owncloud.android.datamodel.UploadsStorageManager
19+
import com.owncloud.android.db.OCUpload
1920
import com.owncloud.android.lib.common.utils.Log_OC
2021
import com.owncloud.android.utils.SyncedFolderUtils
2122
import java.io.File
@@ -80,6 +81,21 @@ class FileSystemRepository(
8081
return filtered
8182
}
8283

84+
suspend fun updateRemotePath(upload: OCUpload, syncedFolder: SyncedFolder) {
85+
val syncedFolderIdStr = syncedFolder.id.toString()
86+
87+
try {
88+
dao.updateRemotePath(remotePath = upload.remotePath, localPath = upload.localPath, syncedFolderIdStr)
89+
Log_OC.d(
90+
TAG,
91+
"file system entity remote path updated. remotePath: ${upload.remotePath}, localPath: " +
92+
"${upload.localPath} for syncedFolderId=$syncedFolderIdStr"
93+
)
94+
} catch (e: Exception) {
95+
Log_OC.e(TAG, "updateRemotePath(): ${e.message}", e)
96+
}
97+
}
98+
8399
suspend fun markFileAsHandled(localPath: String, syncedFolder: SyncedFolder) {
84100
val syncedFolderIdStr = syncedFolder.id.toString()
85101

@@ -199,6 +215,7 @@ class FileSystemRepository(
199215
val newEntity = FilesystemEntity(
200216
id = entity?.id,
201217
localPath = localPath,
218+
remotePath = null, // will be updated later
202219
fileIsFolder = if (file.isDirectory) 1 else 0,
203220
fileFoundRecently = System.currentTimeMillis(),
204221
fileSentForUpload = 0, // Reset to 0 to queue for upload

app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import android.content.Context
1313
import android.content.Intent
1414
import com.nextcloud.client.account.User
1515
import com.nextcloud.client.account.UserAccountManager
16+
import com.nextcloud.client.database.entity.SyncedFolderEntity
1617
import com.nextcloud.client.database.entity.UploadEntity
1718
import com.nextcloud.client.database.entity.toOCUpload
1819
import com.nextcloud.client.database.entity.toUploadEntity
@@ -40,6 +41,7 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult
4041
import com.owncloud.android.lib.common.utils.Log_OC
4142
import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation
4243
import com.owncloud.android.lib.resources.files.model.RemoteFile
44+
import com.owncloud.android.lib.resources.files.model.ServerFileInterface
4345
import com.owncloud.android.lib.resources.status.OCCapability
4446
import com.owncloud.android.operations.RemoveFileOperation
4547
import com.owncloud.android.operations.UploadFileOperation
@@ -614,4 +616,58 @@ class FileUploadHelper {
614616
}
615617
}
616618
}
619+
620+
/**
621+
* When a synced folder is disabled or deleted, its associated OCUpload entries in the uploads
622+
* table must be cleaned up. Without this, stale upload entries outlive the folder config that
623+
* created them, causing FileUploadWorker to keep retrying uploads for a folder that no longer
624+
* exists or is intentionally turned off, and AutoUploadWorker to re-queue already handled files
625+
* on its next scan via FileSystemRepository.getFilePathsWithIds.
626+
*/
627+
suspend fun removeEntityFromUploadEntities(id: Long) {
628+
uploadsStorageManager.fileSystemDao.getBySyncedFolderId(id.toString())
629+
.filter { it.localPath != null && it.remotePath != null }
630+
.forEach {
631+
Log_OC.d(
632+
TAG,
633+
"deleting upload entity localPath: ${it.localPath}, " + "remotePath: ${it.remotePath}"
634+
)
635+
uploadsStorageManager.uploadDao.deleteByLocalRemotePath(
636+
localPath = it.localPath!!,
637+
remotePath = it.remotePath!!
638+
)
639+
}
640+
}
641+
642+
/**
643+
* Splits a list of files into:
644+
* 1. Files that have an auto-upload folder configured.
645+
* 2. Files that don't.
646+
*/
647+
suspend fun splitFilesByAutoUpload(
648+
files: List<OCFile>,
649+
accountName: String
650+
): Pair<List<SyncedFolderEntity>, List<OCFile>> {
651+
652+
val autoUploadFolders = mutableListOf<SyncedFolderEntity>()
653+
val nonAutoUploadFiles = mutableListOf<OCFile>()
654+
655+
for (file in files) {
656+
val entity = getAutoUploadFolderEntity(file, accountName)
657+
if (entity != null) {
658+
autoUploadFolders.add(entity)
659+
} else {
660+
nonAutoUploadFiles.add(file)
661+
}
662+
}
663+
664+
return autoUploadFolders to nonAutoUploadFiles
665+
}
666+
667+
suspend fun getAutoUploadFolderEntity(file: ServerFileInterface, accountName: String): SyncedFolderEntity? {
668+
val dao = uploadsStorageManager.syncedFolderDao
669+
val normalizedRemotePath = file.remotePath.trimEnd()
670+
if (normalizedRemotePath.isEmpty()) return null
671+
return dao.findByRemotePathAndAccount(normalizedRemotePath, accountName)
672+
}
617673
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Nextcloud - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
package com.nextcloud.utils.extensions
9+
10+
import com.owncloud.android.R
11+
import com.owncloud.android.datamodel.OCFile
12+
import com.owncloud.android.ui.activity.FileActivity
13+
import com.owncloud.android.ui.activity.FileDisplayActivity
14+
import com.owncloud.android.ui.activity.OnFilesRemovedListener
15+
16+
fun FileActivity.removeFiles(
17+
offlineFiles: List<OCFile>,
18+
files: List<OCFile>,
19+
onlyLocalCopy: Boolean,
20+
filesRemovedListener: OnFilesRemovedListener?
21+
) {
22+
connectivityService.isNetworkAndServerAvailable { isAvailable ->
23+
if (isAvailable) {
24+
showLoadingDialog(getString(R.string.wait_a_moment))
25+
26+
(this as? FileDisplayActivity)
27+
?.deleteBatchTracker
28+
?.startBatchDelete(files.size)
29+
30+
if (files.isNotEmpty()) {
31+
val inBackground = (files.size != 1)
32+
fileOperationsHelper?.removeFiles(files, onlyLocalCopy, inBackground)
33+
}
34+
35+
if (offlineFiles.isNotEmpty()) {
36+
filesRemovedListener?.onFilesRemoved()
37+
}
38+
39+
dismissLoadingDialog()
40+
} else {
41+
if (onlyLocalCopy) {
42+
fileOperationsHelper?.removeFiles(files, true, true)
43+
} else {
44+
files.forEach(storageManager::addRemoveFileOfflineOperation)
45+
}
46+
47+
filesRemovedListener?.onFilesRemoved()
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)