diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js
index 02754d7acbb907517a5be9be637dd2fcd6f08af0..53bb3a5c8681e4e18d4f0561c0f2527629df4ae2 100644
--- a/apps/files/js/filelist.js
+++ b/apps/files/js/filelist.js
@@ -30,6 +30,20 @@ window.FileList = {
 	pageNumber: 0,
 	totalPages: 0,
 
+	/**
+	 * Compare two file info objects, sorting by
+	 * folders first, then by name.
+	 */
+	_fileInfoCompare: function(fileInfo1, fileInfo2) {
+		if (fileInfo1.type === 'dir' && fileInfo2.type !== 'dir') {
+			return -1;
+		}
+		if (fileInfo1.type !== 'dir' && fileInfo2.type === 'dir') {
+			return 1;
+		}
+		return fileInfo1.name.localeCompare(fileInfo2.name);
+	},
+
 	/**
 	 * Initialize the file list and its components
 	 */
@@ -42,6 +56,7 @@ window.FileList = {
 		// TODO: FileList should not know about global elements
 		this.$el = $('#filestable');
 		this.$fileList = $('#fileList');
+		this.files = [];
 
 		this.fileSummary = this._createSummary();
 
@@ -321,7 +336,8 @@ window.FileList = {
 		index = this.pageNumber * this.pageSize;
 
 		while (count > 0 && index < this.files.length) {
-			tr = this.add(this.files[index], {updateSummary: false});
+			tr = this._renderRow(this.files[index], {updateSummary: false});
+			this.$fileList.append(tr);
 			if (selected) {
 				tr.addClass('selected');
 				tr.find('input:checkbox').prop('checked', true);
@@ -497,8 +513,10 @@ window.FileList = {
 		tr.append(td);
 		return tr;
 	},
+
 	/**
-	 * Adds an entry to the files table using the data from the given file data
+	 * Adds an entry to the files array and also into the DOM
+	 *
 	 * @param fileData map of file attributes
 	 * @param options map of attributes:
 	 * - "insert" true to insert in a sorted manner, false to append (default)
@@ -506,6 +524,47 @@ window.FileList = {
 	 * @return new tr element (not appended to the table)
 	 */
 	add: function(fileData, options) {
+		var index = -1;
+		var $tr = this._renderRow(fileData, options);
+		options = options || {};
+
+		this.isEmpty = false;
+
+		if (options.insert) {
+			index = this._findInsertionIndex(fileData);
+			if (index < this.files.length) {
+				this.files.splice(index, 0, fileData);
+				this.$fileList.children().eq(index).before($tr);
+			}
+			else {
+				this.files.push(fileData);
+				this.$fileList.append($tr);
+			}
+		}
+		else {
+			this.files.push(fileData);
+			this.$fileList.append($tr);
+		}
+
+		// defaults to true if not defined
+		if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) {
+			this.fileSummary.add(fileData, true);
+			this.updateEmptyContent();
+		}
+		return $tr;
+	},
+
+	/**
+	 * Creates a new row element based on the given attributes
+	 * and returns it.
+	 *
+	 * @param fileData map of file attributes
+	 * @param options map of attributes:
+	 * - "index" optional index at which to insert the element
+	 * - "updateSummary" true to update the summary after adding (default), false otherwise
+	 * @return new tr element (not appended to the table)
+	 */
+	_renderRow: function(fileData, options) {
 		options = options || {};
 		var type = fileData.type || 'file',
 			mime = fileData.mimetype,
@@ -524,16 +583,6 @@ window.FileList = {
 		);
 		var filenameTd = tr.find('td.filename');
 
-		// sorted insert is expensive, so needs to be explicitly
-		// requested
-		if (options.insert) {
-			this.insertElement(fileData.name, type, tr);
-		}
-		else {
-			this.$fileList.append(tr);
-		}
-		FileList.isEmpty = false;
-
 		// TODO: move dragging to FileActions ?
 		// enable drag only for deletable files
 		if (permissions & OC.PERMISSION_DELETE) {
@@ -569,12 +618,6 @@ window.FileList = {
 				filenameTd.css('background-image', 'url(' + previewUrl + ')');
 			}
 		}
-
-		// defaults to true if not defined
-		if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) {
-			this.fileSummary.add(fileData, true);
-			this.updateEmptyContent();
-		}
 		return tr;
 	},
 	/**
@@ -742,9 +785,10 @@ window.FileList = {
 	 * "updateSummary": true to update the summary (default), false otherwise
 	 * @return deleted element
 	 */
-	remove:function(name, options){
+	remove: function(name, options){
 		options = options || {};
 		var fileEl = FileList.findFileEl(name);
+		var index = fileEl.index();
 		if (!fileEl.length) {
 			return null;
 		}
@@ -752,51 +796,32 @@ window.FileList = {
 			// file is only draggable when delete permissions are set
 			fileEl.find('td.filename').draggable('destroy');
 		}
+		this.files.splice(index, 1);
 		fileEl.remove();
 		// TODO: improve performance on batch update
-		FileList.isEmpty = !this.$fileList.find('tr').length;
+		FileList.isEmpty = !this.files.length;
 		if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) {
 			FileList.updateEmptyContent();
 			this.fileSummary.remove({type: fileEl.attr('data-type'), size: fileEl.attr('data-size')}, true);
 		}
 		return fileEl;
 	},
-	insertElement:function(name, type, element) {
-		// find the correct spot to insert the file or folder
-		var pos,
-			fileElements = this.$fileList.find('tr[data-file][data-type="'+type+'"]:not(.hidden)');
-		if (name.localeCompare($(fileElements[0]).attr('data-file')) < 0) {
-			pos = -1;
-		} else if (name.localeCompare($(fileElements[fileElements.length-1]).attr('data-file')) > 0) {
-			pos = fileElements.length - 1;
-		} else {
-			for(pos = 0; pos<fileElements.length-1; pos++) {
-				if (name.localeCompare($(fileElements[pos]).attr('data-file')) > 0
-					&& name.localeCompare($(fileElements[pos+1]).attr('data-file')) < 0)
-				{
-					break;
-				}
-			}
-		}
-		if (fileElements.exists()) {
-			if (pos === -1) {
-				$(fileElements[0]).before(element);
-			} else {
-				$(fileElements[pos]).after(element);
-			}
-		} else if (type === 'dir' && !FileList.isEmpty) {
-			this.$fileList.find('tr[data-file]:first').before(element);
-		} else if (type === 'file' && !FileList.isEmpty) {
-			this.$fileList.find('tr[data-file]:last').before(element);
-		} else {
-			this.$fileList.append(element);
+	/**
+	 * Finds the index of the row before which the given
+	 * fileData should be inserted, considering the current
+	 * sorting
+	 */
+	_findInsertionIndex: function(fileData) {
+		var index = 0;
+		while (index < this.files.length && this._fileInfoCompare(fileData, this.files[index]) > 0) {
+			index++;
 		}
-		FileList.isEmpty = false;
-		FileList.updateEmptyContent();
+		return index;
 	},
 	rename: function(oldname) {
 		var tr, td, input, form;
 		tr = FileList.findFileEl(oldname);
+		var oldFileInfo = this.files[tr.index()];
 		tr.data('renaming',true);
 		td = tr.children('td.filename');
 		input = $('<input type="text" class="filename"/>').val(oldname);
@@ -844,51 +869,18 @@ window.FileList = {
 							file: oldname
 						},
 						success: function(result) {
+							var fileInfo;
 							if (!result || result.status === 'error') {
 								OC.dialogs.alert(result.data.message, t('core', 'Could not rename file'));
-								// revert changes
-								newname = oldname;
-								tr.attr('data-file', newname);
-								var path = td.children('a.name').attr('href');
-								td.children('a.name').attr('href', path.replace(encodeURIComponent(oldname), encodeURIComponent(newname)));
-								var basename = newname;
-								if (newname.indexOf('.') > 0 && tr.data('type') !== 'dir') {
-									basename = newname.substr(0,newname.lastIndexOf('.'));
-								}
-								td.find('a.name span.nametext').text(basename);
-								if (newname.indexOf('.') > 0 && tr.data('type') !== 'dir') {
-									if ( ! td.find('a.name span.extension').exists() ) {
-										td.find('a.name span.nametext').append('<span class="extension"></span>');
-									}
-									td.find('a.name span.extension').text(newname.substr(newname.lastIndexOf('.')));
-								}
-								tr.find('.fileactions').effect('highlight', {}, 5000);
-								tr.effect('highlight', {}, 5000);
-								// remove loading mark and recover old image
-								td.css('background-image', oldBackgroundImage);
+								fileInfo = oldFileInfo;
 							}
 							else {
-								var fileInfo = result.data;
-								tr.attr('data-mime', fileInfo.mime);
-								tr.attr('data-etag', fileInfo.etag);
-								if (fileInfo.isPreviewAvailable) {
-									Files.lazyLoadPreview(directory + '/' + fileInfo.name, result.data.mime, function(previewpath) {
-										tr.find('td.filename').attr('style','background-image:url('+previewpath+')');
-									}, null, null, result.data.etag);
-								}
-								else {
-									tr.find('td.filename')
-										.removeClass('preview')
-										.attr('style','background-image:url('
-												+ OC.Util.replaceSVGIcon(fileInfo.icon)
-												+ ')');
-								}
+								fileInfo = result.data;
 							}
 							// reinsert row
-							tr.detach();
-							FileList.insertElement( tr.attr('data-file'), tr.attr('data-type'),tr );
-							// update file actions in case the extension changed
-							FileActions.display( tr.find('td.filename'), true);
+							FileList.files.splice(tr.index(), 1);
+							tr.remove();
+							FileList.add(fileInfo, {insert: true});
 						}
 					});
 				}
diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js
index 93e7c81cb1f799e9bf21206d5c7bfcf97132694a..1b155f4f1dfb712f47758f59edb9ae130ac97e2a 100644
--- a/apps/files/tests/js/filelistSpec.js
+++ b/apps/files/tests/js/filelistSpec.js
@@ -233,6 +233,7 @@ describe('FileList tests', function() {
 			expect($tr.find('.filesize').text()).toEqual('0 B');
 		});
 		it('adds new file to the end of the list', function() {
+			var $tr;
 			var fileData = {
 				type: 'file',
 				name: 'P comes after O.txt'
@@ -241,15 +242,55 @@ describe('FileList tests', function() {
 			$tr = FileList.add(fileData);
 			expect($tr.index()).toEqual(4);
 		});
-		it('adds new file at correct position in insert mode', function() {
+		it('inserts files in a sorted manner when insert option is enabled', function() {
+			var $tr;
+			for (var i = 0; i < testFiles.length; i++) {
+				FileList.add(testFiles[i], {insert: true});
+			}
+			expect(FileList.files[0].name).toEqual('somedir');
+			expect(FileList.files[1].name).toEqual('One.txt');
+			expect(FileList.files[2].name).toEqual('Three.pdf');
+			expect(FileList.files[3].name).toEqual('Two.jpg');
+		});
+		it('inserts new file at correct position', function() {
+			var $tr;
 			var fileData = {
 				type: 'file',
 				name: 'P comes after O.txt'
 			};
-			FileList.setFiles(testFiles);
+			for (var i = 0; i < testFiles.length; i++) {
+				FileList.add(testFiles[i], {insert: true});
+			}
 			$tr = FileList.add(fileData, {insert: true});
 			// after "One.txt"
+			expect($tr.index()).toEqual(2);
+			expect(FileList.files[2]).toEqual(fileData);
+		});
+		it('inserts new folder at correct position in insert mode', function() {
+			var $tr;
+			var fileData = {
+				type: 'dir',
+				name: 'somedir2 comes after somedir'
+			};
+			for (var i = 0; i < testFiles.length; i++) {
+				FileList.add(testFiles[i], {insert: true});
+			}
+			$tr = FileList.add(fileData, {insert: true});
 			expect($tr.index()).toEqual(1);
+			expect(FileList.files[1]).toEqual(fileData);
+		});
+		it('inserts new file at the end correctly', function() {
+			var $tr;
+			var fileData = {
+				type: 'file',
+				name: 'zzz.txt'
+			};
+			for (var i = 0; i < testFiles.length; i++) {
+				FileList.add(testFiles[i], {insert: true});
+			}
+			$tr = FileList.add(fileData, {insert: true});
+			expect($tr.index()).toEqual(4);
+			expect(FileList.files[4]).toEqual(fileData);
 		});
 		it('removes empty content message and shows summary when adding first file', function() {
 			var fileData = {
@@ -280,6 +321,7 @@ describe('FileList tests', function() {
 			expect($removedEl).toBeDefined();
 			expect($removedEl.attr('data-file')).toEqual('One.txt');
 			expect($('#fileList tr').length).toEqual(3);
+			expect(FileList.files.length).toEqual(3);
 			expect(FileList.findFileEl('One.txt').length).toEqual(0);
 
 			$summary = $('#filestable .summary');
@@ -294,6 +336,7 @@ describe('FileList tests', function() {
 			FileList.setFiles([testFiles[0]]);
 			FileList.remove('One.txt');
 			expect($('#fileList tr').length).toEqual(0);
+			expect(FileList.files.length).toEqual(0);
 			expect(FileList.findFileEl('One.txt').length).toEqual(0);
 
 			$summary = $('#filestable .summary');
@@ -358,6 +401,7 @@ describe('FileList tests', function() {
 			$summary = $('#filestable .summary');
 			expect($summary.hasClass('hidden')).toEqual(true);
 			expect(FileList.isEmpty).toEqual(true);
+			expect(FileList.files.length).toEqual(0);
 			expect($('#filestable thead th').hasClass('hidden')).toEqual(true);
 			expect($('#emptycontent').hasClass('hidden')).toEqual(false);
 		});
@@ -383,37 +427,41 @@ describe('FileList tests', function() {
 		function doRename() {
 			var $input, request;
 
-			FileList.setFiles(testFiles);
+			for (var i = 0; i < testFiles.length; i++) {
+				FileList.add(testFiles[i], {insert: true});
+			}
 
 			// trigger rename prompt
 			FileList.rename('One.txt');
 			$input = FileList.$fileList.find('input.filename');
-			$input.val('One_renamed.txt').blur();
+			$input.val('Tu_after_three.txt').blur();
 
 			expect(fakeServer.requests.length).toEqual(1);
 			request = fakeServer.requests[0];
 			expect(request.url.substr(0, request.url.indexOf('?'))).toEqual(OC.webroot + '/index.php/apps/files/ajax/rename.php');
-			expect(OC.parseQueryString(request.url)).toEqual({'dir': '/subdir', newname: 'One_renamed.txt', file: 'One.txt'});
+			expect(OC.parseQueryString(request.url)).toEqual({'dir': '/subdir', newname: 'Tu_after_three.txt', file: 'One.txt'});
 
 			// element is renamed before the request finishes
 			expect(FileList.findFileEl('One.txt').length).toEqual(0);
-			expect(FileList.findFileEl('One_renamed.txt').length).toEqual(1);
+			expect(FileList.findFileEl('Tu_after_three.txt').length).toEqual(1);
 			// input is gone
 			expect(FileList.$fileList.find('input.filename').length).toEqual(0);
 		}
-		it('Keeps renamed file entry if rename ajax call suceeded', function() {
+		it('Inserts renamed file entry at correct position if rename ajax call suceeded', function() {
 			doRename();
 
 			fakeServer.requests[0].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({
 				status: 'success',
 				data: {
-					name: 'One_renamed.txt'
+					name: 'Tu_after_three.txt',
+					type: 'file'
 				}
 			}));
 
 			// element stays renamed
 			expect(FileList.findFileEl('One.txt').length).toEqual(0);
-			expect(FileList.findFileEl('One_renamed.txt').length).toEqual(1);
+			expect(FileList.findFileEl('Tu_after_three.txt').length).toEqual(1);
+			expect(FileList.findFileEl('Tu_after_three.txt').index()).toEqual(2); // after Two.txt
 
 			expect(alertStub.notCalled).toEqual(true);
 		});
@@ -429,7 +477,8 @@ describe('FileList tests', function() {
 
 			// element was reverted
 			expect(FileList.findFileEl('One.txt').length).toEqual(1);
-			expect(FileList.findFileEl('One_renamed.txt').length).toEqual(0);
+			expect(FileList.findFileEl('One.txt').index()).toEqual(1); // after somedir
+			expect(FileList.findFileEl('Tu_after_three.txt').length).toEqual(0);
 
 			expect(alertStub.calledOnce).toEqual(true);
 		});
@@ -440,12 +489,12 @@ describe('FileList tests', function() {
 			fakeServer.requests[0].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({
 				status: 'success',
 				data: {
-					name: 'One_renamed.txt'
+					name: 'Tu_after_three.txt'
 				}
 			}));
 
-			$tr = FileList.findFileEl('One_renamed.txt');
-			expect($tr.find('a.name').attr('href')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=One_renamed.txt');
+			$tr = FileList.findFileEl('Tu_after_three.txt');
+			expect($tr.find('a.name').attr('href')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=Tu_after_three.txt');
 		});
 		// FIXME: fix this in the source code!
 		xit('Correctly updates file link after rename when path has same name', function() {
@@ -457,20 +506,23 @@ describe('FileList tests', function() {
 			fakeServer.requests[0].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({
 				status: 'success',
 				data: {
-					name: 'One_renamed.txt'
+					name: 'Tu_after_three.txt'
 				}
 			}));
 
-			$tr = FileList.findFileEl('One_renamed.txt');
+			$tr = FileList.findFileEl('Tu_after_three.txt');
 			expect($tr.find('a.name').attr('href')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=One.txt');
 		});
 	});
 	describe('List rendering', function() {
 		it('renders a list of files using add()', function() {
 			var addSpy = sinon.spy(FileList, 'add');
+			expect(FileList.files.length).toEqual(0);
+			expect(FileList.files).toEqual([]);
 			FileList.setFiles(testFiles);
-			expect(addSpy.callCount).toEqual(4);
 			expect($('#fileList tr').length).toEqual(4);
+			expect(FileList.files.length).toEqual(4);
+			expect(FileList.files).toEqual(testFiles);
 			addSpy.restore();
 		});
 		it('updates summary using the file sizes', function() {