From 0be9de5df558232e12e2f582af5d08e1f488ba90 Mon Sep 17 00:00:00 2001
From: Vincent Petry <pvince81@owncloud.com>
Date: Mon, 28 Oct 2013 20:22:06 +0100
Subject: [PATCH] Files, trashbin, public apps use ajax/JSON for the file list

Files app:

- removed file list template, now rendering list from JSON response
- FileList.addFile/addDir is now FileList.add() and takes a JS map with all required
  arguments instead of having a long number of function arguments
- added unit tests for many FileList operations
- fixed newfile.php, newfolder.php and rename.php to return the file's
  full JSON on success
- removed obsolete/unused undo code
- removed download_url / loading options, now using
  Files.getDownloadUrl() for that
- server side now uses Helper::getFileInfo() to prepare file JSON response
- previews are now client-side only

Breadcrumbs are now JS only:

- Added BreadCrumb class to handle breadcrumb rendering and events
- Added unit test for BreadCrumb class
- Moved all relevant JS functions to the BreadCrumb class

Public page now uses ajax to load the file list:

- Added Helper class in sharing app to make it easier to authenticate
  and retrieve the file's real path
- Added ajax/list.php to retrieve the file list
- Fixed FileActions and FileList to work with the ajax list

Core:

- Fixed file picker dialog to use the same list format as files app
---
 apps/files/ajax/list.php                      |  20 +-
 apps/files/ajax/newfile.php                   |  16 +-
 apps/files/ajax/newfolder.php                 |   4 +-
 apps/files/ajax/rawlist.php                   |  54 --
 apps/files/ajax/upload.php                    |  47 +-
 apps/files/index.php                          |  41 +-
 apps/files/js/breadcrumb.js                   | 242 ++++++
 apps/files/js/file-upload.js                  |  61 +-
 apps/files/js/fileactions.js                  |   4 +-
 apps/files/js/filelist.js                     | 740 ++++++++--------
 apps/files/js/files.js                        | 216 ++---
 apps/files/lib/app.php                        |  20 +-
 apps/files/lib/helper.php                     |  94 ++-
 apps/files/templates/index.php                |  15 +-
 apps/files/templates/part.breadcrumb.php      |  17 -
 apps/files/templates/part.list.php            |  67 --
 apps/files/tests/ajax_rename.php              |  30 +-
 apps/files/tests/js/breadcrumbSpec.js         | 248 ++++++
 apps/files/tests/js/fileactionsSpec.js        |  58 +-
 apps/files/tests/js/filelistSpec.js           | 798 +++++++++++++++++-
 apps/files_sharing/ajax/list.php              |  91 ++
 apps/files_sharing/css/public.css             |   5 +
 apps/files_sharing/js/public.js               |  69 +-
 apps/files_sharing/js/share.js                |  24 +-
 apps/files_sharing/lib/helper.php             | 114 +++
 apps/files_sharing/public.php                 |  90 +-
 apps/files_trashbin/ajax/list.php             |  29 +-
 apps/files_trashbin/ajax/preview.php          |   2 +-
 apps/files_trashbin/ajax/undelete.php         |   2 +-
 apps/files_trashbin/css/trash.css             |   3 +-
 apps/files_trashbin/index.php                 |  42 +-
 .../js/disableDefaultActions.js               | Bin 129 -> 102 bytes
 apps/files_trashbin/js/filelist.js            | Bin 1641 -> 2298 bytes
 apps/files_trashbin/js/trash.js               | Bin 7106 -> 7829 bytes
 apps/files_trashbin/lib/helper.php            |  83 +-
 apps/files_trashbin/templates/index.php       |   9 +-
 .../templates/part.breadcrumb.php             |  19 -
 apps/files_trashbin/templates/part.list.php   |  79 --
 core/css/styles.css                           |   7 +
 core/js/js.js                                 |   1 +
 core/js/oc-dialogs.js                         |  15 +-
 41 files changed, 2210 insertions(+), 1266 deletions(-)
 delete mode 100644 apps/files/ajax/rawlist.php
 create mode 100644 apps/files/js/breadcrumb.js
 delete mode 100644 apps/files/templates/part.breadcrumb.php
 delete mode 100644 apps/files/templates/part.list.php
 create mode 100644 apps/files/tests/js/breadcrumbSpec.js
 create mode 100644 apps/files_sharing/ajax/list.php
 create mode 100644 apps/files_sharing/lib/helper.php
 delete mode 100644 apps/files_trashbin/templates/part.breadcrumb.php
 delete mode 100644 apps/files_trashbin/templates/part.list.php

diff --git a/apps/files/ajax/list.php b/apps/files/ajax/list.php
index 3bb35579d5f..0ffae22ec31 100644
--- a/apps/files/ajax/list.php
+++ b/apps/files/ajax/list.php
@@ -12,32 +12,16 @@ if (!$dirInfo->getType() === 'dir') {
 	exit();
 }
 
-$doBreadcrumb = isset($_GET['breadcrumb']);
 $data = array();
 $baseUrl = OCP\Util::linkTo('files', 'index.php') . '?dir=';
 
 $permissions = $dirInfo->getPermissions();
 
-// Make breadcrumb
-if($doBreadcrumb) {
-	$breadcrumb = \OCA\Files\Helper::makeBreadcrumb($dir);
-
-	$breadcrumbNav = new OCP\Template('files', 'part.breadcrumb', '');
-	$breadcrumbNav->assign('breadcrumb', $breadcrumb, false);
-	$breadcrumbNav->assign('baseURL', $baseUrl);
-
-	$data['breadcrumb'] = $breadcrumbNav->fetchPage();
-}
-
 // make filelist
 $files = \OCA\Files\Helper::getFiles($dir);
 
-$list = new OCP\Template("files", "part.list", "");
-$list->assign('files', $files, false);
-$list->assign('baseURL', $baseUrl, false);
-$list->assign('downloadURL', OCP\Util::linkToRoute('download', array('file' => '/')));
-$list->assign('isPublic', false);
-$data['files'] = $list->fetchPage();
+$data['directory'] = $dir;
+$data['files'] = \OCA\Files\Helper::formatFileInfos($files);
 $data['permissions'] = $permissions;
 
 OCP\JSON::success(array('data' => $data));
diff --git a/apps/files/ajax/newfile.php b/apps/files/ajax/newfile.php
index 1234cf11394..7d6be59beab 100644
--- a/apps/files/ajax/newfile.php
+++ b/apps/files/ajax/newfile.php
@@ -112,9 +112,8 @@ if($source) {
 	}
 	if($result) {
 		$meta = \OC\Files\Filesystem::getFileInfo($target);
-		$mime=$meta['mimetype'];
-		$id = $meta['fileid'];
-		$eventSource->send('success', array('mime' => $mime, 'size' => \OC\Files\Filesystem::filesize($target), 'id' => $id, 'etag' => $meta['etag']));
+		$data = \OCA\Files\Helper::formatFileInfo($meta);
+		$eventSource->send('success', $data);
 	} else {
 		$eventSource->send('error', array('message' => $l10n->t('Error while downloading %s to %s', array($source, $target))));
 	}
@@ -139,16 +138,7 @@ if($source) {
 
 	if($success) {
 		$meta = \OC\Files\Filesystem::getFileInfo($target);
-		$id = $meta['fileid'];
-		$mime = $meta['mimetype'];
-		$size = $meta['size'];
-		OCP\JSON::success(array('data' => array(
-			'id' => $id,
-			'mime' => $mime,
-			'size' => $size,
-			'content' => $content,
-			'etag' => $meta['etag'],
-		)));
+		OCP\JSON::success(array('data' => \OCA\Files\Helper::formatFileInfo($meta)));
 		exit();
 	}
 }
diff --git a/apps/files/ajax/newfolder.php b/apps/files/ajax/newfolder.php
index 032447460f3..89c241189d7 100644
--- a/apps/files/ajax/newfolder.php
+++ b/apps/files/ajax/newfolder.php
@@ -58,8 +58,8 @@ if(\OC\Files\Filesystem::mkdir($target)) {
 		$path = '/'.$foldername;
 	}
 	$meta = \OC\Files\Filesystem::getFileInfo($path);
-	$id = $meta['fileid'];
-	OCP\JSON::success(array('data' => array('id' => $id)));
+	$meta['type'] = 'dir'; // missing ?!
+	OCP\JSON::success(array('data' => \OCA\Files\Helper::formatFileInfo($meta)));
 	exit();
 }
 
diff --git a/apps/files/ajax/rawlist.php b/apps/files/ajax/rawlist.php
deleted file mode 100644
index f18bbffb74a..00000000000
--- a/apps/files/ajax/rawlist.php
+++ /dev/null
@@ -1,54 +0,0 @@
-<?php
-
-OCP\JSON::checkLoggedIn();
-\OC::$session->close();
-
-// Load the files
-$dir = isset($_GET['dir']) ? $_GET['dir'] : '';
-$mimetypes = isset($_GET['mimetypes']) ? json_decode($_GET['mimetypes'], true) : '';
-
-// Clean up duplicates from array and deal with non-array requests
-if (is_array($mimetypes)) {
-	$mimetypes = array_unique($mimetypes);
-} elseif (is_null($mimetypes)) {
-	$mimetypes = array($_GET['mimetypes']);
-}
-
-// make filelist
-$files = array();
-/**
- * @var \OCP\Files\FileInfo[] $files
- */
-// If a type other than directory is requested first load them.
-if ($mimetypes && !in_array('httpd/unix-directory', $mimetypes)) {
-	$files = array_merge($files, \OC\Files\Filesystem::getDirectoryContent($dir, 'httpd/unix-directory'));
-}
-
-if (is_array($mimetypes) && count($mimetypes)) {
-	foreach ($mimetypes as $mimetype) {
-		$files = array_merge($files, \OC\Files\Filesystem::getDirectoryContent($dir, $mimetype));
-	}
-} else {
-	$files = array_merge($files, \OC\Files\Filesystem::getDirectoryContent($dir));
-}
-// Sort by name
-usort($files, array('\OCA\Files\Helper', 'fileCmp'));
-
-$result = array();
-foreach ($files as $file) {
-	$fileData = array();
-	$fileData['directory'] = $dir;
-	$fileData['name'] = $file->getName();
-	$fileData['type'] = $file->getType();
-	$fileData['path'] = $file['path'];
-	$fileData['id'] = $file->getId();
-	$fileData['size'] = $file->getSize();
-	$fileData['mtime'] = $file->getMtime();
-	$fileData['mimetype'] = $file->getMimetype();
-	$fileData['isPreviewAvailable'] = \OC::$server->getPreviewManager()->isMimeSupported($file->getMimetype());
-	$fileData["date"] = OCP\Util::formatDate($file->getMtime());
-	$fileData['mimetype_icon'] = \OCA\Files\Helper::determineIcon($file);
-	$result[] = $fileData;
-}
-
-OC_JSON::success(array('data' => $result));
diff --git a/apps/files/ajax/upload.php b/apps/files/ajax/upload.php
index 4ed51c52775..b21a9dfba2e 100644
--- a/apps/files/ajax/upload.php
+++ b/apps/files/ajax/upload.php
@@ -20,6 +20,10 @@ if (empty($_POST['dirToken'])) {
 		die();
 	}
 } else {
+	// TODO: ideally this code should be in files_sharing/ajax/upload.php
+	// and the upload/file transfer code needs to be refactored into a utility method
+	// that could be used there
+
 	// return only read permissions for public upload
 	$allowedPermissions = OCP\PERMISSION_READ;
 	$public_directory = !empty($_POST['subdir']) ? $_POST['subdir'] : '/';
@@ -141,19 +145,14 @@ if (strpos($dir, '..') === false) {
 						$error = $l->t('The target folder has been moved or deleted.');
 						$errorCode = 'targetnotfound';
 					} else {
-						$result[] = array('status' => 'success',
-							'mime' => $meta['mimetype'],
-							'mtime' => $meta['mtime'],
-							'size' => $meta['size'],
-							'id' => $meta['fileid'],
-							'name' => basename($target),
-							'etag' => $meta['etag'],
-							'originalname' => $files['tmp_name'][$i],
-							'uploadMaxFilesize' => $maxUploadFileSize,
-							'maxHumanFilesize' => $maxHumanFileSize,
-							'permissions' => $meta['permissions'] & $allowedPermissions,
-							'directory' => $directory,
-						);
+						$data = \OCA\Files\Helper::formatFileInfo($meta);
+						$data['status'] = 'success';
+						$data['originalname'] = $files['tmp_name'][$i];
+						$data['uploadMaxFilesize'] = $maxUploadFileSize;
+						$data['maxHumanFilesize'] = $maxHumanFileSize;
+						$data['permissions'] = $meta['permissions'] & $allowedPermissions;
+						$data['directory'] = $directory;
+						$result[] = $data;
 					}
 
 				} else {
@@ -169,19 +168,15 @@ if (strpos($dir, '..') === false) {
 			if ($meta === false) {
 				$error = $l->t('Upload failed. Could not get file info.');
 			} else {
-				$result[] = array('status' => 'existserror',
-					'mime' => $meta['mimetype'],
-					'mtime' => $meta['mtime'],
-					'size' => $meta['size'],
-					'id' => $meta['fileid'],
-					'name' => basename($target),
-					'etag' => $meta['etag'],
-					'originalname' => $files['tmp_name'][$i],
-					'uploadMaxFilesize' => $maxUploadFileSize,
-					'maxHumanFilesize' => $maxHumanFileSize,
-					'permissions' => $meta['permissions'] & $allowedPermissions,
-					'directory' => $directory,
-				);
+				$data = \OCA\Files\Helper::formatFileInfo($meta);
+				$data['permissions'] = $data['permissions'] & $allowedPermissions;
+				$data['status'] = 'existserror';
+				$data['originalname'] = $files['tmp_name'][$i];
+				$data['uploadMaxFilesize'] = $maxUploadFileSize;
+				$data['maxHumanFilesize'] = $maxHumanFileSize;
+				$data['permissions'] = $meta['permissions'] & $allowedPermissions;
+				$data['directory'] = $directory;
+				$result[] = $data;
 			}
 		}
 	}
diff --git a/apps/files/index.php b/apps/files/index.php
index 4d765b69e41..a575e02348a 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', 'breadcrumb');
 OCP\Util::addscript('files', 'filelist');
 
 OCP\App::setActiveNavigationEntry('files_index');
@@ -60,37 +61,12 @@ if ($isIE8 && isset($_GET['dir'])){
 	exit();
 }
 
-$ajaxLoad = false;
-$files = array();
 $user = OC_User::getUser();
-if ($isIE8){
-    // after the redirect above, the URL will have a format
-    // like "files#?dir=path" which means that no path was given
-    // (dir is not set). In that specific case, we don't return any
-    // files because the client will take care of switching the dir
-    // to the one from the hash, then ajax-load the initial file list
-    $files = array();
-    $ajaxLoad = true;
-}
-else{
-    $files = \OCA\Files\Helper::getFiles($dir);
-}
 
 $config = \OC::$server->getConfig();
 
-// Make breadcrumb
-$breadcrumb = \OCA\Files\Helper::makeBreadcrumb($dir);
-
-// make breadcrumb und filelist markup
-$list = new OCP\Template('files', 'part.list', '');
-$list->assign('files', $files);
-$list->assign('baseURL', OCP\Util::linkTo('files', 'index.php') . '?dir=');
-$list->assign('downloadURL', OCP\Util::linkToRoute('download', array('file' => '/')));
-$list->assign('isPublic', false);
-$breadcrumbNav = new OCP\Template('files', 'part.breadcrumb', '');
-$breadcrumbNav->assign('breadcrumb', $breadcrumb);
-$breadcrumbNav->assign('baseURL', OCP\Util::linkTo('files', 'index.php') . '?dir=');
-
+// needed for share init, permissions will be reloaded
+// anyway with ajax load
 $permissions = $dirInfo->getPermissions();
 
 // information about storage capacities
@@ -112,20 +88,12 @@ if ($trashEnabled) {
     $trashEmpty = \OCA\Files_Trashbin\Trashbin::isEmpty($user);
 }
 
-$isCreatable = \OC\Files\Filesystem::isCreatable($dir . '/');
-$fileHeader = (!isset($files) or count($files) > 0);
-$emptyContent = ($isCreatable and !$fileHeader) or $ajaxLoad;
-
 OCP\Util::addscript('files', 'fileactions');
 OCP\Util::addscript('files', 'files');
 OCP\Util::addscript('files', 'keyboardshortcuts');
 $tmpl = new OCP\Template('files', 'index', 'user');
-$tmpl->assign('fileList', $list->fetchPage());
-$tmpl->assign('breadcrumb', $breadcrumbNav->fetchPage());
 $tmpl->assign('dir', $dir);
-$tmpl->assign('isCreatable', $isCreatable);
 $tmpl->assign('permissions', $permissions);
-$tmpl->assign('files', $files);
 $tmpl->assign('trash', $trashEnabled);
 $tmpl->assign('trashEmpty', $trashEmpty);
 $tmpl->assign('uploadMaxFilesize', $maxUploadFilesize); // minimium of freeSpace and uploadLimit
@@ -141,8 +109,5 @@ $tmpl->assign("mailNotificationEnabled", $config->getAppValue('core', 'shareapi_
 $tmpl->assign("allowShareWithLink", $config->getAppValue('core', 'shareapi_allow_links', 'yes'));
 $tmpl->assign("encryptionInitStatus", $encryptionInitStatus);
 $tmpl->assign('disableSharing', false);
-$tmpl->assign('ajaxLoad', $ajaxLoad);
-$tmpl->assign('emptyContent', $emptyContent);
-$tmpl->assign('fileHeader', $fileHeader);
 
 $tmpl->printPage();
diff --git a/apps/files/js/breadcrumb.js b/apps/files/js/breadcrumb.js
new file mode 100644
index 00000000000..21010c7dc11
--- /dev/null
+++ b/apps/files/js/breadcrumb.js
@@ -0,0 +1,242 @@
+/**
+* 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 */
+/* global SVGSupport, replaceSVG */
+(function() {
+	/**
+	 * Creates an breadcrumb element in the given container
+	 */
+	var BreadCrumb = function(options){
+		this.$el = $('<div class="breadcrumb"></div>');
+		options = options || {};
+		if (options.onClick) {
+			this.onClick = options.onClick;
+		}
+		if (options.onDrop) {
+			this.onDrop = options.onDrop;
+		}
+		if (options.getCrumbUrl) {
+			this.getCrumbUrl = options.getCrumbUrl;
+		}
+	};
+	BreadCrumb.prototype = {
+		$el: null,
+		dir: null,
+
+		lastWidth: 0,
+		hiddenBreadcrumbs: 0,
+		totalWidth: 0,
+		breadcrumbs: [],
+		onClick: null,
+		onDrop: null,
+
+		/**
+		 * Sets the directory to be displayed as breadcrumb.
+		 * This will re-render the breadcrumb.
+		 * @param dir path to be displayed as breadcrumb
+		 */
+		setDirectory: function(dir) {
+			dir = dir || '/';
+			if (dir !== this.dir) {
+				this.dir = dir;
+				this.render();
+			}
+		},
+
+		/**
+		 * Returns the full URL to the given directory
+		 * @param part crumb data as map
+		 * @param index crumb index
+		 * @return full URL
+		 */
+		getCrumbUrl: function(part, index) {
+			return '#';
+		},
+
+		/**
+		 * Renders the breadcrumb elements
+		 */
+		render: function() {
+			var parts = this._makeCrumbs(this.dir || '/');
+			var $crumb;
+			this.$el.empty();
+			this.breadcrumbs = [];
+
+			for (var i = 0; i < parts.length; i++) {
+				var part = parts[i];
+				var $image;
+				var $link = $('<a></a>').attr('href', this.getCrumbUrl(part, i));
+				$link.text(part.name);
+				$crumb = $('<div class="crumb"></div>');
+				$crumb.append($link);
+				$crumb.attr('data-dir', part.dir);
+
+				if (part.img) {
+					$image = $('<img class="svg"></img>');
+					$image.attr('src', part.img);
+					$link.append($image);
+				}
+				this.breadcrumbs.push($crumb);
+				this.$el.append($crumb);
+				if (this.onClick) {
+					$crumb.on('click', this.onClick);
+				}
+			}
+			$crumb.addClass('last');
+
+			// in case svg is not supported by the browser we need to execute the fallback mechanism
+			if (!SVGSupport()) {
+				replaceSVG();
+			}
+
+			// setup drag and drop
+			if (this.onDrop) {
+				this.$el.find('.crumb:not(.last)').droppable({
+					drop: this.onDrop,
+					tolerance: 'pointer'
+				});
+			}
+
+			this._updateTotalWidth();
+			this.resize($(window).width(), true);
+		},
+
+		/**
+		 * Makes a breadcrumb structure based on the given path
+		 * @param dir path to split into a breadcrumb structure
+		 * @return array of map {dir: path, name: displayName}
+		 */
+		_makeCrumbs: function(dir) {
+			var crumbs = [];
+			var pathToHere = '';
+			// trim leading and trailing slashes
+			dir = dir.replace(/^\/+|\/+$/g, '');
+			var parts = dir.split('/');
+			if (dir === '') {
+				parts = [];
+			}
+			// root part
+			crumbs.push({
+				dir: '/',
+				name: '',
+				img: OC.imagePath('core', 'places/home.svg')
+			});
+			for (var i = 0; i < parts.length; i++) {
+				var part = parts[i];
+				pathToHere = pathToHere + '/' + part;
+				crumbs.push({
+					dir: pathToHere,
+					name: part
+				});
+			}
+			return crumbs;
+		},
+
+		_updateTotalWidth: function () {
+			var self = this;
+
+			this.lastWidth = 0;
+
+			// initialize with some extra space
+			this.totalWidth = 64;
+			// FIXME: this class should not know about global elements
+			if ( $('#navigation').length ) {
+				this.totalWidth += $('#navigation').get(0).offsetWidth;
+			}
+			this.hiddenBreadcrumbs = 0;
+
+			for (var i = 0; i < this.breadcrumbs.length; i++ ) {
+				this.totalWidth += $(this.breadcrumbs[i]).get(0).offsetWidth;
+			}
+
+			$.each($('#controls .actions>div'), function(index, action) {
+				self.totalWidth += $(action).get(0).offsetWidth;
+			});
+
+		},
+
+		/**
+		 * Show/hide breadcrumbs to fit the given width
+		 */
+		resize: function (width, firstRun) {
+			var i, $crumb;
+
+			if (width === this.lastWidth) {
+				return;
+			}
+
+			// window was shrinked since last time or first run ?
+			if ((width < this.lastWidth || firstRun) && width < this.totalWidth) {
+				if (this.hiddenBreadcrumbs === 0 && this.breadcrumbs.length > 1) {
+					// start by hiding the first breadcrumb after home,
+					// that one will have extra three dots displayed
+					$crumb = this.breadcrumbs[1];
+					this.totalWidth -= $crumb.get(0).offsetWidth;
+					$crumb.find('a').addClass('hidden');
+					$crumb.append('<span class="ellipsis">...</span>');
+					this.totalWidth += $crumb.get(0).offsetWidth;
+					this.hiddenBreadcrumbs = 2;
+				}
+				i = this.hiddenBreadcrumbs;
+				// hide subsequent breadcrumbs if the space is still not enough
+				while (width < this.totalWidth && i > 1 && i < this.breadcrumbs.length - 1) {
+					$crumb = this.breadcrumbs[i];
+					this.totalWidth -= $crumb.get(0).offsetWidth;
+					$crumb.addClass('hidden');
+					this.hiddenBreadcrumbs = i;
+					i++;
+				}
+			// window is bigger than last time
+			} else if (width > this.lastWidth && this.hiddenBreadcrumbs > 0) {
+				i = this.hiddenBreadcrumbs;
+				while (width > this.totalWidth && i > 0) {
+					if (this.hiddenBreadcrumbs === 1) {
+						// special handling for last one as it has the three dots
+						$crumb = this.breadcrumbs[1];
+						if ($crumb) {
+							this.totalWidth -= $crumb.get(0).offsetWidth;
+							$crumb.find('.ellipsis').remove();
+							$crumb.find('a').removeClass('hidden');
+							this.totalWidth += $crumb.get(0).offsetWidth;
+						}
+					} else {
+						$crumb = this.breadcrumbs[i];
+						$crumb.removeClass('hidden');
+						this.totalWidth += $crumb.get(0).offsetWidth;
+						if (this.totalWidth > width) {
+							this.totalWidth -= $crumb.get(0).offsetWidth;
+							$crumb.addClass('hidden');
+							break;
+						}
+					}
+					i--;
+					this.hiddenBreadcrumbs = i;
+				}
+			}
+
+			this.lastWidth = width;
+		}
+	};
+
+	window.BreadCrumb = BreadCrumb;
+})();
+
diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js
index 371c83e742c..e5d1eacbd14 100644
--- a/apps/files/js/file-upload.js
+++ b/apps/files/js/file-upload.js
@@ -180,7 +180,7 @@ OC.Upload = {
 	},
 
 	init: function() {
-		if ( $('#file_upload_start').exists() && $('#file_upload_start').is(':visible')) {
+		if ( $('#file_upload_start').exists() ) {
 
 			var file_upload_param = {
 				dropZone: $('#content'), // restrict dropZone to content div
@@ -483,28 +483,6 @@ OC.Upload = {
 			$('#file_upload_start').attr('multiple', 'multiple');
 		}
 
-		//if the breadcrumb is to long, start by replacing foldernames with '...' except for the current folder
-		var crumb=$('div.crumb').first();
-		while($('div.controls').height() > 40 && crumb.next('div.crumb').length > 0) {
-			crumb.children('a').text('...');
-			crumb = crumb.next('div.crumb');
-		}
-		//if that isn't enough, start removing items from the breacrumb except for the current folder and it's parent
-		var crumb = $('div.crumb').first();
-		var next = crumb.next('div.crumb');
-		while($('div.controls').height()>40 && next.next('div.crumb').length > 0) {
-			crumb.remove();
-			crumb = next;
-			next = crumb.next('div.crumb');
-		}
-		//still not enough, start shorting down the current folder name
-		var crumb=$('div.crumb>a').last();
-		while($('div.controls').height() > 40 && crumb.text().length > 6) {
-			var text=crumb.text();
-			text = text.substr(0,text.length-6)+'...';
-			crumb.text(text);
-		}
-
 		$(document).click(function(ev) {
 			// do not close when clicking in the dropdown
 			if ($(ev.target).closest('#new').length){
@@ -617,21 +595,7 @@ OC.Upload = {
 								{dir:$('#dir').val(), filename:name},
 								function(result) {
 									if (result.status === 'success') {
-										var date = new Date();
-										// TODO: ideally addFile should be able to receive
-										// all attributes and set them automatically,
-										// and also auto-load the preview
-										var tr = FileList.addFile(name, 0, date, false, hidden);
-										tr.attr('data-size', result.data.size);
-										tr.attr('data-mime', result.data.mime);
-										tr.attr('data-id', result.data.id);
-										tr.attr('data-etag', result.data.etag);
-										tr.find('.filesize').text(humanFileSize(result.data.size));
-										var path = getPathForPreview(name);
-										Files.lazyLoadPreview(path, result.data.mime, function(previewpath) {
-											tr.find('td.filename').attr('style','background-image:url('+previewpath+')');
-										}, null, null, result.data.etag);
-										FileActions.display(tr.find('td.filename'), true);
+										FileList.add(result.data, {hidden: hidden, insert: true});
 									} else {
 										OC.dialogs.alert(result.data.message, t('core', 'Could not create file'));
 									}
@@ -644,10 +608,7 @@ OC.Upload = {
 								{dir:$('#dir').val(), foldername:name},
 								function(result) {
 									if (result.status === 'success') {
-										var date=new Date();
-										FileList.addDir(name, 0, date, hidden);
-										var tr = FileList.findFileEl(name);
-										tr.attr('data-id', result.data.id);
+										FileList.add(result.data, {hidden: hidden, insert: true});
 									} else {
 										OC.dialogs.alert(result.data.message, t('core', 'Could not create folder'));
 									}
@@ -682,20 +643,10 @@ OC.Upload = {
 								}
 							});
 							eventSource.listen('success',function(data) {
-								var mime = data.mime;
-								var size = data.size;
-								var id = data.id;
+								var file = data;
 								$('#uploadprogressbar').fadeOut();
-								var date = new Date();
-								FileList.addFile(localName, size, date, false, hidden);
-								var tr = FileList.findFileEl(localName);
-								tr.data('mime', mime).data('id', id);
-								tr.attr('data-id', id);
-								var path = $('#dir').val()+'/'+localName;
-								Files.lazyLoadPreview(path, mime, function(previewpath) {
-									tr.find('td.filename').attr('style', 'background-image:url('+previewpath+')');
-								}, null, null, data.etag);
-								FileActions.display(tr.find('td.filename'), true);
+
+								FileList.add(file, {hidden: hidden, insert: true});
 							});
 							eventSource.listen('error',function(error) {
 								$('#uploadprogressbar').fadeOut();
diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js
index 732690f047a..631aebea954 100644
--- a/apps/files/js/fileactions.js
+++ b/apps/files/js/fileactions.js
@@ -8,7 +8,7 @@
  *
  */
 
-/* global OC, FileList	*/
+/* global OC, FileList, Files */
 /* global trashBinApp */
 var FileActions = {
 	actions: {},
@@ -214,7 +214,7 @@ $(document).ready(function () {
 		FileActions.register(downloadScope, 'Download', OC.PERMISSION_READ, function () {
 			return OC.imagePath('core', 'actions/download');
 		}, function (filename) {
-			var url = FileList.getDownloadUrl(filename);
+			var url = Files.getDownloadUrl(filename);
 			if (url) {
 				OC.redirect(url);
 			}
diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js
index cda4e823a73..509929d0e55 100644
--- a/apps/files/js/filelist.js
+++ b/apps/files/js/filelist.js
@@ -8,17 +8,104 @@
  *
  */
 
-/* global OC, t, n, FileList, FileActions, Files */
-/* global procesSelection, dragOptions, SVGSupport, replaceSVG */
-window.FileList={
+/* global OC, t, n, FileList, FileActions, Files, BreadCrumb */
+/* global procesSelection, dragOptions, SVGSupport */
+window.FileList = {
 	appName: t('files', 'Files'),
+	isEmpty: true,
 	useUndo:true,
-	postProcessList: function() {
-		$('#fileList tr').each(function() {
-			//little hack to set unescape filenames in attribute
-			$(this).attr('data-file',decodeURIComponent($(this).attr('data-file')));
+	$el: $('#filestable'),
+	$fileList: $('#fileList'),
+	breadcrumb: null,
+	initialized: false,
+
+	/**
+	 * Initialize the file list and its components
+	 */
+	initialize: function() {
+		var self = this;
+		if (this.initialized) {
+			return;
+		}
+
+		// TODO: FileList should not know about global elements
+		this.$el = $('#filestable');
+		this.$fileList = $('#fileList');
+
+		this.breadcrumb = new BreadCrumb({
+			onClick: this._onClickBreadCrumb,
+			onDrop: this._onDropOnBreadCrumb,
+			getCrumbUrl: function(part, index) {
+				return self.linkTo(part.dir);
+			}
+		});
+
+		$('#controls').prepend(this.breadcrumb.$el);
+
+		$(window).resize(function() {
+			// TODO: debounce this ?
+			var width = $(this).width();
+			FileList.breadcrumb.resize(width, false);
+		});
+	},
+
+	/**
+	 * Event handler when clicking on a bread crumb
+	 */
+	_onClickBreadCrumb: function(e) {
+		var $el = $(e.target).closest('.crumb'),
+			$targetDir = $el.data('dir');
+
+		if ($targetDir !== undefined) {
+			e.preventDefault();
+			FileList.changeDirectory($targetDir);
+		}
+	},
+
+	/**
+	 * Event handler when dropping on a breadcrumb
+	 */
+	_onDropOnBreadCrumb: function( event, ui ) {
+		var target=$(this).data('dir');
+		var dir = FileList.getCurrentDirectory();
+		while(dir.substr(0,1) === '/') {//remove extra leading /'s
+			dir=dir.substr(1);
+		}
+		dir = '/' + dir;
+		if (dir.substr(-1,1) !== '/') {
+			dir = dir + '/';
+		}
+		if (target === dir || target+'/' === dir) {
+			return;
+		}
+		var files = ui.helper.find('tr');
+		$(files).each(function(i,row) {
+			var dir = $(row).data('dir');
+			var file = $(row).data('filename');
+			//slapdash selector, tracking down our original element that the clone budded off of.
+			var origin = $('tr[data-id=' + $(row).data('origin') + ']');
+			var td = origin.children('td.filename');
+			var oldBackgroundImage = td.css('background-image');
+			td.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')');
+			$.post(OC.filePath('files', 'ajax', 'move.php'), { dir: dir, file: file, target: target }, function(result) {
+				if (result) {
+					if (result.status === 'success') {
+						FileList.remove(file);
+						procesSelection();
+						$('#notification').hide();
+					} else {
+						$('#notification').hide();
+						$('#notification').text(result.data.message);
+						$('#notification').fadeIn();
+					}
+				} else {
+					OC.dialogs.alert(t('files', 'Error moving file'), t('files', 'Error'));
+				}
+				td.css('background-image', oldBackgroundImage);
+			});
 		});
 	},
+
 	/**
 	 * Sets a new page title
 	 */
@@ -36,64 +123,133 @@ window.FileList={
 	},
 	/**
 	 * Returns the tr element for a given file name
+	 * @param fileName file name
 	 */
 	findFileEl: function(fileName){
 		// use filterAttr to avoid escaping issues
-		return $('#fileList tr').filterAttr('data-file', fileName);
+		return this.$fileList.find('tr').filterAttr('data-file', fileName);
 	},
-	update:function(fileListHtml) {
-		var $fileList = $('#fileList');
-		$fileList.empty().html(fileListHtml);
-		FileList.updateEmptyContent();
-		$fileList.find('tr').each(function () {
-			FileActions.display($(this).children('td.filename'));
-		});
-		$fileList.trigger(jQuery.Event("fileActionsReady"));
-		FileList.postProcessList();
+	/**
+	 * Sets the files to be displayed in the list.
+	 * This operation will rerender the list and update the summary.
+	 * @param filesArray array of file data (map)
+	 */
+	setFiles:function(filesArray) {
+		// detach to make adding multiple rows faster
+		this.$fileList.detach();
+
+		this.$fileList.empty();
+
+		this.isEmpty = filesArray.length === 0;
+		for (var i = 0; i < filesArray.length; i++) {
+			this.add(filesArray[i], {updateSummary: false});
+		}
+
+		this.$el.find('thead').after(this.$fileList);
+
+		this.updateEmptyContent();
+		this.$fileList.trigger(jQuery.Event("fileActionsReady"));
 		// "Files" might not be loaded in extending apps
 		if (window.Files) {
 			Files.setupDragAndDrop();
 		}
-		FileList.updateFileSummary();
+		this.updateFileSummary();
 		procesSelection();
-
 		$(window).scrollTop(0);
-		$fileList.trigger(jQuery.Event("updated"));
+
+		this.$fileList.trigger(jQuery.Event("updated"));
+	},
+	/**
+	 * If SVG is not supported, replaces the given images's extension
+	 * from ".svg" to ".png".
+	 * If SVG is supported, return the image path as is.
+	 * @param icon image path
+	 * @return fixed image path
+	 */
+	_replaceSVG: function(icon) {
+		if (!SVGSupport()) {
+			var i = icon.lastIndexOf('.svg');
+			if (i >= 0) {
+				icon = icon.substr(0, i) + '.png' + icon.substr(i+4);
+			}
+		}
+		return icon;
 	},
-	createRow:function(type, name, iconurl, linktarget, size, lastModified, permissions) {
-		var td, simpleSize, basename, extension;
+	/**
+	 * Creates a new table row element using the given file data.
+	 * @param fileData map of file attributes
+	 * @param options map of attribute "loading" whether the entry is currently loading
+	 * @return new tr element (not appended to the table)
+	 */
+	_createRow: function(fileData, options) {
+		var td, simpleSize, basename, extension, sizeColor,
+			icon = FileList._replaceSVG(fileData.icon),
+			name = fileData.name,
+			type = fileData.type || 'file',
+			mtime = parseInt(fileData.mtime, 10) || new Date().getTime(),
+			mime = fileData.mimetype,
+			linkUrl;
+		options = options || {};
+
+		if (type === 'dir') {
+			mime = mime || 'httpd/unix-directory';
+		}
 		//containing tr
 		var tr = $('<tr></tr>').attr({
+			"data-id" : fileData.id,
 			"data-type": type,
-			"data-size": size,
+			"data-size": fileData.size,
 			"data-file": name,
-			"data-permissions": permissions
+			"data-mime": mime,
+			"data-mtime": mtime,
+			"data-etag": fileData.etag,
+			"data-permissions": fileData.permissions || this.getDirectoryPermissions()
 		});
+
+		if (type === 'dir') {
+			// use default folder icon
+			icon = icon || OC.imagePath('core', 'filetypes/folder');
+		}
+		else {
+			icon = icon || OC.imagePath('core', 'filetypes/file');
+		}
+
 		// filename td
 		td = $('<td></td>').attr({
-			"class": "filename svg",
-			"style": 'background-image:url('+iconurl+'); background-size: 32px;'
+			"class": "filename",
+			"style": 'background-image:url(' + icon + '); background-size: 32px;'
 		});
-		var rand = Math.random().toString(16).slice(2);
-		td.append('<input id="select-'+rand+'" type="checkbox" /><label for="select-'+rand+'"></label>');
+
+		// linkUrl
+		if (type === 'dir') {
+			linkUrl = FileList.linkTo(FileList.getCurrentDirectory() + '/' + name);
+		}
+		else {
+			linkUrl = Files.getDownloadUrl(name, FileList.getCurrentDirectory());
+		}
+		td.append('<input id="select-' + fileData.id + '" type="checkbox" /><label for="select-' + fileData.id + '"></label>');
 		var link_elem = $('<a></a>').attr({
 			"class": "name",
-			"href": linktarget
+			"href": linkUrl
 		});
-		//split extension from filename for non dirs
+
+		// from here work on the display name
+		name = fileData.displayName || name;
+
+		// split extension from filename for non dirs
 		if (type !== 'dir' && name.indexOf('.') !== -1) {
-			basename=name.substr(0,name.lastIndexOf('.'));
-			extension=name.substr(name.lastIndexOf('.'));
+			basename = name.substr(0, name.lastIndexOf('.'));
+			extension = name.substr(name.lastIndexOf('.'));
 		} else {
-			basename=name;
-			extension=false;
+			basename = name;
+			extension = false;
 		}
 		var name_span=$('<span></span>').addClass('nametext').text(basename);
 		link_elem.append(name_span);
 		if (extension) {
 			name_span.append($('<span></span>').addClass('extension').text(extension));
 		}
-		//dirs can show the number of uploaded files
+		// dirs can show the number of uploaded files
 		if (type === 'dir') {
 			link_elem.append($('<span></span>').attr({
 				'class': 'uploadtext',
@@ -103,98 +259,122 @@ window.FileList={
 		td.append(link_elem);
 		tr.append(td);
 
-		//size column
-		if (size !== t('files', 'Pending')) {
-			simpleSize = humanFileSize(size);
+		// size column
+		if (typeof(fileData.size) !== 'undefined' && fileData.size >= 0) {
+			simpleSize = humanFileSize(parseInt(fileData.size, 10));
+			sizeColor = Math.round(160-Math.pow((fileData.size/(1024*1024)),2));
 		} else {
-			simpleSize=t('files', 'Pending');
+			simpleSize = t('files', 'Pending');
 		}
-		var sizeColor = Math.round(160-Math.pow((size/(1024*1024)),2));
-		var lastModifiedTime = Math.round(lastModified.getTime() / 1000);
+		var lastModifiedTime = Math.round(mtime / 1000);
 		td = $('<td></td>').attr({
 			"class": "filesize",
-			"style": 'color:rgb('+sizeColor+','+sizeColor+','+sizeColor+')'
+			"style": 'color:rgb(' + sizeColor + ',' + sizeColor + ',' + sizeColor + ')'
 		}).text(simpleSize);
 		tr.append(td);
 
 		// date column
-		var modifiedColor = Math.round((Math.round((new Date()).getTime() / 1000)-lastModifiedTime)/60/60/24*5);
+		var modifiedColor = Math.round((Math.round((new Date()).getTime() / 1000) - mtime)/60/60/24*5);
 		td = $('<td></td>').attr({ "class": "date" });
 		td.append($('<span></span>').attr({
 			"class": "modified",
-			"title": formatDate(lastModified),
+			"title": formatDate(mtime),
 			"style": 'color:rgb('+modifiedColor+','+modifiedColor+','+modifiedColor+')'
-		}).text( relative_modified_date(lastModified.getTime() / 1000) ));
+		}).text( relative_modified_date(mtime / 1000) ));
+		tr.find('.filesize').text(simpleSize);
 		tr.append(td);
 		return tr;
 	},
-	addFile:function(name, size, lastModified, loading, hidden, param) {
-		var imgurl;
+	/**
+	 * Adds an entry to the files table using the data from the given file data
+	 * @param fileData map of file attributes
+	 * @param options map of attributes:
+	 * - "insert" true to insert in a sorted manner, false to append (default)
+	 * - "updateSummary" true to update the summary after adding (default), false otherwise
+	 * @return new tr element (not appended to the table)
+	 */
+	add: function(fileData, options) {
+		options = options || {};
+		var type = fileData.type || 'file',
+			mime = fileData.mimetype,
+			permissions = parseInt(fileData.permissions, 10) || 0;
 
-		if (!param) {
-			param = {};
+		if (type === 'dir') {
+			mime = mime || 'httpd/unix-directory';
 		}
+		var tr = this._createRow(
+			fileData,
+			options
+		);
+		var filenameTd = tr.find('td.filename');
 
-		var download_url = null;
-		if (!param.download_url) {
-			download_url = OC.generateUrl(
-				'apps/files/download{file}',
-				{ file: $('#dir').val()+'/'+name });
-		} else {
-			download_url = param.download_url;
+		// 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;
 
-		if (loading) {
-			imgurl = OC.imagePath('core', 'loading.gif');
-		} else {
-			imgurl = OC.imagePath('core', 'filetypes/file');
-		}
-		var tr = this.createRow(
-			'file',
-			name,
-			imgurl,
-			download_url,
-			size,
-			lastModified,
-			$('#permissions').val()
-		);
+		// TODO: move dragging to FileActions ?
+		// enable drag only for deletable files
+		if (permissions & OC.PERMISSION_DELETE) {
+			filenameTd.draggable(dragOptions);
+		}
+		// allow dropping on folders
+		if (fileData.type === 'dir') {
+			filenameTd.droppable(folderDropOptions);
+		}
 
-		FileList.insertElement(name, 'file', tr);
-		if (loading) {
-			tr.data('loading', true);
-		} else {
-			tr.find('td.filename').draggable(dragOptions);
+		if (options.hidden) {
+			tr.addClass('hidden');
 		}
-		if (hidden) {
-			tr.hide();
+
+		// display actions
+		FileActions.display(filenameTd, false);
+
+		if (fileData.isPreviewAvailable) {
+			// lazy load / newly inserted td ?
+			if (!fileData.icon) {
+				Files.lazyLoadPreview(getPathForPreview(fileData.name), mime, function(url) {
+					filenameTd.css('background-image', 'url(' + url + ')');
+				}, null, null, fileData.etag);
+			}
+			else {
+				// set the preview URL directly
+				var urlSpec = {
+						file: FileList.getCurrentDirectory() + '/' + fileData.name,
+						c: fileData.etag
+					};
+				var previewUrl = Files.generatePreviewUrl(urlSpec);
+				previewUrl = previewUrl.replace('(', '%28').replace(')', '%29');
+				filenameTd.css('background-image', 'url(' + previewUrl + ')');
+			}
 		}
-		return tr;
-	},
-	addDir:function(name, size, lastModified, hidden) {
-
-		var tr = this.createRow(
-			'dir',
-			name,
-			OC.imagePath('core', 'filetypes/folder'),
-			OC.linkTo('files', 'index.php')+"?dir="+ encodeURIComponent($('#dir').val()+'/'+name).replace(/%2F/g, '/'),
-			size,
-			lastModified,
-			$('#permissions').val()
-		);
 
-		FileList.insertElement(name, 'dir', tr);
-		var td = tr.find('td.filename');
-		td.draggable(dragOptions);
-		td.droppable(folderDropOptions);
-		if (hidden) {
-			tr.hide();
+		// defaults to true if not defined
+		if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) {
+			this.updateFileSummary();
+			this.updateEmptyContent();
 		}
-		FileActions.display(tr.find('td.filename'), true);
 		return tr;
 	},
+	/**
+	 * Returns the current directory
+	 * @return current directory
+	 */
 	getCurrentDirectory: function(){
 		return $('#dir').val() || '/';
 	},
+	/**
+	 * Returns the directory permissions
+	 * @return permission value as integer
+	 */
+	getDirectoryPermissions: function() {
+		return parseInt($('#permissions').val(), 10);
+	},
 	/**
 	 * @brief Changes the current directory and reload the file list.
 	 * @param targetDir target directory (non URL encoded)
@@ -209,7 +389,7 @@ window.FileList={
 		if (!force && currentDir === targetDir) {
 			return;
 		}
-		FileList.setCurrentDir(targetDir, changeUrl);
+		FileList._setCurrentDir(targetDir, changeUrl);
 		$('#fileList').trigger(
 			jQuery.Event('changeDirectory', {
 				dir: targetDir,
@@ -221,7 +401,13 @@ window.FileList={
 	linkTo: function(dir) {
 		return OC.linkTo('files', 'index.php')+"?dir="+ encodeURIComponent(dir).replace(/%2F/g, '/');
 	},
-	setCurrentDir: function(targetDir, changeUrl) {
+
+	/**
+	 * Sets the current directory name and updates the breadcrumb.
+	 * @param targetDir directory to display
+	 * @param changeUrl true to also update the URL, false otherwise (default)
+	 */
+	_setCurrentDir: function(targetDir, changeUrl) {
 		var url,
 			baseDir = OC.basename(targetDir);
 
@@ -243,6 +429,7 @@ window.FileList={
 				window.location.hash = '?dir='+ encodeURIComponent(targetDir).replace(/%2F/g, '/');
 			}
 		}
+		this.breadcrumb.setDirectory(this.getCurrentDirectory());
 	},
 	/**
 	 * @brief Reloads the file list using ajax call
@@ -253,10 +440,9 @@ window.FileList={
 			FileList._reloadCall.abort();
 		}
 		FileList._reloadCall = $.ajax({
-			url: OC.filePath('files','ajax','list.php'),
+			url: Files.getAjaxUrl('list'),
 			data: {
-				dir : $('#dir').val(),
-				breadcrumb: true
+				dir : $('#dir').val()
 			},
 			error: function(result) {
 				FileList.reloadCallback(result);
@@ -269,8 +455,8 @@ window.FileList={
 	reloadCallback: function(result) {
 		var $controls = $('#controls');
 
-		delete FileList._reloadCall;
-		FileList.hideMask();
+		delete this._reloadCall;
+		this.hideMask();
 
 		if (!result || result.status === 'error') {
 			OC.Notification.show(result.data.message);
@@ -279,7 +465,11 @@ window.FileList={
 
 		if (result.status === 404) {
 			// go back home
-			FileList.changeDirectory('/');
+			this.changeDirectory('/');
+			return;
+		}
+		// aborted ?
+		if (result.status === 0){
 			return;
 		}
 
@@ -288,24 +478,10 @@ window.FileList={
 		Files.updateStorageStatistics(true);
 
 		if (result.data.permissions) {
-			FileList.setDirectoryPermissions(result.data.permissions);
-		}
-
-		if (typeof(result.data.breadcrumb) !== 'undefined') {
-			$controls.find('.crumb').remove();
-			$controls.prepend(result.data.breadcrumb);
-
-			var width = $(window).width();
-			Files.initBreadCrumbs();
-			Files.resizeBreadcrumbs(width, true);
-
-			// in case svg is not supported by the browser we need to execute the fallback mechanism
-			if (!SVGSupport()) {
-				replaceSVG();
-			}
+			this.setDirectoryPermissions(result.data.permissions);
 		}
 
-		FileList.update(result.data.files);
+		this.setFiles(result.data.files);
 	},
 	setDirectoryPermissions: function(permissions) {
 		var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0;
@@ -322,10 +498,14 @@ window.FileList={
 		$('.actions,#file_action_panel').toggleClass('hidden', !show);
 		if (show){
 			// make sure to display according to permissions
-			var permissions =  $('#permissions').val();
+			var permissions = this.getDirectoryPermissions();
 			var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0;
 			$('.creatable').toggleClass('hidden', !isCreatable);
 			$('.notCreatable').toggleClass('hidden', isCreatable);
+			// remove old style breadcrumbs (some apps might create them)
+			$('#controls .crumb').remove();
+			// refresh breadcrumbs in case it was replaced by an app
+			this.breadcrumb.render();
 		}
 		else{
 			$('.creatable, .notCreatable').addClass('hidden');
@@ -341,22 +521,32 @@ window.FileList={
 		this.showActions(!show);
 		$('#filestable').toggleClass('hidden', show);
 	},
-	remove:function(name){
+	/**
+	 * Removes a file entry from the list
+	 * @param name name of the file to remove
+	 * @param options optional options as map:
+	 * "updateSummary": true to update the summary (default), false otherwise
+	 */
+	remove:function(name, options){
+		options = options || {};
 		var fileEl = FileList.findFileEl(name);
 		if (fileEl.data('permissions') & OC.PERMISSION_DELETE) {
 			// file is only draggable when delete permissions are set
 			fileEl.find('td.filename').draggable('destroy');
 		}
 		fileEl.remove();
-		FileList.updateFileSummary();
-		if ( ! $('tr[data-file]').exists() ) {
-			$('#emptycontent').removeClass('hidden');
-			$('#filescontent th').addClass('hidden');
+		// TODO: improve performance on batch update
+		FileList.isEmpty = !this.$fileList.find('tr:not(.summary)').length;
+		if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) {
+			FileList.updateEmptyContent();
+			FileList.updateFileSummary();
 		}
+		return fileEl;
 	},
 	insertElement:function(name, type, element) {
-		//find the correct spot to insert the file or folder
-		var pos, fileElements=$('tr[data-file][data-type="'+type+'"]:visible');
+		// 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) {
@@ -376,35 +566,18 @@ window.FileList={
 			} else {
 				$(fileElements[pos]).after(element);
 			}
-		} else if (type === 'dir' && $('tr[data-file]').exists()) {
-			$('tr[data-file]').first().before(element);
-		} else if (type === 'file' && $('tr[data-file]').exists()) {
-			$('tr[data-file]').last().before(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 {
-			$('#fileList').append(element);
+			this.$fileList.append(element);
 		}
-		$('#emptycontent').addClass('hidden');
-		$('#filestable th').removeClass('hidden');
+		FileList.isEmpty = false;
+		FileList.updateEmptyContent();
 		FileList.updateFileSummary();
 	},
-	loadingDone:function(name, id) {
-		var mime, tr = FileList.findFileEl(name);
-		tr.data('loading', false);
-		mime = tr.data('mime');
-		tr.attr('data-mime', mime);
-		if (id) {
-			tr.attr('data-id', id);
-		}
-		var path = getPathForPreview(name);
-		Files.lazyLoadPreview(path, mime, function(previewpath) {
-			tr.find('td.filename').attr('style','background-image:url('+previewpath+')');
-		}, null, null, tr.attr('data-etag'));
-		tr.find('td.filename').draggable(dragOptions);
-	},
-	isLoading:function(file) {
-		return FileList.findFileEl(file).data('loading');
-	},
-	rename:function(oldname) {
+	rename: function(oldname) {
 		var tr, td, input, form;
 		tr = FileList.findFileEl(oldname);
 		tr.data('renaming',true);
@@ -438,6 +611,7 @@ window.FileList={
 			event.preventDefault();
 			try {
 				var newname = input.val();
+				var directory = FileList.getCurrentDirectory();
 				if (newname !== oldname) {
 					checkInput();
 					// save background image, because it's replaced by a spinner while async request
@@ -480,12 +654,12 @@ window.FileList={
 								tr.attr('data-mime', fileInfo.mime);
 								tr.attr('data-etag', fileInfo.etag);
 								if (fileInfo.isPreviewAvailable) {
-									Files.lazyLoadPreview(fileInfo.directory + '/' + fileInfo.name, result.data.mime, function(previewpath) {
+									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('+fileInfo.icon+')');
+									tr.find('td.filename').removeClass('preview').attr('style','background-image:url('+FileList._replaceSVG(fileInfo.icon)+')');
 								}
 							}
 							// reinsert row
@@ -554,58 +728,12 @@ window.FileList={
 	inList:function(file) {
 		return FileList.findFileEl(file).length;
 	},
-	replace:function(oldName, newName, isNewFile) {
-		// Finish any existing actions
-		var oldFileEl = FileList.findFileEl(oldName);
-		var newFileEl = FileList.findFileEl(newName);
-		oldFileEl.hide();
-		newFileEl.hide();
-		var tr = oldFileEl.clone();
-		tr.attr('data-replace', 'true');
-		tr.attr('data-file', newName);
-		var td = tr.children('td.filename');
-		td.children('a.name .span').text(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) {
-			basename = newName.substr(0, newName.lastIndexOf('.'));
-		}
-		td.children('a.name').empty();
-		var span = $('<span class="nametext"></span>');
-		span.text(basename);
-		td.children('a.name').append(span);
-		if (newName.indexOf('.') > 0) {
-			span.append($('<span class="extension">'+newName.substr(newName.lastIndexOf('.'))+'</span>'));
-		}
-		FileList.insertElement(newName, tr.data('type'), tr);
-		tr.show();
-		FileList.replaceCanceled = false;
-		FileList.replaceOldName = oldName;
-		FileList.replaceNewName = newName;
-		FileList.replaceIsNewFile = isNewFile;
-		FileList.lastAction = function() {
-			FileList.finishReplace();
-		};
-		if (!isNewFile) {
-			OC.Notification.showHtml(t('files', 'replaced {new_name} with {old_name}', {new_name: newName}, {old_name: oldName})+'<span class="undo">'+t('files', 'undo')+'</span>');
-		}
-	},
-	finishReplace:function() {
-		if (!FileList.replaceCanceled && FileList.replaceOldName && FileList.replaceNewName) {
-			$.ajax({url: OC.filePath('files', 'ajax', 'rename.php'), async: false, data: { dir: $('#dir').val(), newname: FileList.replaceNewName, file: FileList.replaceOldName }, success: function(result) {
-				if (result && result.status === 'success') {
-					$('tr[data-replace="true"').removeAttr('data-replace');
-				} else {
-					OC.dialogs.alert(result.data.message, 'Error moving file');
-				}
-				FileList.replaceCanceled = true;
-				FileList.replaceOldName = null;
-				FileList.replaceNewName = null;
-				FileList.lastAction = null;
-			}});
-		}
-	},
+	/**
+	 * Delete the given files from the given dir
+	 * @param files file names list (without path)
+	 * @param dir directory in which to delete the files, defaults to the current
+	 * directory
+	 */
 	do_delete:function(files, dir) {
 		var params;
 		if (files && files.substr) {
@@ -622,7 +750,7 @@ window.FileList={
 			FileList.lastAction();
 		}
 
-		var params = {
+		params = {
 			dir: dir || FileList.getCurrentDirectory()
 		};
 		if (files) {
@@ -643,10 +771,9 @@ window.FileList={
 						}
 						else {
 							$.each(files,function(index,file) {
-								var files = FileList.findFileEl(file);
-								files.remove();
-								files.find('input[type="checkbox"]').removeAttr('checked');
-								files.removeClass('selected');
+								var fileEl = FileList.remove(file, {updateSummary: false});
+								fileEl.find('input[type="checkbox"]').prop('checked', false);
+								fileEl.removeClass('selected');
 							});
 						}
 						procesSelection();
@@ -680,7 +807,7 @@ window.FileList={
 				});
 	},
 	createFileSummary: function() {
-		if( $('#fileList tr').exists() ) {
+		if ( !FileList.isEmpty ) {
 			var summary = this._calculateFileSummary();
 
 			// Get translations
@@ -702,7 +829,7 @@ window.FileList={
 			}
 
 			var $summary = $('<tr class="summary" data-file="undefined"><td><span class="info">'+info+'</span></td>'+fileSize+'<td></td></tr>');
-			$('#fileList').append($summary);
+			this.$fileList.append($summary);
 
 			var $dirInfo = $summary.find('.dirinfo');
 			var $fileInfo = $summary.find('.fileinfo');
@@ -710,12 +837,12 @@ window.FileList={
 
 			// Show only what's necessary, e.g.: no files: don't show "0 files"
 			if (summary.totalDirs === 0) {
-				$dirInfo.hide();
-				$connector.hide();
+				$dirInfo.addClass('hidden');
+				$connector.addClass('hidden');
 			}
 			if (summary.totalFiles === 0) {
-				$fileInfo.hide();
-				$connector.hide();
+				$fileInfo.addClass('hidden');
+				$connector.addClass('hidden');
 			}
 		}
 	},
@@ -740,10 +867,13 @@ window.FileList={
 		return result;
 	},
 	updateFileSummary: function() {
-		var $summary = $('.summary');
+		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 ($('#fileList tr').length === 1 && $summary.length === 1) {
+		if (this.isEmpty && $summary.length === 1) {
 			$summary.remove();
 		}
 		// If there's no summary create one (createFileSummary checks if there's data)
@@ -751,7 +881,7 @@ window.FileList={
 			FileList.createFileSummary();
 		}
 		// There's a summary and data -> Update the summary
-		else if ($('#fileList tr').length > 1 && $summary.length === 1) {
+		else if (!this.isEmpty && $summary.length === 1) {
 			var fileSummary = this._calculateFileSummary();
 			var $dirInfo = $('.summary .dirinfo');
 			var $fileInfo = $('.summary .fileinfo');
@@ -764,19 +894,19 @@ window.FileList={
 
 			// Show only what's necessary (may be hidden)
 			if (fileSummary.totalDirs === 0) {
-				$dirInfo.hide();
-				$connector.hide();
+				$dirInfo.addClass('hidden');
+				$connector.addClass('hidden');
 			} else {
-				$dirInfo.show();
+				$dirInfo.removeClass('hidden');
 			}
 			if (fileSummary.totalFiles === 0) {
-				$fileInfo.hide();
-				$connector.hide();
+				$fileInfo.addClass('hidden');
+				$connector.addClass('hidden');
 			} else {
-				$fileInfo.show();
+				$fileInfo.removeClass('hidden');
 			}
 			if (fileSummary.totalDirs > 0 && fileSummary.totalFiles > 0) {
-				$connector.show();
+				$connector.removeClass('hidden');
 			}
 		}
 	},
@@ -784,10 +914,14 @@ window.FileList={
 		var $fileList = $('#fileList');
 		var permissions = $('#permissions').val();
 		var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0;
-		var exists = $fileList.find('tr:first').exists();
-		$('#emptycontent').toggleClass('hidden', !isCreatable || exists);
-		$('#filestable th').toggleClass('hidden', !exists);
+		$('#emptycontent').toggleClass('hidden', !isCreatable || !FileList.isEmpty);
+		$('#filestable thead th').toggleClass('hidden', FileList.isEmpty);
 	},
+	/**
+	 * Shows the loading mask.
+	 *
+	 * @see #hideMask
+	 */
 	showMask: function() {
 		// in case one was shown before
 		var $mask = $('#content .mask');
@@ -795,23 +929,23 @@ window.FileList={
 			return;
 		}
 
+		this.$el.addClass('hidden');
+
 		$mask = $('<div class="mask transparent"></div>');
 
 		$mask.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')');
 		$mask.css('background-repeat', 'no-repeat');
 		$('#content').append($mask);
 
-		// block UI, but only make visible in case loading takes longer
-		FileList._maskTimeout = window.setTimeout(function() {
-			// reset opacity
-			$mask.removeClass('transparent');
-		}, 250);
+		$mask.removeClass('transparent');
 	},
+	/**
+	 * Hide the loading mask.
+	 * @see #showMask
+	 */
 	hideMask: function() {
-		var $mask = $('#content .mask').remove();
-		if (FileList._maskTimeout) {
-			window.clearTimeout(FileList._maskTimeout);
-		}
+		$('#content .mask').remove();
+		this.$el.removeClass('hidden');
 	},
 	scrollTo:function(file) {
 		//scroll to and highlight preselected file
@@ -850,29 +984,11 @@ window.FileList={
 	 */
 	isAllSelected: function() {
 		return $('#select_all').prop('checked');
-	},
-
-	/**
-	 * Returns the download URL of the given file
-	 * @param filename file name of the file
-	 * @param dir optional directory in which the file name is, defaults to the current directory
-	 */
-	getDownloadUrl: function(filename, dir) {
-		var files = filename;
-		if ($.isArray(filename)) {
-			files = JSON.stringify(filename);
-		}
-		var params = {
-			dir: dir || FileList.getCurrentDirectory(),
-			files: files
-		};
-		return OC.filePath('files', 'ajax', 'download.php') + '?' + OC.buildQueryString(params);
 	}
 };
 
 $(document).ready(function() {
-	var baseDir,
-		isPublic = !!$('#isPublic').val();
+	FileList.initialize();
 
 	// handle upload events
 	var file_upload_start = $('#file_upload_start');
@@ -907,8 +1023,13 @@ $(document).ready(function() {
 					{name: 'requesttoken', value: oc_requesttoken}
 				];
 			};
+		} else {
+			// cancel uploads to current dir if no permission
+			var isCreatable = (FileList.getDirectoryPermissions() & OC.PERMISSION_CREATE) !== 0;
+			if (!isCreatable) {
+				return false;
+			}
 		}
-
 	});
 	file_upload_start.on('fileuploadadd', function(e, data) {
 		OC.Upload.log('filelist handle fileuploadadd', e, data);
@@ -993,31 +1114,11 @@ $(document).ready(function() {
 				if (data.files[0].size>=0) {
 					size=data.files[0].size;
 				}
-				var date=new Date();
-				var param = {};
-				if ($('#publicUploadRequestToken').exists()) {
-					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(file.name, file.size, date, false, false, param);
-
-				// update file data
-				data.context.attr('data-mime',file.mime).attr('data-id',file.id).attr('data-etag', file.etag);
-
-				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'), true);
-
-				var path = getPathForPreview(file.name);
-				Files.lazyLoadPreview(path, file.mime, function(previewpath) {
-					data.context.find('td.filename').attr('style','background-image:url('+previewpath+')');
-				}, null, null, file.etag);
+				data.context = FileList.add(file, {insert: true});
 			}
 		}
 	});
@@ -1049,31 +1150,6 @@ $(document).ready(function() {
 	});
 
 	$('#notification').hide();
-	$('#notification').on('click', '.undo', function() {
-		if (FileList.deleteFiles) {
-			$.each(FileList.deleteFiles,function(index,file) {
-				FileList.findFileEl(file).show();
-			});
-			FileList.deleteCanceled=true;
-			FileList.deleteFiles=null;
-		} else if (FileList.replaceOldName && FileList.replaceNewName) {
-			if (FileList.replaceIsNewFile) {
-				// Delete the new uploaded file
-				FileList.deleteCanceled = false;
-				FileList.deleteFiles = [FileList.replaceOldName];
-			} else {
-				FileList.findFileEl(FileList.replaceOldName).show();
-			}
-			$('tr[data-replace="true"').remove();
-			FileList.findFileEl(FileList.replaceNewName).show();
-			FileList.replaceCanceled = true;
-			FileList.replaceOldName = null;
-			FileList.replaceNewName = null;
-			FileList.replaceIsNewFile = null;
-		}
-		FileList.lastAction = null;
-		OC.Notification.hide();
-	});
 	$('#notification:first-child').on('click', '.replace', function() {
 		OC.Notification.hide(function() {
 			FileList.replace($('#notification > span').attr('data-oldName'), $('#notification > span').attr('data-newName'), $('#notification > span').attr('data-isNewFile'));
@@ -1081,7 +1157,7 @@ $(document).ready(function() {
 	});
 	$('#notification:first-child').on('click', '.suggest', function() {
 		var file = $('#notification > span').attr('data-oldName');
-		FileList.findFileEl(file).show();
+		FileList.findFileEl(file).removeClass('hidden');
 		OC.Notification.hide();
 	});
 	$('#notification:first-child').on('click', '.cancel', function() {
@@ -1130,34 +1206,32 @@ $(document).ready(function() {
 	}
 
 	// disable ajax/history API for public app (TODO: until it gets ported)
-	if (!isPublic) {
-		// fallback to hashchange when no history support
-		if (!window.history.pushState) {
-			$(window).on('hashchange', function() {
-				FileList.changeDirectory(parseCurrentDirFromUrl(), false);
-			});
+	// fallback to hashchange when no history support
+	if (!window.history.pushState) {
+		$(window).on('hashchange', function() {
+			FileList.changeDirectory(parseCurrentDirFromUrl(), false);
+		});
+	}
+	window.onpopstate = function(e) {
+		var targetDir;
+		if (e.state && e.state.dir) {
+			targetDir = e.state.dir;
 		}
-		window.onpopstate = function(e) {
-			var targetDir;
-			if (e.state && e.state.dir) {
-				targetDir = e.state.dir;
-			}
-			else{
-				// read from URL
-				targetDir = parseCurrentDirFromUrl();
-			}
-			if (targetDir) {
-				FileList.changeDirectory(targetDir, false);
-			}
-		};
-
-		if (parseInt($('#ajaxLoad').val(), 10) === 1) {
-			// need to initially switch the dir to the one from the hash (IE8)
-			FileList.changeDirectory(parseCurrentDirFromUrl(), false, true);
+		else{
+			// read from URL
+			targetDir = parseCurrentDirFromUrl();
+		}
+		if (targetDir) {
+			FileList.changeDirectory(targetDir, false);
 		}
+	};
 
-		FileList.setCurrentDir(parseCurrentDirFromUrl(), false);
-	}
+	var dir = parseCurrentDirFromUrl();
+	// trigger ajax load, deferred to let sub-apps do their overrides first
+	setTimeout(function() {
+		FileList.changeDirectory(dir, false, true);
+	}, 0);
 
 	FileList.createFileSummary();
 });
+
diff --git a/apps/files/js/files.js b/apps/files/js/files.js
index 1137364db4a..4c2d87d808c 100644
--- a/apps/files/js/files.js
+++ b/apps/files/js/files.js
@@ -161,80 +161,33 @@ var Files = {
 		});
 	},
 
-	lastWidth: 0,
-
-	initBreadCrumbs: function () {
-		var $controls = $('#controls');
-
-		Files.lastWidth = 0;
-		Files.breadcrumbs = [];
-
-		// initialize with some extra space
-		Files.breadcrumbsWidth = 64;
-		if ( document.getElementById("navigation") ) {
-			Files.breadcrumbsWidth += $('#navigation').get(0).offsetWidth;
+	/**
+	 * Returns the download URL of the given file(s)
+	 * @param filename string or array of file names to download
+	 * @param dir optional directory in which the file name is, defaults to the current directory
+	 */
+	getDownloadUrl: function(filename, dir) {
+		if ($.isArray(filename)) {
+			filename = JSON.stringify(filename);
 		}
-		Files.hiddenBreadcrumbs = 0;
-
-		$.each($('.crumb'), function(index, breadcrumb) {
-			Files.breadcrumbs[index] = breadcrumb;
-			Files.breadcrumbsWidth += $(breadcrumb).get(0).offsetWidth;
-		});
-
-		$.each($('#controls .actions>div'), function(index, action) {
-			Files.breadcrumbsWidth += $(action).get(0).offsetWidth;
-		});
-
-		// event handlers for breadcrumb items
-		$controls.find('.crumb a').on('click', onClickBreadcrumb);
-
-		// setup drag and drop
-		$controls.find('.crumb:not(.last)').droppable(crumbDropOptions);
+		var params = {
+			dir: dir || FileList.getCurrentDirectory(),
+			files: filename
+		};
+		return this.getAjaxUrl('download', params);
 	},
 
-	resizeBreadcrumbs: function (width, firstRun) {
-		if (width !== Files.lastWidth) {
-			if ((width < Files.lastWidth || firstRun) && width < Files.breadcrumbsWidth) {
-				if (Files.hiddenBreadcrumbs === 0) {
-					bc = $(Files.breadcrumbs[1]).get(0);
-					if (typeof bc != 'undefined') {
-						Files.breadcrumbsWidth -= bc.offsetWidth;
-						$(Files.breadcrumbs[1]).find('a').hide();
-						$(Files.breadcrumbs[1]).append('<span>...</span>');
-						Files.breadcrumbsWidth += bc.offsetWidth;
-						Files.hiddenBreadcrumbs = 2;
-					}
-				}
-				var i = Files.hiddenBreadcrumbs;
-				while (width < Files.breadcrumbsWidth && i > 1 && i < Files.breadcrumbs.length - 1) {
-					Files.breadcrumbsWidth -= $(Files.breadcrumbs[i]).get(0).offsetWidth;
-					$(Files.breadcrumbs[i]).hide();
-					Files.hiddenBreadcrumbs = i;
-					i++;
-				}
-			} else if (width > Files.lastWidth && Files.hiddenBreadcrumbs > 0) {
-				var i = Files.hiddenBreadcrumbs;
-				while (width > Files.breadcrumbsWidth && i > 0) {
-					if (Files.hiddenBreadcrumbs === 1) {
-						Files.breadcrumbsWidth -= $(Files.breadcrumbs[1]).get(0).offsetWidth;
-						$(Files.breadcrumbs[1]).find('span').remove();
-						$(Files.breadcrumbs[1]).find('a').show();
-						Files.breadcrumbsWidth += $(Files.breadcrumbs[1]).get(0).offsetWidth;
-					} else {
-						$(Files.breadcrumbs[i]).show();
-						Files.breadcrumbsWidth += $(Files.breadcrumbs[i]).get(0).offsetWidth;
-						if (Files.breadcrumbsWidth > width) {
-							Files.breadcrumbsWidth -= $(Files.breadcrumbs[i]).get(0).offsetWidth;
-							$(Files.breadcrumbs[i]).hide();
-							break;
-						}
-					}
-					i--;
-					Files.hiddenBreadcrumbs = i;
-				}
-			}
-			Files.lastWidth = width;
+	/**
+	 * Returns the ajax URL for a given action
+	 * @param action action string
+	 * @param params optional params map
+	 */
+	getAjaxUrl: function(action, params) {
+		var q = '';
+		if (params) {
+			q = '?' + OC.buildQueryString(params);
 		}
+		return OC.filePath('files', 'ajax', action + '.php') + q;
 	}
 };
 $(document).ready(function() {
@@ -245,14 +198,10 @@ $(document).ready(function() {
 	Files.displayEncryptionWarning();
 	Files.bindKeyboardShortcuts(document, jQuery);
 
-	FileList.postProcessList();
 	Files.setupDragAndDrop();
 
 	$('#file_action_panel').attr('activeAction', false);
 
-	// allow dropping on the "files" app icon
-	$('ul#apps li:first-child').data('dir','').droppable(crumbDropOptions);
-
 	// Triggers invisible file input
 	$('#upload a').on('click', function() {
 		$(this).parent().children('#file_upload_start').trigger('click');
@@ -311,7 +260,7 @@ $(document).ready(function() {
 			var filename=$(this).parent().parent().attr('data-file');
 			var tr = FileList.findFileEl(filename);
 			var renaming=tr.data('renaming');
-			if (!renaming && !FileList.isLoading(filename)) {
+			if (!renaming) {
 				FileActions.currentFile = $(this).parent();
 				var mime=FileActions.getCurrentMimeType();
 				var type=FileActions.getCurrentType();
@@ -377,15 +326,15 @@ $(document).ready(function() {
 			dir = OC.dirname(dir) || '/';
 		}
 		else {
-			files = getSelectedFilesTrash('name');
+			files = Files.getSelectedFiles('name');
 		}
 		OC.Notification.show(t('files','Your download is being prepared. This might take some time if the files are big.'));
-		OC.redirect(FileList.getDownloadUrl(files, dir));
+		OC.redirect(Files.getDownloadUrl(files, dir));
 		return false;
 	});
 
 	$('.delete-selected').click(function(event) {
-		var files=getSelectedFilesTrash('name');
+		var files = Files.getSelectedFiles('name');
 		event.preventDefault();
 		if (FileList.isAllSelected()) {
 			files = null;
@@ -403,16 +352,6 @@ $(document).ready(function() {
 	//do a background scan if needed
 	scanFiles();
 
-	Files.initBreadCrumbs();
-
-	$(window).resize(function() {
-		var width = $(this).width();
-		Files.resizeBreadcrumbs(width, false);
-	});
-
-	var width = $(this).width();
-	Files.resizeBreadcrumbs(width, true);
-
 	// display storage warnings
 	setTimeout(Files.displayStorageWarnings, 100);
 	OC.Notification.setDefault(Files.displayStorageWarnings);
@@ -503,7 +442,7 @@ var createDragShadow = function(event) {
 		$(event.target).parents('tr').find('td input:first').prop('checked',true);
 	}
 
-	var selectedFiles = getSelectedFilesTrash();
+	var selectedFiles = Files.getSelectedFiles();
 
 	if (!isDragSelected && selectedFiles.length === 1) {
 		//revert the selection
@@ -619,52 +558,8 @@ var folderDropOptions={
 	tolerance: 'pointer'
 };
 
-var crumbDropOptions={
-	drop: function( event, ui ) {
-		var target=$(this).data('dir');
-		var dir = $('#dir').val();
-		while(dir.substr(0,1) === '/') {//remove extra leading /'s
-				dir=dir.substr(1);
-		}
-		dir = '/' + dir;
-		if (dir.substr(-1,1) !== '/') {
-			dir = dir + '/';
-		}
-		if (target === dir || target+'/' === dir) {
-			return;
-		}
-		var files = ui.helper.find('tr');
-		$(files).each(function(i,row) {
-			var dir = $(row).data('dir');
-			var file = $(row).data('filename');
-			//slapdash selector, tracking down our original element that the clone budded off of.
-			var origin = $('tr[data-id=' + $(row).data('origin') + ']');
-			var td = origin.children('td.filename');
-			var oldBackgroundImage = td.css('background-image');
-			td.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')');
-			$.post(OC.filePath('files', 'ajax', 'move.php'), { dir: dir, file: file, target: target }, function(result) {
-				if (result) {
-					if (result.status === 'success') {
-						FileList.remove(file);
-						procesSelection();
-						$('#notification').hide();
-					} else {
-						$('#notification').hide();
-						$('#notification').text(result.data.message);
-						$('#notification').fadeIn();
-					}
-				} else {
-					OC.dialogs.alert(t('files', 'Error moving file'), t('files', 'Error'));
-				}
-				td.css('background-image', oldBackgroundImage);
-			});
-		});
-	},
-	tolerance: 'pointer'
-};
-
 function procesSelection() {
-	var selected = getSelectedFilesTrash();
+	var selected = Files.getSelectedFiles();
 	var selectedFiles = selected.filter(function(el) {
 		return el.type==='file';
 	});
@@ -714,7 +609,7 @@ function procesSelection() {
  * if property is set, an array with that property for each file is returnd
  * if it's ommited an array of objects with all properties is returned
  */
-function getSelectedFilesTrash(property) {
+Files.getSelectedFiles = function(property) {
 	var elements=$('td.filename input:checkbox:checked').parent().parent();
 	var files=[];
 	elements.each(function(i,element) {
@@ -755,25 +650,30 @@ function getPathForPreview(name) {
 	return path;
 }
 
+/**
+ * Generates a preview URL based on the URL space.
+ * @param urlSpec map with {x: width, y: height, file: file path}
+ * @return preview URL
+ */
+Files.generatePreviewUrl = function(urlSpec) {
+	urlSpec = urlSpec || {};
+	if (!urlSpec.x) {
+		urlSpec.x = $('#filestable').data('preview-x');
+	}
+	if (!urlSpec.y) {
+		urlSpec.y = $('#filestable').data('preview-y');
+	}
+	urlSpec.forceIcon = 0;
+	return OC.generateUrl('/core/preview.png?') + $.param(urlSpec);
+}
+
 Files.lazyLoadPreview = function(path, mime, ready, width, height, etag) {
 	// get mime icon url
 	Files.getMimeIcon(mime, function(iconURL) {
-		var urlSpec = {};
 		var previewURL;
+			urlSpec = {};
 		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');
-		}
-		// note: the order of arguments must match the one
-		// from the server's template so that the browser
-		// knows it's the same file for caching
-		urlSpec.x = width;
-		urlSpec.y = height;
 		urlSpec.file = Files.fixPath(path);
 
 		if (etag){
@@ -784,15 +684,9 @@ Files.lazyLoadPreview = function(path, mime, ready, width, height, etag) {
 			console.warn('Files.lazyLoadPreview(): missing etag argument');
 		}
 
-		if ( $('#isPublic').length ) {
-			urlSpec.t = $('#dirToken').val();
-			previewURL = OC.generateUrl('/publicpreview.png?') + $.param(urlSpec);
-		} else {
-			previewURL = OC.generateUrl('/core/preview.png?') + $.param(urlSpec);
-		}
+		previewURL = Files.generatePreviewUrl(urlSpec);
 		previewURL = previewURL.replace('(', '%28');
 		previewURL = previewURL.replace(')', '%29');
-		previewURL += '&forceIcon=0';
 
 		// preload image to prevent delay
 		// this will make the browser cache the image
@@ -841,14 +735,8 @@ function checkTrashStatus() {
 	});
 }
 
-function onClickBreadcrumb(e) {
-	var $el = $(e.target).closest('.crumb'),
-		$targetDir = $el.data('dir'),
-		isPublic = !!$('#isPublic').val();
-
-	if ($targetDir !== undefined && !isPublic) {
-		e.preventDefault();
-		FileList.changeDirectory(decodeURIComponent($targetDir));
-	}
+// override core's fileDownloadPath (legacy)
+function fileDownloadPath(dir, file) {
+	return Files.getDownloadUrl(file, dir);
 }
 
diff --git a/apps/files/lib/app.php b/apps/files/lib/app.php
index fea88faa92a..adfca669577 100644
--- a/apps/files/lib/app.php
+++ b/apps/files/lib/app.php
@@ -84,25 +84,7 @@ class App {
 		) {
 			// successful rename
 			$meta = $this->view->getFileInfo($dir . '/' . $newname);
-			if ($meta['mimetype'] === 'httpd/unix-directory') {
-				$meta['type'] = 'dir';
-			}
-			else {
-				$meta['type'] = 'file';
-			}
-			// these need to be set for determineIcon()
-			$meta['isPreviewAvailable'] = \OC::$server->getPreviewManager()->isMimeSupported($meta['mimetype']);
-			$meta['directory'] = $dir;
-			$fileinfo = array(
-				'id' => $meta['fileid'],
-				'mime' => $meta['mimetype'],
-				'size' => $meta['size'],
-				'etag' => $meta['etag'],
-				'directory' => $meta['directory'],
-				'name' => $newname,
-				'isPreviewAvailable' => $meta['isPreviewAvailable'],
-				'icon' => \OCA\Files\Helper::determineIcon($meta)
-			);
+			$fileinfo = \OCA\Files\Helper::formatFileInfo($meta);
 			$result['success'] = true;
 			$result['data'] = $fileinfo;
 		} else {
diff --git a/apps/files/lib/helper.php b/apps/files/lib/helper.php
index c41e2d15581..b765fdaf3e3 100644
--- a/apps/files/lib/helper.php
+++ b/apps/files/lib/helper.php
@@ -19,11 +19,17 @@ class Helper
 					 'usedSpacePercent'  => (int)$storageInfo['relative']);
 	}
 
+	/**
+	 * Determine icon for a given file
+	 *
+	 * @param \OC\Files\FileInfo $file file info
+	 * @return string icon URL
+	 */
 	public static function determineIcon($file) {
 		if($file['type'] === 'dir') {
 			$dir = $file['directory'];
 			$icon = \OC_Helper::mimetypeIcon('dir');
-			$absPath = \OC\Files\Filesystem::getView()->getAbsolutePath($dir.'/'.$file['name']);
+			$absPath = $file->getPath();
 			$mount = \OC\Files\Filesystem::getMountManager()->find($absPath);
 			if (!is_null($mount)) {
 				$sid = $mount->getStorageId();
@@ -38,11 +44,7 @@ class Helper
 				}
 			}
 		}else{
-			if($file['isPreviewAvailable']) {
-				$pathForPreview = $file['directory'] . '/' . $file['name'];
-				return \OC_Helper::previewIcon($pathForPreview) . '&c=' . $file['etag'];
-			}
-			$icon = \OC_Helper::mimetypeIcon($file['mimetype']);
+			$icon = \OC_Helper::mimetypeIcon($file->getMimetype());
 		}
 
 		return substr($icon, 0, -3) . 'svg';
@@ -69,52 +71,58 @@ class Helper
 	}
 
 	/**
-	 * Retrieves the contents of the given directory and
-	 * returns it as a sorted array.
-	 * @param string $dir path to the directory
-	 * @return array of files
+	 * Formats the file info to be returned as JSON to the client.
+	 *
+	 * @param \OCP\Files\FileInfo file info
+	 * @return array formatted file info
 	 */
-	public static function getFiles($dir) {
-		$content = \OC\Files\Filesystem::getDirectoryContent($dir);
-		$files = array();
+	public static function formatFileInfo($i) {
+		$entry = array();
 
-		foreach ($content as $i) {
-			$i['date'] = \OCP\Util::formatDate($i['mtime']);
-			if ($i['type'] === 'file') {
-				$fileinfo = pathinfo($i['name']);
-				$i['basename'] = $fileinfo['filename'];
-				if (!empty($fileinfo['extension'])) {
-					$i['extension'] = '.' . $fileinfo['extension'];
-				} else {
-					$i['extension'] = '';
-				}
-			}
-			$i['directory'] = $dir;
-			$i['isPreviewAvailable'] = \OC::$server->getPreviewManager()->isMimeSupported($i['mimetype']);
-			$i['icon'] = \OCA\Files\Helper::determineIcon($i);
-			$files[] = $i;
+		$entry['id'] = $i['fileid'];
+		$entry['date'] = \OCP\Util::formatDate($i['mtime']);
+		$entry['mtime'] = $i['mtime'] * 1000;
+		// only pick out the needed attributes
+		$entry['icon'] = \OCA\Files\Helper::determineIcon($i);
+		if (\OC::$server->getPreviewManager()->isMimeSupported($i['mimetype'])) {
+			$entry['isPreviewAvailable'] = true;
+		}
+		$entry['name'] = $i['name'];
+		$entry['permissions'] = $i['permissions'];
+		$entry['mimetype'] = $i['mimetype'];
+		$entry['size'] = $i['size'];
+		$entry['type'] = $i['type'];
+		$entry['etag'] = $i['etag'];
+		if (isset($i['displayname_owner'])) {
+			$entry['shareOwner'] = $i['displayname_owner'];
 		}
+		return $entry;
+	}
 
-		usort($files, array('\OCA\Files\Helper', 'fileCmp'));
+	/**
+	 * Format file info for JSON
+	 * @param \OCP\Files\FileInfo[] $fileInfos file infos
+	 */
+	public static function formatFileInfos($fileInfos) {
+		$files = array();
+		foreach ($fileInfos as $i) {
+			$files[] = self::formatFileInfo($i);
+		}
 
 		return $files;
 	}
 
 	/**
-	 * Splits the given path into a breadcrumb structure.
-	 * @param string $dir path to process
-	 * @return array where each entry is a hash of the absolute
-	 * directory path and its name
+	 * Retrieves the contents of the given directory and
+	 * returns it as a sorted array of FileInfo.
+	 *
+	 * @param string $dir path to the directory
+	 * @return \OCP\Files\FileInfo[] files
 	 */
-	public static function makeBreadcrumb($dir){
-		$breadcrumb = array();
-		$pathtohere = '';
-		foreach (explode('/', $dir) as $i) {
-			if ($i !== '') {
-				$pathtohere .= '/' . $i;
-				$breadcrumb[] = array('dir' => $pathtohere, 'name' => $i);
-			}
-		}
-		return $breadcrumb;
+	public static function getFiles($dir) {
+		$content = \OC\Files\Filesystem::getDirectoryContent($dir);
+
+		usort($content, array('\OCA\Files\Helper', 'fileCmp'));
+		return $content;
 	}
 }
diff --git a/apps/files/templates/index.php b/apps/files/templates/index.php
index 34acd9c4f51..95edd625cb3 100644
--- a/apps/files/templates/index.php
+++ b/apps/files/templates/index.php
@@ -1,6 +1,5 @@
 <div id="controls">
-	<?php print_unescaped($_['breadcrumb']); ?>
-		<div class="actions creatable <?php if (!$_['isCreatable']):?>hidden<?php endif; ?>">
+		<div class="actions creatable hidden">
 			<?php if(!isset($_['dirToken'])):?>
 			<div id="new" class="button">
 				<a><?php p($l->t('New'));?></a>
@@ -48,20 +47,20 @@
 			</div>
 		</div>
 		<div id="file_action_panel"></div>
-		<div class="notCreatable notPublic <?php if ($_['isCreatable'] or $_['isPublic'] ):?>hidden<?php endif; ?>">
+		<div class="notCreatable notPublic hidden">
 			<?php p($l->t('You don’t have permission to upload or create files here'))?>
 		</div>
 	<input type="hidden" name="permissions" value="<?php p($_['permissions']); ?>" id="permissions">
 </div>
 
-<div id="emptycontent" <?php if (!$_['emptyContent']):?>class="hidden"<?php endif; ?>><?php p($l->t('Nothing in here. Upload something!'))?></div>
+<div id="emptycontent" class="hidden"><?php p($l->t('Nothing in here. Upload something!'))?></div>
 
 <input type="hidden" id="disableSharing" data-status="<?php p($_['disableSharing']); ?>" />
 
 <table id="filestable" data-allow-public-upload="<?php p($_['publicUploadEnabled'])?>" data-preview-x="36" data-preview-y="36">
 	<thead>
 		<tr>
-			<th <?php if (!$_['fileHeader']):?>class="hidden"<?php endif; ?> id='headerName'>
+			<th class="hidden" id='headerName'>
 				<div id="headerName-container">
 					<input type="checkbox" id="select_all" />
 					<label for="select_all"></label>
@@ -77,8 +76,8 @@
 					</span>
 				</div>
 			</th>
-			<th <?php if (!$_['fileHeader']):?>class="hidden"<?php endif; ?> id="headerSize"><?php p($l->t('Size')); ?></th>
-			<th <?php if (!$_['fileHeader']):?>class="hidden"<?php endif; ?> id="headerDate">
+			<th class="hidden" id="headerSize"><?php p($l->t('Size')); ?></th>
+			<th class="hidden" id="headerDate">
 				<span id="modified"><?php p($l->t( 'Modified' )); ?></span>
 				<?php if ($_['permissions'] & OCP\PERMISSION_DELETE): ?>
 					<span class="selectedActions"><a href="" class="delete-selected">
@@ -91,7 +90,6 @@
 		</tr>
 	</thead>
 	<tbody id="fileList">
-		<?php print_unescaped($_['fileList']); ?>
 	</tbody>
 </table>
 <div id="editor"></div><!-- FIXME Do not use this div in your app! It is deprecated and will be removed in the future! -->
@@ -111,7 +109,6 @@
 
 <!-- config hints for javascript -->
 <input type="hidden" name="filesApp" id="filesApp" value="1" />
-<input type="hidden" name="ajaxLoad" id="ajaxLoad" value="<?php p($_['ajaxLoad']); ?>" />
 <input type="hidden" name="allowZipDownload" id="allowZipDownload" value="<?php p($_['allowZipDownload']); ?>" />
 <input type="hidden" name="usedSpacePercent" id="usedSpacePercent" value="<?php p($_['usedSpacePercent']); ?>" />
 <?php if (!$_['isPublic']) :?>
diff --git a/apps/files/templates/part.breadcrumb.php b/apps/files/templates/part.breadcrumb.php
deleted file mode 100644
index 69b4cbca10d..00000000000
--- a/apps/files/templates/part.breadcrumb.php
+++ /dev/null
@@ -1,17 +0,0 @@
-<div class="crumb svg <?php if(!count($_["breadcrumb"])) p('last');?>" data-dir=''>
-	<a href="<?php print_unescaped($_['baseURL']); ?>">
-		<?php if(isset($_['rootBreadCrumb'])):
-			echo $_['rootBreadCrumb'];
-		else:?>
-			<img src="<?php print_unescaped(OCP\image_path('core', 'places/home.svg'));?>" class="svg" />
-		<?php endif;?>
-	</a>
-</div>
-<?php for($i=0; $i<count($_["breadcrumb"]); $i++):
-	$crumb = $_["breadcrumb"][$i];
-	$dir = \OCP\Util::encodePath($crumb["dir"]); ?>
-	<div class="crumb <?php if($i == count($_["breadcrumb"])-1) p('last');?> svg"
-		 data-dir='<?php p($dir);?>'>
-	<a href="<?php p($_['baseURL'].$dir); ?>"><?php p($crumb["name"]); ?></a>
-	</div>
-<?php endfor;
diff --git a/apps/files/templates/part.list.php b/apps/files/templates/part.list.php
deleted file mode 100644
index 8a7a1e370eb..00000000000
--- a/apps/files/templates/part.list.php
+++ /dev/null
@@ -1,67 +0,0 @@
-<?php $totalfiles = 0;
-$totaldirs = 0;
-$totalsize = 0; ?>
-<?php foreach($_['files'] as $file):
-	// the bigger the file, the darker the shade of grey; megabytes*2
-	$simple_size_color = intval(160-$file['size']/(1024*1024)*2);
-	if($simple_size_color<0) $simple_size_color = 0;
-	$relative_modified_date = OCP\relative_modified_date($file['mtime']);
-	// the older the file, the brighter the shade of grey; days*14
-	$relative_date_color = round((time()-$file['mtime'])/60/60/24*14);
-	if($relative_date_color>160) $relative_date_color = 160;
-	$name = \OCP\Util::encodePath($file['name']);
-	$directory = \OCP\Util::encodePath($file['directory']); ?>
-	<tr data-id="<?php p($file['fileid']); ?>"
-		data-file="<?php p($name);?>"
-		data-type="<?php ($file['type'] == 'dir')?p('dir'):p('file')?>"
-		data-mime="<?php p($file['mimetype'])?>"
-		data-size="<?php p($file['size']);?>"
-		data-etag="<?php p($file['etag']);?>"
-		data-permissions="<?php p($file['permissions']); ?>"
-
-		<?php if(isset($file['displayname_owner'])): ?>
-			data-share-owner="<?php p($file['displayname_owner']) ?>"
-		<?php endif; ?>
-		>
-
-		<?php if(isset($file['isPreviewAvailable']) and $file['isPreviewAvailable']): ?>
-		<td class="filename svg preview-icon"
-		<?php else: ?>
-		<td class="filename svg"
-		<?php endif; ?>
-		    style="background-image:url(<?php print_unescaped($file['icon']); ?>)"
-			>
-		<?php if(!isset($_['readonly']) || !$_['readonly']): ?>
-			<input id="select-<?php p($file['fileid']); ?>" type="checkbox" />
-			<label for="select-<?php p($file['fileid']); ?>"></label>
-		<?php endif; ?>
-		<?php if($file['type'] == 'dir'): ?>
-			<a class="name" href="<?php p(rtrim($_['baseURL'],'/').'/'.trim($directory,'/').'/'.$name); ?>" title="">
-				<span class="nametext">
-					<?php print_unescaped(htmlspecialchars($file['name']));?>
-				</span>
-				<span class="uploadtext" currentUploads="0">
-				</span>
-			</a>
-		<?php else: ?>
-			<a class="name" href="<?php p(rtrim($_['downloadURL'],'/').'/'.trim($directory,'/').'/'.$name); ?>">
-				<label class="filetext" title="" for="select-<?php p($file['fileid']); ?>"></label>
-				<span class="nametext"><?php print_unescaped(htmlspecialchars($file['basename']));?><span class='extension'><?php p($file['extension']);?></span></span>
-			</a>
-		<?php endif; ?>
-		</td>
-		<td class="filesize"
-			style="color:rgb(<?php p($simple_size_color.','.$simple_size_color.','.$simple_size_color) ?>)">
-				<?php print_unescaped(OCP\human_file_size($file['size'])); ?>
-		</td>
-		<td class="date">
-			<span class="modified"
-				  title="<?php p($file['date']); ?>"
-				  style="color:rgb(<?php p($relative_date_color.','
-												.$relative_date_color.','
-												.$relative_date_color) ?>)">
-				<?php p($relative_modified_date); ?>
-			</span>
-		</td>
-	</tr>
-<?php endforeach;
diff --git a/apps/files/tests/ajax_rename.php b/apps/files/tests/ajax_rename.php
index e53c0fb3dd1..cb62d22a7e2 100644
--- a/apps/files/tests/ajax_rename.php
+++ b/apps/files/tests/ajax_rename.php
@@ -92,28 +92,32 @@ class Test_OC_Files_App_Rename extends \PHPUnit_Framework_TestCase {
 
 		$this->viewMock->expects($this->any())
 			->method('getFileInfo')
-			->will($this->returnValue(array(
+			->will($this->returnValue(new \OC\Files\FileInfo(
+				'/test',
+				null,
+				'/test',	
+				array(
 				'fileid' => 123,
 				'type' => 'dir',
 				'mimetype' => 'httpd/unix-directory',
+				'mtime' => 0,
+				'permissions' => 31,
 				'size' => 18,
 				'etag' => 'abcdef',
 				'directory' => '/',
 				'name' => 'new_name',
-			)));
+			))));
 
 		$result = $this->files->rename($dir, $oldname, $newname);
 
 		$this->assertTrue($result['success']);
 		$this->assertEquals(123, $result['data']['id']);
 		$this->assertEquals('new_name', $result['data']['name']);
-		$this->assertEquals('/test', $result['data']['directory']);
 		$this->assertEquals(18, $result['data']['size']);
-		$this->assertEquals('httpd/unix-directory', $result['data']['mime']);
+		$this->assertEquals('httpd/unix-directory', $result['data']['mimetype']);
 		$icon = \OC_Helper::mimetypeIcon('dir');
 		$icon = substr($icon, 0, -3) . 'svg';
 		$this->assertEquals($icon, $result['data']['icon']);
-		$this->assertFalse($result['data']['isPreviewAvailable']);
 	}
 
 	/**
@@ -148,29 +152,33 @@ class Test_OC_Files_App_Rename extends \PHPUnit_Framework_TestCase {
 
 		$this->viewMock->expects($this->any())
 			->method('getFileInfo')
-			->will($this->returnValue(array(
+			->will($this->returnValue(new \OC\Files\FileInfo(
+				'/',
+				null,
+				'/',
+				array(
 				'fileid' => 123,
 				'type' => 'dir',
 				'mimetype' => 'httpd/unix-directory',
+				'mtime' => 0,
+				'permissions' => 31,
 				'size' => 18,
 				'etag' => 'abcdef',
 				'directory' => '/',
 				'name' => 'new_name',
-			)));
+			))));
 
 		$result = $this->files->rename($dir, $oldname, $newname);
 
 		$this->assertTrue($result['success']);
 		$this->assertEquals(123, $result['data']['id']);
-		$this->assertEquals('newname', $result['data']['name']);
-		$this->assertEquals('/', $result['data']['directory']);
+		$this->assertEquals('new_name', $result['data']['name']);
 		$this->assertEquals(18, $result['data']['size']);
-		$this->assertEquals('httpd/unix-directory', $result['data']['mime']);
+		$this->assertEquals('httpd/unix-directory', $result['data']['mimetype']);
 		$this->assertEquals('abcdef', $result['data']['etag']);
 		$icon = \OC_Helper::mimetypeIcon('dir');
 		$icon = substr($icon, 0, -3) . 'svg';
 		$this->assertEquals($icon, $result['data']['icon']);
-		$this->assertFalse($result['data']['isPreviewAvailable']);
 	}
 
 	/**
diff --git a/apps/files/tests/js/breadcrumbSpec.js b/apps/files/tests/js/breadcrumbSpec.js
new file mode 100644
index 00000000000..1bfe5308a27
--- /dev/null
+++ b/apps/files/tests/js/breadcrumbSpec.js
@@ -0,0 +1,248 @@
+/**
+* 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 BreadCrumb */
+describe('BreadCrumb tests', function() {
+	describe('Rendering', function() {
+		var bc;
+		beforeEach(function() {
+			bc = new BreadCrumb({
+				getCrumbUrl: function(part, index) {
+					// for testing purposes
+					return part.dir + '#' + index;
+				}
+			});
+		});
+		afterEach(function() {
+			bc = null;
+		});
+		it('Renders its own container', function() {
+			bc.render();
+			expect(bc.$el.hasClass('breadcrumb')).toEqual(true);
+		});
+		it('Renders root by default', function() {
+			var $crumbs;
+			bc.render();
+			$crumbs = bc.$el.find('.crumb');
+			expect($crumbs.length).toEqual(1);
+			expect($crumbs.eq(0).find('a').attr('href')).toEqual('/#0');
+			expect($crumbs.eq(0).find('img').length).toEqual(1);
+			expect($crumbs.eq(0).attr('data-dir')).toEqual('/');
+		});
+		it('Renders root when switching to root', function() {
+			var $crumbs;
+			bc.setDirectory('/somedir');
+			bc.setDirectory('/');
+			$crumbs = bc.$el.find('.crumb');
+			expect($crumbs.length).toEqual(1);
+			expect($crumbs.eq(0).attr('data-dir')).toEqual('/');
+		});
+		it('Renders last crumb with "last" class', function() {
+			bc.setDirectory('/abc/def');
+			expect(bc.$el.find('.crumb:last').hasClass('last')).toEqual(true);
+		});
+		it('Renders single path section', function() {
+			var $crumbs;
+			bc.setDirectory('/somedir');
+			$crumbs = bc.$el.find('.crumb');
+			expect($crumbs.length).toEqual(2);
+			expect($crumbs.eq(0).find('a').attr('href')).toEqual('/#0');
+			expect($crumbs.eq(0).find('img').length).toEqual(1);
+			expect($crumbs.eq(0).attr('data-dir')).toEqual('/');
+			expect($crumbs.eq(1).find('a').attr('href')).toEqual('/somedir#1');
+			expect($crumbs.eq(1).find('img').length).toEqual(0);
+			expect($crumbs.eq(1).attr('data-dir')).toEqual('/somedir');
+		});
+		it('Renders multiple path sections and special chars', function() {
+			var $crumbs;
+			bc.setDirectory('/somedir/with space/abc');
+			$crumbs = bc.$el.find('.crumb');
+			expect($crumbs.length).toEqual(4);
+			expect($crumbs.eq(0).find('a').attr('href')).toEqual('/#0');
+			expect($crumbs.eq(0).find('img').length).toEqual(1);
+			expect($crumbs.eq(0).attr('data-dir')).toEqual('/');
+
+			expect($crumbs.eq(1).find('a').attr('href')).toEqual('/somedir#1');
+			expect($crumbs.eq(1).find('img').length).toEqual(0);
+			expect($crumbs.eq(1).attr('data-dir')).toEqual('/somedir');
+
+			expect($crumbs.eq(2).find('a').attr('href')).toEqual('/somedir/with space#2');
+			expect($crumbs.eq(2).find('img').length).toEqual(0);
+			expect($crumbs.eq(2).attr('data-dir')).toEqual('/somedir/with space');
+
+			expect($crumbs.eq(3).find('a').attr('href')).toEqual('/somedir/with space/abc#3');
+			expect($crumbs.eq(3).find('img').length).toEqual(0);
+			expect($crumbs.eq(3).attr('data-dir')).toEqual('/somedir/with space/abc');
+		});
+	});
+	describe('Events', function() {
+		it('Calls onClick handler when clicking on a crumb', function() {
+			var handler = sinon.stub();
+			var bc = new BreadCrumb({
+				onClick: handler
+			});
+			bc.setDirectory('/one/two/three/four');
+			bc.$el.find('.crumb:eq(3)').click();
+			expect(handler.calledOnce).toEqual(true);
+			expect(handler.getCall(0).thisValue).toEqual(bc.$el.find('.crumb').get(3));
+
+			handler.reset();
+			bc.$el.find('.crumb:eq(0) a').click();
+			expect(handler.calledOnce).toEqual(true);
+			expect(handler.getCall(0).thisValue).toEqual(bc.$el.find('.crumb').get(0));
+		});
+		it('Calls onDrop handler when dropping on a crumb', function() {
+			var droppableStub = sinon.stub($.fn, 'droppable');
+			var handler = sinon.stub();
+			var bc = new BreadCrumb({
+				onDrop: handler
+			});
+			bc.setDirectory('/one/two/three/four');
+			expect(droppableStub.calledOnce).toEqual(true);
+
+			expect(droppableStub.getCall(0).args[0].drop).toBeDefined();
+			// simulate drop
+			droppableStub.getCall(0).args[0].drop({dummy: true});
+
+			expect(handler.calledOnce).toEqual(true);
+			expect(handler.getCall(0).args[0]).toEqual({dummy: true});
+
+			droppableStub.restore();
+		});
+	});
+	describe('Resizing', function() {
+		var bc, widthStub, dummyDir,
+			oldUpdateTotalWidth;
+
+		beforeEach(function() {
+			dummyDir = '/short name/longer name/looooooooooooonger/even longer long long long longer long/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/last one';
+
+			oldUpdateTotalWidth = BreadCrumb.prototype._updateTotalWidth;
+			BreadCrumb.prototype._updateTotalWidth = function() {
+				// need to set display:block for correct offsetWidth (no CSS loaded here)
+				$('div.crumb').css({
+					'display': 'block',
+					'float': 'left'
+				});
+
+				return oldUpdateTotalWidth.apply(this, arguments);
+			};
+
+			bc = new BreadCrumb();
+			widthStub = sinon.stub($.fn, 'width');
+			// append dummy navigation and controls
+			// as they are currently used for measurements
+			$('#testArea').append(
+				'<div id="navigation" style="width: 80px"></div>',
+				'<div id="controls"></div>'
+			);
+
+			// make sure we know the test screen width
+			$('#testArea').css('width', 1280);
+
+			// use test area as we need it for measurements
+			$('#controls').append(bc.$el);
+			$('#controls').append('<div class="actions"><div>Dummy action with a given width</div></div>');
+		});
+		afterEach(function() {
+			BreadCrumb.prototype._updateTotalWidth = oldUpdateTotalWidth;
+			widthStub.restore();
+			bc = null;
+		});
+		it('Hides breadcrumbs to fit window', function() {
+			var $crumbs;
+
+			widthStub.returns(500);
+			// triggers resize implicitly
+			bc.setDirectory(dummyDir);
+			$crumbs = bc.$el.find('.crumb');
+
+			// first one is always visible
+			expect($crumbs.eq(0).hasClass('hidden')).toEqual(false);
+			// second one has ellipsis
+			expect($crumbs.eq(1).hasClass('hidden')).toEqual(false);
+			expect($crumbs.eq(1).find('.ellipsis').length).toEqual(1);
+			// there is only one ellipsis in total
+			expect($crumbs.find('.ellipsis').length).toEqual(1);
+			// subsequent elements are hidden
+			expect($crumbs.eq(2).hasClass('hidden')).toEqual(true);
+			expect($crumbs.eq(3).hasClass('hidden')).toEqual(true);
+			expect($crumbs.eq(4).hasClass('hidden')).toEqual(true);
+			expect($crumbs.eq(5).hasClass('hidden')).toEqual(true);
+			expect($crumbs.eq(6).hasClass('hidden')).toEqual(false);
+		});
+		it('Updates ellipsis on window size increase', function() {
+			var $crumbs;
+
+			widthStub.returns(500);
+			// triggers resize implicitly
+			bc.setDirectory(dummyDir);
+			$crumbs = bc.$el.find('.crumb');
+
+			// simulate increase
+			$('#testArea').css('width', 1800);
+			bc.resize(1800);
+
+			// first one is always visible
+			expect($crumbs.eq(0).hasClass('hidden')).toEqual(false);
+			// second one has ellipsis
+			expect($crumbs.eq(1).hasClass('hidden')).toEqual(false);
+			expect($crumbs.eq(1).find('.ellipsis').length).toEqual(1);
+			// there is only one ellipsis in total
+			expect($crumbs.find('.ellipsis').length).toEqual(1);
+			// subsequent elements are hidden
+			expect($crumbs.eq(2).hasClass('hidden')).toEqual(true);
+			expect($crumbs.eq(3).hasClass('hidden')).toEqual(true);
+			expect($crumbs.eq(4).hasClass('hidden')).toEqual(true);
+			// the rest is visible
+			expect($crumbs.eq(5).hasClass('hidden')).toEqual(false);
+			expect($crumbs.eq(6).hasClass('hidden')).toEqual(false);
+		});
+		it('Updates ellipsis on window size decrease', function() {
+			var $crumbs;
+
+			$('#testArea').css('width', 2000);
+			widthStub.returns(2000);
+			// triggers resize implicitly
+			bc.setDirectory(dummyDir);
+			$crumbs = bc.$el.find('.crumb');
+
+			// simulate decrease
+			bc.resize(500);
+			$('#testArea').css('width', 500);
+
+			// first one is always visible
+			expect($crumbs.eq(0).hasClass('hidden')).toEqual(false);
+			// second one has ellipsis
+			expect($crumbs.eq(1).hasClass('hidden')).toEqual(false);
+			expect($crumbs.eq(1).find('.ellipsis').length).toEqual(1);
+			// there is only one ellipsis in total
+			expect($crumbs.find('.ellipsis').length).toEqual(1);
+			// subsequent elements are hidden
+			expect($crumbs.eq(2).hasClass('hidden')).toEqual(true);
+			expect($crumbs.eq(3).hasClass('hidden')).toEqual(true);
+			expect($crumbs.eq(4).hasClass('hidden')).toEqual(true);
+			// the rest is visible
+			expect($crumbs.eq(5).hasClass('hidden')).toEqual(true);
+			expect($crumbs.eq(6).hasClass('hidden')).toEqual(false);
+		});
+	});
+});
diff --git a/apps/files/tests/js/fileactionsSpec.js b/apps/files/tests/js/fileactionsSpec.js
index 80c04b5b242..3c22c84b866 100644
--- a/apps/files/tests/js/fileactionsSpec.js
+++ b/apps/files/tests/js/fileactionsSpec.js
@@ -22,6 +22,7 @@
 /* global OC, FileActions, FileList */
 describe('FileActions tests', function() {
 	var $filesTable;
+
 	beforeEach(function() {
 		// init horrible parameters
 		var $body = $('body');
@@ -34,17 +35,20 @@ describe('FileActions tests', function() {
 		$('#dir, #permissions, #filestable').remove();
 	});
 	it('calling display() sets file actions', function() {
-		// note: download_url is actually the link target, not the actual download URL...
-		var $tr = FileList.addFile('testName.txt', 1234, new Date(), false, false, {download_url: 'test/download/url'});
-
-		// no actions before call
-		expect($tr.find('.action.action-download').length).toEqual(0);
-		expect($tr.find('.action.action-rename').length).toEqual(0);
-		expect($tr.find('.action.delete').length).toEqual(0);
+		var fileData = {
+			id: 18,
+			type: 'file',
+			name: 'testName.txt',
+			mimetype: 'plain/text',
+			size: '1234',
+			etag: 'a01234c',
+			mtime: '123456'
+		};
 
-		FileActions.display($tr.find('td.filename'), true);
+		// note: FileActions.display() is called implicitly
+		var $tr = FileList.add(fileData);
 
-		// actions defined after cal
+		// actions defined after call
 		expect($tr.find('.action.action-download').length).toEqual(1);
 		expect($tr.find('.action.action-download').attr('data-action')).toEqual('Download');
 		expect($tr.find('.nametext .action.action-rename').length).toEqual(1);
@@ -52,7 +56,16 @@ describe('FileActions tests', function() {
 		expect($tr.find('.action.delete').length).toEqual(1);
 	});
 	it('calling display() twice correctly replaces file actions', function() {
-		var $tr = FileList.addFile('testName.txt', 1234, new Date(), false, false, {download_url: 'test/download/url'});
+		var fileData = {
+			id: 18,
+			type: 'file',
+			name: 'testName.txt',
+			mimetype: 'plain/text',
+			size: '1234',
+			etag: 'a01234c',
+			mtime: '123456'
+		};
+		var $tr = FileList.add(fileData);
 
 		FileActions.display($tr.find('td.filename'), true);
 		FileActions.display($tr.find('td.filename'), true);
@@ -64,19 +77,36 @@ describe('FileActions tests', function() {
 	});
 	it('redirects to download URL when clicking download', function() {
 		var redirectStub = sinon.stub(OC, 'redirect');
-		// note: download_url is actually the link target, not the actual download URL...
-		var $tr = FileList.addFile('test download File.txt', 1234, new Date(), false, false, {download_url: 'test/download/url'});
+		var fileData = {
+			id: 18,
+			type: 'file',
+			name: 'testName.txt',
+			mimetype: 'plain/text',
+			size: '1234',
+			etag: 'a01234c',
+			mtime: '123456'
+		};
+		var $tr = FileList.add(fileData);
 		FileActions.display($tr.find('td.filename'), true);
 
 		$tr.find('.action-download').click();
 
 		expect(redirectStub.calledOnce).toEqual(true);
-		expect(redirectStub.getCall(0).args[0]).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=test%20download%20File.txt');
+		expect(redirectStub.getCall(0).args[0]).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=testName.txt');
 		redirectStub.restore();
 	});
 	it('deletes file when clicking delete', function() {
 		var deleteStub = sinon.stub(FileList, 'do_delete');
-		var $tr = FileList.addFile('test delete File.txt', 1234, new Date());
+		var fileData = {
+			id: 18,
+			type: 'file',
+			name: 'testName.txt',
+			mimetype: 'plain/text',
+			size: '1234',
+			etag: 'a01234c',
+			mtime: '123456'
+		};
+		var $tr = FileList.add(fileData);
 		FileActions.display($tr.find('td.filename'), true);
 
 		$tr.find('.action.delete').click();
diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js
index 8f4cb86ab4a..ca85a360cf5 100644
--- a/apps/files/tests/js/filelistSpec.js
+++ b/apps/files/tests/js/filelistSpec.js
@@ -21,6 +21,9 @@
 
 /* global OC, FileList */
 describe('FileList tests', function() {
+	var testFiles, alertStub, notificationStub,
+		pushStateStub;
+
 	beforeEach(function() {
 		// init horrible parameters
 		var $body = $('body');
@@ -28,45 +31,784 @@ describe('FileList tests', function() {
 		$body.append('<input type="hidden" id="permissions" value="31"></input>');
 		// dummy files table
 		$body.append('<table id="filestable"></table>');
+
+		// prevents URL changes during tests
+		pushStateStub = sinon.stub(window.history, 'pushState');
+
+		alertStub = sinon.stub(OC.dialogs, 'alert');
+		notificationStub = sinon.stub(OC.Notification, 'show');
+
+		// init parameters and test table elements
+		$('#testArea').append(
+			'<input type="hidden" id="dir" value="/subdir"></input>' +
+			'<input type="hidden" id="permissions" value="31"></input>' +
+			// dummy controls
+			'<div id="controls">' +
+			'   <div class="actions creatable"></div>' +
+			'   <div class="notCreatable"></div>' +
+			'</div>' +
+			// dummy table
+			'<table id="filestable">' +
+			'<thead><tr><th class="hidden">Name</th></tr></thead>' +
+		   	'<tbody id="fileList"></tbody>' +
+			'</table>' +
+			'<div id="emptycontent">Empty content message</div>'
+		);
+
+		testFiles = [{
+			id: 1,
+			type: 'file',
+			name: 'One.txt',
+			mimetype: 'text/plain',
+			size: 12
+		}, {
+			id: 2,
+			type: 'file',
+			name: 'Two.jpg',
+			mimetype: 'image/jpeg',
+			size: 12049
+		}, {
+			id: 3,
+			type: 'file',
+			name: 'Three.pdf',
+			mimetype: 'application/pdf',
+			size: 58009
+		}, {
+			id: 4,
+			type: 'dir',
+			name: 'somedir',
+			mimetype: 'httpd/unix-directory',
+			size: 250
+		}];
+
+		FileList.initialize();
 	});
 	afterEach(function() {
+		testFiles = undefined;
+		FileList.initialized = false;
+		FileList.isEmpty = true;
+		delete FileList._reloadCall;
+
 		$('#dir, #permissions, #filestable').remove();
+		notificationStub.restore();
+		alertStub.restore();
+		pushStateStub.restore();
+	});
+	describe('Getters', function() {
+		it('Returns the current directory', function() {
+			$('#dir').val('/one/two/three');
+			expect(FileList.getCurrentDirectory()).toEqual('/one/two/three');
+		});
+		it('Returns the directory permissions as int', function() {
+			$('#permissions').val('23');
+			expect(FileList.getDirectoryPermissions()).toEqual(23);
+		});
+	});
+	describe('Adding files', function() {
+		var clock, now;
+		beforeEach(function() {
+			// to prevent date comparison issues
+			clock = sinon.useFakeTimers();
+			now = new Date();
+		});
+		afterEach(function() {
+			clock.restore();
+		});
+		it('generates file element with correct attributes when calling add() with file data', function() {
+			var fileData = {
+				id: 18,
+				type: 'file',
+				name: 'testName.txt',
+				mimetype: 'plain/text',
+				size: '1234',
+				etag: 'a01234c',
+				mtime: '123456'
+			};
+			var $tr = FileList.add(fileData);
+
+			expect($tr).toBeDefined();
+			expect($tr[0].tagName.toLowerCase()).toEqual('tr');
+			expect($tr.attr('data-id')).toEqual('18');
+			expect($tr.attr('data-type')).toEqual('file');
+			expect($tr.attr('data-file')).toEqual('testName.txt');
+			expect($tr.attr('data-size')).toEqual('1234');
+			expect($tr.attr('data-etag')).toEqual('a01234c');
+			expect($tr.attr('data-permissions')).toEqual('31');
+			expect($tr.attr('data-mime')).toEqual('plain/text');
+			expect($tr.attr('data-mtime')).toEqual('123456');
+			expect($tr.find('a.name').attr('href')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=testName.txt');
+
+			expect($tr.find('.filesize').text()).toEqual('1 kB');
+			expect(FileList.findFileEl('testName.txt')[0]).toEqual($tr[0]);
+		});
+		it('generates dir element with correct attributes when calling add() with dir data', function() {
+			var fileData = {
+				id: 19,
+				type: 'dir',
+				name: 'testFolder',
+				mimetype: 'httpd/unix-directory',
+				size: '1234',
+				etag: 'a01234c',
+				mtime: '123456'
+			};
+			var $tr = FileList.add(fileData);
+
+			expect($tr).toBeDefined();
+			expect($tr[0].tagName.toLowerCase()).toEqual('tr');
+			expect($tr.attr('data-id')).toEqual('19');
+			expect($tr.attr('data-type')).toEqual('dir');
+			expect($tr.attr('data-file')).toEqual('testFolder');
+			expect($tr.attr('data-size')).toEqual('1234');
+			expect($tr.attr('data-etag')).toEqual('a01234c');
+			expect($tr.attr('data-permissions')).toEqual('31');
+			expect($tr.attr('data-mime')).toEqual('httpd/unix-directory');
+			expect($tr.attr('data-mtime')).toEqual('123456');
+
+			expect($tr.find('.filesize').text()).toEqual('1 kB');
+
+			expect(FileList.findFileEl('testFolder')[0]).toEqual($tr[0]);
+		});
+		it('generates file element with default attributes when calling add() with minimal data', function() {
+			var fileData = {
+				type: 'file',
+				name: 'testFile.txt'
+			};
+
+		    clock.tick(123456);
+			var $tr = FileList.add(fileData);
+
+			expect($tr).toBeDefined();
+			expect($tr[0].tagName.toLowerCase()).toEqual('tr');
+			expect($tr.attr('data-id')).toEqual(null);
+			expect($tr.attr('data-type')).toEqual('file');
+			expect($tr.attr('data-file')).toEqual('testFile.txt');
+			expect($tr.attr('data-size')).toEqual(null);
+			expect($tr.attr('data-etag')).toEqual(null);
+			expect($tr.attr('data-permissions')).toEqual('31');
+			expect($tr.attr('data-mime')).toEqual(null);
+			expect($tr.attr('data-mtime')).toEqual('123456');
+
+			expect($tr.find('.filesize').text()).toEqual('Pending');
+		});
+		it('generates dir element with default attributes when calling add() with minimal data', function() {
+			var fileData = {
+				type: 'dir',
+				name: 'testFolder'
+			};
+		    clock.tick(123456);
+			var $tr = FileList.add(fileData);
+
+			expect($tr).toBeDefined();
+			expect($tr[0].tagName.toLowerCase()).toEqual('tr');
+			expect($tr.attr('data-id')).toEqual(null);
+			expect($tr.attr('data-type')).toEqual('dir');
+			expect($tr.attr('data-file')).toEqual('testFolder');
+			expect($tr.attr('data-size')).toEqual(null);
+			expect($tr.attr('data-etag')).toEqual(null);
+			expect($tr.attr('data-permissions')).toEqual('31');
+			expect($tr.attr('data-mime')).toEqual('httpd/unix-directory');
+			expect($tr.attr('data-mtime')).toEqual('123456');
+
+			expect($tr.find('.filesize').text()).toEqual('Pending');
+		});
+		it('generates file element with zero size when size is explicitly zero', function() {
+			var fileData = {
+				type: 'dir',
+				name: 'testFolder',
+				size: '0'
+			};
+			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() {
+			var fileData = {
+				type: 'file',
+				name: 'P comes after O.txt'
+			};
+			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 = {
+				type: 'file',
+				name: 'P comes after O.txt'
+			};
+			FileList.setFiles(testFiles);
+			$tr = FileList.add(fileData, {insert: true});
+			// after "One.txt"
+			expect($tr.index()).toEqual(1);
+		});
+		it('removes empty content message and shows summary when adding first file', function() {
+			var fileData = {
+				type: 'file',
+				name: 'first file.txt',
+				size: 12
+			};
+			FileList.setFiles([]);
+			expect(FileList.isEmpty).toEqual(true);
+			FileList.add(fileData);
+			$summary = $('#fileList .summary');
+			expect($summary.length).toEqual(1);
+			// yes, ugly...
+			expect($summary.find('.info').text()).toEqual('0 folders and 1 file');
+			expect($summary.find('.dirinfo').hasClass('hidden')).toEqual(true);
+			expect($summary.find('.fileinfo').hasClass('hidden')).toEqual(false);
+			expect($summary.find('.filesize').text()).toEqual('12 B');
+			expect($('#filestable thead th').hasClass('hidden')).toEqual(false);
+			expect($('#emptycontent').hasClass('hidden')).toEqual(true);
+			expect(FileList.isEmpty).toEqual(false);
+		});
 	});
-	it('generates file element with correct attributes when calling addFile', function() {
-		var lastMod = new Date(10000);
-		// note: download_url is actually the link target, not the actual download URL...
-		var $tr = FileList.addFile('testName.txt', 1234, lastMod, false, false, {download_url: 'test/download/url'});
-
-		expect($tr).toBeDefined();
-		expect($tr[0].tagName.toLowerCase()).toEqual('tr');
-		expect($tr.find('a:first').attr('href')).toEqual('test/download/url');
-		expect($tr.attr('data-type')).toEqual('file');
-		expect($tr.attr('data-file')).toEqual('testName.txt');
-		expect($tr.attr('data-size')).toEqual('1234');
-		expect($tr.attr('data-permissions')).toEqual('31');
-		//expect($tr.attr('data-mime')).toEqual('plain/text');
+	describe('Removing files from the list', function() {
+		it('Removes file from list when calling remove() and updates summary', function() {
+			var $removedEl;
+			FileList.setFiles(testFiles);
+			$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.findFileEl('One.txt').length).toEqual(0);
+
+			$summary = $('#fileList .summary');
+			expect($summary.length).toEqual(1);
+			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);
+			expect($summary.find('.filesize').text()).toEqual('69 kB');
+			expect(FileList.isEmpty).toEqual(false);
+		});
+		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.findFileEl('One.txt').length).toEqual(0);
+
+			$summary = $('#fileList .summary');
+			expect($summary.length).toEqual(0);
+			expect($('#filestable thead th').hasClass('hidden')).toEqual(true);
+			expect($('#emptycontent').hasClass('hidden')).toEqual(false);
+			expect(FileList.isEmpty).toEqual(true);
+		});
 	});
-	it('generates dir element with correct attributes when calling addDir', function() {
-		var lastMod = new Date(10000);
-		var $tr = FileList.addDir('testFolder', 1234, lastMod, false);
-
-		expect($tr).toBeDefined();
-		expect($tr[0].tagName.toLowerCase()).toEqual('tr');
-		expect($tr.attr('data-type')).toEqual('dir');
-		expect($tr.attr('data-file')).toEqual('testFolder');
-		expect($tr.attr('data-size')).toEqual('1234');
-		expect($tr.attr('data-permissions')).toEqual('31');
-		//expect($tr.attr('data-mime')).toEqual('httpd/unix-directory');
+	describe('Deleting files', function() {
+		function doDelete() {
+			var request, query;
+			// note: normally called from FileActions
+			FileList.do_delete(['One.txt', 'Two.jpg']);
+
+			expect(fakeServer.requests.length).toEqual(1);
+			request = fakeServer.requests[0];
+			expect(request.url).toEqual(OC.webroot + '/index.php/apps/files/ajax/delete.php');
+
+			query = fakeServer.requests[0].requestBody;
+			expect(OC.parseQueryString(query)).toEqual({'dir': '/subdir', files: '["One.txt","Two.jpg"]'});
+		}
+		it('calls delete.php, removes the deleted entries and updates summary', function() {
+			FileList.setFiles(testFiles);
+			doDelete();
+
+			fakeServer.requests[0].respond(
+				200,
+				{ 'Content-Type': 'application/json' },
+				JSON.stringify({status: 'success'})
+			);
+
+			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);
+
+			$summary = $('#fileList .summary');
+			expect($summary.length).toEqual(1);
+			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);
+			expect($summary.find('.filesize').text()).toEqual('57 kB');
+			expect(FileList.isEmpty).toEqual(false);
+			expect($('#filestable thead th').hasClass('hidden')).toEqual(false);
+			expect($('#emptycontent').hasClass('hidden')).toEqual(true);
+
+			expect(notificationStub.notCalled).toEqual(true);
+		});
+		it('updates summary when deleting last file', function() {
+			FileList.setFiles([testFiles[0], testFiles[1]]);
+			doDelete();
+
+			fakeServer.requests[0].respond(
+				200,
+				{ 'Content-Type': 'application/json' },
+				JSON.stringify({status: 'success'})
+			);
+
+			expect(FileList.$fileList.find('tr:not(.summary)').length).toEqual(0);
+
+			$summary = $('#fileList .summary');
+			expect($summary.length).toEqual(0);
+			expect(FileList.isEmpty).toEqual(true);
+			expect($('#filestable thead th').hasClass('hidden')).toEqual(true);
+			expect($('#emptycontent').hasClass('hidden')).toEqual(false);
+		});
+		it('bring back deleted item when delete call failed', function() {
+			FileList.setFiles(testFiles);
+			doDelete();
+
+			fakeServer.requests[0].respond(
+				200,
+				{ 'Content-Type': 'application/json' },
+				JSON.stringify({status: 'error', data: {message: 'WOOT'}})
+			);
+
+			// 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(notificationStub.calledOnce).toEqual(true);
+		});
+	});
+	describe('Renaming files', function() {
+		function doRename() {
+			var $input, request;
+
+			FileList.setFiles(testFiles);
+
+			// trigger rename prompt
+			FileList.rename('One.txt');
+			$input = FileList.$fileList.find('input.filename');
+			$input.val('One_renamed.txt').blur();
+
+			expect(fakeServer.requests.length).toEqual(1);
+			var 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'});
+
+			// element is renamed before the request finishes
+			expect(FileList.findFileEl('One.txt').length).toEqual(0);
+			expect(FileList.findFileEl('One_renamed.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() {
+			doRename();
+
+			fakeServer.requests[0].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({
+				status: 'success',
+				data: {
+					name: 'One_renamed.txt'
+				}
+			}));
+
+			// element stays renamed
+			expect(FileList.findFileEl('One.txt').length).toEqual(0);
+			expect(FileList.findFileEl('One_renamed.txt').length).toEqual(1);
+
+			expect(alertStub.notCalled).toEqual(true);
+		});
+		it('Reverts file entry if rename ajax call failed', function() {
+			doRename();
+
+			fakeServer.requests[0].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({
+				status: 'error',
+				data: {
+					message: 'Something went wrong'
+				}
+			}));
+
+			// element was reverted
+			expect(FileList.findFileEl('One.txt').length).toEqual(1);
+			expect(FileList.findFileEl('One_renamed.txt').length).toEqual(0);
+
+			expect(alertStub.calledOnce).toEqual(true);
+		});
+		it('Correctly updates file link after rename', function() {
+			var $tr;
+			doRename();
+
+			fakeServer.requests[0].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({
+				status: 'success',
+				data: {
+					name: 'One_renamed.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');
+		});
+		// FIXME: fix this in the source code!
+		xit('Correctly updates file link after rename when path has same name', function() {
+			var $tr;
+			// evil case: because of buggy code
+			$('#dir').val('/One.txt/subdir');
+			doRename();
+
+			fakeServer.requests[0].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({
+				status: 'success',
+				data: {
+					name: 'One_renamed.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.txt');
+		});
+	});
+	describe('List rendering', function() {
+		it('renders a list of files using add()', function() {
+			var addSpy = sinon.spy(FileList, 'add');
+			FileList.setFiles(testFiles);
+			expect(addSpy.callCount).toEqual(4);
+			expect($('#fileList tr:not(.summary)').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);
+			expect($summary.find('.info').text()).toEqual('1 folder and 3 files');
+			expect($summary.find('.filesize').text()).toEqual('69 kB');
+		});
+		it('shows headers, summary and hide empty content message after setting files', 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);
+		});
+		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);
+		});
+		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);
+		});
+		it('calling findFileEl() can find existing file element', function() {
+			FileList.setFiles(testFiles);
+			expect(FileList.findFileEl('Two.jpg').length).toEqual(1);
+		});
+		it('calling findFileEl() returns empty when file not found in file', function() {
+			FileList.setFiles(testFiles);
+			expect(FileList.findFileEl('unexist.dat').length).toEqual(0);
+		});
+		it('only add file if in same current directory', function() {
+			$('#dir').val('/current dir');
+			var fileData = {
+				type: 'file',
+				name: 'testFile.txt',
+				directory: '/current dir'
+			};
+			var $tr = FileList.add(fileData);
+			expect(FileList.findFileEl('testFile.txt').length).toEqual(1);
+		});
+		it('triggers "fileActionsReady" event after update', function() {
+			var handler = sinon.stub();
+			FileList.$fileList.on('fileActionsReady', handler);
+			FileList.setFiles(testFiles);
+			expect(handler.calledOnce).toEqual(true);
+		});
+		it('triggers "updated" event after update', function() {
+			var handler = sinon.stub();
+			FileList.$fileList.on('updated', handler);
+			FileList.setFiles(testFiles);
+			expect(handler.calledOnce).toEqual(true);
+		});
+	});
+	describe('file previews', function() {
+		var previewLoadStub;
+
+		function getImageUrl($el) {
+			// might be slightly different cross-browser
+			var url = $el.css('background-image');
+			var r = url.match(/url\(['"]?([^'")]*)['"]?\)/);
+			if (!r) {
+				return url;
+			}
+			return r[1];
+		}
+
+		beforeEach(function() {
+			previewLoadStub = sinon.stub(Files, 'lazyLoadPreview');
+		});
+		afterEach(function() {
+			previewLoadStub.restore();
+		});
+		it('renders default icon for file when none provided and no preview is available', function() {
+			var fileData = {
+				type: 'file',
+				name: 'testFile.txt'
+			};
+			var $tr = FileList.add(fileData);
+			var $td = $tr.find('td.filename');
+			expect(getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/file.svg');
+			expect(previewLoadStub.notCalled).toEqual(true);
+		});
+		it('renders default icon for dir when none provided and no preview is available', function() {
+			var fileData = {
+				type: 'dir',
+				name: 'test dir'
+			};
+			var $tr = FileList.add(fileData);
+			var $td = $tr.find('td.filename');
+			expect(getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/folder.svg');
+			expect(previewLoadStub.notCalled).toEqual(true);
+		});
+		it('renders provided icon for file when provided', function() {
+			var fileData = {
+				type: 'file',
+				name: 'test dir',
+				icon: OC.webroot + '/core/img/filetypes/application-pdf.svg'
+			};
+			var $tr = FileList.add(fileData);
+			var $td = $tr.find('td.filename');
+			expect(getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/application-pdf.svg');
+			expect(previewLoadStub.notCalled).toEqual(true);
+		});
+		it('renders preview when no icon was provided and preview is available', function() {
+			var fileData = {
+				type: 'file',
+				name: 'test dir',
+				isPreviewAvailable: true
+			};
+			var $tr = FileList.add(fileData);
+			var $td = $tr.find('td.filename');
+			expect(getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/file.svg');
+			expect(previewLoadStub.calledOnce).toEqual(true);
+			// third argument is callback
+			previewLoadStub.getCall(0).args[2](OC.webroot + '/somepath.png');
+			expect(getImageUrl($td)).toEqual(OC.webroot + '/somepath.png');
+		});
+		it('renders default file type icon when no icon was provided and no preview is available', function() {
+			var fileData = {
+				type: 'file',
+				name: 'test dir',
+				isPreviewAvailable: false
+			};
+			var $tr = FileList.add(fileData);
+			var $td = $tr.find('td.filename');
+			expect(getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/file.svg');
+			expect(previewLoadStub.notCalled).toEqual(true);
+		});
+	});
+	describe('viewer mode', function() {
+		it('enabling viewer mode hides files table and action buttons', function() {
+			FileList.setViewerMode(true);
+			expect($('#filestable').hasClass('hidden')).toEqual(true);
+			expect($('.actions').hasClass('hidden')).toEqual(true);
+			expect($('.notCreatable').hasClass('hidden')).toEqual(true);
+		});
+		it('disabling viewer mode restores files table and action buttons', function() {
+			FileList.setViewerMode(true);
+			FileList.setViewerMode(false);
+			expect($('#filestable').hasClass('hidden')).toEqual(false);
+			expect($('.actions').hasClass('hidden')).toEqual(false);
+			expect($('.notCreatable').hasClass('hidden')).toEqual(true);
+		});
+		it('disabling viewer mode restores files table and action buttons with correct permissions', function() {
+			$('#permissions').val(0);
+			FileList.setViewerMode(true);
+			FileList.setViewerMode(false);
+			expect($('#filestable').hasClass('hidden')).toEqual(false);
+			expect($('.actions').hasClass('hidden')).toEqual(true);
+			expect($('.notCreatable').hasClass('hidden')).toEqual(false);
+		});
+	});
+	describe('loading file list', function() {
+		beforeEach(function() {
+			var data = {
+				status: 'success',
+				data: {
+					files: testFiles,
+					permissions: 31
+				}
+			};
+			fakeServer.respondWith(/\/index\.php\/apps\/files\/ajax\/list.php\?dir=%2F(subdir|anothersubdir)/, [
+					200, {
+						"Content-Type": "application/json"
+					},
+					JSON.stringify(data)
+			]);
+		});
+		it('fetches file list from server and renders it when reload() is called', function() {
+			FileList.reload();
+			expect(fakeServer.requests.length).toEqual(1);
+			var url = fakeServer.requests[0].url;
+			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.findFileEl('One.txt').length).toEqual(1);
+		});
+		it('switches dir and fetches file list when calling changeDirectory()', function() {
+			FileList.changeDirectory('/anothersubdir');
+			expect(FileList.getCurrentDirectory()).toEqual('/anothersubdir');
+			expect(fakeServer.requests.length).toEqual(1);
+			var url = fakeServer.requests[0].url;
+			var query = url.substr(url.indexOf('?') + 1);
+			expect(OC.parseQueryString(query)).toEqual({'dir': '/anothersubdir'});
+			fakeServer.respond();
+		});
+		it('switches to root dir when current directory does not exist', function() {
+			fakeServer.respondWith(/\/index\.php\/apps\/files\/ajax\/list.php\?dir=%2funexist/, [
+					404, {
+						"Content-Type": "application/json"
+					},
+					''
+			]);
+			FileList.changeDirectory('/unexist');
+			fakeServer.respond();
+			expect(FileList.getCurrentDirectory()).toEqual('/');
+		});
+		it('shows mask before loading file list then hides it at the end', function() {
+			var showMaskStub = sinon.stub(FileList, 'showMask');
+			var hideMaskStub = sinon.stub(FileList, 'hideMask');
+			FileList.changeDirectory('/anothersubdir');
+			expect(showMaskStub.calledOnce).toEqual(true);
+			expect(hideMaskStub.calledOnce).toEqual(false);
+			fakeServer.respond();
+			expect(showMaskStub.calledOnce).toEqual(true);
+			expect(hideMaskStub.calledOnce).toEqual(true);
+			showMaskStub.restore();
+			hideMaskStub.restore();
+		});
+		it('changes URL to target dir', function() {
+			FileList.changeDirectory('/somedir');
+			expect(pushStateStub.calledOnce).toEqual(true);
+			expect(pushStateStub.getCall(0).args[0]).toEqual({dir: '/somedir'});
+			expect(pushStateStub.getCall(0).args[2]).toEqual(OC.webroot + '/index.php/apps/files?dir=/somedir');
+		});
+		it('refreshes breadcrumb after update', function() {
+			var setDirSpy = sinon.spy(FileList.breadcrumb, 'setDirectory');
+			FileList.changeDirectory('/anothersubdir');
+			fakeServer.respond();
+			expect(FileList.breadcrumb.setDirectory.calledOnce).toEqual(true);
+			expect(FileList.breadcrumb.setDirectory.calledWith('/anothersubdir')).toEqual(true);
+			setDirSpy.restore();
+		});
+	});
+	describe('breadcrumb events', function() {
+		beforeEach(function() {
+			var data = {
+				status: 'success',
+				data: {
+					files: testFiles,
+					permissions: 31
+				}
+			};
+			fakeServer.respondWith(/\/index\.php\/apps\/files\/ajax\/list.php\?dir=%2Fsubdir/, [
+					200, {
+						"Content-Type": "application/json"
+					},
+					JSON.stringify(data)
+			]);
+		});
+		it('clicking on root breadcrumb changes directory to root', function() {
+			FileList.changeDirectory('/subdir/two/three with space/four/five');
+			fakeServer.respond();
+			var changeDirStub = sinon.stub(FileList, 'changeDirectory');
+			FileList.breadcrumb.$el.find('.crumb:eq(0)').click();
+
+			expect(changeDirStub.calledOnce).toEqual(true);
+			expect(changeDirStub.getCall(0).args[0]).toEqual('/');
+			changeDirStub.restore();
+		});
+		it('clicking on breadcrumb changes directory', function() {
+			FileList.changeDirectory('/subdir/two/three with space/four/five');
+			fakeServer.respond();
+			var changeDirStub = sinon.stub(FileList, 'changeDirectory');
+			FileList.breadcrumb.$el.find('.crumb:eq(3)').click();
+
+			expect(changeDirStub.calledOnce).toEqual(true);
+			expect(changeDirStub.getCall(0).args[0]).toEqual('/subdir/two/three with space');
+			changeDirStub.restore();
+		});
+		it('dropping files on breadcrumb calls move operation', function() {
+			var request, query, testDir = '/subdir/two/three with space/four/five';
+			FileList.changeDirectory(testDir);
+			fakeServer.respond();
+			var $crumb = FileList.breadcrumb.$el.find('.crumb:eq(3)');
+			// no idea what this is but is required by the handler
+			var ui = {
+				helper: {
+					find: sinon.stub()
+				}
+			};
+			// returns a list of tr that were dragged
+			// FIXME: why are their attributes different than the
+			// regular file trs ?
+			ui.helper.find.returns([
+				$('<tr data-filename="One.txt" data-dir="' + testDir + '"></tr>'),
+				$('<tr data-filename="Two.jpg" data-dir="' + testDir + '"></tr>')
+			]);
+			// simulate drop event
+			FileList._onDropOnBreadCrumb.call($crumb, new $.Event('drop'), ui);
+
+			// will trigger two calls to move.php (first one was previous list.php)
+			expect(fakeServer.requests.length).toEqual(3);
+
+			request = fakeServer.requests[1];
+			expect(request.method).toEqual('POST');
+			expect(request.url).toEqual(OC.webroot + '/index.php/apps/files/ajax/move.php');
+			query = OC.parseQueryString(request.requestBody);
+			expect(query).toEqual({
+				target: '/subdir/two/three with space',
+				dir: testDir,
+				file: 'One.txt'
+			});
+
+			request = fakeServer.requests[2];
+			expect(request.method).toEqual('POST');
+			expect(request.url).toEqual(OC.webroot + '/index.php/apps/files/ajax/move.php');
+			query = OC.parseQueryString(request.requestBody);
+			expect(query).toEqual({
+				target: '/subdir/two/three with space',
+				dir: testDir,
+				file: 'Two.jpg'
+			});
+		});
+		it('dropping files on same dir breadcrumb does nothing', function() {
+			var request, query, testDir = '/subdir/two/three with space/four/five';
+			FileList.changeDirectory(testDir);
+			fakeServer.respond();
+			var $crumb = FileList.breadcrumb.$el.find('.crumb:last');
+			// no idea what this is but is required by the handler
+			var ui = {
+				helper: {
+					find: sinon.stub()
+				}
+			};
+			// returns a list of tr that were dragged
+			// FIXME: why are their attributes different than the
+			// regular file trs ?
+			ui.helper.find.returns([
+				$('<tr data-filename="One.txt" data-dir="' + testDir + '"></tr>'),
+				$('<tr data-filename="Two.jpg" data-dir="' + testDir + '"></tr>')
+			]);
+			// simulate drop event
+			FileList._onDropOnBreadCrumb.call($crumb, new $.Event('drop'), ui);
+
+			// no extra server request
+			expect(fakeServer.requests.length).toEqual(1);
+		});
 	});
 	describe('Download Url', function() {
 		it('returns correct download URL for single files', function() {
-			expect(FileList.getDownloadUrl('some file.txt')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=some%20file.txt');
-			expect(FileList.getDownloadUrl('some file.txt', '/anotherpath/abc')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fanotherpath%2Fabc&files=some%20file.txt');
+			expect(Files.getDownloadUrl('some file.txt')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=some%20file.txt');
+			expect(Files.getDownloadUrl('some file.txt', '/anotherpath/abc')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fanotherpath%2Fabc&files=some%20file.txt');
 			$('#dir').val('/');
-			expect(FileList.getDownloadUrl('some file.txt')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2F&files=some%20file.txt');
+			expect(Files.getDownloadUrl('some file.txt')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2F&files=some%20file.txt');
 		});
 		it('returns correct download URL for multiple files', function() {
-			expect(FileList.getDownloadUrl(['a b c.txt', 'd e f.txt'])).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=%5B%22a%20b%20c.txt%22%2C%22d%20e%20f.txt%22%5D');
+			expect(Files.getDownloadUrl(['a b c.txt', 'd e f.txt'])).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=%5B%22a%20b%20c.txt%22%2C%22d%20e%20f.txt%22%5D');
+		});
+		it('returns the correct ajax URL', function() {
+			expect(Files.getAjaxUrl('test', {a:1, b:'x y'})).toEqual(OC.webroot + '/index.php/apps/files/ajax/test.php?a=1&b=x%20y');
 		});
 	});
 });
diff --git a/apps/files_sharing/ajax/list.php b/apps/files_sharing/ajax/list.php
new file mode 100644
index 00000000000..4b645496253
--- /dev/null
+++ b/apps/files_sharing/ajax/list.php
@@ -0,0 +1,91 @@
+<?php
+/**
+ * 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/>.
+ *
+ */
+
+// only need filesystem apps
+$RUNTIME_APPTYPES=array('filesystem');
+
+// Init owncloud
+
+if(!\OC_App::isEnabled('files_sharing')){
+	exit;
+}
+
+if(!isset($_GET['t'])){
+	\OC_Response::setStatus(400); //400 Bad Request
+	\OC_Log::write('core-preview', 'No token parameter was passed', \OC_Log::DEBUG);
+	exit;
+}
+
+$token = $_GET['t'];
+
+$password = null;
+if (isset($_POST['password'])) {
+	$password = $_POST['password'];
+}
+
+$relativePath = null;
+if (isset($_GET['dir'])) {
+	$relativePath = $_GET['dir'];
+}
+
+$data = \OCA\Files_Sharing\Helper::setupFromToken($token, $relativePath, $password);
+
+$linkItem = $data['linkItem'];
+// Load the files
+$dir = $data['realPath'];
+
+$dir = \OC\Files\Filesystem::normalizePath($dir);
+if (!\OC\Files\Filesystem::is_dir($dir . '/')) {
+	\OC_Response::setStatus(404);
+	\OCP\JSON::error(array('success' => false));
+	exit();
+}
+
+$data = array();
+$baseUrl = OCP\Util::linkTo('files_sharing', 'index.php') . '?t=' . urlencode($token) . '&dir=';
+
+// make filelist
+$files = \OCA\Files\Helper::getFiles($dir);
+
+$formattedFiles = array();
+foreach ($files as $file) {
+	$entry = \OCA\Files\Helper::formatFileInfo($file);
+	unset($entry['directory']); // for now
+	$entry['permissions'] = \OCP\PERMISSION_READ;
+	$formattedFiles[] = $entry;
+}
+
+$data['directory'] = $relativePath;
+$data['files'] = $formattedFiles;
+$data['dirToken'] = $linkItem['token'];
+
+$permissions = $linkItem['permissions'];
+
+// if globally disabled
+if (OC_Appconfig::getValue('core', 'shareapi_allow_public_upload', 'yes') === 'no') {
+	// only allow reading
+	$permissions = \OCP\PERMISSION_READ;
+}
+
+$data['permissions'] = $permissions;
+
+OCP\JSON::success(array('data' => $data));
diff --git a/apps/files_sharing/css/public.css b/apps/files_sharing/css/public.css
index 5246a4b2fec..f0b9b04491f 100644
--- a/apps/files_sharing/css/public.css
+++ b/apps/files_sharing/css/public.css
@@ -35,6 +35,11 @@ body {
 	background: #fff;
 	text-align: center;
 	margin: 45px auto 0;
+	min-height: 150px;
+}
+
+#preview .notCreatable {
+	display: none;
 }
 
 #noPreview {
diff --git a/apps/files_sharing/js/public.js b/apps/files_sharing/js/public.js
index 06c168969de..9ce8985f1fd 100644
--- a/apps/files_sharing/js/public.js
+++ b/apps/files_sharing/js/public.js
@@ -8,16 +8,7 @@
  *
  */
 
-/* global OC, FileList, FileActions */
-
-// Override download path to files_sharing/public.php
-function fileDownloadPath(dir, file) {
-	var url = $('#downloadURL').val();
-	if (url.indexOf('&path=') != -1) {
-		url += '/'+file;
-	}
-	return url;
-}
+/* global OC, FileActions, FileList, Files */
 
 $(document).ready(function() {
 
@@ -31,31 +22,43 @@ $(document).ready(function() {
 				action($('#filename').val());
 			}
 		}
-		FileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function(filename) {
-			var tr = FileList.findFileEl(filename);
-			if (tr.length > 0) {
-				window.location = $(tr).find('a.name').attr('href');
-			}
-		});
+	}
 
-		// override since the format is different
-		FileList.getDownloadUrl = function(filename, dir) {
-			if ($.isArray(filename)) {
-				filename = JSON.stringify(filename);
-			}
-			var path = dir || FileList.getCurrentDirectory();
-			var params = {
-				service: 'files',
-				t: $('#sharingToken').val(),
-				path: path,
-				download: null
-			};
-			if (filename) {
-				params.files = filename;
-			}
-			return OC.filePath('', '', 'public.php') + '?' + OC.buildQueryString(params);
+	// override since the format is different
+	Files.getDownloadUrl = function(filename, dir) {
+		if ($.isArray(filename)) {
+			filename = JSON.stringify(filename);
+		}
+		var path = dir || FileList.getCurrentDirectory();
+		var params = {
+			service: 'files',
+			t: $('#sharingToken').val(),
+			path: path,
+			files: filename,
+			download: null
 		};
-	}
+		return OC.filePath('', '', 'public.php') + '?' + OC.buildQueryString(params);
+	};
+
+	Files.getAjaxUrl = function(action, params) {
+		params = params || {};
+		params.t = $('#sharingToken').val();
+		return OC.filePath('files_sharing', 'ajax', action + '.php') + '?' + OC.buildQueryString(params);
+	};
+
+	FileList.linkTo = function(dir) {
+		var params = {
+			service: 'files',
+			t: $('#sharingToken').val(),
+			dir: dir
+		};
+		return OC.filePath('', '', 'public.php') + '?' + OC.buildQueryString(params);
+	};
+
+	Files.generatePreviewUrl = function(urlSpec) {
+		urlSpec.t = $('#dirToken').val();
+		return OC.generateUrl('/apps/files_sharing/ajax/publicpreview.php?') + $.param(urlSpec);
+	};
 
 	var file_upload_start = $('#file_upload_start');
 	file_upload_start.on('fileuploadadd', function(e, data) {
diff --git a/apps/files_sharing/js/share.js b/apps/files_sharing/js/share.js
index 9f0ed12f935..ea518f3b70e 100644
--- a/apps/files_sharing/js/share.js
+++ b/apps/files_sharing/js/share.js
@@ -1,15 +1,35 @@
+/*
+ * Copyright (c) 2014
+ *
+ * This file is licensed under the Affero General Public License version 3
+ * or later.
+ *
+ * See the COPYING-README file.
+ *
+ */
+
+/* global OC, t, FileList, FileActions */
 $(document).ready(function() {
 
 	var disableSharing = $('#disableSharing').data('status'),
 		sharesLoaded = false;
 
 	if (typeof OC.Share !== 'undefined' && typeof FileActions !== 'undefined'  && !disableSharing) {
+		var oldCreateRow = FileList._createRow;
+		FileList._createRow = function(fileData) {
+			var tr = oldCreateRow.apply(this, arguments);
+			if (fileData.shareOwner) {
+				tr.attr('data-share-owner', fileData.shareOwner);
+			}
+			return tr;
+		};
+
 		$('#fileList').on('fileActionsReady',function(){
 
-			var allShared = $('#fileList').find('[data-share-owner]').find('[data-Action="Share"]');
+			var allShared = $('#fileList').find('[data-share-owner] [data-Action="Share"]');
 			allShared.addClass('permanent');
 			allShared.find('span').text(function(){
-				$owner = $(this).closest('tr').attr('data-share-owner');
+				var $owner = $(this).closest('tr').attr('data-share-owner');
 				return ' ' + t('files_sharing', 'Shared by {owner}', {owner: $owner});
 			});
 
diff --git a/apps/files_sharing/lib/helper.php b/apps/files_sharing/lib/helper.php
new file mode 100644
index 00000000000..b602fe3599d
--- /dev/null
+++ b/apps/files_sharing/lib/helper.php
@@ -0,0 +1,114 @@
+<?php
+
+namespace OCA\Files_Sharing;
+
+class Helper {
+
+	/**
+	 * Sets up the filesystem and user for public sharing
+	 * @param string $token string share token
+	 * @param string $relativePath optional path relative to the share
+	 * @param string $password optional password
+	 */
+	public static function setupFromToken($token, $relativePath = null, $password = null) {
+		\OC_User::setIncognitoMode(true);
+
+		$linkItem = \OCP\Share::getShareByToken($token);
+		if($linkItem === false || ($linkItem['item_type'] !== 'file' && $linkItem['item_type'] !== 'folder')) {
+			\OC_Response::setStatus(404);
+			\OC_Log::write('core-preview', 'Passed token parameter is not valid', \OC_Log::DEBUG);
+			exit;
+		}
+
+		if(!isset($linkItem['uid_owner']) || !isset($linkItem['file_source'])) {
+			\OC_Response::setStatus(500);
+			\OC_Log::write('core-preview', 'Passed token seems to be valid, but it does not contain all necessary information . ("' . $token . '")', \OC_Log::WARN);
+			exit;
+		}
+
+		$type = $linkItem['item_type'];
+		$fileSource = $linkItem['file_source'];
+		$shareOwner = $linkItem['uid_owner'];
+		$rootLinkItem = \OCP\Share::resolveReShare($linkItem);
+		$path = null;
+		if (isset($rootLinkItem['uid_owner'])) {
+			\OCP\JSON::checkUserExists($rootLinkItem['uid_owner']);
+			\OC_Util::tearDownFS();
+			\OC_Util::setupFS($rootLinkItem['uid_owner']);
+			$path = \OC\Files\Filesystem::getPath($linkItem['file_source']);
+		}
+
+		if ($path === null) {
+			\OCP\Util::writeLog('share', 'could not resolve linkItem', \OCP\Util::DEBUG);
+			\OC_Response::setStatus(404);
+			\OCP\JSON::error(array('success' => false));
+			exit();
+		}
+
+		if (!isset($linkItem['item_type'])) {
+			\OCP\Util::writeLog('share', 'No item type set for share id: ' . $linkItem['id'], \OCP\Util::ERROR);
+			\OC_Response::setStatus(404);
+			\OCP\JSON::error(array('success' => false));
+			exit();
+		}
+
+		if (isset($linkItem['share_with'])) {
+			if (!self::authenticate($linkItem, $password)) {
+				\OC_Response::setStatus(403);
+				\OCP\JSON::error(array('success' => false));
+				exit();
+			}
+		}
+
+		$basePath = $path;
+		$rootName = basename($path);
+
+		if ($relativePath !== null && \OC\Files\Filesystem::isReadable($basePath . $relativePath)) {
+			$path .= \OC\Files\Filesystem::normalizePath($relativePath);
+		}
+
+		return array(
+			'linkItem' => $linkItem,
+			'basePath' => $basePath,
+			'realPath' => $path
+		);
+	}
+
+	/**
+	 * Authenticate link item with the given password
+	 * or with the session if no password was given.
+	 * @param array $linkItem link item array
+	 * @param string $password optional password
+	 *
+	 * @return true if authorized, false otherwise
+	 */
+	public static function authenticate($linkItem, $password) {
+		if ($password !== null) {
+			if ($linkItem['share_type'] == \OCP\Share::SHARE_TYPE_LINK) {
+				// Check Password
+				$forcePortable = (CRYPT_BLOWFISH != 1);
+				$hasher = new PasswordHash(8, $forcePortable);
+				if (!($hasher->CheckPassword($password.OC_Config::getValue('passwordsalt', ''),
+											 $linkItem['share_with']))) {
+					return false;
+				} else {
+					// Save item id in session for future requests
+					\OC::$session->set('public_link_authenticated', $linkItem['id']);
+				}
+			} else {
+				\OCP\Util::writeLog('share', 'Unknown share type '.$linkItem['share_type']
+					.' for share id '.$linkItem['id'], \OCP\Util::ERROR);
+				return false;
+			}
+
+		}
+		else {
+			// not authenticated ?
+			if ( ! \OC::$session->exists('public_link_authenticated')
+				|| \OC::$session->get('public_link_authenticated') !== $linkItem['id']) {
+				return false;
+			}
+		}
+		return true;
+	}
+}
diff --git a/apps/files_sharing/public.php b/apps/files_sharing/public.php
index fe61dd4d5a0..ba2494a7b1a 100644
--- a/apps/files_sharing/public.php
+++ b/apps/files_sharing/public.php
@@ -11,31 +11,6 @@ if ($appConfig->getValue('core', 'shareapi_allow_links', 'yes') !== 'yes') {
 	exit();
 }
 
-function fileCmp($a, $b) {
-	if ($a['type'] == 'dir' and $b['type'] != 'dir') {
-		return -1;
-	} elseif ($a['type'] != 'dir' and $b['type'] == 'dir') {
-		return 1;
-	} else {
-		return strnatcasecmp($a['name'], $b['name']);
-	}
-}
-
-function determineIcon($file, $sharingRoot, $sharingToken) {
-	// for folders we simply reuse the files logic
-	if($file['type'] == 'dir') {
-		return \OCA\Files\Helper::determineIcon($file);
-	}
-
-	$relativePath = substr($file['path'], 6);
-	$relativePath = substr($relativePath, strlen($sharingRoot));
-	if($file['isPreviewAvailable']) {
-		return OCP\publicPreview_icon($relativePath, $sharingToken) . '&c=' . $file['etag'];
-	}
-	$icon = OCP\mimetype_icon($file['mimetype']);
-	return substr($icon, 0, -3) . 'svg';
-}
-
 if (isset($_GET['t'])) {
 	$token = $_GET['t'];
 	$linkItem = OCP\Share::getShareByToken($token, false);
@@ -153,13 +128,10 @@ if (isset($path)) {
 		$tmpl->assign('mimetype', \OC\Files\Filesystem::getMimeType($path));
 		$tmpl->assign('dirToken', $linkItem['token']);
 		$tmpl->assign('sharingToken', $token);
-		$allowPublicUploadEnabled = (bool) ($linkItem['permissions'] & OCP\PERMISSION_CREATE);
-		if ($appConfig->getValue('core', 'shareapi_allow_public_upload', 'yes') === 'no') {
-			$allowPublicUploadEnabled = false;
-		}
-		if ($linkItem['item_type'] !== 'folder') {
-			$allowPublicUploadEnabled = false;
-		}
+		$tmpl->assign('uploadMaxFilesize', $maxUploadFilesize);
+		$tmpl->assign('uploadMaxHumanFilesize', OCP\Util::humanFileSize($maxUploadFilesize));
+		$tmpl->assign('freeSpace', $freeSpace);
+		$tmpl->assign('uploadLimit', $uploadLimit); // PHP upload limit
 
 		$urlLinkIdentifiers= (isset($token)?'&t='.$token:'')
 							.(isset($_GET['dir'])?'&dir='.$_GET['dir']:'')
@@ -170,64 +142,18 @@ if (isset($path)) {
 
 			OCP\Util::addStyle('files', 'files');
 			OCP\Util::addStyle('files', 'upload');
+			OCP\Util::addScript('files', 'breadcrumb');
 			OCP\Util::addScript('files', 'files');
 			OCP\Util::addScript('files', 'filelist');
 			OCP\Util::addscript('files', 'keyboardshortcuts');
 			$files = array();
 			$rootLength = strlen($basePath) + 1;
-			$totalSize = 0;
-			foreach (\OC\Files\Filesystem::getDirectoryContent($path) as $i) {
-				$totalSize += $i['size'];
-				$i['date'] = OCP\Util::formatDate($i['mtime']);
-				if ($i['type'] == 'file') {
-					$fileinfo = pathinfo($i['name']);
-					$i['basename'] = $fileinfo['filename'];
-					if (!empty($fileinfo['extension'])) {
-						$i['extension'] = '.' . $fileinfo['extension'];
-					} else {
-						$i['extension'] = '';
-					}
-				}
-				$i['isPreviewAvailable'] = \OC::$server->getPreviewManager()->isMimeSupported($i['mimetype']);
-				$i['directory'] = $getPath;
-				$i['permissions'] = OCP\PERMISSION_READ;
-				$i['icon'] = determineIcon($i, $basePath, $token);
-				$files[] = $i;
-			}
-			usort($files, "fileCmp");
-
-			// Make breadcrumb
-			$breadcrumb = array();
-			$pathtohere = '';
-			foreach (explode('/', $getPath) as $i) {
-				if ($i != '') {
-					$pathtohere .= '/' . $i;
-					$breadcrumb[] = array('dir' => $pathtohere, 'name' => $i);
-				}
-			}
-			$list = new OCP\Template('files', 'part.list', '');
-			$list->assign('files', $files);
-			$list->assign('baseURL', OCP\Util::linkToPublic('files') . $urlLinkIdentifiers . '&path=');
-			$list->assign('downloadURL',
-				OCP\Util::linkToPublic('files') . $urlLinkIdentifiers . '&download&path=');
-			$list->assign('isPublic', true);
-			$list->assign('sharingtoken', $token);
-			$list->assign('sharingroot', $basePath);
-			$breadcrumbNav = new OCP\Template('files', 'part.breadcrumb', '');
-			$breadcrumbNav->assign('breadcrumb', $breadcrumb);
-			$breadcrumbNav->assign('rootBreadCrumb', $rootName);
-			$breadcrumbNav->assign('baseURL', OCP\Util::linkToPublic('files') . $urlLinkIdentifiers . '&path=');
 			$maxUploadFilesize=OCP\Util::maxUploadFilesize($path);
-			$fileHeader = (!isset($files) or count($files) > 0);
-			$emptyContent = ($allowPublicUploadEnabled and !$fileHeader);
 
 			$freeSpace=OCP\Util::freeSpace($path);
 			$uploadLimit=OCP\Util::uploadLimit();
 			$folder = new OCP\Template('files', 'index', '');
-			$folder->assign('fileList', $list->fetchPage());
-			$folder->assign('breadcrumb', $breadcrumbNav->fetchPage());
 			$folder->assign('dir', $getPath);
-			$folder->assign('isCreatable', $allowPublicUploadEnabled);
 			$folder->assign('dirToken', $linkItem['token']);
 			$folder->assign('permissions', OCP\PERMISSION_READ);
 			$folder->assign('isPublic',true);
@@ -239,15 +165,11 @@ if (isset($path)) {
 			$folder->assign('uploadLimit', $uploadLimit); // PHP upload limit
 			$folder->assign('allowZipDownload', intval(OCP\Config::getSystemValue('allowZipDownload', true)));
 			$folder->assign('usedSpacePercent', 0);
-			$folder->assign('fileHeader', $fileHeader);
 			$folder->assign('disableSharing', true);
 			$folder->assign('trash', false);
-			$folder->assign('emptyContent', $emptyContent);
-			$folder->assign('ajaxLoad', false);
 			$tmpl->assign('folder', $folder->fetchPage());
 			$maxInputFileSize = OCP\Config::getSystemValue('maxZipInputSize', OCP\Util::computerFileSize('800 MB'));
-			$allowZip = OCP\Config::getSystemValue('allowZipDownload', true)
-						&& ( $maxInputFileSize === 0 || $totalSize <= $maxInputFileSize);
+			$allowZip = OCP\Config::getSystemValue('allowZipDownload', true);
 			$tmpl->assign('allowZipDownload', intval($allowZip));
 			$tmpl->assign('downloadURL',
 				OCP\Util::linkToPublic('files') . $urlLinkIdentifiers . '&download&path=' . urlencode($getPath));
diff --git a/apps/files_trashbin/ajax/list.php b/apps/files_trashbin/ajax/list.php
index cec18c46525..89a55114524 100644
--- a/apps/files_trashbin/ajax/list.php
+++ b/apps/files_trashbin/ajax/list.php
@@ -4,21 +4,8 @@ OCP\JSON::checkLoggedIn();
 
 // Load the files
 $dir = isset( $_GET['dir'] ) ? $_GET['dir'] : '';
-$doBreadcrumb = isset( $_GET['breadcrumb'] ) ? true : false;
 $data = array();
 
-// Make breadcrumb
-if($doBreadcrumb) {
-	$breadcrumb = \OCA\Files_Trashbin\Helper::makeBreadcrumb($dir);
-
-	$breadcrumbNav = new OCP\Template('files_trashbin', 'part.breadcrumb', '');
-	$breadcrumbNav->assign('breadcrumb', $breadcrumb, false);
-	$breadcrumbNav->assign('baseURL', OCP\Util::linkTo('files_trashbin', 'index.php') . '?dir=');
-	$breadcrumbNav->assign('home', OCP\Util::linkTo('files', 'index.php'));
-
-	$data['breadcrumb'] = $breadcrumbNav->fetchPage();
-}
-
 // make filelist
 try {
 	$files = \OCA\Files_Trashbin\Helper::getTrashFiles($dir);
@@ -27,19 +14,11 @@ try {
 	exit();
 }
 
-$dirlisting = false;
-if ($dir && $dir !== '/') {
-    $dirlisting = true;
-}
-
 $encodedDir = \OCP\Util::encodePath($dir);
-$list = new OCP\Template('files_trashbin', 'part.list', '');
-$list->assign('files', $files, false);
-$list->assign('baseURL', OCP\Util::linkTo('files_trashbin', 'index.php'). '?dir='.$encodedDir);
-$list->assign('downloadURL', OCP\Util::linkToRoute('download', array('file' => '/')));
-$list->assign('dirlisting', $dirlisting);
-$list->assign('disableDownloadActions', true);
-$data['files'] = $list->fetchPage();
+
+$data['permissions'] = 0;
+$data['directory'] = $dir;
+$data['files'] = \OCA\Files_Trashbin\Helper::formatFileInfos($files);
 
 OCP\JSON::success(array('data' => $data));
 
diff --git a/apps/files_trashbin/ajax/preview.php b/apps/files_trashbin/ajax/preview.php
index ce64d9ecc9f..32905b2a71c 100644
--- a/apps/files_trashbin/ajax/preview.php
+++ b/apps/files_trashbin/ajax/preview.php
@@ -34,7 +34,7 @@ try{
 	if ($view->is_dir($file)) {
 		$mimetype = 'httpd/unix-directory';
 	} else {
-		$pathInfo = pathinfo($file);
+		$pathInfo = pathinfo(ltrim($file, '/'));
 		$fileName = $pathInfo['basename'];
 		// if in root dir
 		if ($pathInfo['dirname'] === '.') {
diff --git a/apps/files_trashbin/ajax/undelete.php b/apps/files_trashbin/ajax/undelete.php
index 9c3ccba7ed8..2b00078669e 100644
--- a/apps/files_trashbin/ajax/undelete.php
+++ b/apps/files_trashbin/ajax/undelete.php
@@ -19,7 +19,7 @@ if (isset($_POST['allfiles']) and $_POST['allfiles'] === 'true') {
 	foreach (OCA\Files_Trashbin\Helper::getTrashFiles($dir) as $file) {
 		$fileName = $file['name'];
 		if (!$dirListing) {
-			$fileName .= '.d' . $file['timestamp'];
+			$fileName .= '.d' . $file['mtime'];
 		}
 		$list[] = $fileName;
 	}
diff --git a/apps/files_trashbin/css/trash.css b/apps/files_trashbin/css/trash.css
index 97819f4e80b..7ca3e355fc2 100644
--- a/apps/files_trashbin/css/trash.css
+++ b/apps/files_trashbin/css/trash.css
@@ -1,3 +1,4 @@
-#fileList td a.file, #fileList td a.file span {
+#fileList tr[data-type="file"] td a.name,
+#fileList tr[data-type="file"] td a.name span {
     cursor: default;
 }
diff --git a/apps/files_trashbin/index.php b/apps/files_trashbin/index.php
index f0c5b0508b8..e63fe1e4188 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', 'breadcrumb');
 OCP\Util::addScript('files', 'filelist');
 // filelist overrides
 OCP\Util::addScript('files_trashbin', 'filelist');
@@ -34,48 +35,7 @@ if ($isIE8 && isset($_GET['dir'])){
 	exit();
 }
 
-$ajaxLoad = false;
-
-if (!$isIE8){
-	try {
-		$files = \OCA\Files_Trashbin\Helper::getTrashFiles($dir);
-	} catch (Exception $e) {
-		header('Location: ' . OCP\Util::linkTo('files_trashbin', 'index.php'));
-		exit();
-	}
-}
-else{
-	$files = array();
-	$ajaxLoad = true;
-}
-
-$dirlisting = false;
-if ($dir && $dir !== '/') {
-    $dirlisting = true;
-}
-
-$breadcrumb = \OCA\Files_Trashbin\Helper::makeBreadcrumb($dir);
-
-$breadcrumbNav = new OCP\Template('files_trashbin', 'part.breadcrumb', '');
-$breadcrumbNav->assign('breadcrumb', $breadcrumb);
-$breadcrumbNav->assign('baseURL', OCP\Util::linkTo('files_trashbin', 'index.php') . '?dir=');
-$breadcrumbNav->assign('home', OCP\Util::linkTo('files', 'index.php'));
-
-$list = new OCP\Template('files_trashbin', 'part.list', '');
-$list->assign('files', $files);
-
-$encodedDir = \OCP\Util::encodePath($dir);
-$list->assign('baseURL', OCP\Util::linkTo('files_trashbin', 'index.php'). '?dir='.$encodedDir);
-$list->assign('downloadURL', OCP\Util::linkTo('files_trashbin', 'download.php') . '?file='.$encodedDir);
-$list->assign('dirlisting', $dirlisting);
-$list->assign('disableDownloadActions', true);
-
-$tmpl->assign('dirlisting', $dirlisting);
-$tmpl->assign('breadcrumb', $breadcrumbNav->fetchPage());
-$tmpl->assign('fileList', $list->fetchPage());
-$tmpl->assign('files', $files);
 $tmpl->assign('dir', $dir);
 $tmpl->assign('disableSharing', true);
-$tmpl->assign('ajaxLoad', true);
 
 $tmpl->printPage();
diff --git a/apps/files_trashbin/js/disableDefaultActions.js b/apps/files_trashbin/js/disableDefaultActions.js
index afa80cacd6b04756f9abf296ca0908061561bd6f..50ceaf4696fe1aa041dfea3c29d02b8e513dc6da 100644
GIT binary patch
delta 8
PcmZo<Oq&oiF;@ft4KxDX

delta 23
ecmYdmWSkJhmy%hWn3R(moRL_RnU_A%RS^JW00?jZ

diff --git a/apps/files_trashbin/js/filelist.js b/apps/files_trashbin/js/filelist.js
index a88459b0a9aaa016b4980d731a5cffeba9eb4bd0..7795daf2775bbc609c82c7dfcac868d565cccc48 100644
GIT binary patch
literal 2298
zcmZ`*ZExE)5dN(Eic6MNa_mV-w|!WPY)zeH3%0aFmH`90E}*5eMT8awl1iMV`R}_U
zDcR224+ccu+jGyo(8U2Pl%5F%@23-R6F8R&f0Ne1L9vs~yQXwfH`xdtcamO|i?)3y
zmI%k-vUE-c_Qn}u>zQoQ38bf}&|w8$l8#>QY?$6uL3o9WvsFtT8oY;a$clfmd_4L)
zuRa`p^TWsSe&J1vgM*y}4q&RAJ2Vc2_X`aJkPWyRVXn<mIDgOAd3E&i+wZ;yr@ho5
zWye^&P41mt*V4khR0=waFcTKNVUz{UAaAsT8A4f$W<fd0IY<Y!u+V7Oi;?!gcJsMh
z1>bL4VZ<^39Kw3fZw$K5G*E_AB1K-4<;4b13v`jjb3z{39?HRBeMu7jA{O6I^FE2p
z3t#Uf$(=Ajyp%O5uoo_CzFmMES6HqsD7!3Jym%2zbA`>q)$k)6g@AoTulC{SBL{k-
zUrddI)729fo#Sg&{ef=U8H0^Gm1YZy-IS2k8swIQY=+F8Fied!0nKyw+?whkb5!05
zkZQN2u-5mHhr=WV6q}5#opLrzLa(=bd<<zCGauj)y5GeDuccF%4Y&U<S?^{<06a^Z
zv`ncl9bW1CElSG%c8iYTc9V*Lswm+6;;+lIS8#du^T{tK(_f)$oK$d6b0M3G=4FP?
zp#iH^$HPoexH67wLHrFkzQ|pYIFqxlEOixq(7elkF&*Uwm--GfpIxlKGX&hHIq-6$
zo2ioJ?ORHXqIJvJW)`uJ8PPr}lI&-ghk}gqQfZ5pmP}r<T{@Z&_=J$)iZxN?{yPiz
z`$HY0uo6yWX(bIWp(G)y=F-q=(+#Z-3$<S21ZY?JHKAm4OFe&!mM6Tzx#$#a8^8a^
zn7bkb=AJl=ErEG!@QzzM=m##=jU3hnbsUyXn}=)-@pCP4QdNX}AWl@(W*5+rq{Ad|
z9Zo613Fwx~YU4zN18K%<`1A=LsUo53M8;+a>e=;dMvNXA*MoF<WyoehCTtj{0cbZ2
zry`Ba+L}5!E_ZN}wi4~gC4WTndc3*6jQnk1QW)Bu7B3o?ZA&I_bl5ZIOR>8K_uN$_
zZL7q?yRaDtKXEf9$<_^p+&+&X&8rl~a4B4!&z08Z85SrA%;6eN?0gy}s$X6TrQ-hS
z9j_}yTy4(=H{G>n5dBL_d5s>4vgno8RM7d{L+kr7Z|gQ4jrV>c%j3N<U{mS}|Gc`G
z>SarvPF%828dhSZ#H{%G<$1B7XS(oHv5lu|X=d<j*}4aMZygzLV)MZKq;hKhUjol-
z`c;1nbH*CDqRz2oyx+_E4PsvTA*A&Ybi&B67v!+?HgZ@JJH1#aoGM{$me#VWkpEo-
aBi{>sWb4F?UUyf2(P(#VMfpCBIOczwsMcEm

delta 675
zcmbtR%W4!s6s0>MnJz?pfEcpy#ssRn({yX%s)s>gB&!6o$WjQ^-8IvdRJubwW&~+w
z>rRGRh+p7O2>K!ZfcOWlteS}#_b!UMaL+mSoO^%Y|9NMY#W3b=LAZoB!vu~Pr^igH
z1Qf&>$}(stR0vj45R|v1f+<rqw9^L21$hT0;T-)Xr8kfPjC4~f);3}E9tF2;S+~;@
zA}_<Jj{-J=@QBTp<I?jbb!g=rWySfYk&>bnYC-N$u$MXmp#y8dbL&1GS@Wkv)ggA3
zJXeCqx?l}Xu*2Gq+i2-^eT}2U@+zWcBnKxER7^k)dm-Mn6^_yg;o$-l6oQhf6#A;j
zK?zMQx^ry1mR97esDet(RkW15&AiH0kn%FT0onXZkrS!Ql1lkMIw<JuLc2af^CX{-
z^Ytu_(frHtHpFmNGigu8IcQ0tAd)uZil)fi^In=$Z_8YGPuAU~%}4K<`R;9-AKr6w
z*WWb<{)2~h&4MZ|8gQ(|4D;59$C9d{7J@eFH52{S{mmyIn{WQU`Q_tB?!a!Kg}}~W
t02sU7n0W2gMz?TlCYw*q=gsk2hmhIm?VE$%jydZcnh*AU?Y;0j^as}k(9Zw>

diff --git a/apps/files_trashbin/js/trash.js b/apps/files_trashbin/js/trash.js
index efe1e89f0bf3fc2c67a1d5e2f643d860995996bd..5cd49e19aaacab86488be40b35ccfe60e1bf2ba5 100644
GIT binary patch
delta 1859
zcma)7-EQ1O6jlOi<wZh5qDrZRdb(7#_osGJK!}7TEhz}3mO#=7iLg|&>+yPsJ$7ft
z*<D2|UvW>h;t{w9_6gvQH-HyF@D`kz@n(P23ofGAK7Zf&&UeoK{@#zBAAW9qBq#|F
zM3MH|@Ci%kgT7)Jm-gimlgd6xXa>*!xK*AL0U=GOqTvx8d_2mb1Dp=QzIeg6p5JPG
z<A<l)ci-=}ZU>FMTFnWX&6)#EILP=p6$%JMSP3{uDKHKyrVwQ!CCYr)pZ##>-rMiI
z3o0{K#F!XcX@Y!{?Mcie3|W#uAu05Tq$aYmRq2hLX9{{0`Z3`H8Ul-eDTs*#o<Y4T
zRR>u_5gXau_j4jhY7t&y#mr?B?hC34!J%)P!F<aO&7kH)1vezZfU1X<E6wcZ*q5gv
zHsGS>IQj|6J3R2`(5-O~ZJK4IqaUNg3fo`%bVU0LM{LEjj(6_Z2tT2GpknwCc5Iz_
zA>($s)7AMW_*b=|Vc@4p?fE}{UlBCT&gt~ejholoa8cwTQS`V-QzFK@5RpXE$*-H&
zug>0ZKHa#Ydy@W))ULi<Jx@vP&Dr;_{<iswq{^7R)9&o>wVz&^UAy+xM(Ipe<Zy^$
z5G=&OX!*g((Iaf(j0*5#(mxyDYUoiSpE{FPqTkb6HO@X#Yeah@669#kxQAmy!7?7w
zk?md+WL}}uDLb%TaZ)vp<R^@uJ;^*b(%raiaE-U0$GMAOSEyR|eyRkKagXt}uvUFP
zWTI1t7Es>LLV9xa*+G`(WyUF22BVJ-I{ieihb3}AITgtF7lNKMI$Ue45Xo^)`xgEJ
z{X7W0JvShEE(6oZCA5HiM@B(za+X8g*1p?7V7GmvaBrS>9gBLH4SAB0a194B%5H;+
zmKIJB=tT;v0J+eX`<A<$x0HHp4-nA*NUB%h8IIP~DI7h<<&usu?Yr2CUZ8>{;g<y!
z<6|WlAIx2K<dW2<<&B%A<^$(t`@nG5jvAkyRah7IYE7UhPE4+E5(z`7SF3HVrmW+r
zl^C1jLwGp94(2xKD}@`0m!=LS9VyDCKCvbdq?w>53Xhn8NHEI7WV|?YsDDjGmV}RS
zH;m>b(y}<)el9YVsc}yIr+R&?3-<CtmvYHxd2#F{btSz*y246DAElLyJ%uM1xf6pT
z#df7x5S)Hk<O%DecClR{l}Ulrn@oPeWy~TgdwI&YyC%xm@ET#E3v_jGA9leF+_rwv
zH)$7CwUn1y)@!yxx{DV=uaM*5&j)xKJ;Q|e_VC{a2F3K3jn^OBTCD3=C-((sdBtUY
W?~Sn%hcoSLZA~|~$n>up|NaNv9Z!M)

delta 1087
zcmY*YO=}cE5G9EaX7Yg&4JxcuFl1(ub@n2N8x}tzLLdh*h=(MH-kF+go0;jcyLaOn
zmc8j!8T=ETg#81?|KLG9>dk|QpjWGBcXs2Y>8^hD>ecJ&ca2ZC-h8<7kjJ>gm9B!>
z-3v4qse<*I{r2lh{(1U){$+M9|2T8a@_dPj=%73JGjn-%urRkhHQ1k@J(us#R|c=n
zpHAoB7BA$#7ncXw((9QOSX+aYlHBKNN3f8mhi*hwQkI}M_`Cdcs&89Xtp?b^R6~oU
zVT@8iBqTH?vM`YCqzPPOq6oO^I@be{YAIsncz%d69x{!t;}^JkDzxjj1aSj%s^A1M
z4~}Rf(k-*QKW6kcZL3F%ZRIyBFZ#9hbsN^<Zc}oM;1D%1h{+NV5hyAWXbi#VRs}3$
zk|m(Dq;?>aB15UW;EK#x=YekFBuWhCY?49Z^EN8tDp(}ywoHLZ*=Jpfm|!N9;!T2f
zSWM8uhq6E&jB8a$xLFl{i9mLN?q+CGc{C}?70IRtWd%L!!ZFt^(!uluV=P7%v`AW?
z3R22YP$7=UxONnXBq8s^2@$m;n$O4(HRNP*TvBq<J7y_*tl3dJE#}F1x;rw`jD&K_
zuDlc5TIev+5@SL$pc?tlixsOLv@kfr(8+&YTI$nl8A$O0jlBapmT2F!;Fe`vUL9Q+
z+Hp?994zBrebp@iKGRydPRKO7VTc{CYEYWoRhlK)8EF43b_^&J$p16g|Dbf`c*NQx
z#TJkLYud7kcKocZr~wkNS6Xslrqk+StgsLs?Hb1>z~S|9fUVIK6<nKk&z@LrLJ!1o
z8q>df#B|FIxvUl*dT?Urr&pcZ=8uFUh@Z7G$Ab-U?wHJmvw>+KLVWUgdrKslNa=A3
Mom9S;zpwoG2d|%P;{X5v

diff --git a/apps/files_trashbin/lib/helper.php b/apps/files_trashbin/lib/helper.php
index 9c24332a964..e6ca73520a6 100644
--- a/apps/files_trashbin/lib/helper.php
+++ b/apps/files_trashbin/lib/helper.php
@@ -27,6 +27,10 @@ class Helper
 		if ($dirContent === false) {
 			return $result;
 		}
+
+		list($storage, $internalPath) = $view->resolvePath($dir);
+		$absoluteDir = $view->getAbsolutePath($dir);
+
 		if (is_resource($dirContent)) {
 			while (($entryName = readdir($dirContent)) !== false) {
 				if (!\OC\Files\Filesystem::isIgnoredDir($entryName)) {
@@ -40,76 +44,41 @@ class Helper
 						$parts = explode('/', ltrim($dir, '/'));
 						$timestamp = substr(pathinfo($parts[0], PATHINFO_EXTENSION), 1);
 					}
-					$result[] = array(
-						'id' => $id,
-						'timestamp' => $timestamp,
-						'mime' => \OC_Helper::getFileNameMimeType($id),
+					$i = array(
+						'name' => $id,
+						'mtime' => $timestamp,
+						'mimetype' => \OC_Helper::getFileNameMimeType($id),
 						'type' => $view->is_dir($dir . '/' . $entryName) ? 'dir' : 'file',
-						'location' => $dir,
+						'directory' => ($dir === '/') ? '' : $dir,
 					);
+					$result[] = new FileInfo($absoluteDir . '/' . $i['name'], $storage, $internalPath . '/' . $i['name'], $i);
 				}
 			}
 			closedir($dirContent);
 		}
 
-		$files = array();
-		$id = 0;
-		list($storage, $internalPath) = $view->resolvePath($dir);
-		$absoluteDir = $view->getAbsolutePath($dir);
-		foreach ($result as $r) {
-			$i = array();
-			$i['id'] = $id++;
-			$i['name'] = $r['id'];
-			$i['date'] = \OCP\Util::formatDate($r['timestamp']);
-			$i['timestamp'] = $r['timestamp'];
-			$i['etag'] = $r['timestamp']; // add fake etag, it is only needed to identify the preview image
-			$i['mimetype'] = $r['mime'];
-			$i['type'] = $r['type'];
-			if ($i['type'] === 'file') {
-				$fileinfo = pathinfo($r['id']);
-				$i['basename'] = $fileinfo['filename'];
-				$i['extension'] = isset($fileinfo['extension']) ? ('.'.$fileinfo['extension']) : '';
-			}
-			$i['directory'] = $r['location'];
-			if ($i['directory'] === '/') {
-				$i['directory'] = '';
-			}
-			$i['permissions'] = \OCP\PERMISSION_READ;
-			if (\OCP\App::isEnabled('files_encryption')) {
-				$i['isPreviewAvailable'] = false;
-			} else {
-				$i['isPreviewAvailable'] = \OC::$server->getPreviewManager()->isMimeSupported($r['mime']);
-			}
-			$i['icon'] = \OCA\Files\Helper::determineIcon($i);
-			$files[] = new FileInfo($absoluteDir . '/' . $i['name'], $storage, $internalPath . '/' . $i['name'], $i);
-		}
-
-		usort($files, array('\OCA\Files\Helper', 'fileCmp'));
+		usort($result, array('\OCA\Files\Helper', 'fileCmp'));
 
-		return $files;
+		return $result;
 	}
 
 	/**
-	 * Splits the given path into a breadcrumb structure.
-	 * @param string $dir path to process
-	 * @return array where each entry is a hash of the absolute
-	 * directory path and its name
+	 * Format file infos for JSON
+	 * @param \OCP\Files\FileInfo[] $fileInfos file infos
 	 */
-	public static function makeBreadcrumb($dir){
-		// Make breadcrumb
-		$pathtohere = '';
-		$breadcrumb = array();
-		foreach (explode('/', $dir) as $i) {
-			if ($i !== '') {
-				if ( preg_match('/^(.+)\.d[0-9]+$/', $i, $match) ) {
-					$name = $match[1];
-				} else {
-					$name = $i;
-				}
-				$pathtohere .= '/' . $i;
-				$breadcrumb[] = array('dir' => $pathtohere, 'name' => $name);
+	public static function formatFileInfos($fileInfos) {
+		$files = array();
+		$id = 0;
+		foreach ($fileInfos as $i) {
+			$entry = \OCA\Files\Helper::formatFileInfo($i);
+			$entry['id'] = $id++;
+			$entry['etag'] = $entry['mtime']; // add fake etag, it is only needed to identify the preview image
+			$entry['permissions'] = \OCP\PERMISSION_READ;
+			if (\OCP\App::isEnabled('files_encryption')) {
+				$entry['isPreviewAvailable'] = false;
 			}
+			$files[] = $entry;
 		}
-		return $breadcrumb;
+		return $files;
 	}
 }
diff --git a/apps/files_trashbin/templates/index.php b/apps/files_trashbin/templates/index.php
index f9264d4352c..615cf8bdd00 100644
--- a/apps/files_trashbin/templates/index.php
+++ b/apps/files_trashbin/templates/index.php
@@ -1,13 +1,11 @@
 <div id="controls">
-	<?php print_unescaped($_['breadcrumb']); ?>
-		<div id="file_action_panel"></div>
+	<div id="file_action_panel"></div>
 </div>
 <div id='notification'></div>
 
-<div id="emptycontent" <?php if (!(isset($_['files']) && count($_['files']) === 0 && $_['dirlisting'] === false && !$_['ajaxLoad'])):?>class="hidden"<?php endif; ?>><?php p($l->t('Nothing in here. Your trash bin is empty!'))?></div>
+<div id="emptycontent" class="hidden"><?php p($l->t('Nothing in here. Your trash bin is empty!'))?></div>
 
-<input type="hidden" name="ajaxLoad" id="ajaxLoad" value="<?php p($_['ajaxLoad']); ?>" />
-<input type="hidden" id="disableSharing" data-status="<?php p($_['disableSharing']); ?>"></input>
+<input type="hidden" id="permissions" value="0"></input>
 <input type="hidden" name="dir" value="<?php p($_['dir']) ?>" id="dir">
 
 <table id="filestable">
@@ -40,6 +38,5 @@
 		</tr>
 	</thead>
 	<tbody id="fileList">
-		<?php print_unescaped($_['fileList']); ?>
 	</tbody>
 </table>
diff --git a/apps/files_trashbin/templates/part.breadcrumb.php b/apps/files_trashbin/templates/part.breadcrumb.php
deleted file mode 100644
index fdf78c190d0..00000000000
--- a/apps/files_trashbin/templates/part.breadcrumb.php
+++ /dev/null
@@ -1,19 +0,0 @@
-<div class="crumb home">
-		<a href="<?php print_unescaped($_['home']); ?>">
-			<img src="<?php print_unescaped(OCP\image_path('core', 'places/home.svg'));?>" class="svg" />
-		</a>
-</div>
-<div class="crumb svg"
-	 data-dir='/'>
-	<a href="<?php p($_['baseURL']); ?>"><?php p($l->t("Deleted Files")); ?></a>
-</div>
-<?php if(count($_["breadcrumb"])):?>
-<?php endif;?>
-<?php for($i=0; $i<count($_["breadcrumb"]); $i++):
-	$crumb = $_["breadcrumb"][$i];
-	$dir = \OCP\Util::encodePath($crumb["dir"]); ?>
-	<div class="crumb <?php if($i === count($_["breadcrumb"])-1) p('last');?> svg"
-		 data-dir='<?php p($dir);?>'>
-	<a href="<?php p($_['baseURL'].$dir); ?>"><?php p($crumb["name"]); ?></a>
-	</div>
-<?php endfor;
diff --git a/apps/files_trashbin/templates/part.list.php b/apps/files_trashbin/templates/part.list.php
deleted file mode 100644
index c32d9fd54da..00000000000
--- a/apps/files_trashbin/templates/part.list.php
+++ /dev/null
@@ -1,79 +0,0 @@
-<?php foreach($_['files'] as $file):
-	$relative_deleted_date = OCP\relative_modified_date($file['timestamp']);
-	// the older the file, the brighter the shade of grey; days*14
-	$relative_date_color = round((time()-$file['date'])/60/60/24*14);
-	if($relative_date_color>200) $relative_date_color = 200;
-	$name = \OCP\Util::encodePath($file['name']);
-	$directory = \OCP\Util::encodePath($file['directory']); ?>
-	<tr data-filename="<?php p($file['name']);?>"
-		data-type="<?php ($file['type'] === 'dir')?p('dir'):p('file')?>"
-		data-mime="<?php p($file['mimetype'])?>"
-		data-permissions='<?php p($file['permissions']); ?>'
-		<?php if ( $_['dirlisting'] ): ?>
-		id="<?php p($file['directory'].'/'.$file['name']);?>"
-		data-file="<?php p($name);?>"
-		data-timestamp=''
-		data-dirlisting=1
-		<?php  else: ?>
-		id="<?php p($file['name'].'.d'.$file['timestamp']);?>"
-		data-file="<?php p($file['name'].'.d'.$file['timestamp']);?>"
-		data-timestamp='<?php p($file['timestamp']);?>'
-		data-dirlisting=0
-		<?php endif; ?>>
-		<?php if($file['isPreviewAvailable']): ?>
-		<td class="filename svg preview-icon"
-		<?php else: ?>
-		<td class="filename svg"
-		<?php endif; ?>
-		<?php if($file['type'] === 'dir'): ?>
-			style="background-image:url(<?php print_unescaped(OCP\mimetype_icon('dir')); ?>)"
-		<?php else: ?>
-				<?php if($file['isPreviewAvailable']): ?>
-				style="background-image:url(<?php print_unescaped(OCA\Files_Trashbin\Trashbin::preview_icon(!$_['dirlisting'] ? ($file['name'].'.d'.$file['timestamp']) : ($file['directory'].'/'.$file['name']))); ?>)"
-				<?php else: ?>
-				style="background-image:url(<?php print_unescaped(OCP\mimetype_icon($file['mimetype'])); ?>)"
-				<?php endif; ?>
-		<?php endif; ?>
-			>
-		<?php if(!isset($_['readonly']) || !$_['readonly']): ?>
-			<input id="select-<?php p($file['id']); ?>" type="checkbox" />
-			<label for="select-<?php p($file['id']); ?>"></label>
-		<?php endif; ?>
-		<?php if($file['type'] === 'dir'): ?>
-			<?php if( $_['dirlisting'] ): ?>
-				<a class="name dir" href="<?php p($_['baseURL'].'/'.$name); ?>" title="">
-			<?php else: ?>
-				<a class="name dir" href="<?php p($_['baseURL'].'/'.$name.'.d'.$file['timestamp']); ?>" title="">
-			<?php endif; ?>
-		<?php else: ?>
-			<?php if( $_['dirlisting'] ): ?>
-				<a class="name file" href="<?php p($_['downloadURL'].'/'.$name); ?>" title="">
-			<?php else: ?>
-				<a class="name file" href="<?php p($_['downloadURL'].'/'.$name.'.d'.$file['timestamp']);?>" title="">
-			<?php endif; ?>
-		<?php endif; ?>
-			<span class="nametext">
-				<?php if($file['type'] === 'dir'):?>
-					<?php print_unescaped(htmlspecialchars($file['name']));?>
-				<?php else:?>
-					<?php print_unescaped(htmlspecialchars($file['basename']));?><span
-						class='extension'><?php p($file['extension']);?></span>
-				<?php endif;?>
-			</span>
-			<?php if($file['type'] === 'dir'):?>
-				<span class="uploadtext" currentUploads="0">
-				</span>
-			<?php endif;?>
-			</a>
-		</td>
-		<td class="date">
-			<span class="modified"
-				  title="<?php p($file['date']); ?>"
-				  style="color:rgb(<?php p($relative_date_color.','
-												.$relative_date_color.','
-												.$relative_date_color) ?>)">
-				<?php p($relative_deleted_date); ?>
-			</span>
-		</td>
-	</tr>
-<?php endforeach;
diff --git a/core/css/styles.css b/core/css/styles.css
index 8a12057529d..f978eab7cac 100644
--- a/core/css/styles.css
+++ b/core/css/styles.css
@@ -283,6 +283,10 @@ input[type="submit"].enabled {
 	padding: 7px 10px
 }
 
+#controls .button.hidden {
+	display: none;
+}
+
 #content { position:relative; height:100%; width:100%; }
 #content .hascontrols {
 	position: relative;
@@ -922,6 +926,9 @@ div.crumb {
 	background: url('../img/breadcrumb.svg') no-repeat right center;
 	height: 44px;
 }
+div.crumb.hidden {
+	display: none;
+}
 div.crumb a,
 div.crumb span {
 	position: relative;
diff --git a/core/js/js.js b/core/js/js.js
index e907db2837e..9a3b2ee6a5d 100644
--- a/core/js/js.js
+++ b/core/js/js.js
@@ -159,6 +159,7 @@ function escapeHTML(s) {
 * @param file The filename
 * @param dir The directory the file is in - e.g. $('#dir').val()
 * @return string
+* @deprecated use Files.getDownloadURL() instead
 */
 function fileDownloadPath(dir, file) {
 	return OC.filePath('files', 'ajax', 'download.php')+'?files='+encodeURIComponent(file)+'&dir='+encodeURIComponent(dir);
diff --git a/core/js/oc-dialogs.js b/core/js/oc-dialogs.js
index d1bcb4659b8..73ed20b7553 100644
--- a/core/js/oc-dialogs.js
+++ b/core/js/oc-dialogs.js
@@ -514,7 +514,7 @@ var OCdialogs = {
 		}
 
 		return $.getJSON(
-			OC.filePath('files', 'ajax', 'rawlist.php'),
+			OC.filePath('files', 'ajax', 'list.php'),
 			{
 				dir: dir,
 				mimetypes: JSON.stringify(mimeType)
@@ -539,7 +539,7 @@ var OCdialogs = {
 		this.$filelist.empty().addClass('loading');
 		this.$filePicker.data('path', dir);
 		$.when(this._getFileList(dir, this.$filePicker.data('mimetype'))).then(function(response) {
-			$.each(response.data, function(index, file) {
+			$.each(response.data.files, function(index, file) {
 				if (file.type === 'dir') {
 					dirs.push(file);
 				} else {
@@ -555,9 +555,16 @@ var OCdialogs = {
 					type: entry.type,
 					dir: dir,
 					filename: entry.name,
-					date: OC.mtime2date(entry.mtime)
+					date: OC.mtime2date(Math.floor(entry.mtime / 1000))
 				});
-				$li.find('img').attr('src', entry.mimetype_icon);
+				$li.find('img').attr('src', entry.icon);
+				if (entry.isPreviewAvailable) {
+					var urlSpec = {
+						file: dir + '/' + entry.name
+					};
+					var previewUrl = OC.generateUrl('/core/preview.png?') + $.param(urlSpec);
+					$li.find('img').attr('src', previewUrl);
+				}
 				self.$filelist.append($li);
 			});
 
-- 
GitLab