Skip to content

Commit d265279

Browse files
committed
Fix download sync and conflict issues
1 parent 45933d5 commit d265279

6 files changed

Lines changed: 39 additions & 16 deletions

File tree

opencloudApp/src/main/java/eu/opencloud/android/usecases/synchronization/SynchronizeFileUseCase.kt

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,20 @@ class SynchronizeFileUseCase(
7878
} catch (exception: FileNotFoundException) {
7979
Timber.i(exception, "File does not exist anymore in remote")
8080

81-
// 2.1 File does not exist anymore in remote
82-
// If it still exists locally, but file has different path, another operation could have been done simultaneously
83-
val localDbFile = fileToSynchronize.id?.let { fileRepository.getFileById(it) }
81+
if (changedLocally) {
82+
Timber.w("File deleted remotely but changed locally. Uploading local version instead of deleting.")
83+
val uuid = requestForUpload(accountName, fileToSynchronize)
84+
return SyncType.UploadEnqueued(uuid)
85+
} else {
86+
// 2.1 File does not exist anymore in remote
87+
// If it still exists locally, but file has different path, another operation could have been done simultaneously
88+
val localDbFile = fileToSynchronize.id?.let { fileRepository.getFileById(it) }
8489

85-
if (localDbFile != null && (localDbFile.remotePath == fileToSynchronize.remotePath && localDbFile.spaceId == fileToSynchronize.spaceId)) {
86-
fileRepository.deleteFiles(listOf(fileToSynchronize), true)
90+
if (localDbFile != null && (localDbFile.remotePath == fileToSynchronize.remotePath && localDbFile.spaceId == fileToSynchronize.spaceId)) {
91+
fileRepository.deleteFiles(listOf(fileToSynchronize), true)
92+
}
93+
return SyncType.FileNotFound
8794
}
88-
return SyncType.FileNotFound
8995
}
9096

9197
// 3. File not downloaded -> Download it
@@ -205,7 +211,10 @@ class SynchronizeFileUseCase(
205211
}
206212

207213
private fun renameLocalFile(oldPath: String, newPath: String): Boolean = try {
208-
File(oldPath).renameTo(File(newPath))
214+
val oldFile = File(oldPath)
215+
oldFile.copyTo(File(newPath), overwrite = true)
216+
oldFile.delete()
217+
true
209218
} catch (e: Exception) {
210219
Timber.e(e, "Failed to rename local file from $oldPath to $newPath")
211220
false

opencloudApp/src/main/java/eu/opencloud/android/workers/DownloadEverythingWorker.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ class DownloadEverythingWorker(
9191
updateNotification("Found ${accounts.size} accounts")
9292

9393
accounts.forEachIndexed { accountIndex, account ->
94+
if (isStopped) {
95+
Timber.i("Worker stopped by system. Halting account processing.")
96+
return@forEachIndexed
97+
}
98+
9499
val accountName = account.name
95100
Timber.i("Processing account ${accountIndex + 1}/${accounts.size}: $accountName")
96101
updateNotification("Account ${accountIndex + 1}/${accounts.size}: $accountName")
@@ -210,6 +215,10 @@ class DownloadEverythingWorker(
210215
Timber.d("Folder ${folder.remotePath} contains ${folderContent.size} items")
211216

212217
folderContent.forEach { item ->
218+
if (isStopped) {
219+
Timber.i("Worker stopped by system. Halting folder processing.")
220+
return@forEach
221+
}
213222
if (item.isFolder) {
214223
// Recursively process subfolders
215224
processFolderRecursively(accountName, item, spaceId)

opencloudApp/src/main/java/eu/opencloud/android/workers/LocalFileSyncWorker.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ class LocalFileSyncWorker(
8686
Timber.d("Found ${downloadedFiles.size} downloaded files for account $accountName")
8787

8888
downloadedFiles.forEach { file ->
89+
if (isStopped) {
90+
Timber.i("Worker stopped by system. Halting sync.")
91+
return@forEach
92+
}
8993
if (!file.isFolder) {
9094
totalFilesChecked++
9195
try {

opencloudApp/src/main/java/eu/opencloud/android/workers/UploadFileFromFileSystemWorker.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,8 @@ class UploadFileFromFileSystemWorker(
266266
} catch (e: Exception) {
267267
Timber.w(e, "Failed to refresh parent folder after creating conflicted copy")
268268
}
269+
// ABORT the upload to prevent overwriting the server file
270+
throw IllegalStateException("Upload aborted due to conflict resolution policy. Saved as conflicted copy.")
269271
} else {
270272
Timber.w("Failed to copy local file to conflicted copy")
271273
}

opencloudData/src/main/java/eu/opencloud/android/data/providers/ScopedStorageProvider.kt

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,4 @@ class ScopedStorageProvider(
3030
) : LocalStorageProvider(rootFolderName) {
3131

3232
override fun getPrimaryStorageDirectory(): File = Environment.getExternalStorageDirectory()
33-
34-
override fun getAccountDirectoryPath(accountName: String): String {
35-
val sanitizedName = accountName.replace("/", "_").replace("\\", "_").replace(":", "_")
36-
return getRootFolderPath() + File.separator + sanitizedName
37-
}
3833
}

opencloudData/src/test/java/eu/opencloud/android/data/providers/ScopedStorageProviderTest.kt

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,10 @@ class ScopedStorageProviderTest {
8383

8484
@Test
8585
fun `getDefaultSavePathFor returns the path with spaces when there is a space`() {
86-
// ScopedStorageProvider overrides getAccountDirectoryPath and does NOT use Uri.encode
87-
val accountDirectoryPath = filesDir.absolutePath + File.separator + rootFolderName + File.separator + accountName
86+
mockkStatic(Uri::class)
87+
every { Uri.encode(accountName, "@") } returns uriEncoded
88+
89+
val accountDirectoryPath = filesDir.absolutePath + File.separator + rootFolderName + File.separator + uriEncoded
8890
val expectedPath = accountDirectoryPath + File.separator + spaceId + File.separator + remotePath
8991
val actualPath = scopedStorageProvider.getDefaultSavePathFor(accountName, remotePath, spaceId)
9092

@@ -99,8 +101,10 @@ class ScopedStorageProviderTest {
99101
fun `getDefaultSavePathFor returns the path without spaces when there is not space`() {
100102
val spaceId = null
101103

102-
// ScopedStorageProvider overrides getAccountDirectoryPath and does NOT use Uri.encode
103-
val accountDirectoryPath = filesDir.absolutePath + File.separator + rootFolderName + File.separator + accountName
104+
mockkStatic(Uri::class)
105+
every { Uri.encode(accountName, "@") } returns uriEncoded
106+
107+
val accountDirectoryPath = filesDir.absolutePath + File.separator + rootFolderName + File.separator + uriEncoded
104108
val expectedPath = accountDirectoryPath + remotePath
105109
val actualPath = scopedStorageProvider.getDefaultSavePathFor(accountName, remotePath, spaceId)
106110

0 commit comments

Comments
 (0)