Merge pull request #292 from nextcloud/recent-files
Add "Recent" file listing
This commit is contained in:
commit
352e24e703
|
@ -28,6 +28,7 @@
|
|||
*/
|
||||
\OCP\App::registerAdmin('files', 'admin');
|
||||
|
||||
$l = \OC::$server->getL10N('files');
|
||||
|
||||
\OC::$server->getNavigationManager()->add(function () {
|
||||
$urlGenerator = \OC::$server->getURLGenerator();
|
||||
|
@ -49,8 +50,7 @@ $templateManager->registerTemplate('application/vnd.oasis.opendocument.presentat
|
|||
$templateManager->registerTemplate('application/vnd.oasis.opendocument.text', 'core/templates/filetemplates/template.odt');
|
||||
$templateManager->registerTemplate('application/vnd.oasis.opendocument.spreadsheet', 'core/templates/filetemplates/template.ods');
|
||||
|
||||
\OCA\Files\App::getNavigationManager()->add(function () {
|
||||
$l = \OC::$server->getL10N('files');
|
||||
\OCA\Files\App::getNavigationManager()->add(function () use ($l) {
|
||||
return [
|
||||
'id' => 'files',
|
||||
'appname' => 'files',
|
||||
|
@ -60,6 +60,16 @@ $templateManager->registerTemplate('application/vnd.oasis.opendocument.spreadshe
|
|||
];
|
||||
});
|
||||
|
||||
\OCA\Files\App::getNavigationManager()->add(function () use ($l) {
|
||||
return [
|
||||
'id' => 'recent',
|
||||
'appname' => 'files',
|
||||
'script' => 'recentlist.php',
|
||||
'order' => 2,
|
||||
'name' => $l->t('Recent'),
|
||||
];
|
||||
});
|
||||
|
||||
\OC::$server->getActivityManager()->registerExtension(function() {
|
||||
return new \OCA\Files\Activity(
|
||||
\OC::$server->query('L10NFactory'),
|
||||
|
|
|
@ -50,6 +50,11 @@ $application->registerRoutes(
|
|||
'verb' => 'GET',
|
||||
'requirements' => array('tagName' => '.+'),
|
||||
),
|
||||
array(
|
||||
'name' => 'API#getRecentFiles',
|
||||
'url' => '/api/v1/recent/',
|
||||
'verb' => 'GET'
|
||||
),
|
||||
array(
|
||||
'name' => 'API#updateFileSorting',
|
||||
'url' => '/api/v1/sorting',
|
||||
|
|
|
@ -174,6 +174,11 @@
|
|||
*/
|
||||
_clientSideSort: true,
|
||||
|
||||
/**
|
||||
* Whether or not users can change the sort attribute or direction
|
||||
*/
|
||||
_allowSorting: true,
|
||||
|
||||
/**
|
||||
* Current directory
|
||||
* @type String
|
||||
|
@ -718,7 +723,7 @@
|
|||
$target = $target.closest('a');
|
||||
}
|
||||
sort = $target.attr('data-sort');
|
||||
if (sort) {
|
||||
if (sort && this._allowSorting) {
|
||||
if (this._sort === sort) {
|
||||
this.setSort(sort, (this._sortDirection === 'desc')?'asc':'desc', true, true);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* 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.RecentFileList
|
||||
* @augments OCA.Files.RecentFileList
|
||||
*
|
||||
* @classdesc Recent file list.
|
||||
* Displays the list of recently modified files
|
||||
*
|
||||
* @param $el container element with existing markup for the #controls
|
||||
* and a table
|
||||
* @param [options] map of options, see other parameters
|
||||
*/
|
||||
var RecentFileList = function ($el, options) {
|
||||
options.sorting = {
|
||||
mode: 'mtime',
|
||||
direction: 'desc'
|
||||
};
|
||||
this.initialize($el, options);
|
||||
this._allowSorting = false;
|
||||
};
|
||||
RecentFileList.prototype = _.extend({}, OCA.Files.FileList.prototype,
|
||||
/** @lends OCA.Files.RecentFileList.prototype */ {
|
||||
id: 'recent',
|
||||
appName: t('files', 'Recent'),
|
||||
|
||||
_clientSideSort: true,
|
||||
_allowSelection: false,
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
initialize: function () {
|
||||
OCA.Files.FileList.prototype.initialize.apply(this, arguments);
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
OC.Plugins.attach('OCA.Files.RecentFileList', 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 () {
|
||||
this.showMask();
|
||||
if (this._reloadCall) {
|
||||
this._reloadCall.abort();
|
||||
}
|
||||
|
||||
// there is only root
|
||||
this._setCurrentDir('/', false);
|
||||
|
||||
this._reloadCall = $.ajax({
|
||||
url: OC.generateUrl('/apps/files/api/v1/recent'),
|
||||
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));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
OCA.Files.RecentFileList = RecentFileList;
|
||||
})(OCA);
|
||||
});
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* 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.RecentPlugin
|
||||
*
|
||||
* Registers the recent file list from the files app sidebar.
|
||||
*/
|
||||
OCA.Files.RecentPlugin = {
|
||||
name: 'Recent',
|
||||
|
||||
/**
|
||||
* @type OCA.Files.RecentFileList
|
||||
*/
|
||||
recentFileList: null,
|
||||
|
||||
attach: function () {
|
||||
var self = this;
|
||||
$('#app-content-recent').on('show.plugin-recent', function (e) {
|
||||
self.showFileList($(e.target));
|
||||
});
|
||||
$('#app-content-recent').on('hide.plugin-recent', function () {
|
||||
self.hideFileList();
|
||||
});
|
||||
},
|
||||
|
||||
detach: function () {
|
||||
if (this.recentFileList) {
|
||||
this.recentFileList.destroy();
|
||||
OCA.Files.fileActions.off('setDefault.plugin-recent', this._onActionsUpdated);
|
||||
OCA.Files.fileActions.off('registerAction.plugin-recent', this._onActionsUpdated);
|
||||
$('#app-content-recent').off('.plugin-recent');
|
||||
this.recentFileList = null;
|
||||
}
|
||||
},
|
||||
|
||||
showFileList: function ($el) {
|
||||
if (!this.recentFileList) {
|
||||
this.recentFileList = this._createRecentFileList($el);
|
||||
}
|
||||
return this.recentFileList;
|
||||
},
|
||||
|
||||
hideFileList: function () {
|
||||
if (this.recentFileList) {
|
||||
this.recentFileList.$fileList.empty();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the recent file list.
|
||||
*
|
||||
* @param $el container for the file list
|
||||
* @return {OCA.Files.RecentFileList} file list
|
||||
*/
|
||||
_createRecentFileList: function ($el) {
|
||||
var fileActions = this._createFileActions();
|
||||
// register recent list for sidebar section
|
||||
return new OCA.Files.RecentFileList(
|
||||
$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-recent', this._onActionsUpdated);
|
||||
OCA.Files.fileActions.on('registerAction.plugin-recent', 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});
|
||||
var path = OC.joinPaths(context.$file.attr('data-path'), filename);
|
||||
OCA.Files.App.fileList.changeDirectory(path, true, true);
|
||||
});
|
||||
fileActions.setDefault('dir', 'Open');
|
||||
return fileActions;
|
||||
},
|
||||
|
||||
_onActionsUpdated: function (ev) {
|
||||
if (ev.action) {
|
||||
this.recentFileList.fileActions.registerAction(ev.action);
|
||||
} else if (ev.defaultAction) {
|
||||
this.recentFileList.fileActions.setDefault(
|
||||
ev.defaultAction.mime,
|
||||
ev.defaultAction.name
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
})(OCA);
|
||||
|
||||
OC.Plugins.register('OCA.Files.App', OCA.Files.RecentPlugin);
|
||||
|
|
@ -47,7 +47,8 @@ class Application extends App {
|
|||
$c->query('TagService'),
|
||||
$server->getPreviewManager(),
|
||||
$server->getShareManager(),
|
||||
$server->getConfig()
|
||||
$server->getConfig(),
|
||||
$server->getUserFolder()
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ namespace OCA\Files\Controller;
|
|||
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCP\Files\Folder;
|
||||
use OCP\IConfig;
|
||||
use OCP\IRequest;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
|
@ -39,7 +40,7 @@ use OCP\AppFramework\Http\Response;
|
|||
use OCA\Files\Service\TagService;
|
||||
use OCP\IPreview;
|
||||
use OCP\Share\IManager;
|
||||
use OCP\Files\Node;
|
||||
use OC\Files\Node\Node;
|
||||
use OCP\IUserSession;
|
||||
|
||||
/**
|
||||
|
@ -58,12 +59,18 @@ class ApiController extends Controller {
|
|||
private $userSession;
|
||||
/** IConfig */
|
||||
private $config;
|
||||
/** @var Folder */
|
||||
private $userFolder;
|
||||
|
||||
/**
|
||||
* @param string $appName
|
||||
* @param IRequest $request
|
||||
* @param IUserSession $userSession
|
||||
* @param TagService $tagService
|
||||
* @param IPreview $previewManager
|
||||
* @param IManager $shareManager
|
||||
* @param IConfig $config
|
||||
* @param Folder $userFolder
|
||||
*/
|
||||
public function __construct($appName,
|
||||
IRequest $request,
|
||||
|
@ -71,13 +78,15 @@ class ApiController extends Controller {
|
|||
TagService $tagService,
|
||||
IPreview $previewManager,
|
||||
IManager $shareManager,
|
||||
IConfig $config) {
|
||||
IConfig $config,
|
||||
Folder $userFolder) {
|
||||
parent::__construct($appName, $request);
|
||||
$this->userSession = $userSession;
|
||||
$this->tagService = $tagService;
|
||||
$this->previewManager = $previewManager;
|
||||
$this->shareManager = $shareManager;
|
||||
$this->config = $config;
|
||||
$this->userFolder = $userFolder;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -142,6 +151,28 @@ class ApiController extends Controller {
|
|||
return new DataResponse($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \OCP\Files\Node[] $nodes
|
||||
* @return array
|
||||
*/
|
||||
private function formatNodes(array $nodes) {
|
||||
return array_values(array_map(function (Node $node) {
|
||||
/** @var \OC\Files\Node\Node $shareTypes */
|
||||
$shareTypes = $this->getShareTypes($node);
|
||||
$file = \OCA\Files\Helper::formatFileInfo($node->getFileInfo());
|
||||
$parts = explode('/', dirname($node->getPath()), 4);
|
||||
if (isset($parts[3])) {
|
||||
$file['path'] = '/' . $parts[3];
|
||||
} else {
|
||||
$file['path'] = '/';
|
||||
}
|
||||
if (!empty($shareTypes)) {
|
||||
$file['shareTypes'] = $shareTypes;
|
||||
}
|
||||
return $file;
|
||||
}, $nodes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all files tagged with the given tag.
|
||||
*
|
||||
|
@ -151,27 +182,27 @@ class ApiController extends Controller {
|
|||
* @return DataResponse
|
||||
*/
|
||||
public function getFilesByTag($tagName) {
|
||||
$files = array();
|
||||
$nodes = $this->tagService->getFilesByTag($tagName);
|
||||
foreach ($nodes as &$node) {
|
||||
$shareTypes = $this->getShareTypes($node);
|
||||
$fileInfo = $node->getFileInfo();
|
||||
$file = \OCA\Files\Helper::formatFileInfo($fileInfo);
|
||||
$parts = explode('/', dirname($fileInfo->getPath()), 4);
|
||||
if(isset($parts[3])) {
|
||||
$file['path'] = '/' . $parts[3];
|
||||
} else {
|
||||
$file['path'] = '/';
|
||||
}
|
||||
$files = $this->formatNodes($nodes);
|
||||
foreach ($files as &$file) {
|
||||
$file['tags'] = [$tagName];
|
||||
if (!empty($shareTypes)) {
|
||||
$file['shareTypes'] = $shareTypes;
|
||||
}
|
||||
$files[] = $file;
|
||||
}
|
||||
return new DataResponse(['files' => $files]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of recently modifed files.
|
||||
*
|
||||
* @NoAdminRequired
|
||||
*
|
||||
* @return DataResponse
|
||||
*/
|
||||
public function getRecentFiles() {
|
||||
$nodes = $this->userFolder->getRecent(100);
|
||||
$files = $this->formatNodes($nodes);
|
||||
return new DataResponse(['files' => $files]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of share types for outgoing shares
|
||||
*
|
||||
|
|
|
@ -173,9 +173,11 @@ class ViewController extends Controller {
|
|||
\OCP\Util::addscript('files', 'search');
|
||||
|
||||
\OCP\Util::addScript('files', 'favoritesfilelist');
|
||||
\OCP\Util::addScript('files', 'recentfilelist');
|
||||
\OCP\Util::addScript('files', 'tagsplugin');
|
||||
\OCP\Util::addScript('files', 'gotoplugin');
|
||||
\OCP\Util::addScript('files', 'favoritesplugin');
|
||||
\OCP\Util::addScript('files', 'recentplugin');
|
||||
|
||||
\OCP\Util::addScript('files', 'detailfileinfoview');
|
||||
\OCP\Util::addScript('files', 'sidebarpreviewmanager');
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
// Check if we are a user
|
||||
OCP\User::checkLoggedIn();
|
||||
|
||||
$tmpl = new OCP\Template('files', 'recentlist', '');
|
||||
|
||||
$tmpl->printPage();
|
|
@ -0,0 +1,42 @@
|
|||
<?php /** @var $l OC_L10N */ ?>
|
||||
<div id='notification'></div>
|
||||
|
||||
<div id="emptycontent" class="hidden"></div>
|
||||
|
||||
<input type="hidden" name="dir" value="" id="dir">
|
||||
|
||||
<div class="nofilterresults hidden">
|
||||
<div class="icon-search"></div>
|
||||
<h2><?php p($l->t('No entries found in this folder')); ?></h2>
|
||||
<p></p>
|
||||
</div>
|
||||
|
||||
<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></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></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">
|
||||
<span><?php p($l->t('Delete')) ?></span>
|
||||
<span class="icon icon-delete"></span>
|
||||
</a></span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="fileList">
|
||||
</tbody>
|
||||
<tfoot>
|
||||
</tfoot>
|
||||
</table>
|
|
@ -59,6 +59,8 @@ class ApiControllerTest extends TestCase {
|
|||
private $shareManager;
|
||||
/** @var \OCP\IConfig */
|
||||
private $config;
|
||||
/** @var \OC\Files\Node\Folder */
|
||||
private $userFolder;
|
||||
|
||||
public function setUp() {
|
||||
$this->request = $this->getMockBuilder('\OCP\IRequest')
|
||||
|
@ -82,6 +84,9 @@ class ApiControllerTest extends TestCase {
|
|||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$this->config = $this->getMock('\OCP\IConfig');
|
||||
$this->userFolder = $this->getMockBuilder('\OC\Files\Node\Folder')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$this->apiController = new ApiController(
|
||||
$this->appName,
|
||||
|
@ -90,7 +95,8 @@ class ApiControllerTest extends TestCase {
|
|||
$this->tagService,
|
||||
$this->preview,
|
||||
$this->shareManager,
|
||||
$this->config
|
||||
$this->config,
|
||||
$this->userFolder
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -191,7 +191,16 @@ class ViewControllerTest extends TestCase {
|
|||
'appname' => 'files',
|
||||
'script' => 'list.php',
|
||||
'order' => 0,
|
||||
'name' => new \OC_L10N_String(new \OC_L10N('files'), 'All files', []),
|
||||
'name' => (string)new \OC_L10N_String(new \OC_L10N('files'), 'All files', []),
|
||||
'active' => false,
|
||||
'icon' => '',
|
||||
],
|
||||
[
|
||||
'id' => 'recent',
|
||||
'appname' => 'files',
|
||||
'script' => 'recentlist.php',
|
||||
'order' => 2,
|
||||
'name' => (string)new \OC_L10N_String(new \OC_L10N('files'), 'Recent', []),
|
||||
'active' => false,
|
||||
'icon' => '',
|
||||
],
|
||||
|
@ -209,7 +218,7 @@ class ViewControllerTest extends TestCase {
|
|||
'appname' => 'files_sharing',
|
||||
'script' => 'list.php',
|
||||
'order' => 10,
|
||||
'name' => new \OC_L10N_String(new \OC_L10N('files_sharing'), 'Shared with you', []),
|
||||
'name' => (string)new \OC_L10N_String(new \OC_L10N('files_sharing'), 'Shared with you', []),
|
||||
'active' => false,
|
||||
'icon' => '',
|
||||
],
|
||||
|
@ -218,7 +227,7 @@ class ViewControllerTest extends TestCase {
|
|||
'appname' => 'files_sharing',
|
||||
'script' => 'list.php',
|
||||
'order' => 15,
|
||||
'name' => new \OC_L10N_String(new \OC_L10N('files_sharing'), 'Shared with others', []),
|
||||
'name' => (string)new \OC_L10N_String(new \OC_L10N('files_sharing'), 'Shared with others', []),
|
||||
'active' => false,
|
||||
'icon' => '',
|
||||
],
|
||||
|
@ -227,7 +236,7 @@ class ViewControllerTest extends TestCase {
|
|||
'appname' => 'files_sharing',
|
||||
'script' => 'list.php',
|
||||
'order' => 20,
|
||||
'name' => new \OC_L10N_String(new \OC_L10N('files_sharing'), 'Shared by link', []),
|
||||
'name' => (string)new \OC_L10N_String(new \OC_L10N('files_sharing'), 'Shared by link', []),
|
||||
'active' => false,
|
||||
'icon' => '',
|
||||
],
|
||||
|
@ -236,7 +245,7 @@ class ViewControllerTest extends TestCase {
|
|||
'appname' => 'systemtags',
|
||||
'script' => 'list.php',
|
||||
'order' => 25,
|
||||
'name' => new \OC_L10N_String(new \OC_L10N('systemtags'), 'Tags', []),
|
||||
'name' => (string)new \OC_L10N_String(new \OC_L10N('systemtags'), 'Tags', []),
|
||||
'active' => false,
|
||||
'icon' => '',
|
||||
],
|
||||
|
@ -245,7 +254,7 @@ class ViewControllerTest extends TestCase {
|
|||
'appname' => 'files_trashbin',
|
||||
'script' => 'list.php',
|
||||
'order' => 50,
|
||||
'name' => new \OC_L10N_String(new \OC_L10N('files_trashbin'), 'Deleted files', []),
|
||||
'name' => (string)new \OC_L10N_String(new \OC_L10N('files_trashbin'), 'Deleted files', []),
|
||||
'active' => false,
|
||||
'icon' => '',
|
||||
],
|
||||
|
@ -272,6 +281,10 @@ class ViewControllerTest extends TestCase {
|
|||
'id' => 'files',
|
||||
'content' => null,
|
||||
],
|
||||
[
|
||||
'id' => 'recent',
|
||||
'content' => null,
|
||||
],
|
||||
[
|
||||
'id' => 'favorites',
|
||||
'content' => null,
|
||||
|
|
|
@ -26,7 +26,10 @@
|
|||
|
||||
namespace OC\Files\Node;
|
||||
|
||||
use OC\DB\QueryBuilder\Literal;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\Files\FileInfo;
|
||||
use OCP\Files\Mount\IMountPoint;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\Files\NotPermittedException;
|
||||
|
||||
|
@ -358,4 +361,79 @@ class Folder extends Node implements \OCP\Files\Folder {
|
|||
$uniqueName = \OC_Helper::buildNotExistingFileNameForView($this->getPath(), $name, $this->view);
|
||||
return trim($this->getRelativePath($uniqueName), '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $limit
|
||||
* @param int $offset
|
||||
* @return \OCP\Files\Node[]
|
||||
*/
|
||||
public function getRecent($limit, $offset = 0) {
|
||||
$mimetypeLoader = \OC::$server->getMimeTypeLoader();
|
||||
$mounts = $this->root->getMountsIn($this->path);
|
||||
$mounts[] = $this->getMountPoint();
|
||||
|
||||
$mounts = array_filter($mounts, function (IMountPoint $mount) {
|
||||
return $mount->getStorage();
|
||||
});
|
||||
$storageIds = array_map(function (IMountPoint $mount) {
|
||||
return $mount->getStorage()->getCache()->getNumericStorageId();
|
||||
}, $mounts);
|
||||
/** @var IMountPoint[] $mountMap */
|
||||
$mountMap = array_combine($storageIds, $mounts);
|
||||
$folderMimetype = $mimetypeLoader->getId(FileInfo::MIMETYPE_FOLDER);
|
||||
|
||||
//todo look into options of filtering path based on storage id (only search in files/ for home storage, filter by share root for shared, etc)
|
||||
|
||||
$builder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
|
||||
$query = $builder
|
||||
->select('f.*')
|
||||
->from('filecache', 'f')
|
||||
->andWhere($builder->expr()->in('f.storage', $builder->createNamedParameter($storageIds, IQueryBuilder::PARAM_INT_ARRAY)))
|
||||
->andWhere($builder->expr()->orX(
|
||||
// handle non empty folders separate
|
||||
$builder->expr()->neq('f.mimetype', $builder->createNamedParameter($folderMimetype, IQueryBuilder::PARAM_INT)),
|
||||
$builder->expr()->eq('f.size', new Literal(0))
|
||||
))
|
||||
->orderBy('f.mtime', 'DESC')
|
||||
->setMaxResults($limit)
|
||||
->setFirstResult($offset);
|
||||
|
||||
$result = $query->execute()->fetchAll();
|
||||
|
||||
$files = array_filter(array_map(function (array $entry) use ($mountMap, $mimetypeLoader) {
|
||||
$mount = $mountMap[$entry['storage']];
|
||||
$entry['internalPath'] = $entry['path'];
|
||||
$entry['mimetype'] = $mimetypeLoader->getMimetypeById($entry['mimetype']);
|
||||
$entry['mimepart'] = $mimetypeLoader->getMimetypeById($entry['mimepart']);
|
||||
$path = $this->getAbsolutePath($mount, $entry['path']);
|
||||
if (is_null($path)) {
|
||||
return null;
|
||||
}
|
||||
$fileInfo = new \OC\Files\FileInfo($path, $mount->getStorage(), $entry['internalPath'], $entry, $mount);
|
||||
return $this->root->createNode($fileInfo->getPath(), $fileInfo);
|
||||
}, $result));
|
||||
|
||||
return array_values(array_filter($files, function (Node $node) {
|
||||
$relative = $this->getRelativePath($node->getPath());
|
||||
return $relative !== null && $relative !== '/';
|
||||
}));
|
||||
}
|
||||
|
||||
private function getAbsolutePath(IMountPoint $mount, $path) {
|
||||
$storage = $mount->getStorage();
|
||||
if ($storage->instanceOfStorage('\OC\Files\Storage\Wrapper\Jail')) {
|
||||
/** @var \OC\Files\Storage\Wrapper\Jail $storage */
|
||||
$jailRoot = $storage->getSourcePath('');
|
||||
$rootLength = strlen($jailRoot) + 1;
|
||||
if ($path === $jailRoot) {
|
||||
return $mount->getMountPoint();
|
||||
} else if (substr($path, 0, $rootLength) === $jailRoot . '/') {
|
||||
return $mount->getMountPoint() . substr($path, $rootLength);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return $mount->getMountPoint() . $path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -471,5 +471,10 @@ class LazyRoot implements IRootFolder {
|
|||
return $this->__call(__FUNCTION__, func_get_args());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getRecent($limit, $offset = 0) {
|
||||
return $this->__call(__FUNCTION__, func_get_args());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -377,7 +377,7 @@ class Jail extends Wrapper {
|
|||
*/
|
||||
public function getCache($path = '', $storage = null) {
|
||||
if (!$storage) {
|
||||
$storage = $this;
|
||||
$storage = $this->storage;
|
||||
}
|
||||
$sourceCache = $this->storage->getCache($this->getSourcePath($path), $storage);
|
||||
return new CacheJail($sourceCache, $this->rootPath);
|
||||
|
|
|
@ -58,6 +58,11 @@ interface FileInfo {
|
|||
*/
|
||||
const SPACE_UNLIMITED = -3;
|
||||
|
||||
/**
|
||||
* @since 9.1.0
|
||||
*/
|
||||
const MIMETYPE_FOLDER = 'httpd/unix-directory';
|
||||
|
||||
/**
|
||||
* Get the Etag of the file or folder
|
||||
*
|
||||
|
|
|
@ -175,4 +175,12 @@ interface Folder extends Node {
|
|||
* @since 8.1.0
|
||||
*/
|
||||
public function getNonExistingName($name);
|
||||
|
||||
/**
|
||||
* @param int $limit
|
||||
* @param int $offset
|
||||
* @return \OCP\Files\Node[]
|
||||
* @since 9.1.0
|
||||
*/
|
||||
public function getRecent($limit, $offset = 0);
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@ use OC\Files\Cache\Cache;
|
|||
use OC\Files\FileInfo;
|
||||
use OC\Files\Mount\MountPoint;
|
||||
use OC\Files\Node\Node;
|
||||
use OC\Files\Storage\Temporary;
|
||||
use OC\Files\Storage\Wrapper\Jail;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\Files\NotPermittedException;
|
||||
use OC\Files\View;
|
||||
|
@ -760,9 +762,9 @@ class FolderTest extends \Test\TestCase {
|
|||
public function uniqueNameProvider() {
|
||||
return [
|
||||
// input, existing, expected
|
||||
['foo', [] , 'foo'],
|
||||
['foo', ['foo'] , 'foo (2)'],
|
||||
['foo', ['foo', 'foo (2)'] , 'foo (3)']
|
||||
['foo', [], 'foo'],
|
||||
['foo', ['foo'], 'foo (2)'],
|
||||
['foo', ['foo', 'foo (2)'], 'foo (3)']
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -782,7 +784,7 @@ class FolderTest extends \Test\TestCase {
|
|||
->method('file_exists')
|
||||
->will($this->returnCallback(function ($path) use ($existingFiles, $folderPath) {
|
||||
foreach ($existingFiles as $existing) {
|
||||
if ($folderPath . '/' . $existing === $path){
|
||||
if ($folderPath . '/' . $existing === $path) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -792,4 +794,167 @@ class FolderTest extends \Test\TestCase {
|
|||
$node = new \OC\Files\Node\Folder($root, $view, $folderPath);
|
||||
$this->assertEquals($expected, $node->getNonExistingName($name));
|
||||
}
|
||||
|
||||
public function testRecent() {
|
||||
$manager = $this->getMock('\OC\Files\Mount\Manager');
|
||||
$folderPath = '/bar/foo';
|
||||
/**
|
||||
* @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view
|
||||
*/
|
||||
$view = $this->getMock('\OC\Files\View');
|
||||
/** @var \PHPUnit_Framework_MockObject_MockObject|\OC\Files\Node\Root $root */
|
||||
$root = $this->getMock('\OC\Files\Node\Root', array('getUser', 'getMountsIn', 'getMount'), array($manager, $view, $this->user));
|
||||
/** @var \PHPUnit_Framework_MockObject_MockObject|\OC\Files\FileInfo $folderInfo */
|
||||
$folderInfo = $this->getMockBuilder('\OC\Files\FileInfo')
|
||||
->disableOriginalConstructor()->getMock();
|
||||
|
||||
$baseTime = 1000;
|
||||
$storage = new Temporary();
|
||||
$mount = new MountPoint($storage, '');
|
||||
|
||||
$folderInfo->expects($this->any())
|
||||
->method('getMountPoint')
|
||||
->will($this->returnValue($mount));
|
||||
|
||||
$cache = $storage->getCache();
|
||||
|
||||
$id1 = $cache->put('bar/foo/inside.txt', [
|
||||
'storage_mtime' => $baseTime,
|
||||
'mtime' => $baseTime,
|
||||
'mimetype' => 'text/plain',
|
||||
'size' => 3
|
||||
]);
|
||||
$id2 = $cache->put('bar/foo/old.txt', [
|
||||
'storage_mtime' => $baseTime - 100,
|
||||
'mtime' => $baseTime - 100,
|
||||
'mimetype' => 'text/plain',
|
||||
'size' => 3
|
||||
]);
|
||||
$cache->put('bar/asd/outside.txt', [
|
||||
'storage_mtime' => $baseTime,
|
||||
'mtime' => $baseTime,
|
||||
'mimetype' => 'text/plain',
|
||||
'size' => 3
|
||||
]);
|
||||
$id3 = $cache->put('bar/foo/older.txt', [
|
||||
'storage_mtime' => $baseTime - 600,
|
||||
'mtime' => $baseTime - 600,
|
||||
'mimetype' => 'text/plain',
|
||||
'size' => 3
|
||||
]);
|
||||
|
||||
$node = new \OC\Files\Node\Folder($root, $view, $folderPath, $folderInfo);
|
||||
|
||||
|
||||
$nodes = $node->getRecent(5);
|
||||
$ids = array_map(function (Node $node) {
|
||||
return (int)$node->getId();
|
||||
}, $nodes);
|
||||
$this->assertEquals([$id1, $id2, $id3], $ids);
|
||||
}
|
||||
|
||||
public function testRecentFolder() {
|
||||
$manager = $this->getMock('\OC\Files\Mount\Manager');
|
||||
$folderPath = '/bar/foo';
|
||||
/**
|
||||
* @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view
|
||||
*/
|
||||
$view = $this->getMock('\OC\Files\View');
|
||||
/** @var \PHPUnit_Framework_MockObject_MockObject|\OC\Files\Node\Root $root */
|
||||
$root = $this->getMock('\OC\Files\Node\Root', array('getUser', 'getMountsIn', 'getMount'), array($manager, $view, $this->user));
|
||||
/** @var \PHPUnit_Framework_MockObject_MockObject|\OC\Files\FileInfo $folderInfo */
|
||||
$folderInfo = $this->getMockBuilder('\OC\Files\FileInfo')
|
||||
->disableOriginalConstructor()->getMock();
|
||||
|
||||
$baseTime = 1000;
|
||||
$storage = new Temporary();
|
||||
$mount = new MountPoint($storage, '');
|
||||
|
||||
$folderInfo->expects($this->any())
|
||||
->method('getMountPoint')
|
||||
->will($this->returnValue($mount));
|
||||
|
||||
$cache = $storage->getCache();
|
||||
|
||||
$id1 = $cache->put('bar/foo/folder', [
|
||||
'storage_mtime' => $baseTime,
|
||||
'mtime' => $baseTime,
|
||||
'mimetype' => \OCP\Files\FileInfo::MIMETYPE_FOLDER,
|
||||
'size' => 3
|
||||
]);
|
||||
$id2 = $cache->put('bar/foo/folder/bar.txt', [
|
||||
'storage_mtime' => $baseTime,
|
||||
'mtime' => $baseTime,
|
||||
'mimetype' => 'text/plain',
|
||||
'size' => 3,
|
||||
'parent' => $id1
|
||||
]);
|
||||
$id3 = $cache->put('bar/foo/folder/asd.txt', [
|
||||
'storage_mtime' => $baseTime - 100,
|
||||
'mtime' => $baseTime - 100,
|
||||
'mimetype' => 'text/plain',
|
||||
'size' => 3,
|
||||
'parent' => $id1
|
||||
]);
|
||||
|
||||
$node = new \OC\Files\Node\Folder($root, $view, $folderPath, $folderInfo);
|
||||
|
||||
|
||||
$nodes = $node->getRecent(5);
|
||||
$ids = array_map(function (Node $node) {
|
||||
return (int)$node->getId();
|
||||
}, $nodes);
|
||||
$this->assertEquals([$id2, $id3], $ids);
|
||||
$this->assertEquals($baseTime, $nodes[0]->getMTime());
|
||||
$this->assertEquals($baseTime - 100, $nodes[1]->getMTime());
|
||||
}
|
||||
|
||||
public function testRecentJail() {
|
||||
$manager = $this->getMock('\OC\Files\Mount\Manager');
|
||||
$folderPath = '/bar/foo';
|
||||
/**
|
||||
* @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view
|
||||
*/
|
||||
$view = $this->getMock('\OC\Files\View');
|
||||
/** @var \PHPUnit_Framework_MockObject_MockObject|\OC\Files\Node\Root $root */
|
||||
$root = $this->getMock('\OC\Files\Node\Root', array('getUser', 'getMountsIn', 'getMount'), array($manager, $view, $this->user));
|
||||
/** @var \PHPUnit_Framework_MockObject_MockObject|\OC\Files\FileInfo $folderInfo */
|
||||
$folderInfo = $this->getMockBuilder('\OC\Files\FileInfo')
|
||||
->disableOriginalConstructor()->getMock();
|
||||
|
||||
$baseTime = 1000;
|
||||
$storage = new Temporary();
|
||||
$jail = new Jail([
|
||||
'storage' => $storage,
|
||||
'root' => 'folder'
|
||||
]);
|
||||
$mount = new MountPoint($jail, '/bar/foo');
|
||||
|
||||
$folderInfo->expects($this->any())
|
||||
->method('getMountPoint')
|
||||
->will($this->returnValue($mount));
|
||||
|
||||
$cache = $storage->getCache();
|
||||
|
||||
$id1 = $cache->put('folder/inside.txt', [
|
||||
'storage_mtime' => $baseTime,
|
||||
'mtime' => $baseTime,
|
||||
'mimetype' => 'text/plain',
|
||||
'size' => 3
|
||||
]);
|
||||
$cache->put('outside.txt', [
|
||||
'storage_mtime' => $baseTime - 100,
|
||||
'mtime' => $baseTime - 100,
|
||||
'mimetype' => 'text/plain',
|
||||
'size' => 3
|
||||
]);
|
||||
|
||||
$node = new \OC\Files\Node\Folder($root, $view, $folderPath, $folderInfo);
|
||||
|
||||
$nodes = $node->getRecent(5);
|
||||
$ids = array_map(function (Node $node) {
|
||||
return (int)$node->getId();
|
||||
}, $nodes);
|
||||
$this->assertEquals([$id1], $ids);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue