diff --git a/apps/files/appinfo/application.php b/apps/files/appinfo/application.php
index 7ca48bab47445769a6c00d2f164bb82e2ab76bb6..13ff60daf89cc8b3b109215e55b1f2f675e18d78 100644
--- a/apps/files/appinfo/application.php
+++ b/apps/files/appinfo/application.php
@@ -11,6 +11,8 @@ namespace OCA\Files\Appinfo;
 use OC\AppFramework\Utility\SimpleContainer;
 use OCA\Files\Controller\ApiController;
 use OCP\AppFramework\App;
+use \OCA\Files\Service\TagService;
+use \OCP\IContainer;
 
 class Application extends App {
 	public function __construct(array $urlParams=array()) {
@@ -21,10 +23,44 @@ class Application extends App {
 		/**
 		 * Controllers
 		 */
-		$container->registerService('APIController', function (SimpleContainer $c) {
+		$container->registerService('APIController', function (IContainer $c) {
 			return new ApiController(
 				$c->query('AppName'),
-				$c->query('Request')
+				$c->query('Request'),
+				$c->query('TagService')
+			);
+		});
+
+		/**
+		 * Core
+		 */
+		$container->registerService('L10N', function(IContainer $c) {
+			return $c->query('ServerContainer')->getL10N($c->query('AppName'));
+		});
+
+		/**
+		 * Services
+		 */
+		$container->registerService('Tagger', function(IContainer $c)  {
+			return $c->query('ServerContainer')->getTagManager()->load('files');
+		});
+		$container->registerService('TagService', function(IContainer $c)  {
+			$homeFolder = $c->query('ServerContainer')->getUserFolder();
+			return new TagService(
+				$c->query('ServerContainer')->getUserSession(),
+				$c->query('Tagger'),
+				$homeFolder
+			);
+		});
+
+		/**
+		 * Controllers
+		 */
+		$container->registerService('APIController', function (IContainer $c) {
+			return new ApiController(
+				$c->query('AppName'),
+				$c->query('Request'),
+				$c->query('TagService')
 			);
 		});
 	}
diff --git a/apps/files/appinfo/routes.php b/apps/files/appinfo/routes.php
index 96790a048555aca11a79e4c919d427631ee9408e..349284ec52d9786b3d43fd96ea45d41d0ce9b4fd 100644
--- a/apps/files/appinfo/routes.php
+++ b/apps/files/appinfo/routes.php
@@ -9,10 +9,31 @@
 namespace OCA\Files\Appinfo;
 
 $application = new Application();
-$application->registerRoutes($this, array('routes' => array(
-	array('name' => 'API#getThumbnail', 'url' => '/api/v1/thumbnail/{x}/{y}/{file}', 'verb' => 'GET', 'requirements' => array('file' => '.+')),
-)));
-
+$application->registerRoutes(
+	$this,
+	array(
+		'routes' => array(
+			array(
+				'name' => 'API#getThumbnail',
+				'url' => '/api/v1/thumbnail/{x}/{y}/{file}',
+				'verb' => 'GET',
+				'requirements' => array('file' => '.+')
+			),
+			array(
+				'name' => 'API#updateFileTags',
+				'url' => '/api/v1/files/{path}',
+				'verb' => 'POST',
+				'requirements' => array('path' => '.+'),
+			),
+			array(
+				'name' => 'API#getFilesByTag',
+				'url' => '/api/v1/tags/{tagName}/files',
+				'verb' => 'GET',
+				'requirements' => array('tagName' => '.+'),
+			),
+		)
+	)
+);
 
 /** @var $this \OC\Route\Router */
 
diff --git a/apps/files/controller/apicontroller.php b/apps/files/controller/apicontroller.php
index 89d24a5c47f02c5cf92725ef89e1d114e3dd0c79..902731a0492c9058592104378130e4e5def177b5 100644
--- a/apps/files/controller/apicontroller.php
+++ b/apps/files/controller/apicontroller.php
@@ -12,13 +12,21 @@ use OCP\AppFramework\Http;
 use OCP\AppFramework\Controller;
 use OCP\IRequest;
 use OCP\AppFramework\Http\JSONResponse;
+use OCP\AppFramework\Http\DataResponse;
 use OCP\AppFramework\Http\DownloadResponse;
 use OC\Preview;
+use OCA\Files\Service\TagService;
 
 class ApiController extends Controller {
 
-	public function __construct($appName, IRequest $request){
+	/**
+	 * @var TagService $tagService
+	 */
+	private $tagService;
+
+	public function __construct($appName, IRequest $request, TagService $tagService){
 		parent::__construct($appName, $request);
+		$this->tagService = $tagService;
 	}
 
 
@@ -49,4 +57,50 @@ class ApiController extends Controller {
 		}
 	}
 
+	/**
+	 * Updates the info of the specified file path
+	 * The passed tags are absolute, which means they will
+	 * replace the actual tag selection.
+	 *
+	 * @NoAdminRequired
+	 * @CORS
+	 *
+	 * @param string $path path
+	 * @param array  $tags array of tags
+	 */
+	public function updateFileTags($path, $tags = null) {
+		$result = array();
+		// if tags specified or empty array, update tags
+		if (!is_null($tags)) {
+			try {
+				$this->tagService->updateFileTags($path, $tags);
+			} catch (\OCP\Files\NotFoundException $e) {
+				return new DataResponse($e->getMessage(), Http::STATUS_NOT_FOUND);
+			}
+			$result['tags'] = $tags;
+		}
+		return new DataResponse($result, Http::STATUS_OK);
+	}
+
+	/**
+	 * Returns a list of all files tagged with the given tag.
+	 *
+	 * @NoAdminRequired
+	 * @CORS
+	 *
+	 * @param array $tagName tag name to filter by
+	 */
+	public function getFilesByTag($tagName) {
+		$files = array();
+		$fileInfos = $this->tagService->getFilesByTag($tagName);
+		foreach ($fileInfos as &$fileInfo) {
+			$file = \OCA\Files\Helper::formatFileInfo($fileInfo);
+			$parts = explode('/', dirname($fileInfo->getPath()), 4);
+			$file['path'] = '/' . $parts[3];
+			$file['tags'] = array($tagName);
+			$files[] = $file;
+		}
+		return new DataResponse(array('files' => $files), Http::STATUS_OK);
+	}
+
 }
diff --git a/apps/files/css/files.css b/apps/files/css/files.css
index d8336847e005f6b2f1b985987a4124d5aee816d0..3829759a14ecd76ba7f7369cf3ab01c768f6f214 100644
--- a/apps/files/css/files.css
+++ b/apps/files/css/files.css
@@ -191,10 +191,15 @@ table th#headerName {
 	width: 9999px; /* not really sure why this works better than 100% … table styling */
 	padding: 0;
 }
+
 #headerName-container {
 	position: relative;
 	height: 50px;
 }
+.has-favorites #headerName-container {
+	padding-left: 50px;
+}
+
 table th#headerSize, table td.filesize {
 	text-align: right;
 }
@@ -286,6 +291,10 @@ table td.filename .nametext {
 	max-width: 800px;
 	height: 100%;
 }
+.has-favorites #fileList td.filename a.name {
+	left: 50px;
+	margin-right: 50px;
+}
 
 table td.filename .nametext .innernametext {
 	text-overflow: ellipsis;
@@ -403,6 +412,9 @@ table td.filename .uploadtext {
 	left: 18px;
 	z-index: 10;
 }
+.has-favorites .select-all {
+	left: 68px;
+}
 
 #fileList tr td.filename {
 	position: relative;
@@ -417,6 +429,18 @@ table td.filename .uploadtext {
 	height: 50px;
 }
 
+#fileList tr td.filename .favorite {
+	display: inline-block;
+	float: left;
+}
+#fileList tr td.filename .action-favorite {
+	display: block;
+	float: left;
+	width: 30px;
+	line-height: 100%;
+	text-align: center;
+}
+
 #uploadsize-message,#delete-confirm { display:none; }
 
 /* File actions */
@@ -442,7 +466,7 @@ table td.filename .uploadtext {
 	padding: 17px 14px;
 }
 
-#fileList .action.action-share-notification span, #fileList a {
+#fileList .action.action-share-notification span, #fileList a.name {
 	cursor: default !important;
 }
 
diff --git a/apps/files/index.php b/apps/files/index.php
index 929bc5e79da383a089e22dd829b70794e221200e..02076226c1abeca94fec1a184ca780306b34762e 100644
--- a/apps/files/index.php
+++ b/apps/files/index.php
@@ -20,6 +20,7 @@
  * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
  *
  */
+use OCA\Files\Appinfo\Application;
 
 // Check if we are a user
 OCP\User::checkLoggedIn();
@@ -38,8 +39,16 @@ OCP\Util::addscript('files', 'filesummary');
 OCP\Util::addscript('files', 'breadcrumb');
 OCP\Util::addscript('files', 'filelist');
 
+\OCP\Util::addScript('files', 'favoritesfilelist');
+\OCP\Util::addScript('files', 'tagsplugin');
+\OCP\Util::addScript('files', 'favoritesplugin');
+
+\OC_Util::addVendorScript('core', 'handlebars/handlebars');
+
 OCP\App::setActiveNavigationEntry('files_index');
 
+$l = \OC::$server->getL10N('files');
+
 $isIE8 = false;
 preg_match('/MSIE (.*?);/', $_SERVER['HTTP_USER_AGENT'], $matches);
 if (count($matches) > 0 && $matches[1] <= 9) {
@@ -79,6 +88,16 @@ function sortNavigationItems($item1, $item2) {
 	return $item1['order'] - $item2['order'];
 }
 
+\OCA\Files\App::getNavigationManager()->add(
+	array(
+		'id' => 'favorites',
+		'appname' => 'files',
+		'script' => 'simplelist.php',
+		'order' => 50,
+		'name' => $l->t('Favorites')
+	)
+);
+
 $navItems = \OCA\Files\App::getNavigationManager()->getAll();
 usort($navItems, 'sortNavigationItems');
 $nav->assign('navigationItems', $navItems);
diff --git a/apps/files/js/app.js b/apps/files/js/app.js
index ee5330485e7052db2943ab5016a994de83adc0cc..adb1893bb0eee89d8cc8ede3590c0a63d7367c93 100644
--- a/apps/files/js/app.js
+++ b/apps/files/js/app.js
@@ -80,6 +80,8 @@
 			// refer to the one of the "files" view
 			window.FileList = this.fileList;
 
+			OC.Plugins.attach('OCA.Files.App', this);
+
 			this._setupEvents();
 			// trigger URL change event handlers
 			this._onPopState(urlParams);
diff --git a/apps/files/js/favoritesfilelist.js b/apps/files/js/favoritesfilelist.js
new file mode 100644
index 0000000000000000000000000000000000000000..0d555ce609d47d308463d7c6ad1644c2b6ca1f21
--- /dev/null
+++ b/apps/files/js/favoritesfilelist.js
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2014 Vincent Petry <pvince81@owncloud.com>
+ *
+ * This file is licensed under the Affero General Public License version 3
+ * or later.
+ *
+ * See the COPYING-README file.
+ *
+ */
+
+// HACK: this piece needs to be loaded AFTER the files app (for unit tests)
+$(document).ready(function() {
+	(function(OCA) {
+		/**
+		 * @class OCA.Files.FavoritesFileList
+		 * @augments OCA.Files.FavoritesFileList
+		 *
+		 * @classdesc Favorites file list.
+		 * Displays the list of files marked as favorites
+		 *
+		 * @param $el container element with existing markup for the #controls
+		 * and a table
+		 * @param [options] map of options, see other parameters
+		 */
+		var FavoritesFileList = function($el, options) {
+			this.initialize($el, options);
+		};
+		FavoritesFileList.prototype = _.extend({}, OCA.Files.FileList.prototype,
+			/** @lends OCA.Files.FavoritesFileList.prototype */ {
+			id: 'favorites',
+			appName: 'Favorites',
+
+			_clientSideSort: true,
+			_allowSelection: false,
+
+			/**
+			 * @private
+			 */
+			initialize: function($el, options) {
+				OCA.Files.FileList.prototype.initialize.apply(this, arguments);
+				if (this.initialized) {
+					return;
+				}
+				OC.Plugins.attach('OCA.Files.FavoritesFileList', this);
+			},
+
+			updateEmptyContent: function() {
+				var dir = this.getCurrentDirectory();
+				if (dir === '/') {
+					// root has special permissions
+					this.$el.find('#emptycontent').toggleClass('hidden', !this.isEmpty);
+					this.$el.find('#filestable thead th').toggleClass('hidden', this.isEmpty);
+				}
+				else {
+					OCA.Files.FileList.prototype.updateEmptyContent.apply(this, arguments);
+				}
+			},
+
+			getDirectoryPermissions: function() {
+				return OC.PERMISSION_READ | OC.PERMISSION_DELETE;
+			},
+
+			updateStorageStatistics: function() {
+				// no op because it doesn't have
+				// storage info like free space / used space
+			},
+
+			reload: function() {
+				var tagName = OC.TAG_FAVORITE;
+				this.showMask();
+				if (this._reloadCall) {
+					this._reloadCall.abort();
+				}
+				this._reloadCall = $.ajax({
+					url: OC.generateUrl('/apps/files/api/v1/tags/{tagName}/files', {tagName: tagName}),
+					type: 'GET',
+					dataType: 'json'
+				});
+				var callBack = this.reloadCallback.bind(this);
+				return this._reloadCall.then(callBack, callBack);
+			},
+
+			reloadCallback: function(result) {
+				delete this._reloadCall;
+				this.hideMask();
+
+				if (result.files) {
+					this.setFiles(result.files.sort(this._sortComparator));
+				}
+				else {
+					// TODO: error handling
+				}
+			}
+		});
+
+		OCA.Files.FavoritesFileList = FavoritesFileList;
+	})(OCA);
+});
+
diff --git a/apps/files/js/favoritesplugin.js b/apps/files/js/favoritesplugin.js
new file mode 100644
index 0000000000000000000000000000000000000000..417a32ef804b3a6cedd6b0bd62b35720108713e7
--- /dev/null
+++ b/apps/files/js/favoritesplugin.js
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2014 Vincent Petry <pvince81@owncloud.com>
+ *
+ * This file is licensed under the Affero General Public License version 3
+ * or later.
+ *
+ * See the COPYING-README file.
+ *
+ */
+
+(function(OCA) {
+	/**
+	 * @namespace OCA.Files.FavoritesPlugin
+	 *
+	 * Registers the favorites file list from the files app sidebar.
+	 */
+	OCA.Files.FavoritesPlugin = {
+		name: 'Favorites',
+
+		/**
+		 * @type OCA.Files.FavoritesFileList
+		 */
+		favoritesFileList: null,
+
+		attach: function() {
+			var self = this;
+			$('#app-content-favorites').on('show.plugin-favorites', function(e) {
+				self.showFileList($(e.target));
+			});
+			$('#app-content-favorites').on('hide.plugin-favorites', function() {
+				self.hideFileList();
+			});
+		},
+
+		detach: function() {
+			if (this.favoritesFileList) {
+				this.favoritesFileList.destroy();
+				OCA.Files.fileActions.off('setDefault.plugin-favorites', this._onActionsUpdated);
+				OCA.Files.fileActions.off('registerAction.plugin-favorites', this._onActionsUpdated);
+				$('#app-content-favorites').off('.plugin-favorites');
+				this.favoritesFileList = null;
+			}
+		},
+
+		showFileList: function($el) {
+			if (!this.favoritesFileList) {
+				this.favoritesFileList = this._createFavoritesFileList($el);
+			}
+			return this.favoritesFileList;
+		},
+
+		hideFileList: function() {
+			if (this.favoritesFileList) {
+				this.favoritesFileList.$fileList.empty();
+			}
+		},
+
+		/**
+		 * Creates the favorites file list.
+		 *
+		 * @param $el container for the file list
+		 * @return {OCA.Files.FavoritesFileList} file list
+		 */
+		_createFavoritesFileList: function($el) {
+			var fileActions = this._createFileActions();
+			// register favorite list for sidebar section
+			return new OCA.Files.FavoritesFileList(
+				$el, {
+					fileActions: fileActions,
+					scrollContainer: $('#app-content')
+				}
+			);
+		},
+
+		_createFileActions: function() {
+			// inherit file actions from the files app
+			var fileActions = new OCA.Files.FileActions();
+			// note: not merging the legacy actions because legacy apps are not
+			// compatible with the sharing overview and need to be adapted first
+			fileActions.registerDefaultActions();
+			fileActions.merge(OCA.Files.fileActions);
+
+			if (!this._globalActionsInitialized) {
+				// in case actions are registered later
+				this._onActionsUpdated = _.bind(this._onActionsUpdated, this);
+				OCA.Files.fileActions.on('setDefault.plugin-favorites', this._onActionsUpdated);
+				OCA.Files.fileActions.on('registerAction.plugin-favorites', this._onActionsUpdated);
+				this._globalActionsInitialized = true;
+			}
+
+			// when the user clicks on a folder, redirect to the corresponding
+			// folder in the files app instead of opening it directly
+			fileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename, context) {
+				OCA.Files.App.setActiveView('files', {silent: true});
+				OCA.Files.App.fileList.changeDirectory(context.$file.attr('data-path') + '/' + filename, true, true);
+			});
+			fileActions.setDefault('dir', 'Open');
+			return fileActions;
+		},
+
+		_onActionsUpdated: function(ev) {
+			if (ev.action) {
+				this.favoritesFileList.fileActions.registerAction(ev.action);
+			} else if (ev.defaultAction) {
+				this.favoritesFileList.fileActions.setDefault(
+					ev.defaultAction.mime,
+					ev.defaultAction.name
+				);
+			}
+		}
+	};
+
+})(OCA);
+
+OC.Plugins.register('OCA.Files.App', OCA.Files.FavoritesPlugin);
+
diff --git a/apps/files/js/tagsplugin.js b/apps/files/js/tagsplugin.js
new file mode 100644
index 0000000000000000000000000000000000000000..a6757431ffaeea2f49a051a92ef83244a9f14fa8
--- /dev/null
+++ b/apps/files/js/tagsplugin.js
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2014 Vincent Petry <pvince81@owncloud.com>
+ *
+ * This file is licensed under the Affero General Public License version 3
+ * or later.
+ *
+ * See the COPYING-README file.
+ *
+ */
+
+/* global Handlebars */
+
+(function(OCA) {
+
+	var TEMPLATE_FAVORITE_ACTION =
+		'<a href="#" ' +
+		'class="action action-favorite {{#isFavorite}}permanent{{/isFavorite}}">' +
+		'<img class="svg" alt="{{altText}}" src="{{imgFile}}" />' +
+		'</a>';
+
+	/**
+	 * Returns the path to the star image
+	 *
+	 * @param {boolean} state true if starred, false otherwise
+	 * @return {string} path to star image
+	 */
+	function getStarImage(state) {
+		return OC.imagePath('core', state ? 'actions/starred' : 'actions/star');
+	}
+
+	/**
+	 * Render the star icon with the given state
+	 *
+	 * @param {boolean} state true if starred, false otherwise
+	 * @return {Object} jQuery object
+	 */
+	function renderStar(state) {
+		if (!this._template) {
+			this._template = Handlebars.compile(TEMPLATE_FAVORITE_ACTION);
+		}
+		return this._template({
+			isFavorite: state,
+			altText: state ? t('core', 'Favorited') : t('core', 'Favorite'),
+			imgFile: getStarImage(state)
+		});
+	}
+
+	/**
+	 * Toggle star icon on action element
+	 *
+	 * @param {Object} action element
+	 * @param {boolean} state true if starred, false otherwise
+	 */
+	function toggleStar($actionEl, state) {
+		$actionEl.find('img').attr('src', getStarImage(state));
+		$actionEl.toggleClass('permanent', state);
+	}
+
+	OCA.Files = OCA.Files || {};
+
+	/**
+	 * @namespace OCA.Files.TagsPlugin
+	 *
+	 * Extends the file actions and file list to include a favorite action icon
+	 * and addition "data-tags" and "data-favorite" attributes.
+	 */
+	OCA.Files.TagsPlugin = {
+		name: 'Tags',
+
+		allowedLists: [
+			'files',
+			'favorites'
+		],
+
+		_extendFileActions: function(fileActions) {
+			var self = this;
+			// register "star" action
+			fileActions.registerAction({
+				name: 'favorite',
+				displayName: 'Favorite',
+				mime: 'all',
+				permissions: OC.PERMISSION_READ,
+				render: function(actionSpec, isDefault, context) {
+					var $file = context.$file;
+					var isFavorite = $file.data('favorite') === true;
+					var $icon = $(renderStar(isFavorite));
+					$file.find('td:first>.favorite').replaceWith($icon);
+					return $icon;
+				},
+				actionHandler: function(fileName, context) {
+					var $actionEl = context.$file.find('.action-favorite');
+					var $file = context.$file;
+					var dir = context.dir || context.fileList.getCurrentDirectory();
+					var tags = $file.attr('data-tags');
+					if (_.isUndefined(tags)) {
+						tags = '';
+					}
+					tags = tags.split('|');
+					tags = _.without(tags, '');
+					var isFavorite = tags.indexOf(OC.TAG_FAVORITE) >= 0;
+					if (isFavorite) {
+						// remove tag from list
+						tags = _.without(tags, OC.TAG_FAVORITE);
+					} else {
+						tags.push(OC.TAG_FAVORITE);
+					}
+					toggleStar($actionEl, !isFavorite);
+
+					self.applyFileTags(
+						dir + '/' + fileName,
+						tags
+					).then(function(result) {
+						// read latest state from result
+						toggleStar($actionEl, (result.tags.indexOf(OC.TAG_FAVORITE) >= 0));
+						$file.attr('data-tags', tags.join('|'));
+						$file.attr('data-favorite', !isFavorite);
+					});
+				}
+			});
+		},
+
+		_extendFileList: function(fileList) {
+			// extend row prototype
+			fileList.$el.addClass('has-favorites');
+			var oldCreateRow = fileList._createRow;
+			fileList._createRow = function(fileData) {
+				var $tr = oldCreateRow.apply(this, arguments);
+				if (fileData.tags) {
+					$tr.attr('data-tags', fileData.tags.join('|'));
+					if (fileData.tags.indexOf(OC.TAG_FAVORITE) >= 0) {
+						$tr.attr('data-favorite', true);
+					}
+				}
+				$tr.find('td:first').prepend('<div class="favorite"></div>');
+				return $tr;
+			};
+		},
+
+		attach: function(fileList) {
+			if (this.allowedLists.indexOf(fileList.id) < 0) {
+				return;
+			}
+			this._extendFileActions(fileList.fileActions);
+			this._extendFileList(fileList);
+		},
+
+		/**
+		 * Replaces the given files' tags with the specified ones.
+		 *
+		 * @param {String} fileName path to the file or folder to tag
+		 * @param {Array.<String>} tagNames array of tag names
+		 */
+		applyFileTags: function(fileName, tagNames) {
+			var encodedPath = OC.encodePath(fileName);
+			while (encodedPath[0] === '/') {
+				encodedPath = encodedPath.substr(1);
+			}
+			return $.ajax({
+				url: OC.generateUrl('/apps/files/api/v1/files/') + encodedPath,
+				contentType: 'application/json',
+				data: JSON.stringify({
+					tags: tagNames || []
+				}),
+				dataType: 'json',
+				type: 'POST'
+			});
+		}
+	};
+})(OCA);
+
+OC.Plugins.register('OCA.Files.FileList', OCA.Files.TagsPlugin);
+
diff --git a/apps/files/lib/helper.php b/apps/files/lib/helper.php
index 97b9d8e704427b0c7e2498aad293059de876a426..7adca3ffa6d49ab8231828bbded7d28498480e9c 100644
--- a/apps/files/lib/helper.php
+++ b/apps/files/lib/helper.php
@@ -122,6 +122,9 @@ class Helper
 		$entry['size'] = $i['size'];
 		$entry['type'] = $i['type'];
 		$entry['etag'] = $i['etag'];
+		if (isset($i['tags'])) {
+			$entry['tags'] = $i['tags'];
+		}
 		if (isset($i['displayname_owner'])) {
 			$entry['shareOwner'] = $i['displayname_owner'];
 		}
@@ -171,10 +174,32 @@ class Helper
 	 */
 	public static function getFiles($dir, $sortAttribute = 'name', $sortDescending = false) {
 		$content = \OC\Files\Filesystem::getDirectoryContent($dir);
+		$content = self::populateTags($content);
 
 		return self::sortFiles($content, $sortAttribute, $sortDescending);
 	}
 
+	/**
+	 * Populate the result set with file tags
+	 *
+	 * @param array file list
+	 * @return file list populated with tags
+	 */
+	public static function populateTags($fileList) {
+		$filesById = array();
+		foreach ($fileList as $fileData) {
+			$filesById[$fileData['fileid']] = $fileData;
+		}
+		$tagger = \OC::$server->getTagManager()->load('files');
+		$tags = $tagger->getTagsForObjects(array_keys($filesById));
+		if ($tags) {
+			foreach ($tags as $fileId => $fileTags) {
+				$filesById[$fileId]['tags'] = $fileTags;
+			}
+		}
+		return $fileList;
+	}
+
 	/**
 	 * Sort the given file info array
 	 *
diff --git a/apps/files/service/tagservice.php b/apps/files/service/tagservice.php
new file mode 100644
index 0000000000000000000000000000000000000000..86885e38ddd481e39f1b2735103e1660d2a4ad8a
--- /dev/null
+++ b/apps/files/service/tagservice.php
@@ -0,0 +1,94 @@
+<?php
+/**
+ * Copyright (c) 2014 Vincent Petry <pvince81@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace OCA\Files\Service;
+
+/**
+ * Service class to manage tags on files.
+ */
+class TagService {
+
+	/**
+	 * @var \OCP\IUserSession
+	 */
+	private $userSession;
+
+	/**
+	 * @var \OCP\ITags
+	 */
+	private $tagger;
+
+	/**
+	 * @var \OCP\Files\Folder
+	 */
+	private $homeFolder;
+
+	public function __construct(
+		\OCP\IUserSession $userSession,
+		\OCP\ITags $tagger,
+		\OCP\Files\Folder $homeFolder
+	) {
+		$this->userSession = $userSession;
+		$this->tagger = $tagger;
+		$this->homeFolder = $homeFolder;
+	}
+
+	/**
+	 * Updates the tags of the specified file path.
+	 * The passed tags are absolute, which means they will
+	 * replace the actual tag selection.
+	 *
+	 * @param string $path path
+	 * @param array  $tags array of tags
+	 * @return array list of tags
+	 * @throws \OCP\NotFoundException if the file does not exist
+	 */
+	public function updateFileTags($path, $tags) {
+		$fileId = $this->homeFolder->get($path)->getId();
+
+		$currentTags = $this->tagger->getTagsForObjects(array($fileId));
+
+		if (!empty($currentTags)) {
+			$currentTags = current($currentTags);
+		}
+
+		$newTags = array_diff($tags, $currentTags);
+		foreach ($newTags as $tag) {
+			$this->tagger->tagAs($fileId, $tag);
+		}
+		$deletedTags = array_diff($currentTags, $tags);
+		foreach ($deletedTags as $tag) {
+			$this->tagger->unTag($fileId, $tag);
+		}
+
+		// TODO: re-read from tagger to make sure the
+		// list is up to date, in case of concurrent changes ?
+		return $tags;
+	}
+
+	/**
+	 * Updates the tags of the specified file path.
+	 * The passed tags are absolute, which means they will
+	 * replace the actual tag selection.
+	 *
+	 * @param array $tagName tag name to filter by
+	 * @return FileInfo[] list of matching files
+	 * @throws \Exception if the tag does not exist
+	 */
+	public function getFilesByTag($tagName) {
+		$nodes = $this->homeFolder->searchByTag(
+			$tagName, $this->userSession->getUser()->getUId()
+		);
+		foreach ($nodes as &$node) {
+			$node = $node->getFileInfo();
+		}
+
+		return $nodes;
+	}
+}
+
diff --git a/apps/files/simplelist.php b/apps/files/simplelist.php
new file mode 100644
index 0000000000000000000000000000000000000000..53e56b4ed323a9ce964eaa04cfdd1e3629fd3be0
--- /dev/null
+++ b/apps/files/simplelist.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * ownCloud - Simple files list
+ *
+ * @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/>.
+ *
+ */
+
+// TODO: move to handlebars
+
+// renders the controls and table headers template
+$tmpl = new OCP\Template('files', 'simplelist', '');
+$tmpl->printPage();
+
diff --git a/apps/files/templates/simplelist.php b/apps/files/templates/simplelist.php
new file mode 100644
index 0000000000000000000000000000000000000000..c00febce653fb54720fe2bc6ea09d08d84a955cb
--- /dev/null
+++ b/apps/files/templates/simplelist.php
@@ -0,0 +1,36 @@
+<div id="controls">
+	<div id="file_action_panel"></div>
+</div>
+<div id='notification'></div>
+
+<div id="emptycontent" class="hidden"></div>
+
+<input type="hidden" name="dir" value="" id="dir">
+
+<table id="filestable">
+	<thead>
+		<tr>
+			<th id='headerName' class="hidden column-name">
+				<div id="headerName-container">
+					<a class="name sort columntitle" data-sort="name"><span><?php p($l->t( 'Name' )); ?></span><span class="sort-indicator"></span></a>
+				</div>
+			</th>
+			<th id="headerSize" class="hidden column-size">
+				<a class="size sort columntitle" data-sort="size"><span><?php p($l->t('Size')); ?></span><span class="sort-indicator"></span></a>
+			</th>
+			<th id="headerDate" class="hidden column-mtime">
+				<a id="modified" class="columntitle" data-sort="mtime"><span><?php p($l->t( 'Modified' )); ?></span><span class="sort-indicator"></span></a>
+					<span class="selectedActions"><a href="" class="delete-selected">
+						<?php p($l->t('Delete'))?>
+						<img class="svg" alt="<?php p($l->t('Delete'))?>"
+							 src="<?php print_unescaped(OCP\image_path("core", "actions/delete.svg")); ?>" />
+					</a></span>
+			</th>
+		</tr>
+	</thead>
+	<tbody id="fileList">
+	</tbody>
+	<tfoot>
+	</tfoot>
+</table>
+
diff --git a/apps/files/tests/js/favoritesfilelistspec.js b/apps/files/tests/js/favoritesfilelistspec.js
new file mode 100644
index 0000000000000000000000000000000000000000..608ddaca18be94fcc96a20fa988a8b06d218180f
--- /dev/null
+++ b/apps/files/tests/js/favoritesfilelistspec.js
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2014 Vincent Petry <pvince81@owncloud.com>
+ *
+ * This file is licensed under the Affero General Public License version 3
+ * or later.
+ *
+ * See the COPYING-README file.
+ *
+ */
+
+describe('OCA.Files.FavoritesFileList tests', function() {
+	var fileList;
+
+	beforeEach(function() {
+		// init parameters and test table elements
+		$('#testArea').append(
+			'<div id="app-content-container">' +
+			// init horrible parameters
+			'<input type="hidden" id="dir" value="/"></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
+			// TODO: at some point this will be rendered by the fileList class itself!
+			'<table id="filestable">' +
+			'<thead><tr>' +
+			'<th id="headerName" class="hidden column-name">' +
+			'<a class="name columntitle" data-sort="name"><span>Name</span><span class="sort-indicator"></span></a>' +
+			'</th>' +
+			'<th class="hidden column-mtime">' +
+			'<a class="columntitle" data-sort="mtime"><span class="sort-indicator"></span></a>' +
+			'</th>' +
+			'</tr></thead>' +
+			'<tbody id="fileList"></tbody>' +
+			'<tfoot></tfoot>' +
+			'</table>' +
+			'<div id="emptycontent">Empty content message</div>' +
+			'</div>'
+		);
+	});
+	afterEach(function() {
+		fileList.destroy();
+		fileList = undefined;
+	});
+
+	describe('loading file list', function() {
+		var response;
+
+		beforeEach(function() {
+			fileList = new OCA.Files.FavoritesFileList(
+				$('#app-content-container')
+			);
+			OCA.Files.FavoritesPlugin.attach(fileList);
+
+			fileList.reload();
+
+			/* jshint camelcase: false */
+			response = {
+				files: [{
+					id: 7,
+					name: 'test.txt',
+					path: '/somedir',
+					size: 123,
+					mtime: 11111000,
+					tags: [OC.TAG_FAVORITE],
+					permissions: OC.PERMISSION_ALL,
+					mimetype: 'text/plain'
+				}]
+			};
+		});
+		it('render files', function() {
+			var request;
+
+			expect(fakeServer.requests.length).toEqual(1);
+			request = fakeServer.requests[0];
+			expect(request.url).toEqual(
+				OC.generateUrl('apps/files/api/v1/tags/{tagName}/files', {tagName: OC.TAG_FAVORITE})
+			);
+
+			fakeServer.requests[0].respond(
+				200,
+				{ 'Content-Type': 'application/json' },
+				JSON.stringify(response)
+			);
+
+			var $rows = fileList.$el.find('tbody tr');
+			var $tr = $rows.eq(0);
+			expect($rows.length).toEqual(1);
+			expect($tr.attr('data-id')).toEqual('7');
+			expect($tr.attr('data-type')).toEqual('file');
+			expect($tr.attr('data-file')).toEqual('test.txt');
+			expect($tr.attr('data-path')).toEqual('/somedir');
+			expect($tr.attr('data-size')).toEqual('123');
+			expect(parseInt($tr.attr('data-permissions'), 10))
+				.toEqual(OC.PERMISSION_ALL);
+			expect($tr.attr('data-mime')).toEqual('text/plain');
+			expect($tr.attr('data-mtime')).toEqual('11111000');
+			expect($tr.find('a.name').attr('href')).toEqual(
+				OC.webroot +
+				'/index.php/apps/files/ajax/download.php' +
+				'?dir=%2Fsomedir&files=test.txt'
+			);
+			expect($tr.find('.nametext').text().trim()).toEqual('test.txt');
+		});
+	});
+});
diff --git a/apps/files/tests/js/favoritespluginspec.js b/apps/files/tests/js/favoritespluginspec.js
new file mode 100644
index 0000000000000000000000000000000000000000..90b40ede74b43afcc2a268b2f42f9389739da6a9
--- /dev/null
+++ b/apps/files/tests/js/favoritespluginspec.js
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2014 Vincent Petry <pvince81@owncloud.com>
+ *
+ * This file is licensed under the Affero General Public License version 3
+ * or later.
+ *
+ * See the COPYING-README file.
+ *
+ */
+
+describe('OCA.Files.FavoritesPlugin tests', function() {
+	var Plugin = OCA.Files.FavoritesPlugin;
+	var fileList;
+
+	beforeEach(function() {
+		$('#testArea').append(
+			'<div id="app-navigation">' +
+			'<ul><li data-id="files"><a>Files</a></li>' +
+			'<li data-id="sharingin"><a></a></li>' +
+			'<li data-id="sharingout"><a></a></li>' +
+			'</ul></div>' +
+			'<div id="app-content">' +
+			'<div id="app-content-files" class="hidden">' +
+			'</div>' +
+			'<div id="app-content-favorites" class="hidden">' +
+			'</div>' +
+			'</div>' +
+			'</div>'
+		);
+		OC.Plugins.attach('OCA.Files.App', Plugin);
+		fileList = Plugin.showFileList($('#app-content-favorites'));
+	});
+	afterEach(function() {
+		OC.Plugins.detach('OCA.Files.App', Plugin);
+	});
+
+	describe('initialization', function() {
+		it('inits favorites list on show', function() {
+			expect(fileList).toBeDefined();
+		});
+	});
+	describe('file actions', function() {
+		var oldLegacyFileActions;
+
+		beforeEach(function() {
+			oldLegacyFileActions = window.FileActions;
+			window.FileActions = new OCA.Files.FileActions();
+		});
+
+		afterEach(function() {
+			window.FileActions = oldLegacyFileActions;
+		});
+		it('provides default file actions', function() {
+			var fileActions = fileList.fileActions;
+
+			expect(fileActions.actions.all).toBeDefined();
+			expect(fileActions.actions.all.Delete).toBeDefined();
+			expect(fileActions.actions.all.Rename).toBeDefined();
+			expect(fileActions.actions.all.Download).toBeDefined();
+
+			expect(fileActions.defaults.dir).toEqual('Open');
+		});
+		it('provides custom file actions', function() {
+			var actionStub = sinon.stub();
+			// regular file action
+			OCA.Files.fileActions.register(
+					'all',
+					'RegularTest',
+					OC.PERMISSION_READ,
+					OC.imagePath('core', 'actions/shared'),
+					actionStub
+			);
+
+			Plugin.favoritesFileList = null;
+			fileList = Plugin.showFileList($('#app-content-favorites'));
+
+			expect(fileList.fileActions.actions.all.RegularTest).toBeDefined();
+		});
+		it('does not provide legacy file actions', function() {
+			var actionStub = sinon.stub();
+			// legacy file action
+			window.FileActions.register(
+					'all',
+					'LegacyTest',
+					OC.PERMISSION_READ,
+					OC.imagePath('core', 'actions/shared'),
+					actionStub
+			);
+
+			Plugin.favoritesFileList = null;
+			fileList = Plugin.showFileList($('#app-content-favorites'));
+
+			expect(fileList.fileActions.actions.all.LegacyTest).not.toBeDefined();
+		});
+		it('redirects to files app when opening a directory', function() {
+			var oldList = OCA.Files.App.fileList;
+			// dummy new list to make sure it exists
+			OCA.Files.App.fileList = new OCA.Files.FileList($('<table><thead></thead><tbody></tbody></table>'));
+
+			var setActiveViewStub = sinon.stub(OCA.Files.App, 'setActiveView');
+			// create dummy table so we can click the dom
+			var $table = '<table><thead></thead><tbody id="fileList"></tbody></table>';
+			$('#app-content-favorites').append($table);
+
+			Plugin.favoritesFileList = null;
+			fileList = Plugin.showFileList($('#app-content-favorites'));
+
+			fileList.setFiles([{
+				name: 'testdir',
+				type: 'dir',
+				path: '/somewhere/inside/subdir',
+				counterParts: ['user2'],
+				shareOwner: 'user2'
+			}]);
+
+			fileList.findFileEl('testdir').find('td a.name').click();
+
+			expect(OCA.Files.App.fileList.getCurrentDirectory()).toEqual('/somewhere/inside/subdir/testdir');
+
+			expect(setActiveViewStub.calledOnce).toEqual(true);
+			expect(setActiveViewStub.calledWith('files')).toEqual(true);
+
+			setActiveViewStub.restore();
+
+			// restore old list
+			OCA.Files.App.fileList = oldList;
+		});
+	});
+});
+
diff --git a/apps/files/tests/js/tagspluginspec.js b/apps/files/tests/js/tagspluginspec.js
new file mode 100644
index 0000000000000000000000000000000000000000..66240575a5c5b1ecac3ed6ec5e508bb4e42dc5f0
--- /dev/null
+++ b/apps/files/tests/js/tagspluginspec.js
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2014 Vincent Petry <pvince81@owncloud.com>
+ *
+ * This file is licensed under the Affero General Public License version 3
+ * or later.
+ *
+ * See the COPYING-README file.
+ *
+ */
+
+describe('OCA.Files.TagsPlugin tests', function() {
+	var fileList;
+	var testFiles;
+
+	beforeEach(function() {
+		var $content = $('<div id="content"></div>');
+		$('#testArea').append($content);
+		// dummy file list
+		var $div = $(
+			'<div>' +
+			'<table id="filestable">' +
+			'<thead></thead>' +
+			'<tbody id="fileList"></tbody>' +
+			'</table>' +
+			'</div>');
+		$('#content').append($div);
+
+		fileList = new OCA.Files.FileList($div);
+		OCA.Files.TagsPlugin.attach(fileList);
+
+		testFiles = [{
+			id: 1,
+			type: 'file',
+			name: 'One.txt',
+			path: '/subdir',
+			mimetype: 'text/plain',
+			size: 12,
+			permissions: OC.PERMISSION_ALL,
+			etag: 'abc',
+			shareOwner: 'User One',
+			isShareMountPoint: false,
+			tags: ['tag1', 'tag2']
+		}];
+	});
+	afterEach(function() {
+		fileList.destroy();
+		fileList = null;
+	});
+
+	describe('Favorites icon', function() {
+		it('renders favorite icon and extra data', function() {
+			var $action, $tr;
+			fileList.setFiles(testFiles);
+			$tr = fileList.$el.find('tbody tr:first');
+			$action = $tr.find('.action-favorite');
+			expect($action.length).toEqual(1);
+			expect($action.hasClass('permanent')).toEqual(false);
+
+			expect($tr.attr('data-tags').split('|')).toEqual(['tag1', 'tag2']);
+			expect($tr.attr('data-favorite')).not.toBeDefined();
+		});
+		it('renders permanent favorite icon and extra data', function() {
+			var $action, $tr;
+			testFiles[0].tags.push(OC.TAG_FAVORITE);
+			fileList.setFiles(testFiles);
+			$tr = fileList.$el.find('tbody tr:first');
+			$action = $tr.find('.action-favorite');
+			expect($action.length).toEqual(1);
+			expect($action.hasClass('permanent')).toEqual(true);
+
+			expect($tr.attr('data-tags').split('|')).toEqual(['tag1', 'tag2', OC.TAG_FAVORITE]);
+			expect($tr.attr('data-favorite')).toEqual('true');
+		});
+		it('adds has-favorites class on table', function() {
+			expect(fileList.$el.hasClass('has-favorites')).toEqual(true);
+		});
+	});
+	describe('Applying tags', function() {
+		it('sends request to server and updates icon', function() {
+			// TODO
+			fileList.setFiles(testFiles);
+		});
+		it('sends all tags to server when applyFileTags() is called ', function() {
+			// TODO
+		});
+	});
+});
diff --git a/apps/files/tests/service/tagservice.php b/apps/files/tests/service/tagservice.php
new file mode 100644
index 0000000000000000000000000000000000000000..158dd77e858c40674502204ddf8aef9ae1e9f155
--- /dev/null
+++ b/apps/files/tests/service/tagservice.php
@@ -0,0 +1,121 @@
+<?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/>.
+ *
+ */
+namespace OCA\Files;
+
+use \OCA\Files\Service\TagService;
+
+class TagServiceTest extends \Test\TestCase {
+
+	/**
+	 * @var string
+	 */
+	private $user;
+
+	/**
+	 * @var \OCP\Files\Folder
+	 */
+	private $root;
+
+	/**
+	 * @var \OCA\Files\Service\TagService
+	 */
+	private $tagService;
+
+	/**
+	 * @var \OCP\ITags
+	 */
+	private $tagger;
+
+	protected function setUp() {
+		parent::setUp();
+		$this->user = $this->getUniqueId('user');
+		\OC_User::createUser($this->user, 'test');
+		\OC_User::setUserId($this->user);
+		\OC_Util::setupFS($this->user);
+		/**
+		 * @var \OCP\IUser
+		 */
+		$user = new \OC\User\User($this->user, null);
+		/**
+		 * @var \OCP\IUserSession
+		 */
+		$userSession = $this->getMock('\OCP\IUserSession');
+		$userSession->expects($this->any())
+			->method('getUser')
+			->withAnyParameters()
+			->will($this->returnValue($user));
+
+		$this->root = \OC::$server->getUserFolder();
+
+		$this->tagger = \OC::$server->getTagManager()->load('files');
+		$this->tagService = new TagService(
+			$userSession,
+			$this->tagger,
+			$this->root
+		);
+	}
+
+	protected function tearDown() {
+		\OC_User::setUserId('');
+		\OC_User::deleteUser($this->user);
+	}
+
+	public function testUpdateFileTags() {
+		$tag1 = 'tag1';
+		$tag2 = 'tag2';
+
+		$subdir = $this->root->newFolder('subdir');
+		$testFile = $subdir->newFile('test.txt');
+		$testFile->putContent('test contents');
+
+		$fileId = $testFile->getId();
+
+		// set tags
+		$this->tagService->updateFileTags('subdir/test.txt', array($tag1, $tag2));
+
+		$this->assertEquals(array($fileId), $this->tagger->getIdsForTag($tag1));
+		$this->assertEquals(array($fileId), $this->tagger->getIdsForTag($tag2));
+
+		// remove tag
+		$result = $this->tagService->updateFileTags('subdir/test.txt', array($tag2));
+		$this->assertEquals(array(), $this->tagger->getIdsForTag($tag1));
+		$this->assertEquals(array($fileId), $this->tagger->getIdsForTag($tag2));
+
+		// clear tags
+		$result = $this->tagService->updateFileTags('subdir/test.txt', array());
+		$this->assertEquals(array(), $this->tagger->getIdsForTag($tag1));
+		$this->assertEquals(array(), $this->tagger->getIdsForTag($tag2));
+
+		// non-existing file
+		$caught = false;
+		try {
+			$this->tagService->updateFileTags('subdir/unexist.txt', array($tag1));
+		} catch (\OCP\Files\NotFoundException $e) {
+			$caught = true;
+		}
+		$this->assertTrue($caught);
+
+		$subdir->delete();
+	}
+}
+
diff --git a/core/js/core.json b/core/js/core.json
index e529315fdb18352725860a8667986a464ebcab75..d3a9e2404e832568012e0b38ec4eb6a21f9901a3 100644
--- a/core/js/core.json
+++ b/core/js/core.json
@@ -4,7 +4,8 @@
 		"jquery/jquery-migrate.min.js",
 		"jquery-ui/ui/jquery-ui.custom.js",
 		"underscore/underscore.js",
-		"moment/min/moment-with-locales.js"
+		"moment/min/moment-with-locales.js",
+		"handlebars/handlebars.js"
 	],
 	"libraries": [
 		"jquery-showpassword.js",
diff --git a/core/js/js.js b/core/js/js.js
index a4f66ac7d904b965011f37a4dbf7fda69762f77f..f01c0eb77c12ca129655edb04967a9292cb05deb 100644
--- a/core/js/js.js
+++ b/core/js/js.js
@@ -66,6 +66,7 @@ var OC={
 	PERMISSION_DELETE:8,
 	PERMISSION_SHARE:16,
 	PERMISSION_ALL:31,
+	TAG_FAVORITE: '_$!<Favorite>!$_',
 	/* jshint camelcase: false */
 	webroot:oc_webroot,
 	appswebroots:(typeof oc_appswebroots !== 'undefined') ? oc_appswebroots:false,
@@ -211,6 +212,24 @@ var OC={
 		return OC.filePath(app,'img',file);
 	},
 
+	/**
+	 * URI-Encodes a file path but keep the path slashes.
+	 *
+	 * @param path path
+	 * @return encoded path
+	 */
+	encodePath: function(path) {
+		if (!path) {
+			return path;
+		}
+		var parts = path.split('/');
+		var result = [];
+		for (var i = 0; i < parts.length; i++) {
+			result.push(encodeURIComponent(parts[i]));
+		}
+		return result.join('/');
+	},
+
 	/**
 	 * Load a script for the server and load it. If the script is already loaded,
 	 * the event handler will be called directly
diff --git a/lib/private/files/node/node.php b/lib/private/files/node/node.php
index b80db28e8ec7cf6431c59cdca0daa8342faf50e3..87d4a4b91569119fc481ddb7583b4485b8915a5a 100644
--- a/lib/private/files/node/node.php
+++ b/lib/private/files/node/node.php
@@ -43,7 +43,12 @@ class Node implements \OCP\Files\Node, FileInfo {
 		$this->path = $path;
 	}
 
-	private function getFileInfo() {
+	/**
+	 * Returns the matching file info
+	 *
+	 * @return \OCP\Files\FileInfo
+	 */
+	public function getFileInfo() {
 		if (!$this->fileInfo) {
 			$this->fileInfo = $this->view->getFileInfo($this->path);
 		}