From cc8efaa037fdf186f413beadd1ed1ab49d2f6756 Mon Sep 17 00:00:00 2001
From: Vincent Petry <pvince81@owncloud.com>
Date: Mon, 28 Sep 2015 17:50:11 +0200
Subject: Show conflict dialog before upload when possible

When uploading files, first check if the files exist in the current file
list. For the ones that do, show a conflict dialog.
For the rest, upload directly.

If the upload operation detects a conflict on the server side, it will
also continue populating the conflict dialog.

From now on, server side conflict can only occur if someone concurrently
uploaded a file into the same folder but the current user hasn't
refreshed the list yet.
---
 apps/files/js/file-upload.js | 65 +++++++++++++++++++++++++++++++-------------
 apps/files/js/filelist.js    | 21 ++++++++++++--
 2 files changed, 64 insertions(+), 22 deletions(-)

diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js
index 17f0f777169..03330ad7c5d 100644
--- a/apps/files/js/file-upload.js
+++ b/apps/files/js/file-upload.js
@@ -18,7 +18,7 @@
  *    - TODO music upload button
  */
 
-/* global Files, FileList, jQuery, oc_requesttoken, humanFileSize, getUniqueName */
+/* global jQuery, oc_requesttoken, humanFileSize */
 
 /**
  * Function that will allow us to know if Ajax uploads are supported
@@ -139,6 +139,9 @@ OC.Upload = {
 		if (data.data) {
 			data.data.append('resolution', 'replace');
 		} else {
+			if (!data.formData) {
+				data.formData = [];
+			}
 			data.formData.push({name:'resolution', value:'replace'}); //hack for ie8
 		}
 		data.submit();
@@ -152,6 +155,9 @@ OC.Upload = {
 		if (data.data) {
 			data.data.append('resolution', 'autorename');
 		} else {
+			if (!data.formData) {
+				data.formData = [];
+			}
 			data.formData.push({name:'resolution', value:'autorename'}); //hack for ie8
 		}
 		data.submit();
@@ -164,8 +170,9 @@ OC.Upload = {
 		}
 	},
 	/**
-	 * TODO checks the list of existing files prior to uploading and shows a simple dialog to choose
+	 * checks the list of existing files prior to uploading and shows a simple dialog to choose
 	 * skip all, replace all or choose which files to keep
+	 *
 	 * @param {array} selection of files to upload
 	 * @param {object} callbacks - object with several callback methods
 	 * @param {function} callbacks.onNoConflicts
@@ -175,14 +182,34 @@ OC.Upload = {
 	 * @param {function} callbacks.onCancel
 	 */
 	checkExistingFiles: function (selection, callbacks) {
-		/*
-		$.each(selection.uploads, function(i, upload) {
-			var $row = OCA.Files.App.fileList.findFileEl(upload.files[0].name);
-			if ($row) {
-				// TODO check filelist before uploading and show dialog on conflicts, use callbacks
+		var fileList = OCA.Files.App.fileList;
+		var conflicts = [];
+		// only keep non-conflicting uploads
+		selection.uploads = _.filter(selection.uploads, function(upload) {
+			var fileInfo = fileList.findFile(upload.files[0].name);
+			if (fileInfo) {
+				conflicts.push([
+					// original
+					_.extend(fileInfo, {
+						directory: fileInfo.directory || fileInfo.path || fileList.getCurrentDirectory()
+					}),
+					// replacement (File object)
+					upload
+				]);
+				return false;
 			}
+			return true;
 		});
-		*/
+		if (conflicts.length) {
+			_.each(conflicts, function(conflictData) {
+				OC.dialogs.fileexists(conflictData[1], conflictData[0], conflictData[1].files[0], OC.Upload);
+			});
+		}
+
+		// upload non-conflicting files
+		// note: when reaching the server they might still meet conflicts
+		// if the folder was concurrently modified, these will get added
+		// to the already visible dialog, if applicable
 		callbacks.onNoConflicts(selection);
 	},
 
@@ -368,18 +395,18 @@ OC.Upload = {
 				},
 				submit: function(e, data) {
 					OC.Upload.rememberUpload(data);
-					if ( ! data.formData ) {
-						var fileDirectory = '';
-						if(typeof data.files[0].relativePath !== 'undefined') {
-							fileDirectory = data.files[0].relativePath;
-						}
-						// noone set update parameters, we set the minimum
-						data.formData = {
-							requesttoken: oc_requesttoken,
-							dir: data.targetDir || FileList.getCurrentDirectory(),
-							file_directory: fileDirectory
-						};
+					if (!data.formData) {
+						data.formData = [];
+					}
+
+					var fileDirectory = '';
+					if(typeof data.files[0].relativePath !== 'undefined') {
+						fileDirectory = data.files[0].relativePath;
 					}
+					// FIXME: prevent re-adding the same
+					data.formData.push({name: 'requesttoken', value: oc_requesttoken});
+					data.formData.push({name: 'dir', value: data.targetDir || FileList.getCurrentDirectory()});
+					data.formData.push({name: 'file_directory', value: fileDirectory});
 				},
 				fail: function(e, data) {
 					OC.Upload.log('fail', e, data);
diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js
index e4a7aadd600..1b069530e69 100644
--- a/apps/files/js/filelist.js
+++ b/apps/files/js/filelist.js
@@ -719,8 +719,23 @@
 			return true;
 		},
 		/**
-		 * Returns the tr element for a given file name
-		 * @param fileName file name
+		 * Returns the file info for the given file name from the internal collection.
+		 *
+		 * @param {string} fileName file name
+		 * @return {OCA.Files.FileInfo} file info or null if it was not found
+		 *
+		 * @since 8.2
+		 */
+		findFile: function(fileName) {
+			return _.find(this.files, function(aFile) {
+				return (aFile.name === fileName);
+			}) || null;
+		},
+		/**
+		 * Returns the tr element for a given file name, but only if it was already rendered.
+		 *
+		 * @param {string} fileName file name
+		 * @return {Object} jQuery object of the matching row
 		 */
 		findFileEl: function(fileName){
 			// use filterAttr to avoid escaping issues
@@ -1877,7 +1892,7 @@
 		 * @return {bool} true if the file exists in the list, false otherwise
 		 */
 		inList:function(file) {
-			return this.findFileEl(file).length;
+			return this.findFile(file);
 		},
 
 		/**
-- 
cgit v1.2.3