diff --git a/apps/files/ajax/upload.php b/apps/files/ajax/upload.php
index 41d3a3eca4fe574577bcb496ced8f003b5fb1dfc..3d5314afc89d0209230b11a4728055945551c70a 100644
--- a/apps/files/ajax/upload.php
+++ b/apps/files/ajax/upload.php
@@ -78,7 +78,7 @@ foreach ($_FILES['files']['error'] as $error) {
 }
 $files = $_FILES['files'];
 
-$error = '';
+$error = false;
 
 $maxUploadFileSize = $storageStats['uploadMaxFilesize'];
 $maxHumanFileSize = OCP\Util::humanFileSize($maxUploadFileSize);
@@ -98,23 +98,57 @@ $result = array();
 if (strpos($dir, '..') === false) {
 	$fileCount = count($files['name']);
 	for ($i = 0; $i < $fileCount; $i++) {
-		$target = OCP\Files::buildNotExistingFileName(stripslashes($dir), $files['name'][$i]);
 		// $path needs to be normalized - this failed within drag'n'drop upload to a sub-folder
-		$target = \OC\Files\Filesystem::normalizePath($target);
-		if (is_uploaded_file($files['tmp_name'][$i]) and \OC\Files\Filesystem::fromTmpFile($files['tmp_name'][$i], $target)) {
+		if (isset($_POST['resolution']) && $_POST['resolution']==='autorename') {
+			// append a number in brackets like 'filename (2).ext'
+			$target = OCP\Files::buildNotExistingFileName(stripslashes($dir), $files['name'][$i]);
+		} else {
+			$target = \OC\Files\Filesystem::normalizePath(stripslashes($dir).'/'.$files['name'][$i]);
+		}
+		
+		if ( ! \OC\Files\Filesystem::file_exists($target)
+			|| (isset($_POST['resolution']) && $_POST['resolution']==='replace')
+		) {
+			// upload and overwrite file
+			if (is_uploaded_file($files['tmp_name'][$i]) and \OC\Files\Filesystem::fromTmpFile($files['tmp_name'][$i], $target)) {
+				
+				// updated max file size after upload
+				$storageStats = \OCA\files\lib\Helper::buildFileStorageStatistics($dir);
+				
+				$meta = \OC\Files\Filesystem::getFileInfo($target);
+				if ($meta === false) {
+					$error = $l->t('Upload failed. Could not get file info.');
+				} else {
+					$result[] = array('status' => 'success',
+						'mime' => $meta['mimetype'],
+						'mtime' => $meta['mtime'],
+						'size' => $meta['size'],
+						'id' => $meta['fileid'],
+						'name' => basename($target),
+						'originalname' => $files['tmp_name'][$i],
+						'uploadMaxFilesize' => $maxUploadFileSize,
+						'maxHumanFilesize' => $maxHumanFileSize,
+						'permissions' => $meta['permissions'],
+					);
+				}
+				
+			} else {
+				$error = $l->t('Upload failed. Could not find uploaded file');
+			}
+			
+		} else {
+			// file already exists
 			$meta = \OC\Files\Filesystem::getFileInfo($target);
-			// updated max file size after upload
-			$storageStats = \OCA\files\lib\Helper::buildFileStorageStatistics($dir);
 			if ($meta === false) {
-				OCP\JSON::error(array('data' => array_merge(array('message' => $l->t('Upload failed')), $storageStats)));
-				exit();
+				$error = $l->t('Upload failed. Could not get file info.');
 			} else {
-				$result[] = array('status' => 'success',
+				$result[] = array('status' => 'existserror',
 					'mime' => $meta['mimetype'],
+					'mtime' => $meta['mtime'],
 					'size' => $meta['size'],
 					'id' => $meta['fileid'],
 					'name' => basename($target),
-					'originalname' => $files['name'][$i],
+					'originalname' => $files['tmp_name'][$i],
 					'uploadMaxFilesize' => $maxUploadFileSize,
 					'maxHumanFilesize' => $maxHumanFileSize,
 					'permissions' => $meta['permissions'],
@@ -122,10 +156,13 @@ if (strpos($dir, '..') === false) {
 			}
 		}
 	}
-	OCP\JSON::encodedPrint($result);
-	exit();
 } else {
 	$error = $l->t('Invalid directory.');
 }
 
-OCP\JSON::error(array('data' => array_merge(array('message' => $error), $storageStats)));
+if ($error === false) {
+	OCP\JSON::encodedPrint($result);
+	exit();
+} else {
+	OCP\JSON::error(array('data' => array_merge(array('message' => $error), $storageStats)));
+}
diff --git a/apps/files/css/files.css b/apps/files/css/files.css
index 0acb3c5d82971ddd40502aeafbff4d18fbd91064..b3ecd1dab93e4cb62cb167881960c7b6aba9f49b 100644
--- a/apps/files/css/files.css
+++ b/apps/files/css/files.css
@@ -360,4 +360,3 @@ table.dragshadow td.size {
 .mask.transparent{
 	opacity: 0;
 }
-
diff --git a/apps/files/css/upload.css b/apps/files/css/upload.css
new file mode 100644
index 0000000000000000000000000000000000000000..2d11e41ba88e3b09520d421d4decf28a95a6c339
--- /dev/null
+++ b/apps/files/css/upload.css
@@ -0,0 +1,119 @@
+
+#upload {
+	height:27px; padding:0; margin-left:0.2em; overflow:hidden;
+	vertical-align: top;
+}
+#upload a {
+	position:relative; display:block; width:100%; height:27px;
+	cursor:pointer; z-index:10;
+	background-image:url('%webroot%/core/img/actions/upload.svg');
+	background-repeat:no-repeat;
+	background-position:7px 6px;
+	opacity:0.65;
+}
+.file_upload_target { display:none; }
+.file_upload_form { display:inline; float:left; margin:0; padding:0; cursor:pointer; overflow:visible; }
+#file_upload_start {
+	float: left;
+	left:0; top:0; width:28px; height:27px; padding:0;
+	font-size:1em;
+	-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; filter:alpha(opacity=0); opacity:0;
+	z-index:20; position:relative; cursor:pointer; overflow:hidden;
+}
+
+#uploadprogresswrapper {
+	display: inline-block;
+	vertical-align: top;
+	margin:0.3em;
+	height: 29px;
+}
+#uploadprogressbar {
+	position:relative;
+	float: left;
+	margin-left: 12px;
+	width: 130px;
+	height: 26px;
+	display:inline-block;
+}
+#uploadprogressbar + stop {
+	font-size: 13px;
+}
+
+.oc-dialog .fileexists table {
+	width: 100%;
+}
+.oc-dialog .fileexists th {
+	padding-left: 0;
+	padding-right: 0;
+}
+.oc-dialog .fileexists th input[type='checkbox'] {
+	margin-right: 3px;
+}
+.oc-dialog .fileexists th:first-child {
+	width: 230px;
+}
+.oc-dialog .fileexists th label {
+	font-weight: normal;
+	color:black;
+}
+.oc-dialog .fileexists th .count {
+	margin-left: 3px;
+}
+.oc-dialog .fileexists .conflicts .template {
+	display: none;
+}
+.oc-dialog .fileexists .conflict {
+	width: 100%;
+	height: 85px;
+}
+.oc-dialog .fileexists .conflict .filename {
+	color:#777;
+	word-break: break-all;
+	clear: left;
+}
+.oc-dialog .fileexists .icon {
+	width: 64px;
+	height: 64px;
+	margin: 0px 5px 5px 5px;
+	background-repeat: no-repeat;
+	background-size: 64px 64px;
+	float: left;
+}
+.oc-dialog .fileexists .replacement {
+	float: left;
+	width: 230px;
+}
+.oc-dialog .fileexists .original {
+	float: left;
+	width: 230px;
+}
+.oc-dialog .fileexists .conflicts {
+	overflow-y:scroll;
+	max-height: 225px;
+}
+.oc-dialog .fileexists .conflict input[type='checkbox'] {
+	float: left;
+}
+.oc-dialog .fileexists .toggle {
+	background-image: url('%webroot%/core/img/actions/triangle-e.png');
+	width: 16px;
+	height: 16px;
+}
+.oc-dialog .fileexists #allfileslabel {
+	float:right;
+}
+.oc-dialog .fileexists #allfiles {
+	vertical-align: bottom;
+	position: relative;
+	top: -3px;
+}
+.oc-dialog .fileexists #allfiles + span{
+	vertical-align: bottom;
+}
+.oc-dialog .oc-dialog-buttonrow {
+	width:100%;
+	text-align:right;
+}
+.oc-dialog .oc-dialog-buttonrow .cancel {
+	float:left;
+}
diff --git a/apps/files/index.php b/apps/files/index.php
index d46d8e32eefcce1cc08cd502a4b016bde1a63418..9e54a706c01f6ad391b8a90e57a5d44c10ff896d 100644
--- a/apps/files/index.php
+++ b/apps/files/index.php
@@ -26,6 +26,7 @@ OCP\User::checkLoggedIn();
 
 // Load the files we need
 OCP\Util::addStyle('files', 'files');
+OCP\Util::addStyle('files', 'upload');
 OCP\Util::addscript('files', 'file-upload');
 OCP\Util::addscript('files', 'jquery.iframe-transport');
 OCP\Util::addscript('files', 'jquery.fileupload');
diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js
index 6a53bebfcc0280a03b13dd89e9a1c3a37ed513e1..b52221ac1fc1953f234a62823c5cd54cffbab055 100644
--- a/apps/files/js/file-upload.js
+++ b/apps/files/js/file-upload.js
@@ -1,3 +1,12 @@
+/**
+ * The file upload code uses several hooks to interact with blueimps jQuery file upload library:
+ * 1. the core upload handling hooks are added when initializing the plugin,
+ * 2. if the browser supports progress events they are added in a separate set after the initialization
+ * 3. every app can add it's own triggers for fileupload
+ *    - files adds d'n'd handlers and also reacts to done events to add new rows to the filelist
+ *    - TODO pictures upload button
+ *    - TODO music upload button
+ */
 
 /**
  * Function that will allow us to know if Ajax uploads are supported
@@ -26,51 +35,278 @@ function supportAjaxUploadWithProgress() {
 	}
 }
 
+/**
+ * keeps track of uploads in progress and implements callbacks for the conflicts dialog
+ * @type {OC.Upload}
+ */
+OC.Upload = {
+	_uploads: [],
+	/**
+	 * cancels a single upload, 
+	 * @deprecated because it was only used when a file currently beeing uploaded was deleted. Now they are added after
+	 * they have been uploaded.
+	 * @param {string} dir
+	 * @param {string} filename
+	 * @returns {unresolved}
+	 */
+	cancelUpload:function(dir, filename) {
+		var self = this;
+		var deleted = false;
+		//FIXME _selections
+		jQuery.each(this._uploads, function(i, jqXHR) {
+			if (selection.dir === dir && selection.uploads[filename]) {
+				deleted = self.deleteSelectionUpload(selection, filename);
+				return false; // end searching through selections
+			}
+		});
+		return deleted;
+	},
+	/**
+	 * deletes the jqHXR object from a data selection
+	 * @param {object} data
+	 */
+	deleteUpload:function(data) {
+		delete data.jqXHR;
+	},
+	/**
+	 * cancels all uploads
+	 */
+	cancelUploads:function() {
+		this.log('canceling uploads');
+		jQuery.each(this._uploads,function(i, jqXHR){
+			jqXHR.abort();
+		});
+		this._uploads = [];
+	},
+	rememberUpload:function(jqXHR){
+		if (jqXHR) {
+			this._uploads.push(jqXHR);
+		}
+	},
+	/**
+	 * Checks the currently known uploads.
+	 * returns true if any hxr has the state 'pending'
+	 * @returns {boolean}
+	 */
+	isProcessing:function(){
+		var count = 0;
+		
+		jQuery.each(this._uploads,function(i, data){
+			if (data.state() === 'pending') {
+				count++;
+			}
+		});
+		return count > 0;
+	},
+	/**
+	 * callback for the conflicts dialog
+	 * @param {object} data
+	 */
+	onCancel:function(data) {
+		this.cancelUploads();
+	},
+	/**
+	 * callback for the conflicts dialog
+	 * calls onSkip, onReplace or onAutorename for each conflict
+	 * @param {object} conflicts - list of conflict elements
+	 */
+	onContinue:function(conflicts) {
+		var self = this;
+		//iterate over all conflicts
+		jQuery.each(conflicts, function (i, conflict) {
+			conflict = $(conflict);
+			var keepOriginal = conflict.find('.original input[type="checkbox"]:checked').length === 1;
+			var keepReplacement = conflict.find('.replacement input[type="checkbox"]:checked').length === 1;
+			if (keepOriginal && keepReplacement) {
+				// when both selected -> autorename
+				self.onAutorename(conflict.data('data'));
+			} else if (keepReplacement) {
+				// when only replacement selected -> overwrite
+				self.onReplace(conflict.data('data'));
+			} else {
+				// when only original seleted -> skip
+				// when none selected -> skip
+				self.onSkip(conflict.data('data'));
+			}
+		});
+	},
+	/**
+	 * handle skipping an upload
+	 * @param {object} data
+	 */
+	onSkip:function(data){
+		this.log('skip', null, data);
+		this.deleteUpload(data);
+	},
+	/**
+	 * handle replacing a file on the server with an uploaded file
+	 * @param {object} data
+	 */
+	onReplace:function(data){
+		this.log('replace', null, data);
+		data.data.append('resolution', 'replace');
+		data.submit();
+	},
+	/**
+	 * handle uploading a file and letting the server decide a new name
+	 * @param {object} data
+	 */
+	onAutorename:function(data){
+		this.log('autorename', null, data);
+		if (data.data) {
+			data.data.append('resolution', 'autorename');
+		} else {
+			data.formData.push({name:'resolution',value:'autorename'}); //hack for ie8
+		}
+		data.submit();
+	},
+	_trace:false, //TODO implement log handler for JS per class?
+	log:function(caption, e, data) {
+		if (this._trace) {
+			console.log(caption);
+			console.log(data);
+		}
+	},
+	/**
+	 * TODO 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
+	 * @param {function} callbacks.onSkipConflicts
+	 * @param {function} callbacks.onReplaceConflicts
+	 * @param {function} callbacks.onChooseConflicts
+	 * @param {function} callbacks.onCancel
+	 */
+	checkExistingFiles: function (selection, callbacks){
+		// TODO check filelist before uploading and show dialog on conflicts, use callbacks
+		callbacks.onNoConflicts(selection);
+	}
+};
+
 $(document).ready(function() {
 
-	if ( $('#file_upload_start').length ) {
+	if ( $('#file_upload_start').exists() ) {
+
 		var file_upload_param = {
 			dropZone: $('#content'), // restrict dropZone to content div
+			autoUpload: false,
+			sequentialUploads: true,
 			//singleFileUploads is on by default, so the data.files array will always have length 1
+			/**
+			 * on first add of every selection
+			 * - check all files of originalFiles array with files in dir
+			 * - on conflict show dialog
+			 *   - skip all -> remember as single skip action for all conflicting files
+			 *   - replace all -> remember as single replace action for all conflicting files
+			 *   - choose -> show choose dialog
+			 *     - mark files to keep
+			 *       - when only existing -> remember as single skip action
+			 *       - when only new -> remember as single replace action
+			 *       - when both -> remember as single autorename action
+			 * - start uploading selection
+			 * @param {object} e
+			 * @param {object} data
+			 * @returns {boolean}
+			 */
 			add: function(e, data) {
-
-				if(data.files[0].type === '' && data.files[0].size == 4096)
-				{
+				OC.Upload.log('add', e, data);
+				var that = $(this);
+			
+				// we need to collect all data upload objects before starting the upload so we can check their existence
+				// and set individual conflict actions. unfortunately there is only one variable that we can use to identify
+				// the selection a data upload is part of, so we have to collect them in data.originalFiles
+				// turning singleFileUploads off is not an option because we want to gracefully handle server errors like
+				// already exists
+			
+				// create a container where we can store the data objects
+				if ( ! data.originalFiles.selection ) {
+					// initialize selection and remember number of files to upload
+					data.originalFiles.selection = {
+						uploads: [],
+						filesToUpload: data.originalFiles.length,
+						totalBytes: 0
+					};
+				}
+				var selection = data.originalFiles.selection;
+			
+				// add uploads
+				if ( selection.uploads.length < selection.filesToUpload ){
+					// remember upload
+					selection.uploads.push(data);
+				}
+			
+				//examine file
+				var file = data.files[0];
+			
+				if (file.type === '' && file.size === 4096) {
 					data.textStatus = 'dirorzero';
-					data.errorThrown = t('files','Unable to upload your file as it is a directory or has 0 bytes');
-					var fu = $(this).data('blueimp-fileupload') || $(this).data('fileupload');
-					fu._trigger('fail', e, data);
-					return true; //don't upload this file but go on with next in queue
+					data.errorThrown = t('files', 'Unable to upload {filename} as it is a directory or has 0 bytes',
+						{filename: file.name}
+					);
 				}
-
-				var totalSize=0;
-				$.each(data.originalFiles, function(i,file){
-					totalSize+=file.size;
-				});
-
-				if(totalSize>$('#max_upload').val()){
+			
+				// add size
+				selection.totalBytes += file.size;
+			
+				//check max upload size
+				if (selection.totalBytes > $('#max_upload').val()) {
 					data.textStatus = 'notenoughspace';
-					data.errorThrown = t('files','Not enough space available');
-					var fu = $(this).data('blueimp-fileupload') || $(this).data('fileupload');
+					data.errorThrown = t('files', 'Not enough space available');
+				}
+			
+				// end upload for whole selection on error
+				if (data.errorThrown) {
+					// trigger fileupload fail
+					var fu = that.data('blueimp-fileupload') || that.data('fileupload');
 					fu._trigger('fail', e, data);
 					return false; //don't upload anything
 				}
 
-				// start the actual file upload
-				var jqXHR = data.submit();
+				// check existing files when all is collected
+				if ( selection.uploads.length >= selection.filesToUpload ) {
+				
+					//remove our selection hack:
+					delete data.originalFiles.selection;
 
-				// remember jqXHR to show warning to user when he navigates away but an upload is still in progress
-				if (typeof data.context !== 'undefined' && data.context.data('type') === 'dir') {
-					var dirName = data.context.data('file');
-					if(typeof uploadingFiles[dirName] === 'undefined') {
-						uploadingFiles[dirName] = {};
-					}
-					uploadingFiles[dirName][data.files[0].name] = jqXHR;
-				} else {
-					uploadingFiles[data.files[0].name] = jqXHR;
+					var callbacks = {
+					
+						onNoConflicts: function (selection) {
+							$.each(selection.uploads, function(i, upload) {
+								upload.submit();
+							});
+						},
+						onSkipConflicts: function (selection) {
+							//TODO mark conflicting files as toskip
+						},
+						onReplaceConflicts: function (selection) {
+							//TODO mark conflicting files as toreplace
+						},
+						onChooseConflicts: function (selection) {
+							//TODO mark conflicting files as chosen
+						},
+						onCancel: function (selection) {
+							$.each(selection.uploads, function(i, upload) {
+								upload.abort();
+							});
+						}
+					};
+
+					OC.Upload.checkExistingFiles(selection, callbacks);
+				
 				}
+
+				return true; // continue adding files
+			},
+			/**
+			 * called after the first add, does NOT have the data param
+			 * @param {object} e
+			 */
+			start: function(e) {
+				OC.Upload.log('start', e, null);
 			},
 			submit: function(e, data) {
+				OC.Upload.rememberUpload(data);
 				if ( ! data.formData ) {
 					// noone set update parameters, we set the minimum
 					data.formData = {
@@ -79,13 +315,8 @@ $(document).ready(function() {
 					};
 				}
 			},
-			/**
-			 * called after the first add, does NOT have the data param
-			 * @param e
-			 */
-			start: function(e) {
-			},
 			fail: function(e, data) {
+				OC.Upload.log('fail', e, data);
 				if (typeof data.textStatus !== 'undefined' && data.textStatus !== 'success' ) {
 					if (data.textStatus === 'abort') {
 						$('#notification').text(t('files', 'Upload cancelled.'));
@@ -99,14 +330,15 @@ $(document).ready(function() {
 						$('#notification').fadeOut();
 					}, 5000);
 				}
-				delete uploadingFiles[data.files[0].name];
+				OC.Upload.deleteUpload(data);
 			},
 			/**
 			 * called for every successful upload
-			 * @param e
-			 * @param data
+			 * @param {object} e
+			 * @param {object} data
 			 */
 			done:function(e, data) {
+				OC.Upload.log('done', e, data);
 				// handle different responses (json or body from iframe for ie)
 				var response;
 				if (typeof data.result === 'string') {
@@ -117,33 +349,34 @@ $(document).ready(function() {
 				}
 				var result=$.parseJSON(response);
 
-				if(typeof result[0] !== 'undefined' && result[0].status === 'success') {
-					var filename = result[0].originalname;
-
-					// delete jqXHR reference
-					if (typeof data.context !== 'undefined' && data.context.data('type') === 'dir') {
-						var dirName = data.context.data('file');
-						delete uploadingFiles[dirName][filename];
-						if ($.assocArraySize(uploadingFiles[dirName]) == 0) {
-							delete uploadingFiles[dirName];
-						}
-					} else {
-						delete uploadingFiles[filename];
-					}
-					var file = result[0];
-				} else {
+				delete data.jqXHR;
+				
+				if(typeof result[0] === 'undefined') {
 					data.textStatus = 'servererror';
-					data.errorThrown = t('files', result.data.message);
+					data.errorThrown = t('files', 'Could not get result from server.');
+					var fu = $(this).data('blueimp-fileupload') || $(this).data('fileupload');
+					fu._trigger('fail', e, data);
+				} else if (result[0].status === 'existserror') {
+					//show "file already exists" dialog
+					var original = result[0];
+					var replacement = data.files[0];
+					var fu = $(this).data('blueimp-fileupload') || $(this).data('fileupload');
+					OC.dialogs.fileexists(data, original, replacement, OC.Upload, fu);
+				} else if (result[0].status !== 'success') {
+					//delete data.jqXHR;
+					data.textStatus = 'servererror';
+					data.errorThrown = result.data.message; // error message has been translated on server
 					var fu = $(this).data('blueimp-fileupload') || $(this).data('fileupload');
 					fu._trigger('fail', e, data);
 				}
 			},
 			/**
 			 * called after last upload
-			 * @param e
-			 * @param data
+			 * @param {object} e
+			 * @param {object} data
 			 */
 			stop: function(e, data) {
+				OC.Upload.log('stop', e, data);
 			}
 		};
 
@@ -155,6 +388,7 @@ $(document).ready(function() {
 
 			// add progress handlers
 			fileupload.on('fileuploadadd', function(e, data) {
+				OC.Upload.log('progress handle fileuploadadd', e, data);
 				//show cancel button
 				//if(data.dataType !== 'iframe') { //FIXME when is iframe used? only for ie?
 				//	$('#uploadprogresswrapper input.stop').show();
@@ -162,23 +396,29 @@ $(document).ready(function() {
 			});
 			// add progress handlers
 			fileupload.on('fileuploadstart', function(e, data) {
+				OC.Upload.log('progress handle fileuploadstart', e, data);
 				$('#uploadprogresswrapper input.stop').show();
 				$('#uploadprogressbar').progressbar({value:0});
 				$('#uploadprogressbar').fadeIn();
 			});
 			fileupload.on('fileuploadprogress', function(e, data) {
+				OC.Upload.log('progress handle fileuploadprogress', e, data);
 				//TODO progressbar in row
 			});
 			fileupload.on('fileuploadprogressall', function(e, data) {
+				OC.Upload.log('progress handle fileuploadprogressall', e, data);
 				var progress = (data.loaded / data.total) * 100;
 				$('#uploadprogressbar').progressbar('value', progress);
 			});
 			fileupload.on('fileuploadstop', function(e, data) {
+				OC.Upload.log('progress handle fileuploadstop', e, data);
+				
 				$('#uploadprogresswrapper input.stop').fadeOut();
 				$('#uploadprogressbar').fadeOut();
 				
 			});
 			fileupload.on('fileuploadfail', function(e, data) {
+				OC.Upload.log('progress handle fileuploadfail', e, data);
 				//if user pressed cancel hide upload progress bar and cancel button
 				if (data.errorThrown === 'abort') {
 					$('#uploadprogresswrapper input.stop').fadeOut();
@@ -201,9 +441,9 @@ $(document).ready(function() {
 	};
 
 	// warn user not to leave the page while upload is in progress
-	$(window).bind('beforeunload', function(e) {
-		if ($.assocArraySize(uploadingFiles) > 0) {
-			return t('files','File upload is in progress. Leaving the page now will cancel the upload.');
+	$(window).on('beforeunload', function(e) {
+		if (OC.Upload.isProcessing()) {
+			return t('files', 'File upload is in progress. Leaving the page now will cancel the upload.');
 		}
 	});
 
@@ -392,4 +632,5 @@ $(document).ready(function() {
 			$('#new>a').click();
 		});
 	});
+	window.file_upload_param = file_upload_param;
 });
diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js
index 009ea62de9342c8dea67d8c28e3fcd7dc2c93038..67d3d5ead8d87b935e99f379a005ae8a105abb57 100644
--- a/apps/files/js/fileactions.js
+++ b/apps/files/js/fileactions.js
@@ -177,7 +177,7 @@ $(document).ready(function () {
 FileActions.register('all', 'Delete', OC.PERMISSION_DELETE, function () {
 	return OC.imagePath('core', 'actions/delete');
 }, function (filename) {
-	if (Files.cancelUpload(filename)) {
+	if (OC.Upload.cancelUpload($('#dir').val(), filename)) {
 		if (filename.substr) {
 			filename = [filename];
 		}
diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js
index fe706801b9e2699a562607fab729b576e24488b6..4fc1b95a0ab5612bc48560b2328589cc83ab3179 100644
--- a/apps/files/js/filelist.js
+++ b/apps/files/js/filelist.js
@@ -680,153 +680,167 @@ $(document).ready(function(){
 
 	// handle upload events
 	var file_upload_start = $('#file_upload_start');
+
 	file_upload_start.on('fileuploaddrop', function(e, data) {
-		// only handle drop to dir if fileList exists
-		if ($('#fileList').length > 0) {
-			var dropTarget = $(e.originalEvent.target).closest('tr');
-			if(dropTarget && dropTarget.data('type') === 'dir') { // drag&drop upload to folder
-				var dirName = dropTarget.data('file');
-				// update folder in form
-				data.formData = function(form) {
-					var formArray = form.serializeArray();
-					// array index 0 contains the max files size
-					// array index 1 contains the request token
-					// array index 2 contains the directory
-					var parentDir = formArray[2]['value'];
-					if (parentDir === '/') {
-						formArray[2]['value'] += dirName;
-					} else {
-						formArray[2]['value'] += '/'+dirName;
-					}
-					return formArray;
+		OC.Upload.log('filelist handle fileuploaddrop', e, data);
+
+		var dropTarget = $(e.originalEvent.target).closest('tr');
+		if(dropTarget && dropTarget.data('type') === 'dir') { // drag&drop upload to folder
+
+			// remember as context
+			data.context = dropTarget;
+
+			var dir = dropTarget.data('file');
+
+			// update folder in form
+			data.formData = function(form) {
+				var formArray = form.serializeArray();
+				// array index 0 contains the max files size
+				// array index 1 contains the request token
+				// array index 2 contains the directory
+				var parentDir = formArray[2]['value'];
+				if (parentDir === '/') {
+					formArray[2]['value'] += dir;
+				} else {
+					formArray[2]['value'] += '/' + dir;
 				}
-			}
-		}
+
+				return formArray;
+			};
+		} 
+
 	});
 	file_upload_start.on('fileuploadadd', function(e, data) {
-		// only add to fileList if it exists
-		if ($('#fileList').length > 0) {
+		OC.Upload.log('filelist handle fileuploadadd', e, data);
 
-			if(FileList.deleteFiles && FileList.deleteFiles.indexOf(data.files[0].name)!=-1){//finish delete if we are uploading a deleted file
-				FileList.finishDelete(null, true); //delete file before continuing
+		//finish delete if we are uploading a deleted file
+		if(FileList.deleteFiles && FileList.deleteFiles.indexOf(data.files[0].name)!==-1){
+			FileList.finishDelete(null, true); //delete file before continuing
+		}
+
+		// add ui visualization to existing folder
+		if(data.context && data.context.data('type') === 'dir') {
+			// add to existing folder
+
+			// update upload counter ui
+			var uploadtext = data.context.find('.uploadtext');
+			var currentUploads = parseInt(uploadtext.attr('currentUploads'));
+			currentUploads += 1;
+			uploadtext.attr('currentUploads', currentUploads);
+
+			var translatedText = n('files', 'Uploading %n file', 'Uploading %n files', currentUploads);
+			if(currentUploads === 1) {
+				var img = OC.imagePath('core', 'loading.gif');
+				data.context.find('td.filename').attr('style','background-image:url('+img+')');
+				uploadtext.text(translatedText);
+				uploadtext.show();
+			} else {
+				uploadtext.text(translatedText);
 			}
+		}
+
+	});
+	/*
+	 * when file upload done successfully add row to filelist
+	 * update counter when uploading to sub folder
+	 */
+	file_upload_start.on('fileuploaddone', function(e, data) {
+		OC.Upload.log('filelist handle fileuploaddone', e, data);
+		
+		var response;
+		if (typeof data.result === 'string') {
+			response = data.result;
+		} else {
+			// fetch response from iframe
+			response = data.result[0].body.innerText;
+		}
+		var result=$.parseJSON(response);
 
-			// add ui visualization to existing folder or as new stand-alone file?
-			var dropTarget = $(e.originalEvent.target).closest('tr');
-			if(dropTarget && dropTarget.data('type') === 'dir') {
-				// add to existing folder
-				var dirName = dropTarget.data('file');
+		if(typeof result[0] !== 'undefined' && result[0].status === 'success') {
+			var file = result[0];
 
-				// set dir context
-				data.context = $('tr').filterAttr('data-type', 'dir').filterAttr('data-file', dirName);
+			if (data.context && data.context.data('type') === 'dir') {
 
 				// update upload counter ui
 				var uploadtext = data.context.find('.uploadtext');
 				var currentUploads = parseInt(uploadtext.attr('currentUploads'));
-				currentUploads += 1;
+				currentUploads -= 1;
 				uploadtext.attr('currentUploads', currentUploads);
 				var translatedText = n('files', 'Uploading %n file', 'Uploading %n files', currentUploads);
-				if(currentUploads === 1) {
-					var img = OC.imagePath('core', 'loading.gif');
+				if(currentUploads === 0) {
+					var img = OC.imagePath('core', 'filetypes/folder.png');
 					data.context.find('td.filename').attr('style','background-image:url('+img+')');
 					uploadtext.text(translatedText);
-					uploadtext.show();
+					uploadtext.hide();
 				} else {
 					uploadtext.text(translatedText);
 				}
+
+				// update folder size
+				var size = parseInt(data.context.data('size'));
+				size += parseInt(file.size);
+				data.context.attr('data-size', size);
+				data.context.find('td.filesize').text(humanFileSize(size));
+
 			} else {
+
 				// add as stand-alone row to filelist
-				var uniqueName = getUniqueName(data.files[0].name);
-				var size=t('files','Pending');
-				if(data.files[0].size>=0){
+				var size=t('files', 'Pending');
+				if (data.files[0].size>=0){
 					size=data.files[0].size;
 				}
 				var date=new Date();
 				var param = {};
 				if ($('#publicUploadRequestToken').length) {
-					param.download_url = document.location.href + '&download&path=/' + $('#dir').val() + '/' + uniqueName;
+					param.download_url = document.location.href + '&download&path=/' + $('#dir').val() + '/' + file.name;
 				}
+				//should the file exist in the list remove it
+				FileList.remove(file.name);
+
 				// create new file context
-				data.context = FileList.addFile(uniqueName,size,date,true,false,param);
+				data.context = FileList.addFile(file.name, file.size, date, false, false, param);
+
+				// update file data
+				data.context.attr('data-mime',file.mime).attr('data-id',file.id);
+
+				var permissions = data.context.data('permissions');
+				if(permissions != file.permissions) {
+					data.context.attr('data-permissions', file.permissions);
+					data.context.data('permissions', file.permissions);
+				}
+				FileActions.display(data.context.find('td.filename'));
 
+				var path = getPathForPreview(file.name);
+				lazyLoadPreview(path, file.mime, function(previewpath){
+					data.context.find('td.filename').attr('style','background-image:url('+previewpath+')');
+				});
 			}
 		}
 	});
-	file_upload_start.on('fileuploaddone', function(e, data) {
-		// only update the fileList if it exists
-		if ($('#fileList').length > 0) {
-			var response;
-			if (typeof data.result === 'string') {
-				response = data.result;
-			} else {
-				// fetch response from iframe
-				response = data.result[0].body.innerText;
-			}
-			var result=$.parseJSON(response);
-
-			if(typeof result[0] !== 'undefined' && result[0].status === 'success') {
-				var file = result[0];
-
-				if (data.context.data('type') === 'file') {
-					// update file data
-					data.context.attr('data-mime',file.mime).attr('data-id',file.id);
-					var size = data.context.data('size');
-					if(size!=file.size){
-						data.context.attr('data-size', file.size);
-						data.context.find('td.filesize').text(humanFileSize(file.size));
-					}
-					var permissions = data.context.data('permissions');
-					if(permissions != file.permissions) {
-						data.context.attr('data-permissions', file.permissions);
-						data.context.data('permissions', file.permissions);
-					}
-					FileActions.display(data.context.find('td.filename'));
-					if (FileList.loadingDone) {
-						FileList.loadingDone(file.name, file.id);
-					}
-				} else {
-					// update upload counter ui
-					var uploadtext = data.context.find('.uploadtext');
-					var currentUploads = parseInt(uploadtext.attr('currentUploads'));
-					currentUploads -= 1;
-					uploadtext.attr('currentUploads', currentUploads);
-					if(currentUploads === 0) {
-						var img = OC.imagePath('core', 'filetypes/folder.png');
-						data.context.find('td.filename').attr('style','background-image:url('+img+')');
-						uploadtext.text('');
-						uploadtext.hide();
-					} else {
-						uploadtext.text(currentUploads + ' ' + t('files', 'files uploading'));
-					}
-
-					// update folder size
-					var size = parseInt(data.context.data('size'));
-					size += parseInt(file.size);
-					data.context.attr('data-size', size);
-					data.context.find('td.filesize').text(humanFileSize(size));
+	file_upload_start.on('fileuploadstop', function(e, data) {
+		OC.Upload.log('filelist handle fileuploadstop', e, data);
 
-				}
-			}
+		//if user pressed cancel hide upload chrome
+		if (data.errorThrown === 'abort') {
+			//cleanup uploading to a dir
+			var uploadtext = $('tr .uploadtext');
+			var img = OC.imagePath('core', 'filetypes/folder.png');
+			uploadtext.parents('td.filename').attr('style','background-image:url('+img+')');
+			uploadtext.fadeOut();
+			uploadtext.attr('currentUploads', 0);
 		}
 	});
 	file_upload_start.on('fileuploadfail', function(e, data) {
-		// only update the fileList if it exists
-		// cleanup files, error notification has been shown by fileupload code
-		var tr = data.context;
-		if (typeof tr === 'undefined') {
-			tr = $('tr').filterAttr('data-file', data.files[0].name);
-		}
-		if (tr.attr('data-type') === 'dir') {
+		OC.Upload.log('filelist handle fileuploadfail', e, data);
+
+		//if user pressed cancel hide upload chrome
+		if (data.errorThrown === 'abort') {
 			//cleanup uploading to a dir
-			var uploadtext = tr.find('.uploadtext');
+			var uploadtext = $('tr .uploadtext');
 			var img = OC.imagePath('core', 'filetypes/folder.png');
-			tr.find('td.filename').attr('style','background-image:url('+img+')');
-			uploadtext.text('');
-			uploadtext.hide(); //TODO really hide already
-		} else {
-			//remove file
-			tr.fadeOut();
-			tr.remove();
+			uploadtext.parents('td.filename').attr('style','background-image:url('+img+')');
+			uploadtext.fadeOut();
+			uploadtext.attr('currentUploads', 0);
 		}
 	});
 
diff --git a/apps/files/js/files.js b/apps/files/js/files.js
index a4fdf3833394666086e28bf7661b46b0f425614e..8ccb448abfbffc31ab3f96ada14a414cdf4ee6c4 100644
--- a/apps/files/js/files.js
+++ b/apps/files/js/files.js
@@ -1,31 +1,4 @@
-var uploadingFiles = {};
 Files={
-	cancelUpload:function(filename) {
-		if(uploadingFiles[filename]) {
-			uploadingFiles[filename].abort();
-			delete uploadingFiles[filename];
-			return true;
-		}
-		return false;
-	},
-	cancelUploads:function() {
-		$.each(uploadingFiles,function(index,file) {
-			if(typeof file['abort'] === 'function') {
-				file.abort();
-				var filename = $('tr').filterAttr('data-file',index);
-				filename.hide();
-				filename.find('input[type="checkbox"]').removeAttr('checked');
-				filename.removeClass('selected');
-			} else {
-				$.each(file,function(i,f) {
-					f.abort();
-					delete file[i];
-				});
-			}
-			delete uploadingFiles[index];
-		});
-		procesSelection();
-	},
 	updateMaxUploadFilesize:function(response) {
 		if(response == undefined) {
 			return;
@@ -208,7 +181,8 @@ $(document).ready(function() {
 
 	// Trigger cancelling of file upload
 	$('#uploadprogresswrapper .stop').on('click', function() {
-		Files.cancelUploads();
+		OC.Upload.cancelUploads();
+		procesSelection();
 	});
 
 	// Show trash bin
@@ -530,7 +504,7 @@ var folderDropOptions={
 						$('#notification').fadeIn();
 					}
 				} else {
-					OC.dialogs.alert(t('Error moving file'), t('core', 'Error'));
+					OC.dialogs.alert(t('files', 'Error moving file'), t('files', 'Error'));
 				}
 			});
 		});
@@ -568,7 +542,7 @@ var crumbDropOptions={
 						$('#notification').fadeIn();
 					}
 				} else {
-					OC.dialogs.alert(t('Error moving file'), t('core', 'Error'));
+					OC.dialogs.alert(t('files', 'Error moving file'), t('files', 'Error'));
 				}
 			});
 		});
@@ -658,15 +632,25 @@ function getPathForPreview(name) {
 	return path;
 }
 
-function lazyLoadPreview(path, mime, ready) {
-	getMimeIcon(mime,ready);
-	var x = $('#filestable').data('preview-x');
-	var y = $('#filestable').data('preview-y');
-	var previewURL = OC.Router.generate('core_ajax_preview', {file: encodeURIComponent(path), x:x, y:y});
-	$.get(previewURL, function() {
-		previewURL = previewURL.replace('(','%28');
-		previewURL = previewURL.replace(')','%29');
-		ready(previewURL + '&reload=true');
+function lazyLoadPreview(path, mime, ready, width, height) {
+	// get mime icon url
+	getMimeIcon(mime, function(iconURL) {
+		ready(iconURL); // set mimeicon URL
+		
+		// now try getting a preview thumbnail URL
+		if ( ! width ) {
+			width = $('#filestable').data('preview-x');
+		}
+		if ( ! height ) {
+			height = $('#filestable').data('preview-y');
+		}
+		var previewURL = OC.Router.generate('core_ajax_preview', {file: encodeURIComponent(path), x:width, y:height});
+		$.get(previewURL, function() {
+			previewURL = previewURL.replace('(', '%28');
+			previewURL = previewURL.replace(')', '%29');
+			//set preview thumbnail URL
+			ready(previewURL + '&reload=true');
+		});
 	});
 }
 
diff --git a/apps/files/js/jquery.fileupload.js b/apps/files/js/jquery.fileupload.js
index a89e9dc2c4498fe50c7d8a4985bd952c639310f1..f9f6cc3a3821d0cf0c44d231a5b81a9b09366541 100644
--- a/apps/files/js/jquery.fileupload.js
+++ b/apps/files/js/jquery.fileupload.js
@@ -1,5 +1,5 @@
 /*
- * jQuery File Upload Plugin 5.9
+ * jQuery File Upload Plugin 5.32.2
  * https://github.com/blueimp/jQuery-File-Upload
  *
  * Copyright 2010, Sebastian Tschan
@@ -10,7 +10,7 @@
  */
 
 /*jslint nomen: true, unparam: true, regexp: true */
-/*global define, window, document, Blob, FormData, location */
+/*global define, window, document, location, File, Blob, FormData */
 
 (function (factory) {
     'use strict';
@@ -27,12 +27,28 @@
 }(function ($) {
     'use strict';
 
+    // Detect file input support, based on
+    // http://viljamis.com/blog/2012/file-upload-support-on-mobile/
+    $.support.fileInput = !(new RegExp(
+        // Handle devices which give false positives for the feature detection:
+        '(Android (1\\.[0156]|2\\.[01]))' +
+            '|(Windows Phone (OS 7|8\\.0))|(XBLWP)|(ZuneWP)|(WPDesktop)' +
+            '|(w(eb)?OSBrowser)|(webOS)' +
+            '|(Kindle/(1\\.0|2\\.[05]|3\\.0))'
+    ).test(window.navigator.userAgent) ||
+        // Feature detection for all other devices:
+        $('<input type="file">').prop('disabled'));
+
     // The FileReader API is not actually used, but works as feature detection,
     // as e.g. Safari supports XHR file uploads via the FormData API,
     // but not non-multipart XHR file uploads:
     $.support.xhrFileUpload = !!(window.XMLHttpRequestUpload && window.FileReader);
     $.support.xhrFormDataFileUpload = !!window.FormData;
 
+    // Detect support for Blob slicing (required for chunked uploads):
+    $.support.blobSlice = window.Blob && (Blob.prototype.slice ||
+        Blob.prototype.webkitSlice || Blob.prototype.mozSlice);
+
     // The fileupload widget listens for change events on file input fields defined
     // via fileInput setting and paste or drop events of the given dropZone.
     // In addition to the default jQuery Widget methods, the fileupload widget
@@ -44,17 +60,16 @@
     $.widget('blueimp.fileupload', {
 
         options: {
-            // The namespace used for event handler binding on the dropZone and
-            // fileInput collections.
-            // If not set, the name of the widget ("fileupload") is used.
-            namespace: undefined,
-            // The drop target collection, by the default the complete document.
-            // Set to null or an empty collection to disable drag & drop support:
+            // The drop target element(s), by the default the complete document.
+            // Set to null to disable drag & drop support:
             dropZone: $(document),
-            // The file input field collection, that is listened for change events.
+            // The paste target element(s), by the default the complete document.
+            // Set to null to disable paste support:
+            pasteZone: $(document),
+            // The file input field(s), that are listened to for change events.
             // If undefined, it is set to the file input fields inside
             // of the widget element on plugin initialization.
-            // Set to null or an empty collection to disable the change listener.
+            // Set to null to disable the change listener.
             fileInput: undefined,
             // By default, the file input field is replaced with a clone after
             // each input field change event. This is required for iframe transport
@@ -63,7 +78,8 @@
             replaceFileInput: true,
             // The parameter name for the file form data (the request argument name).
             // If undefined or empty, the name property of the file input field is
-            // used, or "files[]" if the file input name property is also empty:
+            // used, or "files[]" if the file input name property is also empty,
+            // can be a string or an array of strings:
             paramName: undefined,
             // By default, each file of a selection is uploaded using an individual
             // request for XHR type uploads. Set to false to upload file
@@ -108,6 +124,29 @@
             // global progress calculation. Set the following option to false to
             // prevent recalculating the global progress data:
             recalculateProgress: true,
+            // Interval in milliseconds to calculate and trigger progress events:
+            progressInterval: 100,
+            // Interval in milliseconds to calculate progress bitrate:
+            bitrateInterval: 500,
+            // By default, uploads are started automatically when adding files:
+            autoUpload: true,
+
+            // Error and info messages:
+            messages: {
+                uploadedBytes: 'Uploaded bytes exceed file size'
+            },
+
+            // Translation function, gets the message key to be translated
+            // and an object with context specific data as arguments:
+            i18n: function (message, context) {
+                message = this.messages[message] || message.toString();
+                if (context) {
+                    $.each(context, function (key, value) {
+                        message = message.replace('{' + key + '}', value);
+                    });
+                }
+                return message;
+            },
 
             // Additional form data to be sent along with the file uploads can be set
             // using this option, which accepts an array of objects with name and
@@ -121,48 +160,81 @@
             // The add callback is invoked as soon as files are added to the fileupload
             // widget (via file input selection, drag & drop, paste or add API call).
             // If the singleFileUploads option is enabled, this callback will be
-            // called once for each file in the selection for XHR file uplaods, else
+            // called once for each file in the selection for XHR file uploads, else
             // once for each file selection.
+            //
             // The upload starts when the submit method is invoked on the data parameter.
             // The data object contains a files property holding the added files
-            // and allows to override plugin options as well as define ajax settings.
+            // and allows you to override plugin options as well as define ajax settings.
+            //
             // Listeners for this callback can also be bound the following way:
             // .bind('fileuploadadd', func);
+            //
             // data.submit() returns a Promise object and allows to attach additional
             // handlers using jQuery's Deferred callbacks:
             // data.submit().done(func).fail(func).always(func);
             add: function (e, data) {
-                data.submit();
+                if (data.autoUpload || (data.autoUpload !== false &&
+                        $(this).fileupload('option', 'autoUpload'))) {
+                    data.process().done(function () {
+                        data.submit();
+                    });
+                }
             },
 
             // Other callbacks:
+
             // Callback for the submit event of each file upload:
             // submit: function (e, data) {}, // .bind('fileuploadsubmit', func);
+
             // Callback for the start of each file upload request:
             // send: function (e, data) {}, // .bind('fileuploadsend', func);
+
             // Callback for successful uploads:
             // done: function (e, data) {}, // .bind('fileuploaddone', func);
+
             // Callback for failed (abort or error) uploads:
             // fail: function (e, data) {}, // .bind('fileuploadfail', func);
+
             // Callback for completed (success, abort or error) requests:
             // always: function (e, data) {}, // .bind('fileuploadalways', func);
+
             // Callback for upload progress events:
             // progress: function (e, data) {}, // .bind('fileuploadprogress', func);
+
             // Callback for global upload progress events:
             // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);
+
             // Callback for uploads start, equivalent to the global ajaxStart event:
             // start: function (e) {}, // .bind('fileuploadstart', func);
+
             // Callback for uploads stop, equivalent to the global ajaxStop event:
             // stop: function (e) {}, // .bind('fileuploadstop', func);
-            // Callback for change events of the fileInput collection:
+
+            // Callback for change events of the fileInput(s):
             // change: function (e, data) {}, // .bind('fileuploadchange', func);
-            // Callback for paste events to the dropZone collection:
+
+            // Callback for paste events to the pasteZone(s):
             // paste: function (e, data) {}, // .bind('fileuploadpaste', func);
-            // Callback for drop events of the dropZone collection:
+
+            // Callback for drop events of the dropZone(s):
             // drop: function (e, data) {}, // .bind('fileuploaddrop', func);
-            // Callback for dragover events of the dropZone collection:
+
+            // Callback for dragover events of the dropZone(s):
             // dragover: function (e) {}, // .bind('fileuploaddragover', func);
 
+            // Callback for the start of each chunk upload request:
+            // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func);
+
+            // Callback for successful chunk uploads:
+            // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func);
+
+            // Callback for failed (abort or error) chunk uploads:
+            // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func);
+
+            // Callback for completed (success, abort or error) chunk upload requests:
+            // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func);
+
             // The plugin options are used as settings object for the ajax calls.
             // The following are jQuery ajax settings required for the file uploads:
             processData: false,
@@ -170,15 +242,36 @@
             cache: false
         },
 
-        // A list of options that require a refresh after assigning a new value:
-        _refreshOptionsList: [
-            'namespace',
-            'dropZone',
+        // A list of options that require reinitializing event listeners and/or
+        // special initialization code:
+        _specialOptions: [
             'fileInput',
+            'dropZone',
+            'pasteZone',
             'multipart',
             'forceIframeTransport'
         ],
 
+        _blobSlice: $.support.blobSlice && function () {
+            var slice = this.slice || this.webkitSlice || this.mozSlice;
+            return slice.apply(this, arguments);
+        },
+
+        _BitrateTimer: function () {
+            this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime());
+            this.loaded = 0;
+            this.bitrate = 0;
+            this.getBitrate = function (now, loaded, interval) {
+                var timeDiff = now - this.timestamp;
+                if (!this.bitrate || !interval || timeDiff > interval) {
+                    this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;
+                    this.loaded = loaded;
+                    this.timestamp = now;
+                }
+                return this.bitrate;
+            };
+        },
+
         _isXHRUpload: function (options) {
             return !options.forceIframeTransport &&
                 ((!options.multipart && $.support.xhrFileUpload) ||
@@ -189,9 +282,11 @@
             var formData;
             if (typeof options.formData === 'function') {
                 return options.formData(options.form);
-            } else if ($.isArray(options.formData)) {
+            }
+            if ($.isArray(options.formData)) {
                 return options.formData;
-            } else if (options.formData) {
+            }
+            if ($.type(options.formData) === 'object') {
                 formData = [];
                 $.each(options.formData, function (name, value) {
                     formData.push({name: name, value: value});
@@ -209,28 +304,66 @@
             return total;
         },
 
+        _initProgressObject: function (obj) {
+            var progress = {
+                loaded: 0,
+                total: 0,
+                bitrate: 0
+            };
+            if (obj._progress) {
+                $.extend(obj._progress, progress);
+            } else {
+                obj._progress = progress;
+            }
+        },
+
+        _initResponseObject: function (obj) {
+            var prop;
+            if (obj._response) {
+                for (prop in obj._response) {
+                    if (obj._response.hasOwnProperty(prop)) {
+                        delete obj._response[prop];
+                    }
+                }
+            } else {
+                obj._response = {};
+            }
+        },
+
         _onProgress: function (e, data) {
             if (e.lengthComputable) {
-                var total = data.total || this._getTotal(data.files),
-                    loaded = parseInt(
-                        e.loaded / e.total * (data.chunkSize || total),
-                        10
-                    ) + (data.uploadedBytes || 0);
-                this._loaded += loaded - (data.loaded || data.uploadedBytes || 0);
-                data.lengthComputable = true;
-                data.loaded = loaded;
-                data.total = total;
+                var now = ((Date.now) ? Date.now() : (new Date()).getTime()),
+                    loaded;
+                if (data._time && data.progressInterval &&
+                        (now - data._time < data.progressInterval) &&
+                        e.loaded !== e.total) {
+                    return;
+                }
+                data._time = now;
+                loaded = Math.floor(
+                    e.loaded / e.total * (data.chunkSize || data._progress.total)
+                ) + (data.uploadedBytes || 0);
+                // Add the difference from the previously loaded state
+                // to the global loaded counter:
+                this._progress.loaded += (loaded - data._progress.loaded);
+                this._progress.bitrate = this._bitrateTimer.getBitrate(
+                    now,
+                    this._progress.loaded,
+                    data.bitrateInterval
+                );
+                data._progress.loaded = data.loaded = loaded;
+                data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate(
+                    now,
+                    loaded,
+                    data.bitrateInterval
+                );
                 // Trigger a custom progress event with a total data property set
                 // to the file size(s) of the current upload and a loaded data
                 // property calculated accordingly:
                 this._trigger('progress', e, data);
                 // Trigger a global progress event for all current file uploads,
                 // including ajax calls queued for sequential file uploads:
-                this._trigger('progressall', e, {
-                    lengthComputable: true,
-                    loaded: this._loaded,
-                    total: this._total
-                });
+                this._trigger('progressall', e, this._progress);
             }
         },
 
@@ -254,34 +387,30 @@
             }
         },
 
+        _isInstanceOf: function (type, obj) {
+            // Cross-frame instanceof check
+            return Object.prototype.toString.call(obj) === '[object ' + type + ']';
+        },
+
         _initXHRData: function (options) {
-            var formData,
+            var that = this,
+                formData,
                 file = options.files[0],
                 // Ignore non-multipart setting if not supported:
-                multipart = options.multipart || !$.support.xhrFileUpload;
-            if (!multipart || options.blob) {
-                // For non-multipart uploads and chunked uploads,
-                // file meta data is not part of the request body,
-                // so we transmit this data as part of the HTTP headers.
-                // For cross domain requests, these headers must be allowed
-                // via Access-Control-Allow-Headers or removed using
-                // the beforeSend callback:
-                options.headers = $.extend(options.headers, {
-                    'X-File-Name': file.name,
-                    'X-File-Type': file.type,
-                    'X-File-Size': file.size
-                });
-                if (!options.blob) {
-                    // Non-chunked non-multipart upload:
-                    options.contentType = file.type;
-                    options.data = file;
-                } else if (!multipart) {
-                    // Chunked non-multipart upload:
-                    options.contentType = 'application/octet-stream';
-                    options.data = options.blob;
-                }
+                multipart = options.multipart || !$.support.xhrFileUpload,
+                paramName = options.paramName[0];
+            options.headers = options.headers || {};
+            if (options.contentRange) {
+                options.headers['Content-Range'] = options.contentRange;
+            }
+            if (!multipart || options.blob || !this._isInstanceOf('File', file)) {
+                options.headers['Content-Disposition'] = 'attachment; filename="' +
+                    encodeURI(file.name) + '"';
             }
-            if (multipart && $.support.xhrFormDataFileUpload) {
+            if (!multipart) {
+                options.contentType = file.type;
+                options.data = options.blob || file;
+            } else if ($.support.xhrFormDataFileUpload) {
                 if (options.postMessage) {
                     // window.postMessage does not allow sending FormData
                     // objects, so we just add the File/Blob objects to
@@ -290,19 +419,19 @@
                     formData = this._getFormData(options);
                     if (options.blob) {
                         formData.push({
-                            name: options.paramName,
+                            name: paramName,
                             value: options.blob
                         });
                     } else {
                         $.each(options.files, function (index, file) {
                             formData.push({
-                                name: options.paramName,
+                                name: options.paramName[index] || paramName,
                                 value: file
                             });
                         });
                     }
                 } else {
-                    if (options.formData instanceof FormData) {
+                    if (that._isInstanceOf('FormData', options.formData)) {
                         formData = options.formData;
                     } else {
                         formData = new FormData();
@@ -311,14 +440,18 @@
                         });
                     }
                     if (options.blob) {
-                        formData.append(options.paramName, options.blob, file.name);
+                        formData.append(paramName, options.blob, file.name);
                     } else {
                         $.each(options.files, function (index, file) {
-                            // File objects are also Blob instances.
                             // This check allows the tests to run with
                             // dummy objects:
-                            if (file instanceof Blob) {
-                                formData.append(options.paramName, file, file.name);
+                            if (that._isInstanceOf('File', file) ||
+                                    that._isInstanceOf('Blob', file)) {
+                                formData.append(
+                                    options.paramName[index] || paramName,
+                                    file,
+                                    file.name
+                                );
                             }
                         });
                     }
@@ -330,13 +463,13 @@
         },
 
         _initIframeSettings: function (options) {
+            var targetHost = $('<a></a>').prop('href', options.url).prop('host');
             // Setting the dataType to iframe enables the iframe transport:
             options.dataType = 'iframe ' + (options.dataType || '');
             // The iframe transport accepts a serialized array as form data:
             options.formData = this._getFormData(options);
             // Add redirect url to form data on cross-domain uploads:
-            if (options.redirect && $('<a></a>').prop('href', options.url)
-                    .prop('host') !== location.host) {
+            if (options.redirect && targetHost && targetHost !== location.host) {
                 options.formData.push({
                     name: options.redirectParamName || 'redirect',
                     value: options.redirect
@@ -358,29 +491,58 @@
                     options.dataType = 'postmessage ' + (options.dataType || '');
                 }
             } else {
-                this._initIframeSettings(options, 'iframe');
+                this._initIframeSettings(options);
             }
         },
 
+        _getParamName: function (options) {
+            var fileInput = $(options.fileInput),
+                paramName = options.paramName;
+            if (!paramName) {
+                paramName = [];
+                fileInput.each(function () {
+                    var input = $(this),
+                        name = input.prop('name') || 'files[]',
+                        i = (input.prop('files') || [1]).length;
+                    while (i) {
+                        paramName.push(name);
+                        i -= 1;
+                    }
+                });
+                if (!paramName.length) {
+                    paramName = [fileInput.prop('name') || 'files[]'];
+                }
+            } else if (!$.isArray(paramName)) {
+                paramName = [paramName];
+            }
+            return paramName;
+        },
+
         _initFormSettings: function (options) {
             // Retrieve missing options from the input field and the
             // associated form, if available:
             if (!options.form || !options.form.length) {
                 options.form = $(options.fileInput.prop('form'));
+                // If the given file input doesn't have an associated form,
+                // use the default widget file input's form:
+                if (!options.form.length) {
+                    options.form = $(this.options.fileInput.prop('form'));
+                }
             }
-            if (!options.paramName) {
-                options.paramName = options.fileInput.prop('name') ||
-                    'files[]';
-            }
+            options.paramName = this._getParamName(options);
             if (!options.url) {
                 options.url = options.form.prop('action') || location.href;
             }
             // The HTTP request method must be "POST" or "PUT":
             options.type = (options.type || options.form.prop('method') || '')
                 .toUpperCase();
-            if (options.type !== 'POST' && options.type !== 'PUT') {
+            if (options.type !== 'POST' && options.type !== 'PUT' &&
+                    options.type !== 'PATCH') {
                 options.type = 'POST';
             }
+            if (!options.formAcceptCharset) {
+                options.formAcceptCharset = options.form.attr('accept-charset');
+            }
         },
 
         _getAJAXSettings: function (data) {
@@ -390,6 +552,21 @@
             return options;
         },
 
+        // jQuery 1.6 doesn't provide .state(),
+        // while jQuery 1.8+ removed .isRejected() and .isResolved():
+        _getDeferredState: function (deferred) {
+            if (deferred.state) {
+                return deferred.state();
+            }
+            if (deferred.isResolved()) {
+                return 'resolved';
+            }
+            if (deferred.isRejected()) {
+                return 'rejected';
+            }
+            return 'pending';
+        },
+
         // Maps jqXHR callbacks to the equivalent
         // methods of the given Promise object:
         _enhancePromise: function (promise) {
@@ -414,24 +591,77 @@
             return this._enhancePromise(promise);
         },
 
+        // Adds convenience methods to the data callback argument:
+        _addConvenienceMethods: function (e, data) {
+            var that = this,
+                getPromise = function (data) {
+                    return $.Deferred().resolveWith(that, [data]).promise();
+                };
+            data.process = function (resolveFunc, rejectFunc) {
+                if (resolveFunc || rejectFunc) {
+                    data._processQueue = this._processQueue =
+                        (this._processQueue || getPromise(this))
+                            .pipe(resolveFunc, rejectFunc);
+                }
+                return this._processQueue || getPromise(this);
+            };
+            data.submit = function () {
+                if (this.state() !== 'pending') {
+                    data.jqXHR = this.jqXHR =
+                        (that._trigger('submit', e, this) !== false) &&
+                        that._onSend(e, this);
+                }
+                return this.jqXHR || that._getXHRPromise();
+            };
+            data.abort = function () {
+                if (this.jqXHR) {
+                    return this.jqXHR.abort();
+                }
+                return that._getXHRPromise();
+            };
+            data.state = function () {
+                if (this.jqXHR) {
+                    return that._getDeferredState(this.jqXHR);
+                }
+                if (this._processQueue) {
+                    return that._getDeferredState(this._processQueue);
+                }
+            };
+            data.progress = function () {
+                return this._progress;
+            };
+            data.response = function () {
+                return this._response;
+            };
+        },
+
+        // Parses the Range header from the server response
+        // and returns the uploaded bytes:
+        _getUploadedBytes: function (jqXHR) {
+            var range = jqXHR.getResponseHeader('Range'),
+                parts = range && range.split('-'),
+                upperBytesPos = parts && parts.length > 1 &&
+                    parseInt(parts[1], 10);
+            return upperBytesPos && upperBytesPos + 1;
+        },
+
         // Uploads a file in multiple, sequential requests
         // by splitting the file up in multiple blob chunks.
         // If the second parameter is true, only tests if the file
         // should be uploaded in chunks, but does not invoke any
         // upload requests:
         _chunkedUpload: function (options, testOnly) {
+            options.uploadedBytes = options.uploadedBytes || 0;
             var that = this,
                 file = options.files[0],
                 fs = file.size,
-                ub = options.uploadedBytes = options.uploadedBytes || 0,
+                ub = options.uploadedBytes,
                 mcs = options.maxChunkSize || fs,
-                // Use the Blob methods with the slice implementation
-                // according to the W3C Blob API specification:
-                slice = file.webkitSlice || file.mozSlice || file.slice,
-                upload,
-                n,
+                slice = this._blobSlice,
+                dfd = $.Deferred(),
+                promise = dfd.promise(),
                 jqXHR,
-                pipe;
+                upload;
             if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) ||
                     options.data) {
                 return false;
@@ -440,62 +670,84 @@
                 return true;
             }
             if (ub >= fs) {
-                file.error = 'uploadedBytes';
+                file.error = options.i18n('uploadedBytes');
                 return this._getXHRPromise(
                     false,
                     options.context,
                     [null, 'error', file.error]
                 );
             }
-            // n is the number of blobs to upload,
-            // calculated via filesize, uploaded bytes and max chunk size:
-            n = Math.ceil((fs - ub) / mcs);
-            // The chunk upload method accepting the chunk number as parameter:
-            upload = function (i) {
-                if (!i) {
-                    return that._getXHRPromise(true, options.context);
-                }
-                // Upload the blobs in sequential order:
-                return upload(i -= 1).pipe(function () {
-                    // Clone the options object for each chunk upload:
-                    var o = $.extend({}, options);
-                    o.blob = slice.call(
-                        file,
-                        ub + i * mcs,
-                        ub + (i + 1) * mcs
-                    );
-                    // Store the current chunk size, as the blob itself
-                    // will be dereferenced after data processing:
-                    o.chunkSize = o.blob.size;
-                    // Process the upload data (the blob and potential form data):
-                    that._initXHRData(o);
-                    // Add progress listeners for this chunk upload:
-                    that._initProgressListener(o);
-                    jqXHR = ($.ajax(o) || that._getXHRPromise(false, o.context))
-                        .done(function () {
-                            // Create a progress event if upload is done and
-                            // no progress event has been invoked for this chunk:
-                            if (!o.loaded) {
-                                that._onProgress($.Event('progress', {
-                                    lengthComputable: true,
-                                    loaded: o.chunkSize,
-                                    total: o.chunkSize
-                                }), o);
-                            }
-                            options.uploadedBytes = o.uploadedBytes +=
-                                o.chunkSize;
-                        });
-                    return jqXHR;
-                });
+            // The chunk upload method:
+            upload = function () {
+                // Clone the options object for each chunk upload:
+                var o = $.extend({}, options),
+                    currentLoaded = o._progress.loaded;
+                o.blob = slice.call(
+                    file,
+                    ub,
+                    ub + mcs,
+                    file.type
+                );
+                // Store the current chunk size, as the blob itself
+                // will be dereferenced after data processing:
+                o.chunkSize = o.blob.size;
+                // Expose the chunk bytes position range:
+                o.contentRange = 'bytes ' + ub + '-' +
+                    (ub + o.chunkSize - 1) + '/' + fs;
+                // Process the upload data (the blob and potential form data):
+                that._initXHRData(o);
+                // Add progress listeners for this chunk upload:
+                that._initProgressListener(o);
+                jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) ||
+                        that._getXHRPromise(false, o.context))
+                    .done(function (result, textStatus, jqXHR) {
+                        ub = that._getUploadedBytes(jqXHR) ||
+                            (ub + o.chunkSize);
+                        // Create a progress event if no final progress event
+                        // with loaded equaling total has been triggered
+                        // for this chunk:
+                        if (currentLoaded + o.chunkSize - o._progress.loaded) {
+                            that._onProgress($.Event('progress', {
+                                lengthComputable: true,
+                                loaded: ub - o.uploadedBytes,
+                                total: ub - o.uploadedBytes
+                            }), o);
+                        }
+                        options.uploadedBytes = o.uploadedBytes = ub;
+                        o.result = result;
+                        o.textStatus = textStatus;
+                        o.jqXHR = jqXHR;
+                        that._trigger('chunkdone', null, o);
+                        that._trigger('chunkalways', null, o);
+                        if (ub < fs) {
+                            // File upload not yet complete,
+                            // continue with the next chunk:
+                            upload();
+                        } else {
+                            dfd.resolveWith(
+                                o.context,
+                                [result, textStatus, jqXHR]
+                            );
+                        }
+                    })
+                    .fail(function (jqXHR, textStatus, errorThrown) {
+                        o.jqXHR = jqXHR;
+                        o.textStatus = textStatus;
+                        o.errorThrown = errorThrown;
+                        that._trigger('chunkfail', null, o);
+                        that._trigger('chunkalways', null, o);
+                        dfd.rejectWith(
+                            o.context,
+                            [jqXHR, textStatus, errorThrown]
+                        );
+                    });
             };
-            // Return the piped Promise object, enhanced with an abort method,
-            // which is delegated to the jqXHR object of the current upload,
-            // and jqXHR callbacks mapped to the equivalent Promise methods:
-            pipe = upload(n);
-            pipe.abort = function () {
+            this._enhancePromise(promise);
+            promise.abort = function () {
                 return jqXHR.abort();
             };
-            return this._enhancePromise(pipe);
+            upload();
+            return promise;
         },
 
         _beforeSend: function (e, data) {
@@ -504,99 +756,113 @@
                 // and no other uploads are currently running,
                 // equivalent to the global ajaxStart event:
                 this._trigger('start');
+                // Set timer for global bitrate progress calculation:
+                this._bitrateTimer = new this._BitrateTimer();
+                // Reset the global progress values:
+                this._progress.loaded = this._progress.total = 0;
+                this._progress.bitrate = 0;
             }
+            // Make sure the container objects for the .response() and
+            // .progress() methods on the data object are available
+            // and reset to their initial state:
+            this._initResponseObject(data);
+            this._initProgressObject(data);
+            data._progress.loaded = data.loaded = data.uploadedBytes || 0;
+            data._progress.total = data.total = this._getTotal(data.files) || 1;
+            data._progress.bitrate = data.bitrate = 0;
             this._active += 1;
             // Initialize the global progress values:
-            this._loaded += data.uploadedBytes || 0;
-            this._total += this._getTotal(data.files);
+            this._progress.loaded += data.loaded;
+            this._progress.total += data.total;
         },
 
         _onDone: function (result, textStatus, jqXHR, options) {
-            if (!this._isXHRUpload(options)) {
-                // Create a progress event for each iframe load:
+            var total = options._progress.total,
+                response = options._response;
+            if (options._progress.loaded < total) {
+                // Create a progress event if no final progress event
+                // with loaded equaling total has been triggered:
                 this._onProgress($.Event('progress', {
                     lengthComputable: true,
-                    loaded: 1,
-                    total: 1
+                    loaded: total,
+                    total: total
                 }), options);
             }
-            options.result = result;
-            options.textStatus = textStatus;
-            options.jqXHR = jqXHR;
+            response.result = options.result = result;
+            response.textStatus = options.textStatus = textStatus;
+            response.jqXHR = options.jqXHR = jqXHR;
             this._trigger('done', null, options);
         },
 
         _onFail: function (jqXHR, textStatus, errorThrown, options) {
-            options.jqXHR = jqXHR;
-            options.textStatus = textStatus;
-            options.errorThrown = errorThrown;
-            this._trigger('fail', null, options);
+            var response = options._response;
             if (options.recalculateProgress) {
                 // Remove the failed (error or abort) file upload from
                 // the global progress calculation:
-                this._loaded -= options.loaded || options.uploadedBytes || 0;
-                this._total -= options.total || this._getTotal(options.files);
+                this._progress.loaded -= options._progress.loaded;
+                this._progress.total -= options._progress.total;
             }
+            response.jqXHR = options.jqXHR = jqXHR;
+            response.textStatus = options.textStatus = textStatus;
+            response.errorThrown = options.errorThrown = errorThrown;
+            this._trigger('fail', null, options);
         },
 
         _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {
-            this._active -= 1;
-            options.textStatus = textStatus;
-            if (jqXHRorError && jqXHRorError.always) {
-                options.jqXHR = jqXHRorError;
-                options.result = jqXHRorResult;
-            } else {
-                options.jqXHR = jqXHRorResult;
-                options.errorThrown = jqXHRorError;
-            }
+            // jqXHRorResult, textStatus and jqXHRorError are added to the
+            // options object via done and fail callbacks
             this._trigger('always', null, options);
-            if (this._active === 0) {
-                // The stop callback is triggered when all uploads have
-                // been completed, equivalent to the global ajaxStop event:
-                this._trigger('stop');
-                // Reset the global progress values:
-                this._loaded = this._total = 0;
-            }
         },
 
         _onSend: function (e, data) {
+            if (!data.submit) {
+                this._addConvenienceMethods(e, data);
+            }
             var that = this,
                 jqXHR,
+                aborted,
                 slot,
                 pipe,
                 options = that._getAJAXSettings(data),
-                send = function (resolve, args) {
+                send = function () {
                     that._sending += 1;
+                    // Set timer for bitrate progress calculation:
+                    options._bitrateTimer = new that._BitrateTimer();
                     jqXHR = jqXHR || (
-                        (resolve !== false &&
-                        that._trigger('send', e, options) !== false &&
-                        (that._chunkedUpload(options) || $.ajax(options))) ||
-                        that._getXHRPromise(false, options.context, args)
+                        ((aborted || that._trigger('send', e, options) === false) &&
+                        that._getXHRPromise(false, options.context, aborted)) ||
+                        that._chunkedUpload(options) || $.ajax(options)
                     ).done(function (result, textStatus, jqXHR) {
                         that._onDone(result, textStatus, jqXHR, options);
                     }).fail(function (jqXHR, textStatus, errorThrown) {
                         that._onFail(jqXHR, textStatus, errorThrown, options);
                     }).always(function (jqXHRorResult, textStatus, jqXHRorError) {
-                        that._sending -= 1;
                         that._onAlways(
                             jqXHRorResult,
                             textStatus,
                             jqXHRorError,
                             options
                         );
+                        that._sending -= 1;
+                        that._active -= 1;
                         if (options.limitConcurrentUploads &&
                                 options.limitConcurrentUploads > that._sending) {
                             // Start the next queued upload,
                             // that has not been aborted:
                             var nextSlot = that._slots.shift();
                             while (nextSlot) {
-                                if (!nextSlot.isRejected()) {
+                                if (that._getDeferredState(nextSlot) === 'pending') {
                                     nextSlot.resolve();
                                     break;
                                 }
                                 nextSlot = that._slots.shift();
                             }
                         }
+                        if (that._active === 0) {
+                            // The stop callback is triggered when all uploads have
+                            // been completed, equivalent to the global ajaxStop event:
+                            that._trigger('stop');
+                        }
                     });
                     return jqXHR;
                 };
@@ -609,18 +875,19 @@
                     this._slots.push(slot);
                     pipe = slot.pipe(send);
                 } else {
-                    pipe = (this._sequence = this._sequence.pipe(send, send));
+                    this._sequence = this._sequence.pipe(send, send);
+                    pipe = this._sequence;
                 }
                 // Return the piped Promise object, enhanced with an abort method,
                 // which is delegated to the jqXHR object of the current upload,
                 // and jqXHR callbacks mapped to the equivalent Promise methods:
                 pipe.abort = function () {
-                    var args = [undefined, 'abort', 'abort'];
+                    aborted = [undefined, 'abort', 'abort'];
                     if (!jqXHR) {
                         if (slot) {
-                            slot.rejectWith(args);
+                            slot.rejectWith(options.context, aborted);
                         }
-                        return send(false, args);
+                        return send();
                     }
                     return jqXHR.abort();
                 };
@@ -634,40 +901,43 @@
                 result = true,
                 options = $.extend({}, this.options, data),
                 limit = options.limitMultiFileUploads,
+                paramName = this._getParamName(options),
+                paramNameSet,
+                paramNameSlice,
                 fileSet,
                 i;
             if (!(options.singleFileUploads || limit) ||
                     !this._isXHRUpload(options)) {
                 fileSet = [data.files];
+                paramNameSet = [paramName];
             } else if (!options.singleFileUploads && limit) {
                 fileSet = [];
+                paramNameSet = [];
                 for (i = 0; i < data.files.length; i += limit) {
                     fileSet.push(data.files.slice(i, i + limit));
+                    paramNameSlice = paramName.slice(i, i + limit);
+                    if (!paramNameSlice.length) {
+                        paramNameSlice = paramName;
+                    }
+                    paramNameSet.push(paramNameSlice);
                 }
+            } else {
+                paramNameSet = paramName;
             }
             data.originalFiles = data.files;
             $.each(fileSet || data.files, function (index, element) {
-                var files = fileSet ? element : [element],
-                    newData = $.extend({}, data, {files: files});
-                newData.submit = function () {
-                    newData.jqXHR = this.jqXHR =
-                        (that._trigger('submit', e, this) !== false) &&
-                        that._onSend(e, this);
-                    return this.jqXHR;
-                };
-                return (result = that._trigger('add', e, newData));
+                var newData = $.extend({}, data);
+                newData.files = fileSet ? element : [element];
+                newData.paramName = paramNameSet[index];
+                that._initResponseObject(newData);
+                that._initProgressObject(newData);
+                that._addConvenienceMethods(e, newData);
+                result = that._trigger('add', e, newData);
+                return result;
             });
             return result;
         },
 
-        // File Normalization for Gecko 1.9.1 (Firefox 3.5) support:
-        _normalizeFile: function (index, file) {
-            if (file.name === undefined && file.size === undefined) {
-                file.name = file.fileName;
-                file.size = file.fileSize;
-            }
-        },
-
         _replaceFileInput: function (input) {
             var inputClone = input.clone(true);
             $('<form></form>').append(inputClone)[0].reset();
@@ -677,7 +947,7 @@
             // Avoid memory leaks with the detached file input:
             $.cleanData(input.unbind('remove'));
             // Replace the original file input element in the fileInput
-            // collection with the clone, which has been copied including
+            // elements set with the clone, which has been copied including
             // event handlers:
             this.options.fileInput = this.options.fileInput.map(function (i, el) {
                 if (el === input[0]) {
@@ -692,102 +962,229 @@
             }
         },
 
-        _onChange: function (e) {
-            var that = e.data.fileupload,
-                data = {
-                    files: $.each($.makeArray(e.target.files), that._normalizeFile),
-                    fileInput: $(e.target),
-                    form: $(e.target.form)
-                };
-            if (!data.files.length) {
+        _handleFileTreeEntry: function (entry, path) {
+            var that = this,
+                dfd = $.Deferred(),
+                errorHandler = function (e) {
+                    if (e && !e.entry) {
+                        e.entry = entry;
+                    }
+                    // Since $.when returns immediately if one
+                    // Deferred is rejected, we use resolve instead.
+                    // This allows valid files and invalid items
+                    // to be returned together in one set:
+                    dfd.resolve([e]);
+                },
+                dirReader;
+            path = path || '';
+            if (entry.isFile) {
+                if (entry._file) {
+                    // Workaround for Chrome bug #149735
+                    entry._file.relativePath = path;
+                    dfd.resolve(entry._file);
+                } else {
+                    entry.file(function (file) {
+                        file.relativePath = path;
+                        dfd.resolve(file);
+                    }, errorHandler);
+                }
+            } else if (entry.isDirectory) {
+                dirReader = entry.createReader();
+                dirReader.readEntries(function (entries) {
+                    that._handleFileTreeEntries(
+                        entries,
+                        path + entry.name + '/'
+                    ).done(function (files) {
+                        dfd.resolve(files);
+                    }).fail(errorHandler);
+                }, errorHandler);
+            } else {
+                // Return an empy list for file system items
+                // other than files or directories:
+                dfd.resolve([]);
+            }
+            return dfd.promise();
+        },
+
+        _handleFileTreeEntries: function (entries, path) {
+            var that = this;
+            return $.when.apply(
+                $,
+                $.map(entries, function (entry) {
+                    return that._handleFileTreeEntry(entry, path);
+                })
+            ).pipe(function () {
+                return Array.prototype.concat.apply(
+                    [],
+                    arguments
+                );
+            });
+        },
+
+        _getDroppedFiles: function (dataTransfer) {
+            dataTransfer = dataTransfer || {};
+            var items = dataTransfer.items;
+            if (items && items.length && (items[0].webkitGetAsEntry ||
+                    items[0].getAsEntry)) {
+                return this._handleFileTreeEntries(
+                    $.map(items, function (item) {
+                        var entry;
+                        if (item.webkitGetAsEntry) {
+                            entry = item.webkitGetAsEntry();
+                            if (entry) {
+                                // Workaround for Chrome bug #149735:
+                                entry._file = item.getAsFile();
+                            }
+                            return entry;
+                        }
+                        return item.getAsEntry();
+                    })
+                );
+            }
+            return $.Deferred().resolve(
+                $.makeArray(dataTransfer.files)
+            ).promise();
+        },
+
+        _getSingleFileInputFiles: function (fileInput) {
+            fileInput = $(fileInput);
+            var entries = fileInput.prop('webkitEntries') ||
+                    fileInput.prop('entries'),
+                files,
+                value;
+            if (entries && entries.length) {
+                return this._handleFileTreeEntries(entries);
+            }
+            files = $.makeArray(fileInput.prop('files'));
+            if (!files.length) {
+                value = fileInput.prop('value');
+                if (!value) {
+                    return $.Deferred().resolve([]).promise();
+                }
                 // If the files property is not available, the browser does not
                 // support the File API and we add a pseudo File object with
                 // the input value as name with path information removed:
-                data.files = [{name: e.target.value.replace(/^.*\\/, '')}];
-            }
-            if (that.options.replaceFileInput) {
-                that._replaceFileInput(data.fileInput);
+                files = [{name: value.replace(/^.*\\/, '')}];
+            } else if (files[0].name === undefined && files[0].fileName) {
+                // File normalization for Safari 4 and Firefox 3:
+                $.each(files, function (index, file) {
+                    file.name = file.fileName;
+                    file.size = file.fileSize;
+                });
             }
-            if (that._trigger('change', e, data) === false ||
-                    that._onAdd(e, data) === false) {
-                return false;
+            return $.Deferred().resolve(files).promise();
+        },
+
+        _getFileInputFiles: function (fileInput) {
+            if (!(fileInput instanceof $) || fileInput.length === 1) {
+                return this._getSingleFileInputFiles(fileInput);
             }
+            return $.when.apply(
+                $,
+                $.map(fileInput, this._getSingleFileInputFiles)
+            ).pipe(function () {
+                return Array.prototype.concat.apply(
+                    [],
+                    arguments
+                );
+            });
+        },
+
+        _onChange: function (e) {
+            var that = this,
+                data = {
+                    fileInput: $(e.target),
+                    form: $(e.target.form)
+                };
+            this._getFileInputFiles(data.fileInput).always(function (files) {
+                data.files = files;
+                if (that.options.replaceFileInput) {
+                    that._replaceFileInput(data.fileInput);
+                }
+                if (that._trigger('change', e, data) !== false) {
+                    that._onAdd(e, data);
+                }
+            });
         },
 
         _onPaste: function (e) {
-            var that = e.data.fileupload,
-                cbd = e.originalEvent.clipboardData,
-                items = (cbd && cbd.items) || [],
+            var items = e.originalEvent && e.originalEvent.clipboardData &&
+                    e.originalEvent.clipboardData.items,
                 data = {files: []};
-            $.each(items, function (index, item) {
-                var file = item.getAsFile && item.getAsFile();
-                if (file) {
-                    data.files.push(file);
+            if (items && items.length) {
+                $.each(items, function (index, item) {
+                    var file = item.getAsFile && item.getAsFile();
+                    if (file) {
+                        data.files.push(file);
+                    }
+                });
+                if (this._trigger('paste', e, data) === false ||
+                        this._onAdd(e, data) === false) {
+                    return false;
                 }
-            });
-            if (that._trigger('paste', e, data) === false ||
-                    that._onAdd(e, data) === false) {
-                return false;
             }
         },
 
         _onDrop: function (e) {
-            var that = e.data.fileupload,
-                dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer,
-                data = {
-                    files: $.each(
-                        $.makeArray(dataTransfer && dataTransfer.files),
-                        that._normalizeFile
-                    )
-                };
-            if (that._trigger('drop', e, data) === false ||
-                    that._onAdd(e, data) === false) {
-                return false;
+            e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
+            var that = this,
+                dataTransfer = e.dataTransfer,
+                data = {};
+            if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
+                e.preventDefault();
+                this._getDroppedFiles(dataTransfer).always(function (files) {
+                    data.files = files;
+                    if (that._trigger('drop', e, data) !== false) {
+                        that._onAdd(e, data);
+                    }
+                });
             }
-            e.preventDefault();
         },
 
         _onDragOver: function (e) {
-            var that = e.data.fileupload,
-                dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer;
-            if (that._trigger('dragover', e) === false) {
-                return false;
-            }
+            e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
+            var dataTransfer = e.dataTransfer;
             if (dataTransfer) {
-                dataTransfer.dropEffect = dataTransfer.effectAllowed = 'copy';
+                if (this._trigger('dragover', e) === false) {
+                    return false;
+                }
+                if ($.inArray('Files', dataTransfer.types) !== -1) {
+                    dataTransfer.dropEffect = 'copy';
+                    e.preventDefault();
+                }
             }
-            e.preventDefault();
         },
 
         _initEventHandlers: function () {
-            var ns = this.options.namespace;
             if (this._isXHRUpload(this.options)) {
-                this.options.dropZone
-                    .bind('dragover.' + ns, {fileupload: this}, this._onDragOver)
-                    .bind('drop.' + ns, {fileupload: this}, this._onDrop)
-                    .bind('paste.' + ns, {fileupload: this}, this._onPaste);
+                this._on(this.options.dropZone, {
+                    dragover: this._onDragOver,
+                    drop: this._onDrop
+                });
+                this._on(this.options.pasteZone, {
+                    paste: this._onPaste
+                });
+            }
+            if ($.support.fileInput) {
+                this._on(this.options.fileInput, {
+                    change: this._onChange
+                });
             }
-            this.options.fileInput
-                .bind('change.' + ns, {fileupload: this}, this._onChange);
         },
 
         _destroyEventHandlers: function () {
-            var ns = this.options.namespace;
-            this.options.dropZone
-                .unbind('dragover.' + ns, this._onDragOver)
-                .unbind('drop.' + ns, this._onDrop)
-                .unbind('paste.' + ns, this._onPaste);
-            this.options.fileInput
-                .unbind('change.' + ns, this._onChange);
+            this._off(this.options.dropZone, 'dragover drop');
+            this._off(this.options.pasteZone, 'paste');
+            this._off(this.options.fileInput, 'change');
         },
 
         _setOption: function (key, value) {
-            var refresh = $.inArray(key, this._refreshOptionsList) !== -1;
-            if (refresh) {
+            var reinit = $.inArray(key, this._specialOptions) !== -1;
+            if (reinit) {
                 this._destroyEventHandlers();
             }
-            $.Widget.prototype._setOption.call(this, key, value);
-            if (refresh) {
+            this._super(key, value);
+            if (reinit) {
                 this._initSpecialOptions();
                 this._initEventHandlers();
             }
@@ -796,42 +1193,68 @@
         _initSpecialOptions: function () {
             var options = this.options;
             if (options.fileInput === undefined) {
-                options.fileInput = this.element.is('input:file') ?
-                        this.element : this.element.find('input:file');
+                options.fileInput = this.element.is('input[type="file"]') ?
+                        this.element : this.element.find('input[type="file"]');
             } else if (!(options.fileInput instanceof $)) {
                 options.fileInput = $(options.fileInput);
             }
             if (!(options.dropZone instanceof $)) {
                 options.dropZone = $(options.dropZone);
             }
+            if (!(options.pasteZone instanceof $)) {
+                options.pasteZone = $(options.pasteZone);
+            }
+        },
+
+        _getRegExp: function (str) {
+            var parts = str.split('/'),
+                modifiers = parts.pop();
+            parts.shift();
+            return new RegExp(parts.join('/'), modifiers);
+        },
+
+        _isRegExpOption: function (key, value) {
+            return key !== 'url' && $.type(value) === 'string' &&
+                /^\/.*\/[igm]{0,3}$/.test(value);
+        },
+
+        _initDataAttributes: function () {
+            var that = this,
+                options = this.options;
+            // Initialize options set via HTML5 data-attributes:
+            $.each(
+                $(this.element[0].cloneNode(false)).data(),
+                function (key, value) {
+                    if (that._isRegExpOption(key, value)) {
+                        value = that._getRegExp(value);
+                    }
+                    options[key] = value;
+                }
+            );
         },
 
         _create: function () {
-            var options = this.options,
-                dataOpts = $.extend({}, this.element.data());
-            dataOpts[this.widgetName] = undefined;
-            $.extend(options, dataOpts);
-            options.namespace = options.namespace || this.widgetName;
+            this._initDataAttributes();
             this._initSpecialOptions();
             this._slots = [];
             this._sequence = this._getXHRPromise(true);
-            this._sending = this._active = this._loaded = this._total = 0;
+            this._sending = this._active = 0;
+            this._initProgressObject(this);
             this._initEventHandlers();
         },
 
-        destroy: function () {
-            this._destroyEventHandlers();
-            $.Widget.prototype.destroy.call(this);
-        },
-
-        enable: function () {
-            $.Widget.prototype.enable.call(this);
-            this._initEventHandlers();
+        // This method is exposed to the widget API and allows to query
+        // the number of active uploads:
+        active: function () {
+            return this._active;
         },
 
-        disable: function () {
-            this._destroyEventHandlers();
-            $.Widget.prototype.disable.call(this);
+        // This method is exposed to the widget API and allows to query
+        // the widget upload progress.
+        // It returns an object with loaded, total and bitrate properties
+        // for the running uploads:
+        progress: function () {
+            return this._progress;
         },
 
         // This method is exposed to the widget API and allows adding files
@@ -839,21 +1262,65 @@
         // must have a files property and can contain additional options:
         // .fileupload('add', {files: filesList});
         add: function (data) {
+            var that = this;
             if (!data || this.options.disabled) {
                 return;
             }
-            data.files = $.each($.makeArray(data.files), this._normalizeFile);
-            this._onAdd(null, data);
+            if (data.fileInput && !data.files) {
+                this._getFileInputFiles(data.fileInput).always(function (files) {
+                    data.files = files;
+                    that._onAdd(null, data);
+                });
+            } else {
+                data.files = $.makeArray(data.files);
+                this._onAdd(null, data);
+            }
         },
 
         // This method is exposed to the widget API and allows sending files
         // using the fileupload API. The data parameter accepts an object which
-        // must have a files property and can contain additional options:
+        // must have a files or fileInput property and can contain additional options:
         // .fileupload('send', {files: filesList});
         // The method returns a Promise object for the file upload call.
         send: function (data) {
             if (data && !this.options.disabled) {
-                data.files = $.each($.makeArray(data.files), this._normalizeFile);
+                if (data.fileInput && !data.files) {
+                    var that = this,
+                        dfd = $.Deferred(),
+                        promise = dfd.promise(),
+                        jqXHR,
+                        aborted;
+                    promise.abort = function () {
+                        aborted = true;
+                        if (jqXHR) {
+                            return jqXHR.abort();
+                        }
+                        dfd.reject(null, 'abort', 'abort');
+                        return promise;
+                    };
+                    this._getFileInputFiles(data.fileInput).always(
+                        function (files) {
+                            if (aborted) {
+                                return;
+                            }
+                            if (!files.length) {
+                                dfd.reject();
+                                return;
+                            }
+                            data.files = files;
+                            jqXHR = that._onSend(null, data).then(
+                                function (result, textStatus, jqXHR) {
+                                    dfd.resolve(result, textStatus, jqXHR);
+                                },
+                                function (jqXHR, textStatus, errorThrown) {
+                                    dfd.reject(jqXHR, textStatus, errorThrown);
+                                }
+                            );
+                        }
+                    );
+                    return this._enhancePromise(promise);
+                }
+                data.files = $.makeArray(data.files);
                 if (data.files.length) {
                     return this._onSend(null, data);
                 }
@@ -863,4 +1330,4 @@
 
     });
 
-}));
+}));
\ No newline at end of file
diff --git a/apps/files/js/jquery.iframe-transport.js b/apps/files/js/jquery.iframe-transport.js
index d85c0c112973b2f6a433fb4a375adcb1c08f9bf6..5c9df77976bd9044702546dfec120a784c3bbd54 100644
--- a/apps/files/js/jquery.iframe-transport.js
+++ b/apps/files/js/jquery.iframe-transport.js
@@ -1,5 +1,5 @@
 /*
- * jQuery Iframe Transport Plugin 1.3
+ * jQuery Iframe Transport Plugin 1.7
  * https://github.com/blueimp/jQuery-File-Upload
  *
  * Copyright 2011, Sebastian Tschan
@@ -30,27 +30,45 @@
     // The iframe transport accepts three additional options:
     // options.fileInput: a jQuery collection of file input fields
     // options.paramName: the parameter name for the file form data,
-    //  overrides the name property of the file input field(s)
+    //  overrides the name property of the file input field(s),
+    //  can be a string or an array of strings.
     // options.formData: an array of objects with name and value properties,
     //  equivalent to the return data of .serializeArray(), e.g.:
     //  [{name: 'a', value: 1}, {name: 'b', value: 2}]
     $.ajaxTransport('iframe', function (options) {
-        if (options.async && (options.type === 'POST' || options.type === 'GET')) {
+        if (options.async) {
             var form,
-                iframe;
+                iframe,
+                addParamChar;
             return {
                 send: function (_, completeCallback) {
                     form = $('<form style="display:none;"></form>');
+                    form.attr('accept-charset', options.formAcceptCharset);
+                    addParamChar = /\?/.test(options.url) ? '&' : '?';
+                    // XDomainRequest only supports GET and POST:
+                    if (options.type === 'DELETE') {
+                        options.url = options.url + addParamChar + '_method=DELETE';
+                        options.type = 'POST';
+                    } else if (options.type === 'PUT') {
+                        options.url = options.url + addParamChar + '_method=PUT';
+                        options.type = 'POST';
+                    } else if (options.type === 'PATCH') {
+                        options.url = options.url + addParamChar + '_method=PATCH';
+                        options.type = 'POST';
+                    }
                     // javascript:false as initial iframe src
                     // prevents warning popups on HTTPS in IE6.
                     // IE versions below IE8 cannot set the name property of
                     // elements that have already been added to the DOM,
                     // so we set the name along with the iframe HTML markup:
+                    counter += 1;
                     iframe = $(
                         '<iframe src="javascript:false;" name="iframe-transport-' +
-                            (counter += 1) + '"></iframe>'
+                            counter + '"></iframe>'
                     ).bind('load', function () {
-                        var fileInputClones;
+                        var fileInputClones,
+                            paramNames = $.isArray(options.paramName) ?
+                                    options.paramName : [options.paramName];
                         iframe
                             .unbind('load')
                             .bind('load', function () {
@@ -79,7 +97,12 @@
                                 // (happens on form submits to iframe targets):
                                 $('<iframe src="javascript:false;"></iframe>')
                                     .appendTo(form);
-                                form.remove();
+                                window.setTimeout(function () {
+                                    // Removing the form in a setTimeout call
+                                    // allows Chrome's developer tools to display
+                                    // the response result
+                                    form.remove();
+                                }, 0);
                             });
                         form
                             .prop('target', iframe.prop('name'))
@@ -101,8 +124,11 @@
                                 return fileInputClones[index];
                             });
                             if (options.paramName) {
-                                options.fileInput.each(function () {
-                                    $(this).prop('name', options.paramName);
+                                options.fileInput.each(function (index) {
+                                    $(this).prop(
+                                        'name',
+                                        paramNames[index] || options.paramName
+                                    );
                                 });
                             }
                             // Appending the file input fields to the hidden form
@@ -144,22 +170,36 @@
     });
 
     // The iframe transport returns the iframe content document as response.
-    // The following adds converters from iframe to text, json, html, and script:
+    // The following adds converters from iframe to text, json, html, xml
+    // and script.
+    // Please note that the Content-Type for JSON responses has to be text/plain
+    // or text/html, if the browser doesn't include application/json in the
+    // Accept header, else IE will show a download dialog.
+    // The Content-Type for XML responses on the other hand has to be always
+    // application/xml or text/xml, so IE properly parses the XML response.
+    // See also
+    // https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation
     $.ajaxSetup({
         converters: {
             'iframe text': function (iframe) {
-                return $(iframe[0].body).text();
+                return iframe && $(iframe[0].body).text();
             },
             'iframe json': function (iframe) {
-                return $.parseJSON($(iframe[0].body).text());
+                return iframe && $.parseJSON($(iframe[0].body).text());
             },
             'iframe html': function (iframe) {
-                return $(iframe[0].body).html();
+                return iframe && $(iframe[0].body).html();
+            },
+            'iframe xml': function (iframe) {
+                var xmlDoc = iframe && iframe[0];
+                return xmlDoc && $.isXMLDoc(xmlDoc) ? xmlDoc :
+                        $.parseXML((xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) ||
+                            $(xmlDoc.body).html());
             },
             'iframe script': function (iframe) {
-                return $.globalEval($(iframe[0].body).text());
+                return iframe && $.globalEval($(iframe[0].body).text());
             }
         }
     });
 
-}));
+}));
\ No newline at end of file
diff --git a/apps/files/templates/fileexists.html b/apps/files/templates/fileexists.html
new file mode 100644
index 0000000000000000000000000000000000000000..662177ac7ed79b56ed12360f998b807131e27e2b
--- /dev/null
+++ b/apps/files/templates/fileexists.html
@@ -0,0 +1,26 @@
+<div id="{dialog_name}" title="{title}" class="fileexists">
+	<span class="why">{why}<!-- Which files do you want to keep --></span><br/>
+	<span class="what">{what}<!-- If you select both versions, the copied file will have a number added to its name. --></span><br/>
+	<br/>
+	<table>
+		<th><label><input class="allnewfiles" type="checkbox" />New Files<span class="count"></span></label></th>
+		<th><label><input class="allexistingfiles" type="checkbox" />Already existing files<span class="count"></span></label></th>
+	</table>
+	<div class="conflicts">
+		<div class="template">
+			<div class="filename"></div>
+			<div class="replacement">
+				<input type="checkbox" />
+				<span class="svg icon"></span>
+				<div class="mtime"></div>
+				<div class="size"></div>
+			</div>
+			<div class="original">
+				<input type="checkbox" />
+				<span class="svg icon"></span>
+				<div class="mtime"></div>
+				<div class="size"></div>
+			</div>
+		</div>
+	</div>
+</div>
diff --git a/apps/files_sharing/js/public.js b/apps/files_sharing/js/public.js
index acabc9a5c10f8e48550bc0649e0ce30462237767..67320b1cdb690878d77eb9c408edbade129b70f9 100644
--- a/apps/files_sharing/js/public.js
+++ b/apps/files_sharing/js/public.js
@@ -62,6 +62,9 @@ $(document).ready(function() {
 	$('#controls').append($('#additional_controls div#uploadprogresswrapper'));
 
 	// Cancel upload trigger
-	$('#cancel_upload_button').click(Files.cancelUploads);
+	$('#cancel_upload_button').click(function() {
+		OC.Upload.cancelUploads();
+		procesSelection();
+	});
 
 });
diff --git a/apps/files_sharing/public.php b/apps/files_sharing/public.php
index 6d3a07a9d0b0ac07b0207a6d22b28d490ef11f7a..c997a7950c2fbd8ba8d27153fbc34a0c37763485 100644
--- a/apps/files_sharing/public.php
+++ b/apps/files_sharing/public.php
@@ -170,6 +170,7 @@ if (isset($path)) {
 			$tmpl->assign('dir', $getPath);
 
 			OCP\Util::addStyle('files', 'files');
+			OCP\Util::addStyle('files', 'upload');
 			OCP\Util::addScript('files', 'files');
 			OCP\Util::addScript('files', 'filelist');
 			OCP\Util::addscript('files', 'keyboardshortcuts');
diff --git a/core/img/actions/triangle-e.png b/core/img/actions/triangle-e.png
new file mode 100644
index 0000000000000000000000000000000000000000..09d398f602e7985787925f20826dc839cde6f7df
Binary files /dev/null and b/core/img/actions/triangle-e.png differ
diff --git a/core/img/actions/triangle-e.svg b/core/img/actions/triangle-e.svg
new file mode 100644
index 0000000000000000000000000000000000000000..c3d908b366f8eda379ba03347060c336d38e0f7d
--- /dev/null
+++ b/core/img/actions/triangle-e.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="16px" width="16px" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <path style="block-progression:tb;color:#000000;text-transform:none;text-indent:0" d="m4 12 8-4-7.989-4z"/>
+</svg>
diff --git a/core/js/jquery.ocdialog.js b/core/js/jquery.ocdialog.js
index f1836fd4727f7d09d8ed5c97e80d4d0f62d93149..02cd6ac146635895de58127b0f38a03b0d1a88eb 100644
--- a/core/js/jquery.ocdialog.js
+++ b/core/js/jquery.ocdialog.js
@@ -103,6 +103,9 @@
 					}
 					$.each(value, function(idx, val) {
 						var $button = $('<button>').text(val.text);
+						if (val.classes) {
+							$button.addClass(val.classes);
+						}
 						if(val.defaultButton) {
 							$button.addClass('primary');
 							self.$defaultButton = $button;
diff --git a/core/js/oc-dialogs.js b/core/js/oc-dialogs.js
index 8e8a477772b3859919b140351c142fe8498d8548..ac37b109e76a2ea17c6f7e101e5747f8aae1cbc3 100644
--- a/core/js/oc-dialogs.js
+++ b/core/js/oc-dialogs.js
@@ -220,6 +220,245 @@ var OCdialogs = {
 			}
 		});
 	},
+	_fileexistsshown: false,
+	/**
+	 * Displays file exists dialog
+	 * @param {object} data upload object
+	 * @param {object} original file with name, size and mtime
+	 * @param {object} replacement file with name, size and mtime
+	 * @param {object} controller with onCancel, onSkip, onReplace and onRename methods
+	*/
+	fileexists:function(data, original, replacement, controller) {
+		var self = this;
+
+		var getCroppedPreview = function(file) {
+			var deferred = new $.Deferred();
+			// Only process image files.
+			var type = file.type.split('/').shift();
+			if (window.FileReader && type === 'image') {
+				var reader = new FileReader();
+				reader.onload = function (e) {
+					var blob = new Blob([e.target.result]);
+					window.URL = window.URL || window.webkitURL;
+					var originalUrl = window.URL.createObjectURL(blob);
+					var image = new Image();
+					image.src = originalUrl;
+					image.onload = function () {
+						var url = crop(image);
+						deferred.resolve(url);
+					}
+				};
+				reader.readAsArrayBuffer(file);
+			} else {
+				deferred.reject();
+			}
+			return deferred;
+		};
+
+		var crop = function(img) {
+			var canvas = document.createElement('canvas'),
+				width = img.width,
+				height = img.height,
+				x, y, size;
+
+			// calculate the width and height, constraining the proportions
+			if (width > height) {
+				y = 0;
+				x = (width - height) / 2;
+			} else {
+				y = (height - width) / 2;
+				x = 0;
+			}
+			size = Math.min(width, height);
+
+			// resize the canvas and draw the image data into it
+			canvas.width = 64;
+			canvas.height = 64;
+			var ctx = canvas.getContext("2d");
+			ctx.drawImage(img, x, y, size, size, 0, 0, 64, 64);
+			return canvas.toDataURL("image/png", 0.7);
+		};
+
+		var addConflict = function(conflicts, original, replacement) {
+
+			var conflict = conflicts.find('.template').clone().removeClass('template').addClass('conflict');
+
+			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*1000));
+			// ie sucks
+			if (replacement.size && replacement.lastModifiedDate) {
+				conflict.find('.replacement .size').text(humanFileSize(replacement.size));
+				conflict.find('.replacement .mtime').text(formatDate(replacement.lastModifiedDate));
+			}
+			var path = getPathForPreview(original.name);
+			lazyLoadPreview(path, original.type, function(previewpath){
+				conflict.find('.original .icon').css('background-image','url('+previewpath+')');
+			}, 96, 96);
+			getCroppedPreview(replacement).then(
+				function(path){
+					conflict.find('.replacement .icon').css('background-image','url(' + path + ')');
+				}, function(){
+					getMimeIcon(replacement.type,function(path){
+						conflict.find('.replacement .icon').css('background-image','url(' + path + ')');
+					});
+				}
+			);
+			conflicts.append(conflict);
+
+			//set more recent mtime bold
+			// ie sucks
+			if (replacement.lastModifiedDate && replacement.lastModifiedDate.getTime() > original.mtime*1000) {
+				conflict.find('.replacement .mtime').css('font-weight', 'bold');
+			} else if (replacement.lastModifiedDate && replacement.lastModifiedDate.getTime() < original.mtime*1000) {
+				conflict.find('.original .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');
+			} else if (replacement.size && replacement.size < original.size) {
+				conflict.find('.original .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
+
+		};
+		//var selection = controller.getSelection(data.originalFiles);
+		//if (selection.defaultAction) {
+		//	controller[selection.defaultAction](data);
+		//} else {
+			var dialog_name = 'oc-dialog-fileexists-content';
+			var dialog_id = '#' + dialog_name;
+			if (this._fileexistsshown) {
+				// add conflict
+
+				var conflicts = $(dialog_id+ ' .conflicts');
+				addConflict(conflicts, original, replacement);
+
+				var count = $(dialog_id+ ' .conflict').length;
+				var title = n('files',
+								'{count} file conflict',
+								'{count} file conflicts',
+								count,
+								{count:count}
+							);
+				$(dialog_id).parent().children('.oc-dialog-title').text(title);
+
+				//recalculate dimensions
+				$(window).trigger('resize');
+
+			} else {
+				//create dialog
+				this._fileexistsshown = true;
+				$.when(this._getFileExistsTemplate()).then(function($tmpl) {
+					var title = t('files','One file conflict');
+					var $dlg = $tmpl.octemplate({
+						dialog_name: dialog_name,
+						title: title,
+						type: 'fileexists',
+
+						why: t('files','Which files do you want to keep?'),
+						what: t('files','If you select both versions, the copied file will have a number added to its name.')
+					});
+					$('body').append($dlg);
+
+					var conflicts = $($dlg).find('.conflicts');
+					addConflict(conflicts, original, replacement);
+
+					buttonlist = [{
+							text: t('core', 'Cancel'),
+							classes: 'cancel',
+							click: function(){
+								if ( typeof controller.onCancel !== 'undefined') {
+									controller.onCancel(data);
+								}
+								$(dialog_id).ocdialog('close');
+							}
+						},
+						{
+							text: t('core', 'Continue'),
+							classes: 'continue',
+							click: function(){
+								if ( typeof controller.onContinue !== 'undefined') {
+									controller.onContinue($(dialog_id + ' .conflict'));
+								}
+								$(dialog_id).ocdialog('close');
+							}
+						}];
+
+					$(dialog_id).ocdialog({
+						width: 500,
+						closeOnEscape: true,
+						modal: true,
+						buttons: buttonlist,
+						closeButton: null,
+						close: function(event, ui) {
+								self._fileexistsshown = false;
+							$(this).ocdialog('destroy').remove();
+						}
+					});
+
+					$(dialog_id).css('height','auto');
+
+					//add checkbox toggling actions
+					$(dialog_id).find('.allnewfiles').on('click', function() {
+						var checkboxes = $(dialog_id).find('.conflict .replacement input[type="checkbox"]');
+						checkboxes.prop('checked', $(this).prop('checked'));
+					});
+					$(dialog_id).find('.allexistingfiles').on('click', function() {
+						var checkboxes = $(dialog_id).find('.conflict .original input[type="checkbox"]');
+						checkboxes.prop('checked', $(this).prop('checked'));
+					});
+					$(dialog_id).find('.conflicts').on('click', '.replacement,.original', function() {
+						var checkbox = $(this).find('input[type="checkbox"]');
+						checkbox.prop('checked', !checkbox.prop('checked'));
+					});
+					$(dialog_id).find('.conflicts').on('click', 'input[type="checkbox"]', function() {
+						var checkbox = $(this);
+						checkbox.prop('checked', !checkbox.prop('checked'));
+					});
+
+					//update counters
+					$(dialog_id).on('click', '.replacement,.allnewfiles', function() {
+						var count = $(dialog_id).find('.conflict .replacement input[type="checkbox"]:checked').length;
+						if (count === $(dialog_id+ ' .conflict').length) {
+							$(dialog_id).find('.allnewfiles').prop('checked', true);
+							$(dialog_id).find('.allnewfiles + .count').text(t('files','(all selected)'));
+						} else if (count > 0) {
+							$(dialog_id).find('.allnewfiles').prop('checked', false);
+							$(dialog_id).find('.allnewfiles + .count').text(t('files','({count} selected)',{count:count}));
+						} else {
+							$(dialog_id).find('.allnewfiles').prop('checked', false);
+							$(dialog_id).find('.allnewfiles + .count').text('');
+						}
+					});
+					$(dialog_id).on('click', '.original,.allexistingfiles', function(){
+						var count = $(dialog_id).find('.conflict .original input[type="checkbox"]:checked').length;
+						if (count === $(dialog_id+ ' .conflict').length) {
+							$(dialog_id).find('.allexistingfiles').prop('checked', true);
+							$(dialog_id).find('.allexistingfiles + .count').text(t('files','(all selected)'));
+						} else if (count > 0) {
+							$(dialog_id).find('.allexistingfiles').prop('checked', false);
+							$(dialog_id).find('.allexistingfiles + .count').text(t('files','({count} selected)',{count:count}));
+						} else {
+							$(dialog_id).find('.allexistingfiles').prop('checked', false);
+							$(dialog_id).find('.allexistingfiles + .count').text('');
+						}
+					});
+				})
+				.fail(function() {
+					alert(t('core', 'Error loading file exists template'));
+				});
+			}
+		//}
+	},
 	_getFilePickerTemplate: function() {
 		var defer = $.Deferred();
 		if(!this.$filePickerTemplate) {
@@ -253,6 +492,22 @@ var OCdialogs = {
 		}
 		return defer.promise();
 	},
+	_getFileExistsTemplate: function () {
+		var defer = $.Deferred();
+		if (!this.$fileexistsTemplate) {
+			var self = this;
+			$.get(OC.filePath('files', 'templates', 'fileexists.html'), function (tmpl) {
+				self.$fileexistsTemplate = $(tmpl);
+				defer.resolve(self.$fileexistsTemplate);
+			})
+			.fail(function () {
+				defer.reject();
+			});
+		} else {
+			defer.resolve(this.$fileexistsTemplate);
+		}
+		return defer.promise();
+	},
 	_getFileList: function(dir, mimeType) {
 		if (typeof(mimeType) === "string") {
 			mimeType = [mimeType];