瀏覽代碼

Make file uploads ask the user what to do when the file already exists on remote

Signed-off-by: Alice Gaudon <alice@gaudon.pro>
Alice Gaudon 5 年之前
父節點
當前提交
6783e8ab4c

+ 12 - 12
src/androidTest/java/com/owncloud/android/UploadIT.java

@@ -120,7 +120,7 @@ public class UploadIT extends AbstractIT {
             account,
             null,
             ocUpload,
-            false,
+            FileUploader.NameCollisionPolicy.DEFAULT,
             FileUploader.LOCAL_BEHAVIOUR_COPY,
             targetContext,
             false,
@@ -140,17 +140,17 @@ public class UploadIT extends AbstractIT {
         OCUpload ocUpload = new OCUpload(FileStorageUtils.getSavePath(account.name) + "/empty.txt",
                 "/testUpload/2/3/4/1.txt", account.name);
         UploadFileOperation newUpload = new UploadFileOperation(
-                storageManager,
-                connectivityServiceMock,
-                powerManagementServiceMock,
-                account,
-                null,
-                ocUpload,
-                false,
-                FileUploader.LOCAL_BEHAVIOUR_COPY,
-                targetContext,
-                false,
-                false
+            storageManager,
+            connectivityServiceMock,
+            powerManagementServiceMock,
+            account,
+            null,
+            ocUpload,
+            FileUploader.NameCollisionPolicy.DEFAULT,
+            FileUploader.LOCAL_BEHAVIOUR_COPY,
+            targetContext,
+            false,
+            false
         );
         newUpload.addRenameUploadListener(() -> {
             // dummy

+ 3 - 3
src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java

@@ -84,7 +84,7 @@ public class UploadsStorageManager extends Observable {
         cv.put(ProviderTableMeta.UPLOADS_FILE_SIZE, ocUpload.getFileSize());
         cv.put(ProviderTableMeta.UPLOADS_STATUS, ocUpload.getUploadStatus().value);
         cv.put(ProviderTableMeta.UPLOADS_LOCAL_BEHAVIOUR, ocUpload.getLocalAction());
-        cv.put(ProviderTableMeta.UPLOADS_FORCE_OVERWRITE, ocUpload.isForceOverwrite() ? 1 : 0);
+        cv.put(ProviderTableMeta.UPLOADS_NAME_COLLISION_POLICY, ocUpload.getNameCollisionPolicy().serialize());
         cv.put(ProviderTableMeta.UPLOADS_IS_CREATE_REMOTE_FOLDER, ocUpload.isCreateRemoteFolder() ? 1 : 0);
         cv.put(ProviderTableMeta.UPLOADS_LAST_RESULT, ocUpload.getLastResult().getValue());
         cv.put(ProviderTableMeta.UPLOADS_CREATED_BY, ocUpload.getCreatedBy());
@@ -329,8 +329,8 @@ public class UploadsStorageManager extends Observable {
                     UploadStatus.fromValue(c.getInt(c.getColumnIndex(ProviderTableMeta.UPLOADS_STATUS)))
             );
             upload.setLocalAction(c.getInt(c.getColumnIndex(ProviderTableMeta.UPLOADS_LOCAL_BEHAVIOUR)));
-            upload.setForceOverwrite(c.getInt(
-                    c.getColumnIndex(ProviderTableMeta.UPLOADS_FORCE_OVERWRITE)) == 1);
+            upload.setNameCollisionPolicy(FileUploader.NameCollisionPolicy.deserialize(c.getInt(
+                    c.getColumnIndex(ProviderTableMeta.UPLOADS_NAME_COLLISION_POLICY))));
             upload.setCreateRemoteFolder(c.getInt(
                     c.getColumnIndex(ProviderTableMeta.UPLOADS_IS_CREATE_REMOTE_FOLDER)) == 1);
             upload.setUploadEndTimestamp(c.getLong(c.getColumnIndex(ProviderTableMeta.UPLOADS_UPLOAD_END_TIMESTAMP)));

+ 5 - 5
src/main/java/com/owncloud/android/db/OCUpload.java

@@ -77,9 +77,9 @@ public class OCUpload implements Parcelable {
     @Getter @Setter private int localAction;
 
     /**
-     * Overwrite destination file?
+     * What to do in case of name collision.
      */
-    @Getter @Setter private boolean forceOverwrite;
+    @Getter @Setter private FileUploader.NameCollisionPolicy nameCollisionPolicy;
 
     /**
      * Create destination folder?
@@ -172,7 +172,7 @@ public class OCUpload implements Parcelable {
         fileSize = -1;
         uploadId = -1;
         localAction = FileUploader.LOCAL_BEHAVIOUR_COPY;
-        forceOverwrite = false;
+        nameCollisionPolicy = FileUploader.NameCollisionPolicy.DEFAULT;
         createRemoteFolder = false;
         uploadStatus = UploadStatus.UPLOAD_IN_PROGRESS;
         lastResult = UploadResult.UNKNOWN;
@@ -281,7 +281,7 @@ public class OCUpload implements Parcelable {
         remotePath = source.readString();
         accountName = source.readString();
         localAction = source.readInt();
-        forceOverwrite = source.readInt() == 1;
+        nameCollisionPolicy = FileUploader.NameCollisionPolicy.deserialize(source.readInt());
         createRemoteFolder = source.readInt() == 1;
         try {
             uploadStatus = UploadStatus.valueOf(source.readString());
@@ -312,7 +312,7 @@ public class OCUpload implements Parcelable {
         dest.writeString(remotePath);
         dest.writeString(accountName);
         dest.writeInt(localAction);
-        dest.writeInt(forceOverwrite ? 1 : 0);
+        dest.writeInt(nameCollisionPolicy.serialize());
         dest.writeInt(createRemoteFolder ? 1 : 0);
         dest.writeString(uploadStatus.name());
         dest.writeLong(uploadEndTimestamp);

+ 1 - 1
src/main/java/com/owncloud/android/db/ProviderMeta.java

@@ -208,7 +208,7 @@ public class ProviderMeta {
         public static final String UPLOADS_STATUS = "status";
         public static final String UPLOADS_LOCAL_BEHAVIOUR = "local_behaviour";
         public static final String UPLOADS_UPLOAD_TIME = "upload_time";
-        public static final String UPLOADS_FORCE_OVERWRITE = "force_overwrite";
+        public static final String UPLOADS_NAME_COLLISION_POLICY = "name_collision_policy";
         public static final String UPLOADS_IS_CREATE_REMOTE_FOLDER = "is_create_remote_folder";
         public static final String UPLOADS_UPLOAD_END_TIMESTAMP = "upload_end_timestamp";
         public static final String UPLOADS_LAST_RESULT = "last_result";

+ 11 - 0
src/main/java/com/owncloud/android/files/services/FileDownloader.java

@@ -43,6 +43,8 @@ import com.owncloud.android.R;
 import com.owncloud.android.authentication.AuthenticatorActivity;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.datamodel.UploadsStorageManager;
+import com.owncloud.android.db.OCUpload;
 import com.owncloud.android.lib.common.OwnCloudAccount;
 import com.owncloud.android.lib.common.OwnCloudClient;
 import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
@@ -87,6 +89,7 @@ public class FileDownloader extends Service
     public static final String EXTRA_FILE_PATH = "FILE_PATH";
     public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH";
     public static final String EXTRA_LINKED_TO_PATH = "LINKED_TO";
+    public static final String EXTRA_CONFLICT_UPLOAD = "CONFLICT_UPLOAD";
     public static final String ACCOUNT_NAME = "ACCOUNT_NAME";
 
     private static final int FOREGROUND_SERVICE_ID = 412;
@@ -110,7 +113,10 @@ public class FileDownloader extends Service
 
     private Notification mNotification;
 
+    private OCUpload conflictUpload;
+
     @Inject UserAccountManager accountManager;
+    @Inject UploadsStorageManager uploadsStorageManager;
 
     public static String getDownloadAddedMessage() {
         return FileDownloader.class.getName() + DOWNLOAD_ADDED_MESSAGE;
@@ -195,6 +201,7 @@ public class FileDownloader extends Service
             final String behaviour = intent.getStringExtra(OCFileListFragment.DOWNLOAD_BEHAVIOUR);
             String activityName = intent.getStringExtra(SendShareDialog.ACTIVITY_NAME);
             String packageName = intent.getStringExtra(SendShareDialog.PACKAGE_NAME);
+            this.conflictUpload = intent.getParcelableExtra(FileDownloader.EXTRA_CONFLICT_UPLOAD);
             AbstractList<String> requestedDownloads = new Vector<String>();
             try {
                 DownloadFileOperation newDownload = new DownloadFileOperation(account, file, behaviour, activityName,
@@ -634,6 +641,10 @@ public class FileDownloader extends Service
 
                 // Remove success notification
                 if (downloadResult.isSuccess()) {
+                    if (this.conflictUpload != null) {
+                        uploadsStorageManager.removeUpload(this.conflictUpload);
+                    }
+
                     // Sleep 2 seconds, so show the notification before remove it
                     NotificationUtils.cancelWithDelay(mNotificationManager,
                             R.string.downloader_download_succeeded_ticker, 2000);

+ 41 - 20
src/main/java/com/owncloud/android/files/services/FileUploader.java

@@ -148,9 +148,10 @@ public class FileUploader extends Service
     public static final String KEY_ACCOUNT = "ACCOUNT";
 
     /**
-     * Set to true if remote file is to be overwritten. Default action is to upload with different name.
+     * What {@link NameCollisionPolicy} to do when the file already exists on the remote.
      */
-    public static final String KEY_FORCE_OVERWRITE = "KEY_FORCE_OVERWRITE";
+    public static final String KEY_NAME_COLLISION_POLICY = "KEY_NAME_COLLISION_POLICY";
+
     /**
      * Set to true if remote folder is to be created if it does not exist.
      */
@@ -256,7 +257,7 @@ public class FileUploader extends Service
             }
         }
 
-        public void uploadFileWithOverwrite(
+        public void uploadFileWithNameCollisionPolicy(
                 Context context,
                 Account account,
                 String[] localPaths,
@@ -267,7 +268,7 @@ public class FileUploader extends Service
                 int createdBy,
                 boolean requiresWifi,
                 boolean requiresCharging,
-                boolean overwrite
+                NameCollisionPolicy nameCollisionPolicy
         ) {
             Intent intent = new Intent(context, FileUploader.class);
 
@@ -280,7 +281,7 @@ public class FileUploader extends Service
             intent.putExtra(FileUploader.KEY_CREATED_BY, createdBy);
             intent.putExtra(FileUploader.KEY_WHILE_ON_WIFI_ONLY, requiresWifi);
             intent.putExtra(FileUploader.KEY_WHILE_CHARGING_ONLY, requiresCharging);
-            intent.putExtra(FileUploader.KEY_FORCE_OVERWRITE, overwrite);
+            intent.putExtra(FileUploader.KEY_NAME_COLLISION_POLICY, nameCollisionPolicy);
 
             if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
                 context.startForegroundService(intent);
@@ -292,11 +293,11 @@ public class FileUploader extends Service
         /**
          * Call to upload a file
          */
-        public void uploadFileWithOverwrite(Context context, Account account, String localPath, String remotePath, int
+        public void uploadFileWithNameCollisionPolicy(Context context, Account account, String localPath, String remotePath, int
                 behaviour, String mimeType, boolean createRemoteFile, int createdBy, boolean requiresWifi,
-                                  boolean requiresCharging, boolean overwrite) {
+                                                      boolean requiresCharging, NameCollisionPolicy nameCollisionPolicy) {
 
-            uploadFileWithOverwrite(
+            uploadFileWithNameCollisionPolicy(
                     context,
                     account,
                     new String[]{localPath},
@@ -307,7 +308,7 @@ public class FileUploader extends Service
                     createdBy,
                     requiresWifi,
                     requiresCharging,
-                    overwrite
+                    nameCollisionPolicy
             );
         }
 
@@ -336,13 +337,13 @@ public class FileUploader extends Service
          * Call to update multiple files already uploaded
          */
         public void uploadUpdate(Context context, Account account, OCFile[] existingFiles, Integer behaviour,
-                                        Boolean forceOverwrite) {
+                                        NameCollisionPolicy nameCollisionPolicy) {
             Intent intent = new Intent(context, FileUploader.class);
 
             intent.putExtra(FileUploader.KEY_ACCOUNT, account);
             intent.putExtra(FileUploader.KEY_FILE, existingFiles);
             intent.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, behaviour);
-            intent.putExtra(FileUploader.KEY_FORCE_OVERWRITE, forceOverwrite);
+            intent.putExtra(FileUploader.KEY_NAME_COLLISION_POLICY, nameCollisionPolicy);
 
             if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
                 context.startForegroundService(intent);
@@ -355,9 +356,9 @@ public class FileUploader extends Service
          * Call to update a dingle file already uploaded
          */
         public void uploadUpdate(Context context, Account account, OCFile existingFile, Integer behaviour,
-                                        Boolean forceOverwrite) {
+                                        NameCollisionPolicy nameCollisionPolicy) {
 
-            uploadUpdate(context, account, new OCFile[]{existingFile}, behaviour, forceOverwrite);
+            uploadUpdate(context, account, new OCFile[]{existingFile}, behaviour, nameCollisionPolicy);
         }
 
 
@@ -617,7 +618,10 @@ public class FileUploader extends Service
             }
             // at this point variable "OCFile[] files" is loaded correctly.
 
-            boolean forceOverwrite = intent.getBooleanExtra(KEY_FORCE_OVERWRITE, false);
+            NameCollisionPolicy nameCollisionPolicy = (NameCollisionPolicy) intent.getSerializableExtra(KEY_NAME_COLLISION_POLICY);
+            if(nameCollisionPolicy == null) {
+                nameCollisionPolicy = NameCollisionPolicy.DEFAULT;
+            }
             int localAction = intent.getIntExtra(KEY_LOCAL_BEHAVIOUR, LOCAL_BEHAVIOUR_FORGET);
             boolean isCreateRemoteFolder = intent.getBooleanExtra(KEY_CREATE_REMOTE_FOLDER, false);
             int createdBy = intent.getIntExtra(KEY_CREATED_BY, UploadFileOperation.CREATED_BY_USER);
@@ -628,7 +632,7 @@ public class FileUploader extends Service
 
                     OCUpload ocUpload = new OCUpload(file, account);
                     ocUpload.setFileSize(file.getFileLength());
-                    ocUpload.setForceOverwrite(forceOverwrite);
+                    ocUpload.setNameCollisionPolicy(nameCollisionPolicy);
                     ocUpload.setCreateRemoteFolder(isCreateRemoteFolder);
                     ocUpload.setCreatedBy(createdBy);
                     ocUpload.setLocalAction(localAction);
@@ -644,7 +648,7 @@ public class FileUploader extends Service
                             account,
                             file,
                             ocUpload,
-                            forceOverwrite,
+                            nameCollisionPolicy,
                             localAction,
                             this,
                             onWifiOnly,
@@ -705,7 +709,7 @@ public class FileUploader extends Service
                     account,
                     null,
                     upload,
-                    upload.isForceOverwrite(),  // TODO should be read from DB?
+                    upload.getNameCollisionPolicy(),  // TODO should be read from DB?
                     upload.getLocalAction(),    // TODO should be read from DB?
                     this,
                     onWifiOnly,
@@ -868,9 +872,8 @@ public class FileUploader extends Service
          * If 'file' is a directory, returns 'true' if some of its descendant files
          * is uploading or waiting to upload.
          *
-         * Warning: If remote file exists and !forceOverwrite the original file
-         * is being returned here. That is, it seems as if the original file is
-         * being updated when actually a new file is being uploaded.
+         * Warning: If remote file exists and target was renamed the original file is being returned here.
+         * That is, it seems as if the original file is being updated when actually a new file is being uploaded.
          *
          * @param account Owncloud account where the remote file will be stored.
          * @param file    A file that could be in the queue of pending uploads
@@ -1362,4 +1365,22 @@ public class FileUploader extends Service
         mPendingUploads.remove(account.name);
         mUploadsStorageManager.removeUploads(account.name);
     }
+
+    public enum NameCollisionPolicy {
+        CANCEL,
+        RENAME,
+        OVERWRITE,
+        ASK_USER;
+
+        public static final NameCollisionPolicy DEFAULT = RENAME;
+
+        public static NameCollisionPolicy deserialize(int ordinal) {
+            NameCollisionPolicy[] values = NameCollisionPolicy.values();
+            return ordinal >= 0 && ordinal < values.length ? values[ordinal] : DEFAULT;
+        }
+
+        public int serialize() {
+            return this.ordinal();
+        }
+    }
 }

+ 12 - 12
src/main/java/com/owncloud/android/jobs/FilesSyncJob.java

@@ -203,24 +203,24 @@ public class FilesSyncJob extends Job {
                 remotePath = syncedFolder.getRemotePath();
             }
 
-            requester.uploadFileWithOverwrite(
-                    context,
-                    user.toPlatformAccount(),
-                    file.getAbsolutePath(),
-                    FileStorageUtils.getInstantUploadFilePath(
+            requester.uploadFileWithNameCollisionPolicy(
+                context,
+                user.toPlatformAccount(),
+                file.getAbsolutePath(),
+                FileStorageUtils.getInstantUploadFilePath(
                         file,
                         currentLocale,
                         remotePath,
                         syncedFolder.getLocalPath(),
                         lastModificationTime,
                         subfolderByDate),
-                    uploadAction,
-                    mimeType,
-                    true,           // create parent folder if not existent
-                    UploadFileOperation.CREATED_AS_INSTANT_PICTURE,
-                    needsWifi,
-                    needsCharging,
-                    true
+                uploadAction,
+                mimeType,
+                true,           // create parent folder if not existent
+                UploadFileOperation.CREATED_AS_INSTANT_PICTURE,
+                needsWifi,
+                needsCharging,
+                FileUploader.NameCollisionPolicy.ASK_USER
             );
 
             filesystemDataProvider.updateFilesystemFileAsSentForUpload(path,

+ 2 - 1
src/main/java/com/owncloud/android/operations/SynchronizeFileOperation.java

@@ -292,7 +292,8 @@ public class SynchronizeFileOperation extends SyncOperation {
      */
     private void requestForUpload(OCFile file) {
         FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
-        requester.uploadUpdate(mContext, mAccount, file, FileUploader.LOCAL_BEHAVIOUR_MOVE, true);
+        requester.uploadUpdate(mContext, mAccount, file, FileUploader.LOCAL_BEHAVIOUR_MOVE,
+                               FileUploader.NameCollisionPolicy.ASK_USER);
 
         mTransferWasRequested = true;
     }

+ 57 - 44
src/main/java/com/owncloud/android/operations/UploadFileOperation.java

@@ -90,6 +90,7 @@ import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.atomic.AtomicBoolean;
 
+import androidx.annotation.CheckResult;
 import androidx.annotation.RequiresApi;
 
 
@@ -111,14 +112,14 @@ public class UploadFileOperation extends SyncOperation {
     private OCFile mFile;
 
     /**
-     * Original OCFile which is to be uploaded in case file had to be renamed
-     * (if forceOverwrite==false and remote file already exists).
+     * Original OCFile which is to be uploaded in case file had to be renamed (if nameCollisionPolicy==RENAME and remote
+     * file already exists).
      */
     private OCFile mOldFile;
     private String mRemotePath;
     private String mFolderUnlockToken;
     private boolean mRemoteFolderToBeCreated;
-    private boolean mForceOverwrite;
+    private FileUploader.NameCollisionPolicy mNameCollisionPolicy;
     private int mLocalBehaviour;
     private int mCreatedBy;
     private boolean mOnWifiOnly;
@@ -183,7 +184,7 @@ public class UploadFileOperation extends SyncOperation {
                                Account account,
                                OCFile file,
                                OCUpload upload,
-                               boolean forceOverwrite,
+                               FileUploader.NameCollisionPolicy nameCollisionPolicy,
                                int localBehaviour,
                                Context context,
                                boolean onWifiOnly,
@@ -218,7 +219,7 @@ public class UploadFileOperation extends SyncOperation {
         mOnWifiOnly = onWifiOnly;
         mWhileChargingOnly = whileChargingOnly;
         mRemotePath = upload.getRemotePath();
-        mForceOverwrite = forceOverwrite;
+        mNameCollisionPolicy = nameCollisionPolicy;
         mLocalBehaviour = localBehaviour;
         mOriginalStoragePath = mFile.getStoragePath();
         mContext = context;
@@ -504,7 +505,11 @@ public class UploadFileOperation extends SyncOperation {
             /**** E2E *****/
 
             // check name collision
-            checkNameCollision(client, metadata, parentFile.isEncrypted());
+            RemoteOperationResult collisionResult = checkNameCollision(client, metadata, parentFile.isEncrypted());
+            if (collisionResult != null) {
+                result = collisionResult;
+                return collisionResult;
+            }
 
             String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile);
             expectedFile = new File(expectedPath);
@@ -759,7 +764,11 @@ public class UploadFileOperation extends SyncOperation {
             }
 
             // check name collision
-            checkNameCollision(client, null, false);
+            RemoteOperationResult collisionResult = checkNameCollision(client, null, false);
+            if (collisionResult != null) {
+                result = collisionResult;
+                return collisionResult;
+            }
 
             String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile);
             expectedFile = new File(expectedPath);
@@ -922,24 +931,37 @@ public class UploadFileOperation extends SyncOperation {
         return new RemoteOperationResult(ResultCode.OK);
     }
 
-    private void checkNameCollision(OwnCloudClient client, DecryptedFolderMetadata metadata, boolean encrypted)
-            throws OperationCancelledException {
-        /// automatic rename of file to upload in case of name collision in server
+    @CheckResult
+    private RemoteOperationResult checkNameCollision(OwnCloudClient client, DecryptedFolderMetadata metadata, boolean encrypted)
+        throws OperationCancelledException {
         Log_OC.d(TAG, "Checking name collision in server");
-        if (!mForceOverwrite) {
-            String remotePath = getAvailableRemotePath(client, mRemotePath, metadata, encrypted);
-            mWasRenamed = !remotePath.equals(mRemotePath);
-            if (mWasRenamed) {
-                createNewOCFile(remotePath);
-                Log_OC.d(TAG, "File renamed as " + remotePath);
+
+        if (existsFile(client, mRemotePath, metadata, encrypted)) {
+            switch (mNameCollisionPolicy) {
+                case CANCEL:
+                    Log_OC.d(TAG, "File exists; canceling");
+                    throw new OperationCancelledException();
+                case RENAME:
+                    mRemotePath = getNewAvailableRemotePath(client, mRemotePath, metadata, encrypted);
+                    mWasRenamed = true;
+                    createNewOCFile(mRemotePath);
+                    Log_OC.d(TAG, "File renamed as " + mRemotePath);
+                    mRenameUploadListener.onRenameUpload();
+                    break;
+                case OVERWRITE:
+                    Log_OC.d(TAG, "Overwriting file");
+                    break;
+                case ASK_USER:
+                    Log_OC.d(TAG, "Name collision; asking the user what to do");
+                    return new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
             }
-            mRemotePath = remotePath;
-            mRenameUploadListener.onRenameUpload();
         }
 
         if (mCancellationRequested.get()) {
             throw new OperationCancelledException();
         }
+
+        return null;
     }
 
     private void handleSuccessfulUpload(File temporalFile, File expectedFile, File originalFile,
@@ -1043,8 +1065,8 @@ public class UploadFileOperation extends SyncOperation {
 
 
     /**
-     * Create a new OCFile mFile with new remote path. This is required if forceOverwrite==false.
-     * New file is stored as mFile, original as mOldFile.
+     * Create a new OCFile mFile with new remote path. This is required if nameCollisionPolicy==RENAME. New file is
+     * stored as mFile, original as mOldFile.
      *
      * @param newRemotePath new remote path
      */
@@ -1068,45 +1090,36 @@ public class UploadFileOperation extends SyncOperation {
     }
 
     /**
-     * Checks if remotePath does not exist in the server and returns it, or adds
-     * a suffix to it in order to avoid the server file is overwritten.
+     * Returns a new and available (does not exists on the server) remotePath.
+     * This adds an incremental suffix.
      *
      * @param client     OwnCloud client
      * @param remotePath remote path of the file
      * @param metadata   metadata of encrypted folder
      * @return new remote path
      */
-    private String getAvailableRemotePath(OwnCloudClient client, String remotePath, DecryptedFolderMetadata metadata,
-                                          boolean encrypted) {
-        boolean check = existsFile(client, remotePath, metadata, encrypted);
-        if (!check) {
-            return remotePath;
-        }
-
-        int pos = remotePath.lastIndexOf('.');
+    private String getNewAvailableRemotePath(OwnCloudClient client, String remotePath, DecryptedFolderMetadata metadata,
+                                             boolean encrypted) {
+        int extPos = remotePath.lastIndexOf('.');
         String suffix;
         String extension = "";
         String remotePathWithoutExtension = "";
-        if (pos >= 0) {
-            extension = remotePath.substring(pos + 1);
-            remotePathWithoutExtension = remotePath.substring(0, pos);
+        if (extPos >= 0) {
+            extension = remotePath.substring(extPos + 1);
+            remotePathWithoutExtension = remotePath.substring(0, extPos);
         }
+
         int count = 2;
+        boolean exists;
+        String newPath;
         do {
             suffix = " (" + count + ")";
-            if (pos >= 0) {
-                check = existsFile(client, remotePathWithoutExtension + suffix + "." + extension, metadata, encrypted);
-            } else {
-                check = existsFile(client, remotePath + suffix, metadata, encrypted);
-            }
+            newPath = extPos >= 0 ? remotePathWithoutExtension + suffix + "." + extension : remotePath + suffix;
+            exists = existsFile(client, newPath, metadata, encrypted);
             count++;
-        } while (check);
+        } while (exists);
 
-        if (pos >= 0) {
-            return remotePathWithoutExtension + suffix + "." + extension;
-        } else {
-            return remotePath + suffix;
-        }
+        return newPath;
     }
 
     private boolean existsFile(OwnCloudClient client, String remotePath, DecryptedFolderMetadata metadata,

+ 48 - 3
src/main/java/com/owncloud/android/providers/FileContentProvider.java

@@ -804,7 +804,7 @@ public class FileContentProvider extends ContentProvider {
                        + ProviderTableMeta.UPLOADS_STATUS + INTEGER               // UploadStatus
                        + ProviderTableMeta.UPLOADS_LOCAL_BEHAVIOUR + INTEGER      // Upload LocalBehaviour
                        + ProviderTableMeta.UPLOADS_UPLOAD_TIME + INTEGER
-                       + ProviderTableMeta.UPLOADS_FORCE_OVERWRITE + INTEGER  // boolean
+                       + ProviderTableMeta.UPLOADS_NAME_COLLISION_POLICY + INTEGER  // boolean
                        + ProviderTableMeta.UPLOADS_IS_CREATE_REMOTE_FOLDER + INTEGER  // boolean
                        + ProviderTableMeta.UPLOADS_UPLOAD_END_TIMESTAMP + INTEGER
                        + ProviderTableMeta.UPLOADS_LAST_RESULT + INTEGER     // Upload LastResult
@@ -2106,13 +2106,58 @@ public class FileContentProvider extends ContentProvider {
                 Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion));
             }
 
-            if(oldVersion < 54 && newVersion >= 54) {
-                Log_OC.i(SQL, "Entering in the #54 add synced.existing");
+            if (oldVersion < 54 && newVersion >= 54) {
+                Log_OC.i(SQL, "Entering in the #54 add synced.existing," +
+                    " rename uploads.force_overwrite to uploads.name_collision_policy");
                 db.beginTransaction();
                 try {
+                    // Add synced.existing
                     db.execSQL(ALTER_TABLE + ProviderTableMeta.SYNCED_FOLDERS_TABLE_NAME +
                                    ADD_COLUMN + ProviderTableMeta.SYNCED_FOLDER_EXISTING + " INTEGER "); // boolean
 
+
+                    // Rename uploads.force_overwrite to uploads.name_collision_policy
+                    String tmpTableName = ProviderTableMeta.UPLOADS_TABLE_NAME + "_old";
+                    db.execSQL(ALTER_TABLE + ProviderTableMeta.UPLOADS_TABLE_NAME + " RENAME TO " + tmpTableName);
+                    createUploadsTable(db);
+                    db.execSQL("INSERT INTO " + ProviderTableMeta.UPLOADS_TABLE_NAME + " (" +
+                                   ProviderTableMeta._ID + ", " +
+                                   ProviderTableMeta.UPLOADS_LOCAL_PATH + ", " +
+                                   ProviderTableMeta.UPLOADS_REMOTE_PATH + ", " +
+                                   ProviderTableMeta.UPLOADS_ACCOUNT_NAME + ", " +
+                                   ProviderTableMeta.UPLOADS_FILE_SIZE + ", " +
+                                   ProviderTableMeta.UPLOADS_STATUS + ", " +
+                                   ProviderTableMeta.UPLOADS_LOCAL_BEHAVIOUR + ", " +
+                                   ProviderTableMeta.UPLOADS_UPLOAD_TIME + ", " +
+                                   ProviderTableMeta.UPLOADS_NAME_COLLISION_POLICY + ", " +
+                                   ProviderTableMeta.UPLOADS_IS_CREATE_REMOTE_FOLDER + ", " +
+                                   ProviderTableMeta.UPLOADS_UPLOAD_END_TIMESTAMP + ", " +
+                                   ProviderTableMeta.UPLOADS_LAST_RESULT + ", " +
+                                   ProviderTableMeta.UPLOADS_IS_WHILE_CHARGING_ONLY + ", " +
+                                   ProviderTableMeta.UPLOADS_IS_WIFI_ONLY + ", " +
+                                   ProviderTableMeta.UPLOADS_CREATED_BY + ", " +
+                                   ProviderTableMeta.UPLOADS_FOLDER_UNLOCK_TOKEN +
+                                   ") " +
+                                   " SELECT " +
+                                   ProviderTableMeta._ID + ", " +
+                                   ProviderTableMeta.UPLOADS_LOCAL_PATH + ", " +
+                                   ProviderTableMeta.UPLOADS_REMOTE_PATH + ", " +
+                                   ProviderTableMeta.UPLOADS_ACCOUNT_NAME + ", " +
+                                   ProviderTableMeta.UPLOADS_FILE_SIZE + ", " +
+                                   ProviderTableMeta.UPLOADS_STATUS + ", " +
+                                   ProviderTableMeta.UPLOADS_LOCAL_BEHAVIOUR + ", " +
+                                   ProviderTableMeta.UPLOADS_UPLOAD_TIME + ", " +
+                                   "force_overwrite" + ", " +
+                                   ProviderTableMeta.UPLOADS_IS_CREATE_REMOTE_FOLDER + ", " +
+                                   ProviderTableMeta.UPLOADS_UPLOAD_END_TIMESTAMP + ", " +
+                                   ProviderTableMeta.UPLOADS_LAST_RESULT + ", " +
+                                   ProviderTableMeta.UPLOADS_IS_WHILE_CHARGING_ONLY + ", " +
+                                   ProviderTableMeta.UPLOADS_IS_WIFI_ONLY + ", " +
+                                   ProviderTableMeta.UPLOADS_CREATED_BY + ", " +
+                                   ProviderTableMeta.UPLOADS_FOLDER_UNLOCK_TOKEN +
+                                   " FROM " + tmpTableName);
+                    db.execSQL("DROP TABLE " + tmpTableName);
+
                     upgraded = true;
                     db.setTransactionSuccessful();
                 } finally {

+ 70 - 37
src/main/java/com/owncloud/android/ui/activity/ConflictsResolveActivity.java

@@ -25,6 +25,8 @@ import android.content.Intent;
 import android.os.Bundle;
 
 import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.datamodel.UploadsStorageManager;
+import com.owncloud.android.db.OCUpload;
 import com.owncloud.android.files.services.FileDownloader;
 import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.lib.common.utils.Log_OC;
@@ -32,52 +34,82 @@ import com.owncloud.android.ui.dialog.ConflictsResolveDialog;
 import com.owncloud.android.ui.dialog.ConflictsResolveDialog.Decision;
 import com.owncloud.android.ui.dialog.ConflictsResolveDialog.OnConflictDecisionMadeListener;
 
+import javax.inject.Inject;
+
 
 /**
  * Wrapper activity which will be launched if keep-in-sync file will be modified by external
  * application.
  */
 public class ConflictsResolveActivity extends FileActivity implements OnConflictDecisionMadeListener {
+    /**
+     * A nullable upload entry that must be removed when and if the conflict is resolved.
+     */
+    public static final String EXTRA_CONFLICT_UPLOAD = "CONFLICT_UPLOAD";
+    /**
+     * Specify the upload local behaviour when there is no CONFLICT_UPLOAD.
+     */
+    public static final String EXTRA_LOCAL_BEHAVIOUR = "LOCAL_BEHAVIOUR";
 
     private static final String TAG = ConflictsResolveActivity.class.getSimpleName();
 
+    @Inject UploadsStorageManager uploadsStorageManager;
+
+    private OCUpload conflictUpload;
+    private int localBehaviour = FileUploader.LOCAL_BEHAVIOUR_FORGET;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+
+        if (savedInstanceState != null) {
+            this.conflictUpload = savedInstanceState.getParcelable(EXTRA_CONFLICT_UPLOAD);
+            this.localBehaviour = savedInstanceState.getInt(EXTRA_LOCAL_BEHAVIOUR);
+        } else {
+            this.conflictUpload = getIntent().getParcelableExtra(EXTRA_CONFLICT_UPLOAD);
+            this.localBehaviour = getIntent().getIntExtra(EXTRA_LOCAL_BEHAVIOUR, this.localBehaviour);
+        }
+
+        if (this.conflictUpload != null) {
+            this.localBehaviour = this.conflictUpload.getLocalAction();
+        }
     }
 
     @Override
     public void conflictDecisionMade(Decision decision) {
+        if (decision == Decision.CANCEL) {
+            return;
+        }
 
-        Integer behaviour = null;
-        Boolean forceOverwrite = null;
+        OCFile file = getFile();
+        FileUploader.UploadRequester uploadRequester = new FileUploader.UploadRequester();
 
-        switch (decision) {
-            case CANCEL:
-                finish();
-                return;
-            case OVERWRITE:
-                // use local version -> overwrite on server
-                forceOverwrite = true;
-                break;
-            case KEEP_BOTH:
-                behaviour = FileUploader.LOCAL_BEHAVIOUR_MOVE;
-                break;
-            case SERVER:
-                // use server version -> delete local, request download
-                Intent intent = new Intent(this, FileDownloader.class);
-                intent.putExtra(FileDownloader.EXTRA_ACCOUNT, getAccount());
-                intent.putExtra(FileDownloader.EXTRA_FILE, getFile());
-                startService(intent);
-                finish();
-                return;
-            default:
-                Log_OC.e(TAG, "Unhandled conflict decision " + decision);
-                return;
+        // Upload
+        if (decision == Decision.KEEP_LOCAL || decision == Decision.KEEP_BOTH) {
+            FileUploader.NameCollisionPolicy collisionPolicy = FileUploader.NameCollisionPolicy.OVERWRITE;
+            if (decision == Decision.KEEP_BOTH) {
+                collisionPolicy = FileUploader.NameCollisionPolicy.RENAME;
+            }
+
+            uploadRequester.uploadUpdate(this, getAccount(), file, localBehaviour, collisionPolicy);
+
+            if (this.conflictUpload != null) {
+                uploadsStorageManager.removeUpload(this.conflictUpload);
+            }
+        }
+
+        // Download
+        if (decision == Decision.KEEP_SERVER && !this.shouldDeleteLocal()) {
+            // Overwrite local file
+            Intent intent = new Intent(this, FileDownloader.class);
+            intent.putExtra(FileDownloader.EXTRA_ACCOUNT, getAccount());
+            intent.putExtra(FileDownloader.EXTRA_FILE, file);
+            if (this.conflictUpload != null) {
+                intent.putExtra(FileDownloader.EXTRA_CONFLICT_UPLOAD, this.conflictUpload);
+            }
+            startService(intent);
         }
 
-        FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
-        requester.uploadUpdate(this, getAccount(), getFile(), behaviour, forceOverwrite);
         finish();
     }
 
@@ -87,26 +119,27 @@ public class ConflictsResolveActivity extends FileActivity implements OnConflict
         if (getAccount() != null) {
             OCFile file = getFile();
             if (getFile() == null) {
-                Log_OC.e(TAG, "No conflictive file received");
+                Log_OC.e(TAG, "No file received");
                 finish();
             } else {
-                /// Check whether the 'main' OCFile handled by the Activity is contained in the current Account
-                file = getStorageManager().getFileByPath(file.getRemotePath());   // file = null if not in the
-                // current Account
-                if (file != null) {
-                    setFile(file);
-                    ConflictsResolveDialog d = ConflictsResolveDialog.newInstance(this);
-                    d.showDialog(this);
-
+                // Check whether the file is contained in the current Account
+                if (getStorageManager().fileExists(file.getRemotePath())) {
+                    ConflictsResolveDialog dialog = new ConflictsResolveDialog(this, !this.shouldDeleteLocal());
+                    dialog.showDialog(this);
                 } else {
-                    // account was changed to a different one - just finish
+                    // Account was changed to a different one - just finish
                     finish();
                 }
             }
-
         } else {
             finish();
         }
+    }
 
+    /**
+     * @return whether the local version of the files is to be deleted.
+     */
+    private boolean shouldDeleteLocal() {
+        return localBehaviour == FileUploader.LOCAL_BEHAVIOUR_DELETE;
     }
 }

+ 7 - 1
src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java

@@ -45,6 +45,7 @@ import com.evernote.android.job.JobRequest;
 import com.evernote.android.job.util.support.PersistableBundleCompat;
 import com.nextcloud.client.account.User;
 import com.nextcloud.client.account.UserAccountManager;
+import com.nextcloud.client.core.Clock;
 import com.nextcloud.client.device.PowerManagementService;
 import com.nextcloud.client.network.ConnectivityService;
 import com.nextcloud.java.util.Optional;
@@ -121,6 +122,9 @@ public class UploadListActivity extends FileActivity {
     @Inject
     PowerManagementService powerManagementService;
 
+    @Inject
+    Clock clock;
+
     @Override
     public void showFiles(boolean onDeviceOnly) {
         super.showFiles(onDeviceOnly);
@@ -169,9 +173,11 @@ public class UploadListActivity extends FileActivity {
 
         uploadListAdapter = new UploadListAdapter(this,
                                                   uploadsStorageManager,
+                                                  getStorageManager(),
                                                   userAccountManager,
                                                   connectivityService,
-                                                  powerManagementService);
+                                                  powerManagementService,
+                                                  clock);
 
         final GridLayoutManager lm = new GridLayoutManager(this, 1);
         uploadListAdapter.setLayoutManager(lm);

+ 95 - 20
src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.java

@@ -26,6 +26,7 @@ package com.owncloud.android.ui.adapter;
 
 import android.accounts.Account;
 import android.content.ActivityNotFoundException;
+import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
@@ -44,10 +45,13 @@ import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter;
 import com.afollestad.sectionedrecyclerview.SectionedViewHolder;
 import com.nextcloud.client.account.User;
 import com.nextcloud.client.account.UserAccountManager;
+import com.nextcloud.client.core.Clock;
 import com.nextcloud.client.device.PowerManagementService;
 import com.nextcloud.client.network.ConnectivityService;
 import com.nextcloud.java.util.Optional;
+import com.owncloud.android.MainApp;
 import com.owncloud.android.R;
+import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.ThumbnailsCacheManager;
 import com.owncloud.android.datamodel.UploadsStorageManager;
@@ -56,7 +60,10 @@ import com.owncloud.android.db.OCUpload;
 import com.owncloud.android.db.OCUploadComparator;
 import com.owncloud.android.db.UploadResult;
 import com.owncloud.android.files.services.FileUploader;
+import com.owncloud.android.lib.common.operations.OnRemoteOperationListener;
 import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.operations.RefreshFolderOperation;
+import com.owncloud.android.ui.activity.ConflictsResolveActivity;
 import com.owncloud.android.ui.activity.FileActivity;
 import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.MimeTypeUtil;
@@ -78,9 +85,11 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
     private ProgressListener progressListener;
     private FileActivity parentActivity;
     private UploadsStorageManager uploadsStorageManager;
+    private FileDataStorageManager storageManager;
     private ConnectivityService connectivityService;
     private PowerManagementService powerManagementService;
     private UserAccountManager accountManager;
+    private Clock clock;
     private UploadGroup[] uploadGroups;
     private boolean showUser;
 
@@ -161,15 +170,19 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
 
     public UploadListAdapter(final FileActivity fileActivity,
                              final UploadsStorageManager uploadsStorageManager,
+                             final FileDataStorageManager storageManager,
                              final UserAccountManager accountManager,
                              final ConnectivityService connectivityService,
-                             final PowerManagementService powerManagementService) {
+                             final PowerManagementService powerManagementService,
+                             final Clock clock) {
         Log_OC.d(TAG, "UploadListAdapter");
         this.parentActivity = fileActivity;
         this.uploadsStorageManager = uploadsStorageManager;
+        this.storageManager = storageManager;
         this.accountManager = accountManager;
         this.connectivityService = connectivityService;
         this.powerManagementService = powerManagementService;
+        this.clock = clock;
         uploadGroups = new UploadGroup[3];
 
         shouldShowHeadersForEmptySections(false);
@@ -339,26 +352,58 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
 
         // click on item
         if (item.getUploadStatus() == UploadStatus.UPLOAD_FAILED) {
-            if (UploadResult.CREDENTIAL_ERROR == item.getLastResult()) {
-                itemViewHolder.itemLayout.setOnClickListener(v ->
-                                                                 parentActivity.getFileOperationsHelper().checkCurrentCredentials(
-                                                                     item.getAccount(accountManager)));
-            } else {
-                // not a credentials error
-                itemViewHolder.itemLayout.setOnClickListener(v -> {
-                    File file = new File(item.getLocalPath());
-                    if (file.exists()) {
-                        FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
-                        requester.retry(parentActivity, accountManager, item);
-                        loadUploadItemsFromDb();
-                    } else {
-                        DisplayUtils.showSnackMessage(
-                                v.getRootView().findViewById(android.R.id.content),
-                                R.string.local_file_not_found_message
-                        );
+            final UploadResult uploadResult = item.getLastResult();
+            itemViewHolder.itemLayout.setOnClickListener(v -> {
+                if (uploadResult == UploadResult.CREDENTIAL_ERROR) {
+                    parentActivity.getFileOperationsHelper().checkCurrentCredentials(
+                        item.getAccount(accountManager));
+                    return;
+                } else if (uploadResult == UploadResult.SYNC_CONFLICT) {
+                    String remotePath = item.getRemotePath();
+                    OCFile ocFile = storageManager.getFileByPath(remotePath);
+
+                    if (ocFile == null) { // Remote file doesn't exist, try to refresh folder
+                        OCFile folder = storageManager.getFileByPath(new File(remotePath).getParent() + "/");
+                        if (folder != null && folder.isFolder()) {
+                            if (optionalUser.isPresent()) {
+                                Account userAccount = optionalUser.get().toPlatformAccount();
+                                this.refreshFolder(itemViewHolder, userAccount, folder, (caller, result) -> {
+                                    itemViewHolder.status.setText(status);
+                                    if (result.isSuccess()) {
+                                        OCFile file = storageManager.getFileByPath(remotePath);
+                                        if (file != null) {
+                                            this.openConflictActivity(file, item);
+                                        }
+                                    }
+                                });
+                            }
+                            return;
+                        }
+
+                        // Destination folder doesn't exist anymore
                     }
-                });
-            }
+
+                    if (ocFile != null) {
+                        this.openConflictActivity(ocFile, item);
+                        return;
+                    }
+
+                    // Remote file doesn't exist anymore = there is no more conflict
+                }
+
+                // not a credentials error
+                File file = new File(item.getLocalPath());
+                if (file.exists()) {
+                    FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
+                    requester.retry(parentActivity, accountManager, item);
+                    loadUploadItemsFromDb();
+                } else {
+                    DisplayUtils.showSnackMessage(
+                        v.getRootView().findViewById(android.R.id.content),
+                        R.string.local_file_not_found_message
+                    );
+                }
+            });
         } else {
             itemViewHolder.itemLayout.setOnClickListener(v ->
                     onUploadItemClick(item));
@@ -466,6 +511,36 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
         }
     }
 
+    private void refreshFolder(ItemViewHolder view, Account account, OCFile folder, OnRemoteOperationListener listener) {
+        view.itemLayout.setClickable(false);
+        view.status.setText(R.string.uploads_view_upload_status_fetching_server_version);
+        Context context = MainApp.getAppContext();
+        new RefreshFolderOperation(folder,
+                                   clock.getCurrentTime(),
+                                   false,
+                                   false,
+                                   true,
+                                   storageManager,
+                                   account,
+                                   context)
+            .execute(account, context, (caller, result) -> {
+                view.itemLayout.setClickable(true);
+                listener.onRemoteOperationFinish(caller, result);
+            }, parentActivity.getHandler());
+    }
+
+    private void openConflictActivity(OCFile file, OCUpload upload) {
+        file.setStoragePath(upload.getLocalPath());
+
+        Context context = MainApp.getAppContext();
+        Intent i = new Intent(context, ConflictsResolveActivity.class);
+        i.setFlags(i.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
+        i.putExtra(ConflictsResolveActivity.EXTRA_FILE, file);
+        i.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, upload.getAccount(accountManager));
+        i.putExtra(ConflictsResolveActivity.EXTRA_CONFLICT_UPLOAD, upload);
+        context.startActivity(i);
+    }
+
     /**
      * Gets the status text to show to the user according to the status and last result of the
      * the given upload.

+ 36 - 32
src/main/java/com/owncloud/android/ui/dialog/ConflictsResolveDialog.java

@@ -43,44 +43,48 @@ public class ConflictsResolveDialog extends DialogFragment {
     public enum Decision {
         CANCEL,
         KEEP_BOTH,
-        OVERWRITE,
-        SERVER
+        KEEP_LOCAL,
+        KEEP_SERVER,
     }
 
-    OnConflictDecisionMadeListener mListener;
+    private final OnConflictDecisionMadeListener listener;
+    private final boolean canKeepServer;
 
-    public static ConflictsResolveDialog newInstance(OnConflictDecisionMadeListener listener) {
-        ConflictsResolveDialog f = new ConflictsResolveDialog();
-        f.mListener = listener;
-        return f;
+    public ConflictsResolveDialog(OnConflictDecisionMadeListener listener, boolean canKeepServer) {
+        this.listener = listener;
+        this.canKeepServer = canKeepServer;
     }
 
     @NonNull
     @Override
     public Dialog onCreateDialog(Bundle savedInstanceState) {
-        return new AlertDialog.Builder(requireActivity(), R.style.Theme_ownCloud_Dialog)
-                .setIcon(R.drawable.ic_warning)
-                .setTitle(R.string.conflict_title)
-                .setMessage(getString(R.string.conflict_message))
-                .setPositiveButton(R.string.conflict_use_local_version,
-                        (dialog, which) -> {
-                            if (mListener != null) {
-                                mListener.conflictDecisionMade(Decision.OVERWRITE);
-                            }
-                        })
-                .setNeutralButton(R.string.conflict_keep_both,
-                        (dialog, which) -> {
-                            if (mListener != null) {
-                                mListener.conflictDecisionMade(Decision.KEEP_BOTH);
-                            }
-                        })
-                .setNegativeButton(R.string.conflict_use_server_version,
-                        (dialog, which) -> {
-                            if (mListener != null) {
-                                mListener.conflictDecisionMade(Decision.SERVER);
-                            }
-                        })
-                .create();
+        AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity(), R.style.Theme_ownCloud_Dialog)
+            .setIcon(R.drawable.ic_warning)
+            .setTitle(R.string.conflict_title)
+            .setMessage(getString(R.string.conflict_message))
+            .setPositiveButton(R.string.conflict_use_local_version,
+                               (dialog, which) -> {
+                                   if (listener != null) {
+                                       listener.conflictDecisionMade(Decision.KEEP_LOCAL);
+                                   }
+                               })
+            .setNeutralButton(R.string.conflict_keep_both,
+                              (dialog, which) -> {
+                                  if (listener != null) {
+                                      listener.conflictDecisionMade(Decision.KEEP_BOTH);
+                                  }
+                              });
+
+        if (this.canKeepServer) {
+            builder.setNegativeButton(R.string.conflict_use_server_version,
+                                      (dialog, which) -> {
+                                          if (listener != null) {
+                                              listener.conflictDecisionMade(Decision.KEEP_SERVER);
+                                          }
+                                      });
+        }
+
+        return builder.create();
     }
 
     public void showDialog(AppCompatActivity activity) {
@@ -96,8 +100,8 @@ public class ConflictsResolveDialog extends DialogFragment {
 
     @Override
     public void onCancel(DialogInterface dialog) {
-        if (mListener != null) {
-            mListener.conflictDecisionMade(Decision.CANCEL);
+        if (listener != null) {
+            listener.conflictDecisionMade(Decision.CANCEL);
         }
     }
 

+ 1 - 0
src/main/res/values/strings.xml

@@ -171,6 +171,7 @@
     <string name="uploads_view_upload_status_unknown_fail">Unknown error</string>
     <string name="uploads_view_upload_status_waiting_for_wifi">Waiting for Wi-Fi</string>
     <string name="uploads_view_upload_status_waiting_exit_power_save_mode">Waiting to exit power save mode</string>
+    <string name="uploads_view_upload_status_fetching_server_version">Fetching server version…</string>
     <string name="uploads_view_later_waiting_to_upload">Waiting to upload</string>
     <string name="uploads_view_group_header" translatable="false">%1$s (%2$d)</string>
     <string name="downloader_download_in_progress_ticker">Downloading…</string>