From 85176ec0721f5a1ac2dd8f08dd34f7d8d51b6a19 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= <jfd@butonic.de>
Date: Mon, 8 Dec 2014 15:25:21 +0100
Subject: [PATCH 1/2] return correct result

---
 lib/private/files/view.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lib/private/files/view.php b/lib/private/files/view.php
index 331ab9ba6cd..377a27b1554 100644
--- a/lib/private/files/view.php
+++ b/lib/private/files/view.php
@@ -678,7 +678,7 @@ class View {
 
 			$source = fopen($tmpFile, 'r');
 			if ($source) {
-				$this->file_put_contents($path, $source);
+				$result = $this->file_put_contents($path, $source);
 				// $this->file_put_contents() might have already closed
 				// the resource, so we check it, before trying to close it
 				// to avoid messages in the error log.
@@ -686,7 +686,7 @@ class View {
 					fclose($source);
 				}
 				unlink($tmpFile);
-				return true;
+				return $result;
 			} else {
 				return false;
 			}

From c615b3527f7c472afbc93d3293c7f467a99cbd0b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= <jfd@butonic.de>
Date: Mon, 8 Dec 2014 15:26:31 +0100
Subject: [PATCH 2/2] show readonly message in file conflict dialog, make it
 always selected

---
 apps/files/ajax/upload.php           | 24 ++++++---
 apps/files/css/upload.css            | 22 +++++++++
 apps/files/js/file-upload.js         | 15 +++++-
 apps/files/templates/fileexists.html |  1 +
 core/js/oc-dialogs.js                | 73 ++++++++++++++++------------
 5 files changed, 97 insertions(+), 38 deletions(-)

diff --git a/apps/files/ajax/upload.php b/apps/files/ajax/upload.php
index eb99d0644f7..fcee0166da6 100644
--- a/apps/files/ajax/upload.php
+++ b/apps/files/ajax/upload.php
@@ -117,6 +117,12 @@ if (strpos($dir, '..') === false) {
 	$fileCount = count($files['name']);
 	for ($i = 0; $i < $fileCount; $i++) {
 
+		if (isset($_POST['resolution'])) {
+			$resolution = $_POST['resolution'];
+		} else {
+			$resolution = null;
+		}
+
 		// target directory for when uploading folders
 		$relativePath = '';
 		if(!empty($_POST['file_directory'])) {
@@ -124,7 +130,7 @@ if (strpos($dir, '..') === false) {
 		}
 
 		// $path needs to be normalized - this failed within drag'n'drop upload to a sub-folder
-		if (isset($_POST['resolution']) && $_POST['resolution']==='autorename') {
+		if ($resolution === 'autorename') {
 			// append a number in brackets like 'filename (2).ext'
 			$target = OCP\Files::buildNotExistingFileName(stripslashes($dir . $relativePath), $files['name'][$i]);
 		} else {
@@ -141,9 +147,12 @@ if (strpos($dir, '..') === false) {
 		}
 		$returnedDir = \OC\Files\Filesystem::normalizePath($returnedDir);
 
-		if ( ! \OC\Files\Filesystem::file_exists($target)
-			|| (isset($_POST['resolution']) && $_POST['resolution']==='replace')
-		) {
+
+		$exists = \OC\Files\Filesystem::file_exists($target);
+		if ($exists) {
+			$updatable = \OC\Files\Filesystem::isUpdatable($target);
+		}
+		if ( ! $exists || ($updatable && $resolution === 'replace' ) ) {
 			// upload and overwrite file
 			try
 			{
@@ -181,8 +190,11 @@ if (strpos($dir, '..') === false) {
 				$error = $l->t('Upload failed. Could not get file info.');
 			} else {
 				$data = \OCA\Files\Helper::formatFileInfo($meta);
-				$data['permissions'] = $data['permissions'] & $allowedPermissions;
-				$data['status'] = 'existserror';
+				if ($updatable) {
+					$data['status'] = 'existserror';
+				} else {
+					$data['status'] = 'readonly';
+				}
 				$data['originalname'] = $files['tmp_name'][$i];
 				$data['uploadMaxFilesize'] = $maxUploadFileSize;
 				$data['maxHumanFilesize'] = $maxHumanFileSize;
diff --git a/apps/files/css/upload.css b/apps/files/css/upload.css
index 98754b910de..cc383879fb2 100644
--- a/apps/files/css/upload.css
+++ b/apps/files/css/upload.css
@@ -64,6 +64,28 @@
 	font-size: 13px;
 }
 
+.oc-dialog .fileexists {
+	-webkit-touch-callout: none;
+	-webkit-user-select: none;
+	-khtml-user-select: none;
+	-moz-user-select: none;
+	-ms-user-select: none;
+	user-select: none;
+}
+
+.oc-dialog .fileexists .conflict .filename,
+.oc-dialog .fileexists .conflict .mtime,
+.oc-dialog .fileexists .conflict .size {
+	-webkit-touch-callout: initial;
+	-webkit-user-select: initial;
+	-khtml-user-select: initial;
+	-moz-user-select: initial;
+	-ms-user-select: initial;
+	user-select: initial;
+}
+.oc-dialog .fileexists .conflict .message {
+	color: #e9322d;
+}
 .oc-dialog .fileexists table {
 	width: 100%;
 }
diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js
index ab450dc5cac..9fe623075bc 100644
--- a/apps/files/js/file-upload.js
+++ b/apps/files/js/file-upload.js
@@ -175,7 +175,14 @@ OC.Upload = {
 	 * @param {function} callbacks.onCancel
 	 */
 	checkExistingFiles: function (selection, callbacks) {
-		// TODO check filelist before uploading and show dialog on conflicts, use 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
+			}
+		});
+		*/
 		callbacks.onNoConflicts(selection);
 	},
 
@@ -417,11 +424,15 @@ OC.Upload = {
 						data.textStatus = 'servererror';
 						data.errorThrown = t('files', 'Could not get result from server.');
 						fu._trigger('fail', e, data);
+					} else if (result[0].status === 'readonly') {
+						var original = result[0];
+						var replacement = data.files[0];
+						OC.dialogs.fileexists(data, original, replacement, OC.Upload);
 					} else if (result[0].status === 'existserror') {
 						//show "file already exists" dialog
 						var original = result[0];
 						var replacement = data.files[0];
-						OC.dialogs.fileexists(data, original, replacement, OC.Upload, fu);
+						OC.dialogs.fileexists(data, original, replacement, OC.Upload);
 					} else if (result[0].status !== 'success') {
 						//delete data.jqXHR;
 						data.textStatus = 'servererror';
diff --git a/apps/files/templates/fileexists.html b/apps/files/templates/fileexists.html
index 79beccef3e5..5360a7c8e8f 100644
--- a/apps/files/templates/fileexists.html
+++ b/apps/files/templates/fileexists.html
@@ -20,6 +20,7 @@
 				<span class="svg icon"></span>
 				<div class="mtime"></div>
 				<div class="size"></div>
+				<div class="message"></div>
 			</div>
 		</div>
 	</div>
diff --git a/core/js/oc-dialogs.js b/core/js/oc-dialogs.js
index 9e5afea1a6f..0c046d8ef0e 100644
--- a/core/js/oc-dialogs.js
+++ b/core/js/oc-dialogs.js
@@ -363,56 +363,69 @@ var OCdialogs = {
 			return canvas.toDataURL("image/png", 0.7);
 		};
 
-		var addConflict = function(conflicts, original, replacement) {
+		var addConflict = function($conflicts, original, replacement) {
 
-			var conflict = conflicts.find('.template').clone().removeClass('template').addClass('conflict');
+			var $conflict = $conflicts.find('.template').clone().removeClass('template').addClass('conflict');
+			var $originalDiv = $conflict.find('.original');
+			var $replacementDiv = $conflict.find('.replacement');
 
-			conflict.data('data',data);
+			$conflict.data('data',data);
 
-			conflict.find('.filename').text(original.name);
-			conflict.find('.original .size').text(humanFileSize(original.size));
-			conflict.find('.original .mtime').text(formatDate(original.mtime));
+			$conflict.find('.filename').text(original.name);
+			$originalDiv.find('.size').text(humanFileSize(original.size));
+			$originalDiv.find('.mtime').text(formatDate(original.mtime));
 			// ie sucks
 			if (replacement.size && replacement.lastModifiedDate) {
-				conflict.find('.replacement .size').text(humanFileSize(replacement.size));
-				conflict.find('.replacement .mtime').text(formatDate(replacement.lastModifiedDate));
+				$replacementDiv.find('.size').text(humanFileSize(replacement.size));
+				$replacementDiv.find('.mtime').text(formatDate(replacement.lastModifiedDate));
 			}
 			var path = original.directory + '/' +original.name;
 			Files.lazyLoadPreview(path, original.mimetype, function(previewpath){
-				conflict.find('.original .icon').css('background-image','url('+previewpath+')');
+				$originalDiv.find('.icon').css('background-image','url('+previewpath+')');
 			}, 96, 96, original.etag);
 			getCroppedPreview(replacement).then(
 				function(path){
-					conflict.find('.replacement .icon').css('background-image','url(' + path + ')');
+					$replacementDiv.find('.icon').css('background-image','url(' + path + ')');
 				}, function(){
 					Files.getMimeIcon(replacement.type,function(path){
-						conflict.find('.replacement .icon').css('background-image','url(' + path + ')');
+						$replacementDiv.find('.icon').css('background-image','url(' + path + ')');
 					});
 				}
 			);
-			conflicts.append(conflict);
+			$conflicts.append($conflict);
 
 			//set more recent mtime bold
 			// ie sucks
 			if (replacement.lastModifiedDate && replacement.lastModifiedDate.getTime() > original.mtime) {
-				conflict.find('.replacement .mtime').css('font-weight', 'bold');
+				$replacementDiv.find('.mtime').css('font-weight', 'bold');
 			} else if (replacement.lastModifiedDate && replacement.lastModifiedDate.getTime() < original.mtime) {
-				conflict.find('.original .mtime').css('font-weight', 'bold');
+				$originalDiv.find('.mtime').css('font-weight', 'bold');
 			} else {
 				//TODO add to same mtime collection?
 			}
 
 			// set bigger size bold
 			if (replacement.size && replacement.size > original.size) {
-				conflict.find('.replacement .size').css('font-weight', 'bold');
+				$replacementDiv.find('.size').css('font-weight', 'bold');
 			} else if (replacement.size && replacement.size < original.size) {
-				conflict.find('.original .size').css('font-weight', 'bold');
+				$originalDiv.find('.size').css('font-weight', 'bold');
 			} else {
 				//TODO add to same size collection?
 			}
 
 			//TODO show skip action for files with same size and mtime in bottom row
 
+			// always keep readonly files
+
+			if (original.status === 'readonly') {
+				$originalDiv
+					.addClass('readonly')
+					.find('input[type="checkbox"]')
+						.prop('checked', true)
+						.prop('disabled', true);
+				$originalDiv.find('.message')
+					.text(t('core','read-only'))
+			}
 		};
 		//var selection = controller.getSelection(data.originalFiles);
 		//if (selection.defaultAction) {
@@ -423,8 +436,8 @@ var OCdialogs = {
 		if (this._fileexistsshown) {
 			// add conflict
 
-			var conflicts = $(dialogId+ ' .conflicts');
-			addConflict(conflicts, original, replacement);
+			var $conflicts = $(dialogId+ ' .conflicts');
+			addConflict($conflicts, original, replacement);
 
 			var count = $(dialogId+ ' .conflict').length;
 			var title = n('core',
@@ -456,8 +469,8 @@ var OCdialogs = {
 				});
 				$('body').append($dlg);
 
-				var conflicts = $($dlg).find('.conflicts');
-				addConflict(conflicts, original, replacement);
+				var $conflicts = $dlg.find('.conflicts');
+				addConflict($conflicts, original, replacement);
 
 				var buttonlist = [{
 						text: t('core', 'Cancel'),
@@ -496,20 +509,20 @@ var OCdialogs = {
 
 				//add checkbox toggling actions
 				$(dialogId).find('.allnewfiles').on('click', function() {
-					var checkboxes = $(dialogId).find('.conflict .replacement input[type="checkbox"]');
-					checkboxes.prop('checked', $(this).prop('checked'));
+					var $checkboxes = $(dialogId).find('.conflict .replacement input[type="checkbox"]');
+					$checkboxes.prop('checked', $(this).prop('checked'));
 				});
 				$(dialogId).find('.allexistingfiles').on('click', function() {
-					var checkboxes = $(dialogId).find('.conflict .original input[type="checkbox"]');
-					checkboxes.prop('checked', $(this).prop('checked'));
+					var $checkboxes = $(dialogId).find('.conflict .original:not(.readonly) input[type="checkbox"]');
+					$checkboxes.prop('checked', $(this).prop('checked'));
 				});
-				$(dialogId).find('.conflicts').on('click', '.replacement,.original', function() {
-					var checkbox = $(this).find('input[type="checkbox"]');
-					checkbox.prop('checked', !checkbox.prop('checked'));
+				$(dialogId).find('.conflicts').on('click', '.replacement,.original:not(.readonly)', function() {
+					var $checkbox = $(this).find('input[type="checkbox"]');
+					$checkbox.prop('checked', !$checkbox.prop('checked'));
 				});
-				$(dialogId).find('.conflicts').on('click', 'input[type="checkbox"]', function() {
-					var checkbox = $(this);
-					checkbox.prop('checked', !checkbox.prop('checked'));
+				$(dialogId).find('.conflicts').on('click', '.replacement input[type="checkbox"],.original:not(.readonly) input[type="checkbox"]', function() {
+					var $checkbox = $(this);
+					$checkbox.prop('checked', !checkbox.prop('checked'));
 				});
 
 				//update counters