Skip to content

Commit 2b7d556

Browse files
Merge pull request #16343 from nextcloud/backport/16336/stable-3.35
[stable-3.35] fix(auto-upload): sync conflict handling
2 parents 79e694c + e7e3aec commit 2b7d556

File tree

4 files changed

+72
-84
lines changed

4 files changed

+72
-84
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import com.owncloud.android.db.OCUpload
3737
import com.owncloud.android.db.UploadResult
3838
import com.owncloud.android.lib.common.OwnCloudAccount
3939
import com.owncloud.android.lib.common.OwnCloudClientManagerFactory
40+
import com.owncloud.android.lib.common.operations.RemoteOperationResult
4041
import com.owncloud.android.lib.common.utils.Log_OC
4142
import com.owncloud.android.operations.UploadFileOperation
4243
import com.owncloud.android.ui.activity.SettingsActivity
@@ -346,6 +347,12 @@ class AutoUploadWorker(
346347
TAG,
347348
"❌ upload failed $localPath (${upload.accountName}): ${result.logMessage}"
348349
)
350+
351+
// Mark CONFLICT files as handled to prevent retries
352+
if (result.code == RemoteOperationResult.ResultCode.SYNC_CONFLICT) {
353+
repository.markFileAsHandled(localPath, syncedFolder)
354+
Log_OC.w(TAG, "Marked CONFLICT file as handled: $localPath")
355+
}
349356
}
350357
} catch (e: Exception) {
351358
uploadsStorageManager.updateStatus(

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ class FileUploadWorker(
380380
notificationManager,
381381
operation,
382382
result,
383-
showSameFileAlreadyExistsNotification = {
383+
onSameFileConflict = {
384384
withContext(Dispatchers.Main) {
385385
val showSameFileAlreadyExistsNotification =
386386
inputData.getBoolean(SHOW_SAME_FILE_ALREADY_EXISTS_NOTIFICATION, false)

app/src/main/java/com/nextcloud/client/jobs/utils/UploadErrorNotificationManager.kt

Lines changed: 64 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -31,39 +31,74 @@ import java.io.File
3131
object UploadErrorNotificationManager {
3232
private const val TAG = "UploadErrorNotificationManager"
3333

34+
/**
35+
* Processes the result of an upload operation and manages error notifications.
36+
* * It filters out successful or silent results and handles [ResultCode.SYNC_CONFLICT]
37+
* by checking if the remote file is identical. If it's a "real" conflict or error,
38+
* it displays a notification with relevant actions (e.g., Resolve Conflict, Pause, Cancel).
39+
*
40+
* @param onSameFileConflict Triggered only if result code is SYNC_CONFLICT and files are identical.
41+
*/
42+
@Suppress("ReturnCount")
3443
suspend fun handleResult(
3544
context: Context,
3645
notificationManager: WorkerNotificationManager,
3746
operation: UploadFileOperation,
3847
result: RemoteOperationResult<Any?>,
39-
showSameFileAlreadyExistsNotification: suspend () -> Unit = {}
48+
onSameFileConflict: suspend () -> Unit = {}
4049
) {
4150
Log_OC.d(TAG, "handle upload result with result code: " + result.code)
4251

43-
val notification = withContext(Dispatchers.IO) {
44-
val isSameFileOnRemote = FileUploadHelper.instance().isSameFileOnRemote(
45-
operation.user,
46-
File(operation.storagePath),
47-
operation.remotePath,
48-
context
49-
)
52+
if (result.isSuccess || result.isCancelled || operation.isMissingPermissionThrown) {
53+
Log_OC.d(TAG, "operation is successful, cancelled or lack of storage permission, notification skipped")
54+
return
55+
}
5056

51-
getNotification(
52-
isSameFileOnRemote,
53-
context,
54-
notificationManager.notificationBuilder,
55-
operation,
56-
result,
57-
notifyOnSameFileExists = {
58-
showSameFileAlreadyExistsNotification()
59-
operation.handleLocalBehaviour()
60-
}
61-
)
62-
} ?: return
57+
val silentCodes = setOf(
58+
ResultCode.DELAYED_FOR_WIFI,
59+
ResultCode.DELAYED_FOR_CHARGING,
60+
ResultCode.DELAYED_IN_POWER_SAVE_MODE,
61+
ResultCode.LOCAL_FILE_NOT_FOUND,
62+
ResultCode.LOCK_FAILED
63+
)
64+
65+
if (result.code in silentCodes) {
66+
Log_OC.d(TAG, "silent error code, notification skipped")
67+
return
68+
}
69+
70+
// do not show an error notification when uploading the same file again
71+
if (result.code == ResultCode.SYNC_CONFLICT) {
72+
val isSameFile = withContext(Dispatchers.IO) {
73+
FileUploadHelper.instance().isSameFileOnRemote(
74+
operation.user,
75+
File(operation.storagePath),
76+
operation.remotePath,
77+
context
78+
)
79+
}
80+
81+
if (isSameFile) {
82+
Log_OC.w(TAG, "exact same file already exists on remote, error notification skipped")
83+
84+
// only show notification for manual uploads
85+
onSameFileConflict()
86+
return
87+
}
88+
}
89+
90+
// now we can show error notification
91+
val notification = getNotification(
92+
context,
93+
notificationManager.notificationBuilder,
94+
operation,
95+
result
96+
)
6397

6498
Log_OC.d(TAG, "🔔" + "notification created")
6599

66100
withContext(Dispatchers.Main) {
101+
// if error code is file specific show new notification for each file
67102
if (result.code.isFileSpecificError()) {
68103
notificationManager.showNotification(operation.ocUploadId.toInt(), notification)
69104
} else {
@@ -72,16 +107,12 @@ object UploadErrorNotificationManager {
72107
}
73108
}
74109

75-
private suspend fun getNotification(
76-
isSameFileOnRemote: Boolean,
110+
private fun getNotification(
77111
context: Context,
78112
builder: NotificationCompat.Builder,
79113
operation: UploadFileOperation,
80-
result: RemoteOperationResult<Any?>,
81-
notifyOnSameFileExists: suspend () -> Unit
82-
): Notification? {
83-
if (!shouldShowConflictDialog(isSameFileOnRemote, operation, result, notifyOnSameFileExists)) return null
84-
114+
result: RemoteOperationResult<Any?>
115+
): Notification {
85116
val textId = result.code.toFailedResultTitleId()
86117
val errorMessage = ErrorMessageAdapter.getErrorCauseMessage(result, operation, context.resources)
87118

@@ -94,19 +125,19 @@ object UploadErrorNotificationManager {
94125
setProgress(0, 0, false)
95126
clearActions()
96127

97-
result.code.takeIf { it == ResultCode.SYNC_CONFLICT }?.let {
128+
// actions for all error types
129+
addAction(UploadBroadcastAction.PauseAndCancel(operation).pauseAction(context))
130+
addAction(UploadBroadcastAction.PauseAndCancel(operation).cancelAction(context))
131+
132+
if (result.code == ResultCode.SYNC_CONFLICT) {
98133
addAction(
99134
R.drawable.ic_cloud_upload,
100135
context.getString(R.string.upload_list_resolve_conflict),
101136
conflictResolvePendingIntent(context, operation)
102137
)
103138
}
104139

105-
addAction(UploadBroadcastAction.PauseAndCancel(operation).pauseAction(context))
106-
107-
addAction(UploadBroadcastAction.PauseAndCancel(operation).cancelAction(context))
108-
109-
result.code.takeIf { it == ResultCode.UNAUTHORIZED }?.let {
140+
if (result.code == ResultCode.UNAUTHORIZED) {
110141
setContentIntent(credentialPendingIntent(context, operation))
111142
}
112143
}.build()
@@ -159,33 +190,4 @@ object UploadErrorNotificationManager {
159190
PendingIntent.FLAG_IMMUTABLE
160191
)
161192
}
162-
163-
@Suppress("ReturnCount", "ComplexCondition")
164-
private suspend fun shouldShowConflictDialog(
165-
isSameFileOnRemote: Boolean,
166-
operation: UploadFileOperation,
167-
result: RemoteOperationResult<Any?>,
168-
notifyOnSameFileExists: suspend () -> Unit
169-
): Boolean {
170-
if (result.isSuccess ||
171-
result.isCancelled ||
172-
result.code == ResultCode.USER_CANCELLED ||
173-
operation.isMissingPermissionThrown
174-
) {
175-
Log_OC.w(TAG, "operation is successful, cancelled or lack of storage permission")
176-
return false
177-
}
178-
179-
if (result.code == ResultCode.SYNC_CONFLICT && isSameFileOnRemote) {
180-
Log_OC.w(TAG, "same file exists on remote")
181-
notifyOnSameFileExists()
182-
return false
183-
}
184-
185-
val delayedCodes =
186-
setOf(ResultCode.DELAYED_FOR_WIFI, ResultCode.DELAYED_FOR_CHARGING, ResultCode.DELAYED_IN_POWER_SAVE_MODE)
187-
val invalidCodes = setOf(ResultCode.LOCAL_FILE_NOT_FOUND, ResultCode.LOCK_FAILED)
188-
189-
return result.code !in delayedCodes && result.code !in invalidCodes
190-
}
191193
}

app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1262,27 +1262,6 @@ private RemoteOperationResult checkNameCollision(OCFile parentFile,
12621262
return null;
12631263
}
12641264

1265-
public void handleLocalBehaviour() {
1266-
if (user == null || mFile == null || mContext == null) {
1267-
Log_OC.d(TAG, "handleLocalBehaviour: user, file, or context is null.");
1268-
return;
1269-
}
1270-
1271-
final var client = getClient();
1272-
if (client == null) {
1273-
Log_OC.d(TAG, "handleLocalBehaviour: client is null");
1274-
return;
1275-
}
1276-
1277-
String expectedPath = FileStorageUtils.getDefaultSavePathFor(user.getAccountName(), mFile);
1278-
File expectedFile = new File(expectedPath);
1279-
File originalFile = new File(mOriginalStoragePath);
1280-
String temporalPath = FileStorageUtils.getInternalTemporalPath(user.getAccountName(), mContext) + mFile.getRemotePath();
1281-
File temporalFile = new File(temporalPath);
1282-
1283-
handleLocalBehaviour(temporalFile, expectedFile, originalFile, client);
1284-
}
1285-
12861265
private void deleteNonExistingFile(File file) {
12871266
if (file.exists()) {
12881267
return;

0 commit comments

Comments
 (0)