diff --git a/apps/files/index.php b/apps/files/index.php
index b8ff08c1b05573dadcd74aaeac5dc4d71581f625..a4e9a938507c36541cb7df9f0f600d577095074a 100644
--- a/apps/files/index.php
+++ b/apps/files/index.php
@@ -32,6 +32,7 @@ OCP\Util::addscript('files', 'file-upload');
 OCP\Util::addscript('files', 'jquery.iframe-transport');
 OCP\Util::addscript('files', 'jquery.fileupload');
 OCP\Util::addscript('files', 'jquery-visibility');
+OCP\Util::addscript('files', 'filesummary');
 OCP\Util::addscript('files', 'breadcrumb');
 OCP\Util::addscript('files', 'filelist');
 
diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js
index 21ce1418210dae45ee1ad7a276fa03862848fa89..223f4bb44098f9d35bacbc7022d6f0a474019bc3 100644
--- a/apps/files/js/filelist.js
+++ b/apps/files/js/filelist.js
@@ -8,7 +8,7 @@
  *
  */
 
-/* global OC, t, n, FileList, FileActions, Files, BreadCrumb */
+/* global OC, t, n, FileList, FileActions, Files, FileSummary, BreadCrumb */
 /* global procesSelection, dragOptions, folderDropOptions */
 window.FileList = {
 	appName: t('files', 'Files'),
@@ -17,6 +17,11 @@ window.FileList = {
 	$el: $('#filestable'),
 	$fileList: $('#fileList'),
 	breadcrumb: null,
+
+	/**
+	 * Instance of FileSummary
+	 */
+	fileSummary: null,
 	initialized: false,
 
 	// number of files per page
@@ -38,6 +43,8 @@ window.FileList = {
 		this.$el = $('#filestable');
 		this.$fileList = $('#fileList');
 
+		this.fileSummary = this._createSummary();
+
 		this.breadcrumb = new BreadCrumb({
 			onClick: this._onClickBreadCrumb,
 			onDrop: this._onDropOnBreadCrumb,
@@ -72,7 +79,6 @@ window.FileList = {
 		if (this.pageNumber + 1 >= this.totalPages) {
 			return;
 		}
-		var target = $(document);
 		if ($(window).scrollTop() + $(window).height() > $(document).height() - 20) {
 			this._nextPage(true);
 		}
@@ -206,7 +212,9 @@ window.FileList = {
 		if (window.Files) {
 			Files.setupDragAndDrop();
 		}
-		this.updateFileSummary();
+
+		this.fileSummary.calculate(filesArray);
+
 		procesSelection();
 		$(window).scrollTop(0);
 
@@ -404,7 +412,7 @@ window.FileList = {
 
 		// defaults to true if not defined
 		if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) {
-			this.updateFileSummary();
+			this.fileSummary.add(fileData, true);
 			this.updateEmptyContent();
 		}
 		return tr;
@@ -582,10 +590,10 @@ window.FileList = {
 		}
 		fileEl.remove();
 		// TODO: improve performance on batch update
-		FileList.isEmpty = !this.$fileList.find('tr:not(.summary)').length;
+		FileList.isEmpty = !this.$fileList.find('tr').length;
 		if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) {
 			FileList.updateEmptyContent();
-			FileList.updateFileSummary();
+			this.fileSummary.remove({type: fileEl.attr('data-type'), size: fileEl.attr('data-size')}, true);
 		}
 		return fileEl;
 	},
@@ -621,7 +629,6 @@ window.FileList = {
 		}
 		FileList.isEmpty = false;
 		FileList.updateEmptyContent();
-		FileList.updateFileSummary();
 	},
 	rename: function(oldname) {
 		var tr, td, input, form;
@@ -825,12 +832,13 @@ window.FileList = {
 								var fileEl = FileList.remove(file, {updateSummary: false});
 								fileEl.find('input[type="checkbox"]').prop('checked', false);
 								fileEl.removeClass('selected');
+								FileList.fileSummary.remove({type: fileEl.attr('data-type'), size: fileEl.attr('data-size')});
 							});
 						}
 						procesSelection();
 						checkTrashStatus();
-						FileList.updateFileSummary();
 						FileList.updateEmptyContent();
+						FileList.fileSummary.update();
 						Files.updateStorageStatistics();
 					} else {
 						if (result.status === 'error' && result.data.message) {
@@ -857,108 +865,14 @@ window.FileList = {
 					}
 				});
 	},
-	createFileSummary: function() {
-		if ( !FileList.isEmpty ) {
-			var summary = this._calculateFileSummary();
-
-			// Get translations
-			var directoryInfo = n('files', '%n folder', '%n folders', summary.totalDirs);
-			var fileInfo = n('files', '%n file', '%n files', summary.totalFiles);
-
-			var infoVars = {
-				dirs: '<span class="dirinfo">'+directoryInfo+'</span><span class="connector">',
-				files: '</span><span class="fileinfo">'+fileInfo+'</span>'
-			};
-
-			var info = t('files', '{dirs} and {files}', infoVars);
-
-			// don't show the filesize column, if filesize is NaN (e.g. in trashbin)
-			var fileSize = '';
-			if (!isNaN(summary.totalSize)) {
-				fileSize = '<td class="filesize">'+humanFileSize(summary.totalSize)+'</td>';
-			}
-
-			var $summary = $('<tr class="summary" data-file="undefined"><td><span class="info">'+info+'</span></td>'+fileSize+'<td></td></tr>');
-			this.$fileList.append($summary);
-
-			var $dirInfo = $summary.find('.dirinfo');
-			var $fileInfo = $summary.find('.fileinfo');
-			var $connector = $summary.find('.connector');
+	/**
+	 * Creates the file summary section
+	 */
+	_createSummary: function() {
+		var $tr = $('<tr class="summary"></tr>');
+		this.$el.find('tfoot').append($tr);
 
-			// Show only what's necessary, e.g.: no files: don't show "0 files"
-			if (summary.totalDirs === 0) {
-				$dirInfo.addClass('hidden');
-				$connector.addClass('hidden');
-			}
-			if (summary.totalFiles === 0) {
-				$fileInfo.addClass('hidden');
-				$connector.addClass('hidden');
-			}
-		}
-	},
-	_calculateFileSummary: function() {
-		var result = {
-			totalDirs: 0,
-			totalFiles: 0,
-			totalSize: 0
-		};
-		$.each($('tr[data-file]'), function(index, value) {
-			var $value = $(value);
-			if ($value.data('type') === 'dir') {
-				result.totalDirs++;
-			} else if ($value.data('type') === 'file') {
-				result.totalFiles++;
-			}
-			if ($value.data('size') !== undefined && $value.data('id') !== -1) {
-				//Skip shared as it does not count toward quota
-				result.totalSize += parseInt($value.data('size'));
-			}
-		});
-		return result;
-	},
-	updateFileSummary: function() {
-		var $summary = this.$el.find('.summary');
-
-		// always make it the last element
-		this.$fileList.append($summary.detach());
-
-		// Check if we should remove the summary to show "Upload something"
-		if (this.isEmpty && $summary.length === 1) {
-			$summary.remove();
-		}
-		// If there's no summary create one (createFileSummary checks if there's data)
-		else if ($summary.length === 0) {
-			FileList.createFileSummary();
-		}
-		// There's a summary and data -> Update the summary
-		else if (!this.isEmpty && $summary.length === 1) {
-			var fileSummary = this._calculateFileSummary();
-			var $dirInfo = $('.summary .dirinfo');
-			var $fileInfo = $('.summary .fileinfo');
-			var $connector = $('.summary .connector');
-
-			// Substitute old content with new translations
-			$dirInfo.html(n('files', '%n folder', '%n folders', fileSummary.totalDirs));
-			$fileInfo.html(n('files', '%n file', '%n files', fileSummary.totalFiles));
-			$('.summary .filesize').html(humanFileSize(fileSummary.totalSize));
-
-			// Show only what's necessary (may be hidden)
-			if (fileSummary.totalDirs === 0) {
-				$dirInfo.addClass('hidden');
-				$connector.addClass('hidden');
-			} else {
-				$dirInfo.removeClass('hidden');
-			}
-			if (fileSummary.totalFiles === 0) {
-				$fileInfo.addClass('hidden');
-				$connector.addClass('hidden');
-			} else {
-				$fileInfo.removeClass('hidden');
-			}
-			if (fileSummary.totalDirs > 0 && fileSummary.totalFiles > 0) {
-				$connector.removeClass('hidden');
-			}
-		}
+		return new FileSummary($tr);
 	},
 	updateEmptyContent: function() {
 		var permissions = $('#permissions').val();
@@ -1009,7 +923,7 @@ window.FileList = {
 		}
 	},
 	filter:function(query) {
-		$('#fileList tr:not(.summary)').each(function(i,e) {
+		$('#fileList tr').each(function(i,e) {
 			if ($(e).data('file').toString().toLowerCase().indexOf(query.toLowerCase()) !== -1) {
 				$(e).addClass("searchresult");
 			} else {
@@ -1315,7 +1229,5 @@ $(document).ready(function() {
 	setTimeout(function() {
 		FileList.changeDirectory(dir, false, true);
 	}, 0);
-
-	FileList.createFileSummary();
 });
 
diff --git a/apps/files/js/filesummary.js b/apps/files/js/filesummary.js
new file mode 100644
index 0000000000000000000000000000000000000000..bbe4d43ba49d92a25da078ca65507ed293635d35
--- /dev/null
+++ b/apps/files/js/filesummary.js
@@ -0,0 +1,176 @@
+/**
+* ownCloud
+*
+* @author Vincent Petry
+* @copyright 2014 Vincent Petry <pvince81@owncloud.com>
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+* License as published by the Free Software Foundation; either
+* version 3 of the License, or any later version.
+*
+* This library is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+*
+* You should have received a copy of the GNU Affero General Public
+* License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+/* global OC, n, t */
+
+(function() {
+	/**
+	 * The FileSummary class encapsulates the file summary values and
+	 * the logic to render it in the given container
+	 * @param $tr table row element
+	 * $param summary optional initial summary value
+	 */
+	var FileSummary = function($tr, summary) {
+		this.$el = $tr;
+		this.render();
+	};
+
+	FileSummary.prototype = {
+		summary: {
+			totalFiles: 0,
+			totalDirs: 0,
+			totalSize: 0
+		},
+
+		/**
+		 * Adds file
+		 * @param file file to add
+		 * @param update whether to update the display
+		 */
+		add: function(file, update) {
+			if (file.type === 'dir' || file.mime === 'httpd/unix-directory') {
+				this.summary.totalDirs++;
+			}
+			else {
+				this.summary.totalFiles++;
+			}
+			this.summary.totalSize += parseInt(file.size, 10) || 0;
+			if (!!update) {
+				this.update();
+			}
+		},
+		/**
+		 * Removes file
+		 * @param file file to remove
+		 * @param update whether to update the display
+		 */
+		remove: function(file, update) {
+			if (file.type === 'dir' || file.mime === 'httpd/unix-directory') {
+				this.summary.totalDirs--;
+			}
+			else {
+				this.summary.totalFiles--;
+			}
+			this.summary.totalSize -= parseInt(file.size, 10) || 0;
+			if (!!update) {
+				this.update();
+			}
+		},
+		/**
+		 * Recalculates the summary based on the given files array
+		 * @param files array of files
+		 */
+		calculate: function(files) {
+			var file;
+			var summary = {
+				totalDirs: 0,
+				totalFiles: 0,
+				totalSize: 0
+			};
+
+			for (var i = 0; i < files.length; i++) {
+				file = files[i];
+				if (file.type === 'dir' || file.mime === 'httpd/unix-directory') {
+					summary.totalDirs++;
+				}
+				else {
+					summary.totalFiles++;
+				}
+				summary.totalSize += parseInt(file.size, 10) || 0;
+			}
+			this.setSummary(summary);
+		},
+		/**
+		 * Sets the current summary values
+		 * @param summary map
+		 */
+		setSummary: function(summary) {
+			this.summary = summary;
+			this.update();
+		},
+
+		/**
+		 * Renders the file summary element
+		 */
+		update: function() {
+			if (!this.summary.totalFiles && !this.summary.totalDirs) {
+				this.$el.addClass('hidden');
+				return;
+			}
+			// There's a summary and data -> Update the summary
+			this.$el.removeClass('hidden');
+			var $dirInfo = this.$el.find('.dirinfo');
+			var $fileInfo = this.$el.find('.fileinfo');
+			var $connector = this.$el.find('.connector');
+
+			// Substitute old content with new translations
+			$dirInfo.html(n('files', '%n folder', '%n folders', this.summary.totalDirs));
+			$fileInfo.html(n('files', '%n file', '%n files', this.summary.totalFiles));
+			this.$el.find('.filesize').html(OC.Util.humanFileSize(this.summary.totalSize));
+
+			// Show only what's necessary (may be hidden)
+			if (this.summary.totalDirs === 0) {
+				$dirInfo.addClass('hidden');
+				$connector.addClass('hidden');
+			} else {
+				$dirInfo.removeClass('hidden');
+			}
+			if (this.summary.totalFiles === 0) {
+				$fileInfo.addClass('hidden');
+				$connector.addClass('hidden');
+			} else {
+				$fileInfo.removeClass('hidden');
+			}
+			if (this.summary.totalDirs > 0 && this.summary.totalFiles > 0) {
+				$connector.removeClass('hidden');
+			}
+		},
+		render: function() {
+			var summary = this.summary;
+			var directoryInfo = n('files', '%n folder', '%n folders', summary.totalDirs);
+			var fileInfo = n('files', '%n file', '%n files', summary.totalFiles);
+			var fileSize;
+
+			var infoVars = {
+				dirs: '<span class="dirinfo">'+directoryInfo+'</span><span class="connector">',
+				files: '</span><span class="fileinfo">'+fileInfo+'</span>'
+			};
+
+			// don't show the filesize column, if filesize is NaN (e.g. in trashbin)
+			var fileSize = '';
+			if (!isNaN(summary.totalSize)) {
+				fileSize = '<td class="filesize">' + OC.Util.humanFileSize(summary.totalSize) + '</td>';
+			}
+
+			var info = t('files', '{dirs} and {files}', infoVars);
+
+			var $summary = $('<td><span class="info">'+info+'</span></td>'+fileSize+'<td></td>');
+
+			if (!this.summary.totalFiles && !this.summary.totalDirs) {
+				this.$el.addClass('hidden');
+			}
+
+			this.$el.append($summary);
+		}
+	};
+	window.FileSummary = FileSummary;
+})();
+
diff --git a/apps/files/templates/index.php b/apps/files/templates/index.php
index a8437835d959bf8ca662de8492684a6b26fc82d5..42263c880a7b530ccfa78d12e8a5410dcd81c830 100644
--- a/apps/files/templates/index.php
+++ b/apps/files/templates/index.php
@@ -91,6 +91,8 @@
 	</thead>
 	<tbody id="fileList">
 	</tbody>
+	<tfoot>
+	</tfoot>
 </table>
 <div id="editor"></div><!-- FIXME Do not use this div in your app! It is deprecated and will be removed in the future! -->
 <div id="uploadsize-message" title="<?php p($l->t('Upload too large'))?>">
diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js
index ca85a360cf508a0e684b17e123b76fa7da7039de..6e80d78eee049618cbb5a00392c707e387fd8a05 100644
--- a/apps/files/tests/js/filelistSpec.js
+++ b/apps/files/tests/js/filelistSpec.js
@@ -51,6 +51,7 @@ describe('FileList tests', function() {
 			'<table id="filestable">' +
 			'<thead><tr><th class="hidden">Name</th></tr></thead>' +
 		   	'<tbody id="fileList"></tbody>' +
+			'<tfoot></tfoot>' +
 			'</table>' +
 			'<div id="emptycontent">Empty content message</div>'
 		);
@@ -220,7 +221,7 @@ describe('FileList tests', function() {
 			var $tr = FileList.add(fileData);
 			expect($tr.find('.filesize').text()).toEqual('0 B');
 		});
-		it('adds new file to the end of the list before the summary', function() {
+		it('adds new file to the end of the list', function() {
 			var fileData = {
 				type: 'file',
 				name: 'P comes after O.txt'
@@ -228,7 +229,6 @@ describe('FileList tests', function() {
 			FileList.setFiles(testFiles);
 			$tr = FileList.add(fileData);
 			expect($tr.index()).toEqual(4);
-			expect($tr.next().hasClass('summary')).toEqual(true);
 		});
 		it('adds new file at correct position in insert mode', function() {
 			var fileData = {
@@ -249,8 +249,8 @@ describe('FileList tests', function() {
 			FileList.setFiles([]);
 			expect(FileList.isEmpty).toEqual(true);
 			FileList.add(fileData);
-			$summary = $('#fileList .summary');
-			expect($summary.length).toEqual(1);
+			$summary = $('#filestable .summary');
+			expect($summary.hasClass('hidden')).toEqual(false);
 			// yes, ugly...
 			expect($summary.find('.info').text()).toEqual('0 folders and 1 file');
 			expect($summary.find('.dirinfo').hasClass('hidden')).toEqual(true);
@@ -268,11 +268,11 @@ describe('FileList tests', function() {
 			$removedEl = FileList.remove('One.txt');
 			expect($removedEl).toBeDefined();
 			expect($removedEl.attr('data-file')).toEqual('One.txt');
-			expect($('#fileList tr:not(.summary)').length).toEqual(3);
+			expect($('#fileList tr').length).toEqual(3);
 			expect(FileList.findFileEl('One.txt').length).toEqual(0);
 
-			$summary = $('#fileList .summary');
-			expect($summary.length).toEqual(1);
+			$summary = $('#filestable .summary');
+			expect($summary.hasClass('hidden')).toEqual(false);
 			expect($summary.find('.info').text()).toEqual('1 folder and 2 files');
 			expect($summary.find('.dirinfo').hasClass('hidden')).toEqual(false);
 			expect($summary.find('.fileinfo').hasClass('hidden')).toEqual(false);
@@ -282,11 +282,11 @@ describe('FileList tests', function() {
 		it('Shows empty content when removing last file', function() {
 			FileList.setFiles([testFiles[0]]);
 			FileList.remove('One.txt');
-			expect($('#fileList tr:not(.summary)').length).toEqual(0);
+			expect($('#fileList tr').length).toEqual(0);
 			expect(FileList.findFileEl('One.txt').length).toEqual(0);
 
-			$summary = $('#fileList .summary');
-			expect($summary.length).toEqual(0);
+			$summary = $('#filestable .summary');
+			expect($summary.hasClass('hidden')).toEqual(true);
 			expect($('#filestable thead th').hasClass('hidden')).toEqual(true);
 			expect($('#emptycontent').hasClass('hidden')).toEqual(false);
 			expect(FileList.isEmpty).toEqual(true);
@@ -318,10 +318,10 @@ describe('FileList tests', function() {
 			expect(FileList.findFileEl('One.txt').length).toEqual(0);
 			expect(FileList.findFileEl('Two.jpg').length).toEqual(0);
 			expect(FileList.findFileEl('Three.pdf').length).toEqual(1);
-			expect(FileList.$fileList.find('tr:not(.summary)').length).toEqual(2);
+			expect(FileList.$fileList.find('tr').length).toEqual(2);
 
-			$summary = $('#fileList .summary');
-			expect($summary.length).toEqual(1);
+			$summary = $('#filestable .summary');
+			expect($summary.hasClass('hidden')).toEqual(false);
 			expect($summary.find('.info').text()).toEqual('1 folder and 1 file');
 			expect($summary.find('.dirinfo').hasClass('hidden')).toEqual(false);
 			expect($summary.find('.fileinfo').hasClass('hidden')).toEqual(false);
@@ -342,10 +342,10 @@ describe('FileList tests', function() {
 				JSON.stringify({status: 'success'})
 			);
 
-			expect(FileList.$fileList.find('tr:not(.summary)').length).toEqual(0);
+			expect(FileList.$fileList.find('tr').length).toEqual(0);
 
-			$summary = $('#fileList .summary');
-			expect($summary.length).toEqual(0);
+			$summary = $('#filestable .summary');
+			expect($summary.hasClass('hidden')).toEqual(true);
 			expect(FileList.isEmpty).toEqual(true);
 			expect($('#filestable thead th').hasClass('hidden')).toEqual(true);
 			expect($('#emptycontent').hasClass('hidden')).toEqual(false);
@@ -363,7 +363,7 @@ describe('FileList tests', function() {
 			// files are still in the list
 			expect(FileList.findFileEl('One.txt').length).toEqual(1);
 			expect(FileList.findFileEl('Two.jpg').length).toEqual(1);
-			expect(FileList.$fileList.find('tr:not(.summary)').length).toEqual(4);
+			expect(FileList.$fileList.find('tr').length).toEqual(4);
 
 			expect(notificationStub.calledOnce).toEqual(true);
 		});
@@ -459,14 +459,14 @@ describe('FileList tests', function() {
 			var addSpy = sinon.spy(FileList, 'add');
 			FileList.setFiles(testFiles);
 			expect(addSpy.callCount).toEqual(4);
-			expect($('#fileList tr:not(.summary)').length).toEqual(4);
+			expect($('#fileList tr').length).toEqual(4);
 			addSpy.restore();
 		});
 		it('updates summary using the file sizes', function() {
 			var $summary;
 			FileList.setFiles(testFiles);
-			$summary = $('#fileList .summary');
-			expect($summary.length).toEqual(1);
+			$summary = $('#filestable .summary');
+			expect($summary.hasClass('hidden')).toEqual(false);
 			expect($summary.find('.info').text()).toEqual('1 folder and 3 files');
 			expect($summary.find('.filesize').text()).toEqual('69 kB');
 		});
@@ -474,20 +474,20 @@ describe('FileList tests', function() {
 			FileList.setFiles(testFiles);
 			expect($('#filestable thead th').hasClass('hidden')).toEqual(false);
 			expect($('#emptycontent').hasClass('hidden')).toEqual(true);
-			expect(FileList.$fileList.find('.summary').length).toEqual(1);
+			expect(FileList.$el.find('.summary').hasClass('hidden')).toEqual(false);
 		});
 		it('hides headers, summary and show empty content message after setting empty file list', function(){
 			FileList.setFiles([]);
 			expect($('#filestable thead th').hasClass('hidden')).toEqual(true);
 			expect($('#emptycontent').hasClass('hidden')).toEqual(false);
-			expect(FileList.$fileList.find('.summary').length).toEqual(0);
+			expect(FileList.$el.find('.summary').hasClass('hidden')).toEqual(true);
 		});
 		it('hides headers, empty content message, and summary when list is empty and user has no creation permission', function(){
 			$('#permissions').val(0);
 			FileList.setFiles([]);
 			expect($('#filestable thead th').hasClass('hidden')).toEqual(true);
 			expect($('#emptycontent').hasClass('hidden')).toEqual(true);
-			expect(FileList.$fileList.find('.summary').length).toEqual(0);
+			expect(FileList.$el.find('.summary').hasClass('hidden')).toEqual(true);
 		});
 		it('calling findFileEl() can find existing file element', function() {
 			FileList.setFiles(testFiles);
@@ -642,7 +642,7 @@ describe('FileList tests', function() {
 			var query = url.substr(url.indexOf('?') + 1);
 			expect(OC.parseQueryString(query)).toEqual({'dir': '/subdir'});
 			fakeServer.respond();
-			expect($('#fileList tr:not(.summary)').length).toEqual(4);
+			expect($('#fileList tr').length).toEqual(4);
 			expect(FileList.findFileEl('One.txt').length).toEqual(1);
 		});
 		it('switches dir and fetches file list when calling changeDirectory()', function() {
diff --git a/apps/files/tests/js/filesummarySpec.js b/apps/files/tests/js/filesummarySpec.js
new file mode 100644
index 0000000000000000000000000000000000000000..c493700de3801d2d361092bdec90d8519c6c413e
--- /dev/null
+++ b/apps/files/tests/js/filesummarySpec.js
@@ -0,0 +1,87 @@
+/**
+* ownCloud
+*
+* @author Vincent Petry
+* @copyright 2014 Vincent Petry <pvince81@owncloud.com>
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+* License as published by the Free Software Foundation; either
+* version 3 of the License, or any later version.
+*
+* This library is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+*
+* You should have received a copy of the GNU Affero General Public
+* License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+/* global FileSummary */
+describe('FileSummary tests', function() {
+	var $container;
+
+	beforeEach(function() {
+		$container = $('<table><tr></tr></table>').find('tr');
+	});
+	afterEach(function() {
+		$container = null;
+	});
+
+	it('renders summary as text', function() {
+		var s = new FileSummary($container);
+		s.setSummary({
+			totalDirs: 5,
+			totalFiles: 2,
+			totalSize: 256000
+		});
+		expect($container.hasClass('hidden')).toEqual(false);
+		expect($container.find('.info').text()).toEqual('5 folders and 2 files');
+		expect($container.find('.filesize').text()).toEqual('250 kB');
+	});
+	it('hides summary when no files or folders', function() {
+		var s = new FileSummary($container);
+		s.setSummary({
+			totalDirs: 0,
+			totalFiles: 0,
+			totalSize: 0
+		});
+		expect($container.hasClass('hidden')).toEqual(true);
+	});
+	it('increases summary when adding files', function() {
+		var s = new FileSummary($container);
+		s.setSummary({
+			totalDirs: 5,
+			totalFiles: 2,
+			totalSize: 256000
+		});
+		s.add({type: 'file', size: 256000});
+		s.add({type: 'dir', size: 100});
+		s.update();
+		expect($container.hasClass('hidden')).toEqual(false);
+		expect($container.find('.info').text()).toEqual('6 folders and 3 files');
+		expect($container.find('.filesize').text()).toEqual('500 kB');
+		expect(s.summary.totalDirs).toEqual(6);
+		expect(s.summary.totalFiles).toEqual(3);
+		expect(s.summary.totalSize).toEqual(512100);
+	});
+	it('decreases summary when removing files', function() {
+		var s = new FileSummary($container);
+		s.setSummary({
+			totalDirs: 5,
+			totalFiles: 2,
+			totalSize: 256000
+		});
+		s.remove({type: 'file', size: 128000});
+		s.remove({type: 'dir', size: 100});
+		s.update();
+		expect($container.hasClass('hidden')).toEqual(false);
+		expect($container.find('.info').text()).toEqual('4 folders and 1 file');
+		expect($container.find('.filesize').text()).toEqual('125 kB');
+		expect(s.summary.totalDirs).toEqual(4);
+		expect(s.summary.totalFiles).toEqual(1);
+		expect(s.summary.totalSize).toEqual(127900);
+	});
+});
diff --git a/apps/files_sharing/public.php b/apps/files_sharing/public.php
index ce51eca6ddb8928bfbfbbec9f99cd1abe291d131..3abcbf291ff653a8b1a55404ef3cc465c2629598 100644
--- a/apps/files_sharing/public.php
+++ b/apps/files_sharing/public.php
@@ -138,6 +138,7 @@ if (isset($path)) {
 
 			OCP\Util::addStyle('files', 'files');
 			OCP\Util::addStyle('files', 'upload');
+			OCP\Util::addScript('files', 'filesummary');
 			OCP\Util::addScript('files', 'breadcrumb');
 			OCP\Util::addScript('files', 'files');
 			OCP\Util::addScript('files', 'filelist');
diff --git a/apps/files_trashbin/index.php b/apps/files_trashbin/index.php
index e63fe1e4188e46e2052818e53b3873d5b6b8d1a0..6e6a8a38307faa5dc7ce5da6df14b0e32267697b 100644
--- a/apps/files_trashbin/index.php
+++ b/apps/files_trashbin/index.php
@@ -11,6 +11,7 @@ $tmpl = new OCP\Template('files_trashbin', 'index', 'user');
 
 OCP\Util::addStyle('files', 'files');
 OCP\Util::addStyle('files_trashbin', 'trash');
+OCP\Util::addScript('files', 'filesummary');
 OCP\Util::addScript('files', 'breadcrumb');
 OCP\Util::addScript('files', 'filelist');
 // filelist overrides
diff --git a/apps/files_trashbin/js/trash.js b/apps/files_trashbin/js/trash.js
index f7724d07d2b86ee20c119216ebef49abfb4d5191..4ed5ba1c76e22042dce67f89a69e65694ffb40c7 100644
--- a/apps/files_trashbin/js/trash.js
+++ b/apps/files_trashbin/js/trash.js
@@ -34,10 +34,12 @@ $(document).ready(function() {
 		}
 
 		var files = result.data.success;
+		var $el;
 		for (var i = 0; i < files.length; i++) {
-			FileList.remove(OC.basename(files[i].filename), {updateSummary: false});
+			$el = FileList.remove(OC.basename(files[i].filename), {updateSummary: false});
+			FileList.fileSummary.remove({type: $el.attr('data-type'), size: $el.attr('data-size')});
 		}
-		FileList.updateFileSummary();
+		FileList.fileSummary.update();
 		FileList.updateEmptyContent();
 		enableActions();
 	}
diff --git a/apps/files_trashbin/templates/index.php b/apps/files_trashbin/templates/index.php
index b6c61c9b1c3ea136b2727fda463d8e385a505668..cb64ae9eafafd1d44924568b5dbc0dbc396b3190 100644
--- a/apps/files_trashbin/templates/index.php
+++ b/apps/files_trashbin/templates/index.php
@@ -40,4 +40,6 @@
 	</thead>
 	<tbody id="fileList">
 	</tbody>
+	<tfoot>
+	</tfoot>
 </table>
diff --git a/core/js/js.js b/core/js/js.js
index 0aa8d12b3d67bf90e4408e2f47ca1356ca3363a6..325be6cdc53dd942cdd0faf7092575ec1ac92433 100644
--- a/core/js/js.js
+++ b/core/js/js.js
@@ -1253,6 +1253,9 @@ function relative_modified_date(timestamp) {
  *  @todo Write documentation
  */
 OC.Util = {
+	// TODO: remove original functions from global namespace
+	humanFileSize: humanFileSize,
+	formatDate: formatDate,
 	/**
 	 * Returns whether the browser supports SVG
 	 * @return {boolean} true if the browser supports SVG, false otherwise
diff --git a/core/js/tests/specs/coreSpec.js b/core/js/tests/specs/coreSpec.js
index ccd9f7a128898003adb606154b2d88a028ce3724..65f768fbc5173416387e57e0dfdc234e8632b02d 100644
--- a/core/js/tests/specs/coreSpec.js
+++ b/core/js/tests/specs/coreSpec.js
@@ -474,5 +474,22 @@ describe('Core base tests', function() {
 			);
 		});
 	});
+	describe('Util', function() {
+		describe('humanFileSize', function() {
+			it('renders file sizes with the correct unit', function() {
+				var data = [
+					[0, '0 B'],
+					[125, '125 B'],
+					[128000, '125 kB'],
+					[128000000, '122.1 MB'],
+					[128000000000, '119.2 GB'],
+					[128000000000000, '116.4 TB']
+				];
+				for (var i = 0; i < data.length; i++) {
+					expect(OC.Util.humanFileSize(data[i][0])).toEqual(data[i][1]);
+				}
+			});
+		});
+	});
 });