Merge pull request #11037 from nextcloud/trash-webui-dav

Use trashbin dav endpoint to list trash in webui
This commit is contained in:
Morris Jobke 2018-09-28 16:03:24 +02:00 committed by GitHub
commit d867f9f091
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 584 additions and 788 deletions

View File

@ -1,93 +0,0 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bart Visscher <bartv@thisnet.nl>
* @author Björn Schießle <bjoern@schiessle.org>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Vincent Petry <pvince81@owncloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
use OCP\ILogger;
\OC_JSON::checkLoggedIn();
\OC_JSON::callCheck();
\OC::$server->getSession()->close();
$folder = isset($_POST['dir']) ? $_POST['dir'] : '/';
// "empty trash" command
if (isset($_POST['allfiles']) && (string)$_POST['allfiles'] === 'true'){
$deleteAll = true;
if ($folder === '/' || $folder === '') {
OCA\Files_Trashbin\Trashbin::deleteAll();
$list = array();
} else {
$list[] = $folder;
$folder = dirname($folder);
}
}
else {
$deleteAll = false;
$files = (string)$_POST['files'];
$list = json_decode($files);
}
$folder = rtrim($folder, '/') . '/';
$error = array();
$success = array();
$i = 0;
foreach ($list as $file) {
if ($folder === '/') {
$file = ltrim($file, '/');
$delimiter = strrpos($file, '.d');
$filename = substr($file, 0, $delimiter);
$timestamp = substr($file, $delimiter+2);
} else {
$filename = $folder . '/' . $file;
$timestamp = null;
}
OCA\Files_Trashbin\Trashbin::delete($filename, \OCP\User::getUser(), $timestamp);
if (OCA\Files_Trashbin\Trashbin::file_exists($filename, $timestamp)) {
$error[] = $filename;
\OCP\Util::writeLog('trashbin','can\'t delete ' . $filename . ' permanently.', ILogger::ERROR);
}
// only list deleted files if not deleting everything
else if (!$deleteAll) {
$success[$i]['filename'] = $file;
$success[$i]['timestamp'] = $timestamp;
$i++;
}
}
if ( $error ) {
$filelist = '';
foreach ( $error as $e ) {
$filelist .= $e.', ';
}
$l = \OC::$server->getL10N('files_trashbin');
$message = $l->t("Couldn't delete %s permanently", array(rtrim($filelist, ', ')));
\OC_JSON::error(array("data" => array("message" => $message,
"success" => $success, "error" => $error)));
} else {
\OC_JSON::success(array("data" => array("success" => $success)));
}

View File

@ -1,33 +0,0 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Björn Schießle <bjoern@schiessle.org>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
\OC_JSON::checkLoggedIn();
\OC_JSON::callCheck();
\OC::$server->getSession()->close();
$trashStatus = OCA\Files_Trashbin\Trashbin::isEmpty(OCP\User::getUser());
\OC_JSON::success(array("data" => array("isEmpty" => $trashStatus)));

View File

@ -1,48 +0,0 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Björn Schießle <bjoern@schiessle.org>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Vincent Petry <pvince81@owncloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
\OC_JSON::checkLoggedIn();
\OC::$server->getSession()->close();
// Load the files
$dir = isset($_GET['dir']) ? (string)$_GET['dir'] : '';
$sortAttribute = isset($_GET['sort']) ? (string)$_GET['sort'] : 'name';
$sortDirection = isset($_GET['sortdirection']) ? ($_GET['sortdirection'] === 'desc') : false;
$data = array();
// make filelist
try {
$files = \OCA\Files_Trashbin\Helper::getTrashFiles($dir, \OCP\User::getUser(), $sortAttribute, $sortDirection);
} catch (Exception $e) {
http_response_code(404);
exit();
}
$encodedDir = \OCP\Util::encodePath($dir);
$data['permissions'] = 0;
$data['directory'] = $dir;
$data['files'] = \OCA\Files_Trashbin\Helper::formatFileInfos($files);
\OC_JSON::success(array('data' => $data));

View File

@ -1,98 +0,0 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bart Visscher <bartv@thisnet.nl>
* @author Björn Schießle <bjoern@schiessle.org>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Vincent Petry <pvince81@owncloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
use OCP\ILogger;
\OC_JSON::checkLoggedIn();
\OC_JSON::callCheck();
\OC::$server->getSession()->close();
$dir = '/';
if (isset($_POST['dir'])) {
$dir = rtrim((string)$_POST['dir'], '/'). '/';
}
$allFiles = false;
if (isset($_POST['allfiles']) && (string)$_POST['allfiles'] === 'true') {
$allFiles = true;
$list = array();
$dirListing = true;
if ($dir === '' || $dir === '/') {
$dirListing = false;
}
foreach (OCA\Files_Trashbin\Helper::getTrashFiles($dir, \OCP\User::getUser()) as $file) {
$fileName = $file['name'];
if (!$dirListing) {
$fileName .= '.d' . $file['mtime'];
}
$list[] = $fileName;
}
} else {
$list = json_decode($_POST['files']);
}
$error = array();
$success = array();
$i = 0;
foreach ($list as $file) {
$path = $dir . '/' . $file;
if ($dir === '/') {
$file = ltrim($file, '/');
$delimiter = strrpos($file, '.d');
$filename = substr($file, 0, $delimiter);
$timestamp = substr($file, $delimiter+2);
} else {
$path_parts = pathinfo($file);
$filename = $path_parts['basename'];
$timestamp = null;
}
if ( !OCA\Files_Trashbin\Trashbin::restore($path, $filename, $timestamp) ) {
$error[] = $filename;
\OCP\Util::writeLog('trashbin', 'can\'t restore ' . $filename, ILogger::ERROR);
} else {
$success[$i]['filename'] = $file;
$success[$i]['timestamp'] = $timestamp;
$i++;
}
}
if ( $error ) {
$filelist = '';
foreach ( $error as $e ) {
$filelist .= $e.', ';
}
$l = OC::$server->getL10N('files_trashbin');
$message = $l->t("Couldn't restore %s", array(rtrim($filelist, ', ')));
\OC_JSON::error(array("data" => array("message" => $message,
"success" => $success, "error" => $error)));
} else {
\OC_JSON::success(array("data" => array("success" => $success)));
}

View File

@ -34,13 +34,3 @@ $application->registerRoutes($this, [
], ],
], ],
]); ]);
$this->create('files_trashbin_ajax_delete', 'ajax/delete.php')
->actionInclude('files_trashbin/ajax/delete.php');
$this->create('files_trashbin_ajax_isEmpty', 'ajax/isEmpty.php')
->actionInclude('files_trashbin/ajax/isEmpty.php');
$this->create('files_trashbin_ajax_list', 'ajax/list.php')
->actionInclude('files_trashbin/ajax/list.php');
$this->create('files_trashbin_ajax_undelete', 'ajax/undelete.php')
->actionInclude('files_trashbin/ajax/undelete.php');

View File

@ -18,6 +18,7 @@ return array(
'OCA\\Files_Trashbin\\Expiration' => $baseDir . '/../lib/Expiration.php', 'OCA\\Files_Trashbin\\Expiration' => $baseDir . '/../lib/Expiration.php',
'OCA\\Files_Trashbin\\Helper' => $baseDir . '/../lib/Helper.php', 'OCA\\Files_Trashbin\\Helper' => $baseDir . '/../lib/Helper.php',
'OCA\\Files_Trashbin\\Hooks' => $baseDir . '/../lib/Hooks.php', 'OCA\\Files_Trashbin\\Hooks' => $baseDir . '/../lib/Hooks.php',
'OCA\\Files_Trashbin\\Sabre\\AbstractTrash' => $baseDir . '/../lib/Sabre/AbstractTrash.php',
'OCA\\Files_Trashbin\\Sabre\\ITrash' => $baseDir . '/../lib/Sabre/ITrash.php', 'OCA\\Files_Trashbin\\Sabre\\ITrash' => $baseDir . '/../lib/Sabre/ITrash.php',
'OCA\\Files_Trashbin\\Sabre\\PropfindPlugin' => $baseDir . '/../lib/Sabre/PropfindPlugin.php', 'OCA\\Files_Trashbin\\Sabre\\PropfindPlugin' => $baseDir . '/../lib/Sabre/PropfindPlugin.php',
'OCA\\Files_Trashbin\\Sabre\\RestoreFolder' => $baseDir . '/../lib/Sabre/RestoreFolder.php', 'OCA\\Files_Trashbin\\Sabre\\RestoreFolder' => $baseDir . '/../lib/Sabre/RestoreFolder.php',

View File

@ -33,6 +33,7 @@ class ComposerStaticInitFiles_Trashbin
'OCA\\Files_Trashbin\\Expiration' => __DIR__ . '/..' . '/../lib/Expiration.php', 'OCA\\Files_Trashbin\\Expiration' => __DIR__ . '/..' . '/../lib/Expiration.php',
'OCA\\Files_Trashbin\\Helper' => __DIR__ . '/..' . '/../lib/Helper.php', 'OCA\\Files_Trashbin\\Helper' => __DIR__ . '/..' . '/../lib/Helper.php',
'OCA\\Files_Trashbin\\Hooks' => __DIR__ . '/..' . '/../lib/Hooks.php', 'OCA\\Files_Trashbin\\Hooks' => __DIR__ . '/..' . '/../lib/Hooks.php',
'OCA\\Files_Trashbin\\Sabre\\AbstractTrash' => __DIR__ . '/..' . '/../lib/Sabre/AbstractTrash.php',
'OCA\\Files_Trashbin\\Sabre\\ITrash' => __DIR__ . '/..' . '/../lib/Sabre/ITrash.php', 'OCA\\Files_Trashbin\\Sabre\\ITrash' => __DIR__ . '/..' . '/../lib/Sabre/ITrash.php',
'OCA\\Files_Trashbin\\Sabre\\PropfindPlugin' => __DIR__ . '/..' . '/../lib/Sabre/PropfindPlugin.php', 'OCA\\Files_Trashbin\\Sabre\\PropfindPlugin' => __DIR__ . '/..' . '/../lib/Sabre/PropfindPlugin.php',
'OCA\\Files_Trashbin\\Sabre\\RestoreFolder' => __DIR__ . '/..' . '/../lib/Sabre/RestoreFolder.php', 'OCA\\Files_Trashbin\\Sabre\\RestoreFolder' => __DIR__ . '/..' . '/../lib/Sabre/RestoreFolder.php',

View File

@ -17,12 +17,21 @@ OCA.Trashbin = {};
*/ */
OCA.Trashbin.App = { OCA.Trashbin.App = {
_initialized: false, _initialized: false,
/** @type {OC.Files.Client} */
client: null,
initialize: function($el) { initialize: function ($el) {
if (this._initialized) { if (this._initialized) {
return; return;
} }
this._initialized = true; this._initialized = true;
this.client = new OC.Files.Client({
host: OC.getHost(),
port: OC.getPort(),
root: OC.linkToRemoteBase('dav') + '/trashbin/' + OC.getCurrentUser().uid,
useHTTPS: OC.getProtocol() === 'https'
});
var urlParams = OC.Util.History.parseUrlQuery(); var urlParams = OC.Util.History.parseUrlQuery();
this.fileList = new OCA.Trashbin.FileList( this.fileList = new OCA.Trashbin.FileList(
$('#app-content-trashbin'), { $('#app-content-trashbin'), {
@ -31,22 +40,24 @@ OCA.Trashbin.App = {
scrollTo: urlParams.scrollto, scrollTo: urlParams.scrollto,
config: OCA.Files.App.getFilesConfig(), config: OCA.Files.App.getFilesConfig(),
multiSelectMenu: [ multiSelectMenu: [
{ {
name: 'restore', name: 'restore',
displayName: t('files', 'Restore'), displayName: t('files', 'Restore'),
iconClass: 'icon-history', iconClass: 'icon-history',
}, },
{ {
name: 'delete', name: 'delete',
displayName: t('files', 'Delete'), displayName: t('files', 'Delete'),
iconClass: 'icon-delete', iconClass: 'icon-delete',
} }
] ],
client: this.client
} }
); );
}, },
_createFileActions: function() { _createFileActions: function () {
var client = this.client;
var fileActions = new OCA.Files.FileActions(); var fileActions = new OCA.Files.FileActions();
fileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename, context) { fileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename, context) {
var dir = context.fileList.getCurrentDirectory(); var dir = context.fileList.getCurrentDirectory();
@ -62,17 +73,19 @@ OCA.Trashbin.App = {
mime: 'all', mime: 'all',
permissions: OC.PERMISSION_READ, permissions: OC.PERMISSION_READ,
iconClass: 'icon-history', iconClass: 'icon-history',
actionHandler: function(filename, context) { actionHandler: function (filename, context) {
var fileList = context.fileList; var fileList = context.fileList;
var tr = fileList.findFileEl(filename); var tr = fileList.findFileEl(filename);
var deleteAction = tr.children("td.date").children(".action.delete"); fileList.showFileBusyState(tr, true);
deleteAction.removeClass('icon-delete').addClass('icon-loading-small'); var dir = context.fileList.getCurrentDirectory();
$.post(OC.filePath('files_trashbin', 'ajax', 'undelete.php'), { client.move(OC.joinPaths('trash', dir, filename), OC.joinPaths('restore', filename), true)
files: JSON.stringify([filename]), .then(
dir: fileList.getCurrentDirectory() fileList._removeCallback.bind(fileList, [filename]),
}, function () {
_.bind(fileList._removeCallback, fileList) fileList.showFileBusyState(tr, false);
); OC.Notification.show(t('files_trashbin', 'Error while restoring file from trashbin'));
}
);
} }
}); });
@ -82,33 +95,35 @@ OCA.Trashbin.App = {
mime: 'all', mime: 'all',
permissions: OC.PERMISSION_READ, permissions: OC.PERMISSION_READ,
iconClass: 'icon-delete', iconClass: 'icon-delete',
render: function(actionSpec, isDefault, context) { render: function (actionSpec, isDefault, context) {
var $actionLink = fileActions._makeActionLink(actionSpec, context); var $actionLink = fileActions._makeActionLink(actionSpec, context);
$actionLink.attr('original-title', t('files_trashbin', 'Delete permanently')); $actionLink.attr('original-title', t('files_trashbin', 'Delete permanently'));
$actionLink.children('img').attr('alt', t('files_trashbin', 'Delete permanently')); $actionLink.children('img').attr('alt', t('files_trashbin', 'Delete permanently'));
context.$file.find('td:last').append($actionLink); context.$file.find('td:last').append($actionLink);
return $actionLink; return $actionLink;
}, },
actionHandler: function(filename, context) { actionHandler: function (filename, context) {
var fileList = context.fileList; var fileList = context.fileList;
$('.tipsy').remove(); $('.tipsy').remove();
var tr = fileList.findFileEl(filename); var tr = fileList.findFileEl(filename);
var deleteAction = tr.children("td.date").children(".action.delete"); fileList.showFileBusyState(tr, true);
deleteAction.removeClass('icon-delete').addClass('icon-loading-small'); var dir = context.fileList.getCurrentDirectory();
$.post(OC.filePath('files_trashbin', 'ajax', 'delete.php'), { client.remove(OC.joinPaths('trash', dir, filename))
files: JSON.stringify([filename]), .then(
dir: fileList.getCurrentDirectory() fileList._removeCallback.bind(fileList, [filename]),
}, function () {
_.bind(fileList._removeCallback, fileList) fileList.showFileBusyState(tr, false);
); OC.Notification.show(t('files_trashbin', 'Error while removing file from trashbin'));
}
);
} }
}); });
return fileActions; return fileActions;
} }
}; };
$(document).ready(function() { $(document).ready(function () {
$('#app-content-trashbin').one('show', function() { $('#app-content-trashbin').one('show', function () {
var App = OCA.Trashbin.App; var App = OCA.Trashbin.App;
App.initialize($('#app-content-trashbin')); App.initialize($('#app-content-trashbin'));
// force breadcrumb init // force breadcrumb init

View File

@ -9,6 +9,8 @@
*/ */
(function() { (function() {
var DELETED_REGEXP = new RegExp(/^(.+)\.d[0-9]+$/); var DELETED_REGEXP = new RegExp(/^(.+)\.d[0-9]+$/);
var FILENAME_PROP = '{http://nextcloud.org/ns}trashbin-filename';
var DELETION_TIME_PROP = '{http://nextcloud.org/ns}trashbin-deletion-time';
/** /**
* Convert a file name in the format filename.d12345 to the real file name. * Convert a file name in the format filename.d12345 to the real file name.
@ -36,17 +38,30 @@
* @param [options] map of options * @param [options] map of options
*/ */
var FileList = function($el, options) { var FileList = function($el, options) {
this.client = options.client;
this.initialize($el, options); this.initialize($el, options);
}; };
FileList.prototype = _.extend({}, OCA.Files.FileList.prototype, FileList.prototype = _.extend({}, OCA.Files.FileList.prototype,
/** @lends OCA.Trashbin.FileList.prototype */ { /** @lends OCA.Trashbin.FileList.prototype */ {
id: 'trashbin', id: 'trashbin',
appName: t('files_trashbin', 'Deleted files'), appName: t('files_trashbin', 'Deleted files'),
/** @type {OC.Files.Client} */
client: null,
/** /**
* @private * @private
*/ */
initialize: function() { initialize: function() {
this.client.addFileInfoParser(function(response, data) {
var props = response.propStat[0].properties;
return {
displayName: props[FILENAME_PROP],
mtime: parseInt(props[DELETION_TIME_PROP], 10) * 1000,
hasPreview: true,
path: data.path.substr(6), // remove leading /trash
}
});
var result = OCA.Files.FileList.prototype.initialize.apply(this, arguments); var result = OCA.Files.FileList.prototype.initialize.apply(this, arguments);
this.$el.find('.undelete').click('click', _.bind(this._onClickRestoreSelected, this)); this.$el.find('.undelete').click('click', _.bind(this._onClickRestoreSelected, this));
@ -91,23 +106,6 @@
return tr; return tr;
}, },
_renderRow: function(fileData, options) {
options = options || {};
// make a copy to avoid changing original object
fileData = _.extend({}, fileData);
var dir = this.getCurrentDirectory();
var dirListing = dir !== '' && dir !== '/';
// show deleted time as mtime
if (fileData.mtime) {
fileData.mtime = parseInt(fileData.mtime, 10);
}
if (!dirListing) {
fileData.displayName = fileData.name;
fileData.name = fileData.name + '.d' + Math.floor(fileData.mtime / 1000);
}
return OCA.Files.FileList.prototype._renderRow.call(this, fileData, options);
},
getAjaxUrl: function(action, params) { getAjaxUrl: function(action, params) {
var q = ''; var q = '';
if (params) { if (params) {
@ -140,15 +138,10 @@
this.$el.find('#filestable th').toggleClass('hidden', !exists); this.$el.find('#filestable th').toggleClass('hidden', !exists);
}, },
_removeCallback: function(result) { _removeCallback: function(files) {
if (result.status !== 'success') {
OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error'));
}
var files = result.data.success;
var $el; var $el;
for (var i = 0; i < files.length; i++) { for (var i = 0; i < files.length; i++) {
$el = this.remove(OC.basename(files[i].filename), {updateSummary: false}); $el = this.remove(OC.basename(files[i]), {updateSummary: false});
this.fileSummary.remove({type: $el.attr('data-type'), size: $el.attr('data-size')}); this.fileSummary.remove({type: $el.attr('data-type'), size: $el.attr('data-size')});
} }
this.fileSummary.update(); this.fileSummary.update();
@ -158,97 +151,71 @@
_onClickRestoreSelected: function(event) { _onClickRestoreSelected: function(event) {
event.preventDefault(); event.preventDefault();
var self = this; var self = this;
var allFiles = this.$el.find('.select-all').is(':checked'); var files = _.pluck(this.getSelectedFiles(), 'name');
var files = []; for (var i = 0; i < files.length; i++) {
var params = {}; var tr = this.findFileEl(files[i]);
this.fileMultiSelectMenu.toggleLoading('restore', true); this.showFileBusyState(tr, true);
if (allFiles) {
this.showMask();
params = {
allfiles: true,
dir: this.getCurrentDirectory()
};
}
else {
files = _.pluck(this.getSelectedFiles(), 'name');
for (var i = 0; i < files.length; i++) {
var deleteAction = this.findFileEl(files[i]).children("td.date").children(".action.delete");
deleteAction.removeClass('icon-delete').addClass('icon-loading-small');
}
params = {
files: JSON.stringify(files),
dir: this.getCurrentDirectory()
};
} }
$.post(OC.filePath('files_trashbin', 'ajax', 'undelete.php'), this.fileMultiSelectMenu.toggleLoading('restore', true);
params, var restorePromises = files.map(function(file) {
function(result) { return self.client.move(OC.joinPaths('trash', self.getCurrentDirectory(), file), OC.joinPaths('restore', file), true)
if (allFiles) { .then(
if (result.status !== 'success') { function() {
OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error')); self._removeCallback([file]);
} }
self.hideMask(); );
// simply remove all files });
self.setFiles([]); return Promise.all(restorePromises).then(
} function() {
else {
self._removeCallback(result);
}
self.fileMultiSelectMenu.toggleLoading('restore', false); self.fileMultiSelectMenu.toggleLoading('restore', false);
},
function() {
OC.Notification.show(t('files_trashbin', 'Error while restoring files from trashbin'));
} }
); );
event.preventDefault();
}, },
_onClickDeleteSelected: function(event) { _onClickDeleteSelected: function(event) {
event.preventDefault(); event.preventDefault();
var self = this; var self = this;
var allFiles = this.$el.find('.select-all').is(':checked'); var allFiles = this.$el.find('.select-all').is(':checked');
var files = []; var files = _.pluck(this.getSelectedFiles(), 'name');
var params = {}; for (var i = 0; i < files.length; i++) {
if (allFiles) { var tr = this.findFileEl(files[i]);
params = { this.showFileBusyState(tr, true);
allfiles: true,
dir: this.getCurrentDirectory()
};
}
else {
files = _.pluck(this.getSelectedFiles(), 'name');
params = {
files: JSON.stringify(files),
dir: this.getCurrentDirectory()
};
} }
this.fileMultiSelectMenu.toggleLoading('delete', true);
if (allFiles) { if (allFiles) {
this.showMask(); return this.client.remove(OC.joinPaths('trash', this.getCurrentDirectory()))
} .then(
else { function() {
for (var i = 0; i < files.length; i++) {
var deleteAction = this.findFileEl(files[i]).children("td.date").children(".action.delete");
deleteAction.removeClass('icon-delete').addClass('icon-loading-small');
}
}
$.post(OC.filePath('files_trashbin', 'ajax', 'delete.php'),
params,
function(result) {
if (allFiles) {
if (result.status !== 'success') {
OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error'));
}
self.hideMask(); self.hideMask();
// simply remove all files
self.setFiles([]); self.setFiles([]);
},
function() {
OC.Notification.show(t('files_trashbin', 'Error while emptying trashbin'));
} }
else { );
self._removeCallback(result); } else {
} this.fileMultiSelectMenu.toggleLoading('delete', true);
var deletePromises = files.map(function(file) {
return self.client.remove(OC.joinPaths('trash', self.getCurrentDirectory(), file))
.then(
function() {
self._removeCallback([file]);
}
);
});
return Promise.all(deletePromises).then(
function() {
self.fileMultiSelectMenu.toggleLoading('delete', false); self.fileMultiSelectMenu.toggleLoading('delete', false);
},
function() {
OC.Notification.show(t('files_trashbin', 'Error while removing files from trashbin'));
} }
); );
}
}, },
_onClickFile: function(event) { _onClickFile: function(event) {
@ -277,6 +244,13 @@
return true; return true;
}, },
/**
* Returns list of webdav properties to request
*/
_getWebdavProperties: function() {
return [FILENAME_PROP, DELETION_TIME_PROP].concat(this.filesClient.getPropfindProperties());
},
/** /**
* Reloads the file list using ajax call * Reloads the file list using ajax call
* *
@ -290,39 +264,25 @@
if (this._reloadCall) { if (this._reloadCall) {
this._reloadCall.abort(); this._reloadCall.abort();
} }
this._reloadCall = $.ajax({ this._reloadCall = this.client.getFolderContents(
url: this.getAjaxUrl('list'), 'trash/' + this.getCurrentDirectory(), {
data: { includeParent: false,
dir : this.getCurrentDirectory(), properties: this._getWebdavProperties()
sort: this._sort,
sortdirection: this._sortDirection
} }
}); );
var callBack = this.reloadCallback.bind(this); var callBack = this.reloadCallback.bind(this);
return this._reloadCall.then(callBack, callBack); return this._reloadCall.then(callBack, callBack);
}, },
reloadCallback: function(result) { reloadCallback: function(status, result) {
delete this._reloadCall; delete this._reloadCall;
this.hideMask(); this.hideMask();
if (!result || result.status === 'error') { if (status === 401) {
// if the error is not related to folder we're trying to load, reload the page to handle logout etc
if (result.data.error === 'authentication_error' ||
result.data.error === 'token_expired' ||
result.data.error === 'application_not_enabled'
) {
OC.redirect(OC.generateUrl('apps/files'));
}
OC.Notification.show(result.data.message);
return false;
}
if (result.status === 401) {
return false; return false;
} }
// Firewall Blocked request? // Firewall Blocked request?
if (result.status === 403) { if (status === 403) {
// Go home // Go home
this.changeDirectory('/'); this.changeDirectory('/');
OC.Notification.show(t('files', 'This operation is forbidden')); OC.Notification.show(t('files', 'This operation is forbidden'));
@ -330,24 +290,24 @@
} }
// Did share service die or something else fail? // Did share service die or something else fail?
if (result.status === 500) { if (status === 500) {
// Go home // Go home
this.changeDirectory('/'); this.changeDirectory('/');
OC.Notification.show(t('files', 'This directory is unavailable, please check the logs or contact the administrator')); OC.Notification.show(t('files', 'This directory is unavailable, please check the logs or contact the administrator'));
return false; return false;
} }
if (result.status === 404) { if (status === 404) {
// go back home // go back home
this.changeDirectory('/'); this.changeDirectory('/');
return false; return false;
} }
// aborted ? // aborted ?
if (result.status === 0){ if (status === 0){
return true; return true;
} }
this.setFiles(result.data.files); this.setFiles(result);
return true; return true;
}, },

View File

@ -0,0 +1,69 @@
<?php
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Trashbin\Sabre;
use OCP\Files\FileInfo;
abstract class AbstractTrash implements ITrash {
/** @var FileInfo */
protected $data;
public function __construct(FileInfo $data) {
$this->data = $data;
}
public function getFilename(): string {
return $this->data->getName();
}
public function getDeletionTime(): int {
return $this->data->getMtime();
}
public function getFileId(): int {
return $this->data->getId();
}
public function getFileInfo(): FileInfo {
return $this->data;
}
public function getSize(): int {
return $this->data->getSize();
}
public function getLastModified(): int {
return $this->data->getMtime();
}
public function getContentType(): string {
return $this->data->getMimetype();
}
public function getETag(): string {
return $this->data->getEtag();
}
public function getName(): string {
return $this->data->getName();
}
}

View File

@ -23,6 +23,8 @@ declare(strict_types=1);
*/ */
namespace OCA\Files_Trashbin\Sabre; namespace OCA\Files_Trashbin\Sabre;
use OCP\Files\FileInfo;
interface ITrash { interface ITrash {
public function restore(): bool; public function restore(): bool;
@ -35,4 +37,6 @@ interface ITrash {
public function getSize(); public function getSize();
public function getFileId(): int; public function getFileId(): int;
public function getFileInfo(): FileInfo;
} }

View File

@ -25,6 +25,8 @@ declare(strict_types=1);
namespace OCA\Files_Trashbin\Sabre; namespace OCA\Files_Trashbin\Sabre;
use OCA\DAV\Connector\Sabre\FilesPlugin; use OCA\DAV\Connector\Sabre\FilesPlugin;
use OCP\Constants;
use OCP\IPreview;
use Sabre\DAV\INode; use Sabre\DAV\INode;
use Sabre\DAV\PropFind; use Sabre\DAV\PropFind;
use Sabre\DAV\Server; use Sabre\DAV\Server;
@ -39,7 +41,13 @@ class PropfindPlugin extends ServerPlugin {
/** @var Server */ /** @var Server */
private $server; private $server;
public function __construct() { /** @var IPreview */
private $previewManager;
public function __construct(
IPreview $previewManager
) {
$this->previewManager = $previewManager;
} }
public function initialize(Server $server) { public function initialize(Server $server) {
@ -54,11 +62,11 @@ class PropfindPlugin extends ServerPlugin {
return; return;
} }
$propFind->handle(self::TRASHBIN_FILENAME, function() use ($node) { $propFind->handle(self::TRASHBIN_FILENAME, function () use ($node) {
return $node->getFilename(); return $node->getFilename();
}); });
$propFind->handle(self::TRASHBIN_ORIGINAL_LOCATION, function() use ($node) { $propFind->handle(self::TRASHBIN_ORIGINAL_LOCATION, function () use ($node) {
return $node->getOriginalLocation(); return $node->getOriginalLocation();
}); });
@ -73,6 +81,28 @@ class PropfindPlugin extends ServerPlugin {
$propFind->handle(FilesPlugin::FILEID_PROPERTYNAME, function () use ($node) { $propFind->handle(FilesPlugin::FILEID_PROPERTYNAME, function () use ($node) {
return $node->getFileId(); return $node->getFileId();
}); });
$propFind->handle(FilesPlugin::PERMISSIONS_PROPERTYNAME, function () {
return 'GD'; // read + delete
});
$propFind->handle(FilesPlugin::GETETAG_PROPERTYNAME, function () use ($node) {
// add fake etag, it is only needed to identify the preview image
return $node->getLastModified();
});
$propFind->handle(FilesPlugin::INTERNAL_FILEID_PROPERTYNAME, function () use ($node) {
// add fake etag, it is only needed to identify the preview image
return $node->getFileId();
});
$propFind->handle(FilesPlugin::HAS_PREVIEW_PROPERTYNAME, function () use ($node) {
return $this->previewManager->isAvailable($node->getFileInfo());
});
$propFind->handle(FilesPlugin::MOUNT_TYPE_PROPERTYNAME, function () {
return '';
});
} }
} }

View File

@ -27,16 +27,13 @@ use OCP\Files\FileInfo;
use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\IFile; use Sabre\DAV\IFile;
class TrashFile implements IFile, ITrash { class TrashFile extends AbstractTrash implements IFile, ITrash {
/** @var string */ /** @var string */
private $userId; private $userId;
/** @var FileInfo */
private $data;
public function __construct(string $userId, FileInfo $data) { public function __construct(string $userId, FileInfo $data) {
$this->userId = $userId; $this->userId = $userId;
$this->data = $data; parent::__construct($data);
} }
public function put($data) { public function put($data) {
@ -47,18 +44,6 @@ class TrashFile implements IFile, ITrash {
return $this->data->getStorage()->fopen($this->data->getInternalPath().'.d'.$this->getLastModified(), 'rb'); return $this->data->getStorage()->fopen($this->data->getInternalPath().'.d'.$this->getLastModified(), 'rb');
} }
public function getContentType(): string {
return $this->data->getMimetype();
}
public function getETag(): string {
return $this->data->getEtag();
}
public function getSize(): int {
return $this->data->getSize();
}
public function delete() { public function delete() {
\OCA\Files_Trashbin\Trashbin::delete($this->data->getName(), $this->userId, $this->getLastModified()); \OCA\Files_Trashbin\Trashbin::delete($this->data->getName(), $this->userId, $this->getLastModified());
} }
@ -71,29 +56,11 @@ class TrashFile implements IFile, ITrash {
throw new Forbidden(); throw new Forbidden();
} }
public function getLastModified(): int {
return $this->data->getMtime();
}
public function restore(): bool { public function restore(): bool {
return \OCA\Files_Trashbin\Trashbin::restore($this->getName(), $this->data->getName(), $this->getLastModified()); return \OCA\Files_Trashbin\Trashbin::restore($this->getName(), $this->data->getName(), $this->getLastModified());
} }
public function getFilename(): string {
return $this->data->getName();
}
public function getOriginalLocation(): string { public function getOriginalLocation(): string {
return $this->data['extraData']; return $this->data['extraData'];
} }
public function getDeletionTime(): int {
return $this->getLastModified();
}
public function getFileId(): int {
return $this->data->getId();
}
} }

View File

@ -28,16 +28,13 @@ use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\NotFound; use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\ICollection; use Sabre\DAV\ICollection;
class TrashFolder implements ICollection, ITrash { class TrashFolder extends AbstractTrash implements ICollection, ITrash {
/** @var string */ /** @var string */
private $userId; private $userId;
/** @var FileInfo */
private $data;
public function __construct(string $root, string $userId, FileInfo $data) { public function __construct(string $root, string $userId, FileInfo $data) {
$this->userId = $userId; $this->userId = $userId;
$this->data = $data; parent::__construct($data);
} }
public function createFile($name, $data = null) { public function createFile($name, $data = null) {
@ -100,31 +97,11 @@ class TrashFolder implements ICollection, ITrash {
throw new Forbidden(); throw new Forbidden();
} }
public function getLastModified(): int {
return $this->data->getMtime();
}
public function restore(): bool { public function restore(): bool {
return \OCA\Files_Trashbin\Trashbin::restore($this->getName(), $this->data->getName(), $this->getLastModified()); return \OCA\Files_Trashbin\Trashbin::restore($this->getName(), $this->data->getName(), $this->getLastModified());
} }
public function getFilename(): string {
return $this->data->getName();
}
public function getOriginalLocation(): string { public function getOriginalLocation(): string {
return $this->data['extraData']; return $this->data['extraData'];
} }
public function getDeletionTime(): int {
return $this->getLastModified();
}
public function getSize(): int {
return $this->data->getSize();
}
public function getFileId(): int {
return $this->data->getId();
}
} }

View File

@ -27,16 +27,13 @@ use OCP\Files\FileInfo;
use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\IFile; use Sabre\DAV\IFile;
class TrashFolderFile implements IFile, ITrash { class TrashFolderFile extends AbstractTrash implements IFile, ITrash {
/** @var string */ /** @var string */
private $root; private $root;
/** @var string */ /** @var string */
private $userId; private $userId;
/** @var FileInfo */
private $data;
/** @var string */ /** @var string */
private $location; private $location;
@ -46,8 +43,8 @@ class TrashFolderFile implements IFile, ITrash {
string $location) { string $location) {
$this->root = $root; $this->root = $root;
$this->userId = $userId; $this->userId = $userId;
$this->data = $data;
$this->location = $location; $this->location = $location;
parent::__construct($data);
} }
public function put($data) { public function put($data) {
@ -58,51 +55,19 @@ class TrashFolderFile implements IFile, ITrash {
return $this->data->getStorage()->fopen($this->data->getInternalPath(), 'rb'); return $this->data->getStorage()->fopen($this->data->getInternalPath(), 'rb');
} }
public function getContentType(): string {
return $this->data->getMimetype();
}
public function getETag(): string {
return $this->data->getEtag();
}
public function getSize(): int {
return $this->data->getSize();
}
public function delete() { public function delete() {
\OCA\Files_Trashbin\Trashbin::delete($this->root . '/' . $this->getName(), $this->userId, null); \OCA\Files_Trashbin\Trashbin::delete($this->root . '/' . $this->getName(), $this->userId, null);
} }
public function getName(): string {
return $this->data->getName();
}
public function setName($name) { public function setName($name) {
throw new Forbidden(); throw new Forbidden();
} }
public function getLastModified(): int {
return $this->data->getMtime();
}
public function restore(): bool { public function restore(): bool {
return \OCA\Files_Trashbin\Trashbin::restore($this->root . '/' . $this->getName(), $this->data->getName(), null); return \OCA\Files_Trashbin\Trashbin::restore($this->root . '/' . $this->getName(), $this->data->getName(), null);
} }
public function getFilename(): string {
return $this->data->getName();
}
public function getOriginalLocation(): string { public function getOriginalLocation(): string {
return $this->location . '/' . $this->getFilename(); return $this->location . '/' . $this->getFilename();
} }
public function getDeletionTime(): int {
return $this->getLastModified();
}
public function getFileId(): int {
return $this->data->getId();
}
} }

View File

@ -28,7 +28,7 @@ use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\NotFound; use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\ICollection; use Sabre\DAV\ICollection;
class TrashFolderFolder implements ICollection, ITrash { class TrashFolderFolder extends AbstractTrash implements ICollection, ITrash {
/** @var string */ /** @var string */
private $root; private $root;
@ -36,9 +36,6 @@ class TrashFolderFolder implements ICollection, ITrash {
/** @var string */ /** @var string */
private $userId; private $userId;
/** @var FileInfo */
private $data;
/** @var string */ /** @var string */
private $location; private $location;
@ -48,8 +45,8 @@ class TrashFolderFolder implements ICollection, ITrash {
string $location) { string $location) {
$this->root = $root; $this->root = $root;
$this->userId = $userId; $this->userId = $userId;
$this->data = $data;
$this->location = $location; $this->location = $location;
parent::__construct($data);
} }
public function createFile($name, $data = null) { public function createFile($name, $data = null) {
@ -104,40 +101,15 @@ class TrashFolderFolder implements ICollection, ITrash {
\OCA\Files_Trashbin\Trashbin::delete($this->root . '/' . $this->getName(), $this->userId, null); \OCA\Files_Trashbin\Trashbin::delete($this->root . '/' . $this->getName(), $this->userId, null);
} }
public function getName(): string {
return $this->data->getName();
}
public function setName($name) { public function setName($name) {
throw new Forbidden(); throw new Forbidden();
} }
public function getLastModified(): int {
return $this->data->getMtime();
}
public function restore(): bool { public function restore(): bool {
return \OCA\Files_Trashbin\Trashbin::restore($this->root . '/' . $this->getName(), $this->data->getName(), null); return \OCA\Files_Trashbin\Trashbin::restore($this->root . '/' . $this->getName(), $this->data->getName(), null);
} }
public function getFilename(): string {
return $this->data->getName();
}
public function getOriginalLocation(): string { public function getOriginalLocation(): string {
return $this->location . '/' . $this->getFilename(); return $this->location . '/' . $this->getFilename();
} }
public function getDeletionTime(): int {
return $this->getLastModified();
}
public function getSize(): int {
return $this->data->getSize();
}
public function getFileId(): int {
return $this->data->getId();
}
} }

View File

@ -1,31 +1,38 @@
/** /**
* ownCloud * ownCloud
* *
* @author Vincent Petry * @author Vincent Petry
* @copyright 2014 Vincent Petry <pvince81@owncloud.com> * @copyright 2014 Vincent Petry <pvince81@owncloud.com>
* *
* This library is free software; you can redistribute it and/or * This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either * License as published by the Free Software Foundation; either
* version 3 of the License, or any later version. * version 3 of the License, or any later version.
* *
* This library is distributed in the hope that it will be useful, * This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details. * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
* *
* You should have received a copy of the GNU Affero General Public * 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/>. * License along with this library. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
describe('OCA.Trashbin.FileList tests', function() { describe('OCA.Trashbin.FileList tests', function () {
var testFiles, alertStub, notificationStub, fileList; var testFiles, alertStub, notificationStub, fileList, client;
beforeEach(function() { beforeEach(function () {
alertStub = sinon.stub(OC.dialogs, 'alert'); alertStub = sinon.stub(OC.dialogs, 'alert');
notificationStub = sinon.stub(OC.Notification, 'show'); notificationStub = sinon.stub(OC.Notification, 'show');
client = new OC.Files.Client({
host: 'localhost',
port: 80,
root: '/remote.php/dav/trashbin/user',
useHTTPS: OC.getProtocol() === 'https'
});
// init parameters and test table elements // init parameters and test table elements
$('#testArea').append( $('#testArea').append(
'<div id="app-content-trashbin">' + '<div id="app-content-trashbin">' +
@ -59,21 +66,24 @@ describe('OCA.Trashbin.FileList tests', function() {
testFiles = [{ testFiles = [{
id: 1, id: 1,
type: 'file', type: 'file',
name: 'One.txt', name: 'One.txt.d11111',
displayName: 'One.txt',
mtime: 11111000, mtime: 11111000,
mimetype: 'text/plain', mimetype: 'text/plain',
etag: 'abc' etag: 'abc'
}, { }, {
id: 2, id: 2,
type: 'file', type: 'file',
name: 'Two.jpg', name: 'Two.jpg.d22222',
displayName: 'Two.jpg',
mtime: 22222000, mtime: 22222000,
mimetype: 'image/jpeg', mimetype: 'image/jpeg',
etag: 'def', etag: 'def',
}, { }, {
id: 3, id: 3,
type: 'file', type: 'file',
name: 'Three.pdf', name: 'Three.pdf.d33333',
displayName: 'Three.pdf',
mtime: 33333000, mtime: 33333000,
mimetype: 'application/pdf', mimetype: 'application/pdf',
etag: '123', etag: '123',
@ -81,7 +91,8 @@ describe('OCA.Trashbin.FileList tests', function() {
id: 4, id: 4,
type: 'dir', type: 'dir',
mtime: 99999000, mtime: 99999000,
name: 'somedir', name: 'somedir.d99999',
displayName: 'somedir',
mimetype: 'httpd/unix-directory', mimetype: 'httpd/unix-directory',
etag: '456' etag: '456'
}]; }];
@ -92,20 +103,21 @@ describe('OCA.Trashbin.FileList tests', function() {
$('#app-content-trashbin'), { $('#app-content-trashbin'), {
fileActions: fileActions, fileActions: fileActions,
multiSelectMenu: [{ multiSelectMenu: [{
name: 'restore', name: 'restore',
displayName: t('files', 'Restore'), displayName: t('files', 'Restore'),
iconClass: 'icon-history', iconClass: 'icon-history',
}, },
{ {
name: 'delete', name: 'delete',
displayName: t('files', 'Delete'), displayName: t('files', 'Delete'),
iconClass: 'icon-delete', iconClass: 'icon-delete',
} }
] ],
client: client
} }
); );
}); });
afterEach(function() { afterEach(function () {
testFiles = undefined; testFiles = undefined;
fileList.destroy(); fileList.destroy();
fileList = undefined; fileList = undefined;
@ -114,17 +126,17 @@ describe('OCA.Trashbin.FileList tests', function() {
notificationStub.restore(); notificationStub.restore();
alertStub.restore(); alertStub.restore();
}); });
describe('Initialization', function() { describe('Initialization', function () {
it('Sorts by mtime by default', function() { it('Sorts by mtime by default', function () {
expect(fileList._sort).toEqual('mtime'); expect(fileList._sort).toEqual('mtime');
expect(fileList._sortDirection).toEqual('desc'); expect(fileList._sortDirection).toEqual('desc');
}); });
it('Always returns read and delete permission', function() { it('Always returns read and delete permission', function () {
expect(fileList.getDirectoryPermissions()).toEqual(OC.PERMISSION_READ | OC.PERMISSION_DELETE); expect(fileList.getDirectoryPermissions()).toEqual(OC.PERMISSION_READ | OC.PERMISSION_DELETE);
}); });
}); });
describe('Breadcrumbs', function() { describe('Breadcrumbs', function () {
beforeEach(function() { beforeEach(function () {
var data = { var data = {
status: 'success', status: 'success',
data: { data: {
@ -133,13 +145,13 @@ describe('OCA.Trashbin.FileList tests', function() {
} }
}; };
fakeServer.respondWith(/\/index\.php\/apps\/files_trashbin\/ajax\/list.php\?dir=%2Fsubdir/, [ fakeServer.respondWith(/\/index\.php\/apps\/files_trashbin\/ajax\/list.php\?dir=%2Fsubdir/, [
200, { 200, {
"Content-Type": "application/json" "Content-Type": "application/json"
}, },
JSON.stringify(data) JSON.stringify(data)
]); ]);
}); });
it('links the breadcrumb to the trashbin view', function() { it('links the breadcrumb to the trashbin view', function () {
fileList.changeDirectory('/subdir', false, true); fileList.changeDirectory('/subdir', false, true);
fakeServer.respond(); fakeServer.respond();
var $crumbs = fileList.$el.find('#controls .crumb'); var $crumbs = fileList.$el.find('#controls .crumb');
@ -152,8 +164,8 @@ describe('OCA.Trashbin.FileList tests', function() {
.toEqual(OC.webroot + '/index.php/apps/files?view=trashbin&dir=/subdir'); .toEqual(OC.webroot + '/index.php/apps/files?view=trashbin&dir=/subdir');
}); });
}); });
describe('Rendering rows', function() { describe('Rendering rows', function () {
it('renders rows with the correct data when in root', function() { it('renders rows with the correct data when in root', function () {
// dir listing is false when in root // dir listing is false when in root
$('#dir').val('/'); $('#dir').val('/');
fileList.setFiles(testFiles); fileList.setFiles(testFiles);
@ -174,7 +186,7 @@ describe('OCA.Trashbin.FileList tests', function() {
expect(fileList.findFileEl('One.txt.d11111')[0]).toEqual($tr[0]); expect(fileList.findFileEl('One.txt.d11111')[0]).toEqual($tr[0]);
}); });
it('renders rows with the correct data when in root after calling setFiles with the same data set', function() { it('renders rows with the correct data when in root after calling setFiles with the same data set', function () {
// dir listing is false when in root // dir listing is false when in root
$('#dir').val('/'); $('#dir').val('/');
fileList.setFiles(testFiles); fileList.setFiles(testFiles);
@ -196,11 +208,14 @@ describe('OCA.Trashbin.FileList tests', function() {
expect(fileList.findFileEl('One.txt.d11111')[0]).toEqual($tr[0]); expect(fileList.findFileEl('One.txt.d11111')[0]).toEqual($tr[0]);
}); });
it('renders rows with the correct data when in subdirectory', function() { it('renders rows with the correct data when in subdirectory', function () {
// dir listing is true when in a subdir // dir listing is true when in a subdir
$('#dir').val('/subdir'); $('#dir').val('/subdir');
fileList.setFiles(testFiles); fileList.setFiles(testFiles.map(function (file) {
file.name = file.displayName;
return file;
}));
var $rows = fileList.$el.find('tbody tr'); var $rows = fileList.$el.find('tbody tr');
var $tr = $rows.eq(0); var $tr = $rows.eq(0);
expect($rows.length).toEqual(4); expect($rows.length).toEqual(4);
@ -218,42 +233,42 @@ describe('OCA.Trashbin.FileList tests', function() {
expect(fileList.findFileEl('One.txt')[0]).toEqual($tr[0]); expect(fileList.findFileEl('One.txt')[0]).toEqual($tr[0]);
}); });
it('does not render a size column', function() { it('does not render a size column', function () {
expect(fileList.$el.find('tbody tr .filesize').length).toEqual(0); expect(fileList.$el.find('tbody tr .filesize').length).toEqual(0);
}); });
}); });
describe('File actions', function() { describe('File actions', function () {
describe('Deleting single files', function() { describe('Deleting single files', function () {
// TODO: checks ajax call // TODO: checks ajax call
// TODO: checks spinner // TODO: checks spinner
// TODO: remove item after delete // TODO: remove item after delete
// TODO: bring back item if delete failed // TODO: bring back item if delete failed
}); });
describe('Restoring single files', function() { describe('Restoring single files', function () {
// TODO: checks ajax call // TODO: checks ajax call
// TODO: checks spinner // TODO: checks spinner
// TODO: remove item after restore // TODO: remove item after restore
// TODO: bring back item if restore failed // TODO: bring back item if restore failed
}); });
}); });
describe('file previews', function() { describe('file previews', function () {
// TODO: check that preview URL is going through files_trashbin // TODO: check that preview URL is going through files_trashbin
}); });
describe('loading file list', function() { describe('loading file list', function () {
// TODO: check that ajax URL is going through files_trashbin // TODO: check that ajax URL is going through files_trashbin
}); });
describe('breadcrumbs', function() { describe('breadcrumbs', function () {
// TODO: test label + URL // TODO: test label + URL
}); });
describe('elementToFile', function() { describe('elementToFile', function () {
var $tr; var $tr;
beforeEach(function() { beforeEach(function () {
fileList.setFiles(testFiles); fileList.setFiles(testFiles);
$tr = fileList.findFileEl('One.txt.d11111'); $tr = fileList.findFileEl('One.txt.d11111');
}); });
it('converts data attributes to file info structure', function() { it('converts data attributes to file info structure', function () {
var fileInfo = fileList.elementToFile($tr); var fileInfo = fileList.elementToFile($tr);
expect(fileInfo.id).toEqual(1); expect(fileInfo.id).toEqual(1);
expect(fileInfo.name).toEqual('One.txt.d11111'); expect(fileInfo.name).toEqual('One.txt.d11111');
@ -265,8 +280,8 @@ describe('OCA.Trashbin.FileList tests', function() {
expect(fileInfo.type).toEqual('file'); expect(fileInfo.type).toEqual('file');
}); });
}); });
describe('Global Actions', function() { describe('Global Actions', function () {
beforeEach(function() { beforeEach(function () {
fileList.setFiles(testFiles); fileList.setFiles(testFiles);
fileList.findFileEl('One.txt.d11111').find('input:checkbox').click(); fileList.findFileEl('One.txt.d11111').find('input:checkbox').click();
fileList.findFileEl('Three.pdf.d33333').find('input:checkbox').click(); fileList.findFileEl('Three.pdf.d33333').find('input:checkbox').click();
@ -274,12 +289,12 @@ describe('OCA.Trashbin.FileList tests', function() {
fileList.$el.find('.actions-selected').click(); fileList.$el.find('.actions-selected').click();
}); });
afterEach(function() { afterEach(function () {
fileList.$el.find('.actions-selected').click(); fileList.$el.find('.actions-selected').click();
}); });
describe('Delete', function() { describe('Delete', function () {
it('Shows trashbin actions', function() { it('Shows trashbin actions', function () {
// visible because a few files were selected // visible because a few files were selected
expect($('.selectedActions').is(':visible')).toEqual(true); expect($('.selectedActions').is(':visible')).toEqual(true);
expect($('.selectedActions .item-delete').is(':visible')).toEqual(true); expect($('.selectedActions .item-delete').is(':visible')).toEqual(true);
@ -301,99 +316,82 @@ describe('OCA.Trashbin.FileList tests', function() {
expect($('.selectedActions .item-delete').is(':visible')).toEqual(false); expect($('.selectedActions .item-delete').is(':visible')).toEqual(false);
expect($('.selectedActions .item-restore').is(':visible')).toEqual(false); expect($('.selectedActions .item-restore').is(':visible')).toEqual(false);
}); });
it('Deletes selected files when "Delete" clicked', function() { it('Deletes selected files when "Delete" clicked', function () {
var request; var request;
var $deleteLink = $('.selectedActions .filesSelectMenu .delete'); var promise = fileList._onClickDeleteSelected({
$deleteLink.click(); preventDefault: function () {
expect($deleteLink.find('.icon-loading-small').length).toEqual(1); }
expect(fakeServer.requests.length).toEqual(1); });
request = fakeServer.requests[0]; var files = ["One.txt.d11111", "Three.pdf.d33333", "somedir.d99999"];
expect(request.url).toEqual(OC.webroot + '/index.php/apps/files_trashbin/ajax/delete.php'); expect(fakeServer.requests.length).toEqual(files.length);
expect(OC.parseQueryString(request.requestBody)) for (var i = 0; i < files.length; i++) {
.toEqual({'dir': '/', files: '["One.txt.d11111","Three.pdf.d33333","somedir.d99999"]'}); request = fakeServer.requests[i];
fakeServer.requests[0].respond( expect(request.url).toEqual(OC.webroot + '/remote.php/dav/trashbin/user/trash/' + files[i]);
200, request.respond(200);
{ 'Content-Type': 'application/json' }, }
JSON.stringify({ return promise.then(function () {
status: 'success', expect(fileList.findFileEl('One.txt.d11111').length).toEqual(0);
data: { expect(fileList.findFileEl('Three.pdf.d33333').length).toEqual(0);
success: [ expect(fileList.findFileEl('somedir.d99999').length).toEqual(0);
{filename: 'One.txt.d11111'}, expect(fileList.findFileEl('Two.jpg.d22222').length).toEqual(1);
{filename: 'Three.pdf.d33333'}, });
{filename: 'somedir.d99999'}
]
}
})
);
expect($deleteLink.find('.icon-loading-small').length).toEqual(0);
expect(fileList.findFileEl('One.txt.d11111').length).toEqual(0);
expect(fileList.findFileEl('Three.pdf.d33333').length).toEqual(0);
expect(fileList.findFileEl('somedir.d99999').length).toEqual(0);
expect(fileList.findFileEl('Two.jpg.d22222').length).toEqual(1);
}); });
it('Deletes all files when all selected when "Delete" clicked', function() { it('Deletes all files when all selected when "Delete" clicked', function () {
var request; var request;
$('.select-all').click(); $('.select-all').click();
$('.selectedActions .filesSelectMenu .delete').click(); var promise = fileList._onClickDeleteSelected({
preventDefault: function () {
}
});
expect(fakeServer.requests.length).toEqual(1); expect(fakeServer.requests.length).toEqual(1);
request = fakeServer.requests[0]; request = fakeServer.requests[0];
expect(request.url).toEqual(OC.webroot + '/index.php/apps/files_trashbin/ajax/delete.php'); expect(request.url).toEqual(OC.webroot + '/remote.php/dav/trashbin/user/trash');
expect(OC.parseQueryString(request.requestBody)) request.respond(200);
.toEqual({'dir': '/', allfiles: 'true'}); return promise.then(function () {
fakeServer.requests[0].respond( expect(fileList.isEmpty).toEqual(true);
200, });
{ 'Content-Type': 'application/json' },
JSON.stringify({status: 'success'})
);
expect(fileList.isEmpty).toEqual(true);
}); });
}); });
describe('Restore', function() { describe('Restore', function () {
it('Restores selected files when "Restore" clicked', function() { it('Restores selected files when "Restore" clicked', function () {
var request; var request;
var $restoreLink = $('.selectedActions .filesSelectMenu .restore'); var promise = fileList._onClickRestoreSelected({
$restoreLink.click(); preventDefault: function () {
expect($restoreLink.find('.icon-loading-small').length).toEqual(1); }
expect(fakeServer.requests.length).toEqual(1); });
request = fakeServer.requests[0]; var files = ["One.txt.d11111", "Three.pdf.d33333", "somedir.d99999"];
expect(request.url).toEqual(OC.webroot + '/index.php/apps/files_trashbin/ajax/undelete.php'); expect(fakeServer.requests.length).toEqual(files.length);
expect(OC.parseQueryString(request.requestBody)) for (var i = 0; i < files.length; i++) {
.toEqual({'dir': '/', files: '["One.txt.d11111","Three.pdf.d33333","somedir.d99999"]'}); request = fakeServer.requests[i];
fakeServer.requests[0].respond( expect(request.url).toEqual(OC.webroot + '/remote.php/dav/trashbin/user/trash/' + files[i]);
200, expect(request.requestHeaders.Destination).toEqual(OC.webroot + '/remote.php/dav/trashbin/user/restore/' + files[i]);
{ 'Content-Type': 'application/json' }, request.respond(200);
JSON.stringify({ }
status: 'success', return promise.then(function() {
data: { expect(fileList.findFileEl('One.txt.d11111').length).toEqual(0);
success: [ expect(fileList.findFileEl('Three.pdf.d33333').length).toEqual(0);
{filename: 'One.txt.d11111'}, expect(fileList.findFileEl('somedir.d99999').length).toEqual(0);
{filename: 'Three.pdf.d33333'}, expect(fileList.findFileEl('Two.jpg.d22222').length).toEqual(1);
{filename: 'somedir.d99999'} });
]
}
})
);
expect($restoreLink.find('.icon-loading-small').length).toEqual(0);
expect(fileList.findFileEl('One.txt.d11111').length).toEqual(0);
expect(fileList.findFileEl('Three.pdf.d33333').length).toEqual(0);
expect(fileList.findFileEl('somedir.d99999').length).toEqual(0);
expect(fileList.findFileEl('Two.jpg.d22222').length).toEqual(1);
}); });
it('Restores all files when all selected when "Restore" clicked', function() { it('Restores all files when all selected when "Restore" clicked', function () {
var request; var request;
$('.select-all').click(); $('.select-all').click();
$('.selectedActions .filesSelectMenu .restore').click(); var promise = fileList._onClickRestoreSelected({
expect(fakeServer.requests.length).toEqual(1); preventDefault: function () {
request = fakeServer.requests[0]; }
expect(request.url).toEqual(OC.webroot + '/index.php/apps/files_trashbin/ajax/undelete.php'); });
expect(OC.parseQueryString(request.requestBody)) var files = ["One.txt.d11111", "Two.jpg.d22222", "Three.pdf.d33333", "somedir.d99999"];
.toEqual({'dir': '/', allfiles: 'true'}); expect(fakeServer.requests.length).toEqual(files.length);
fakeServer.requests[0].respond( for (var i = 0; i < files.length; i++) {
200, request = fakeServer.requests[i];
{ 'Content-Type': 'application/json' }, expect(request.url).toEqual(OC.webroot + '/remote.php/dav/trashbin/user/trash/' + files[i]);
JSON.stringify({status: 'success'}) expect(request.requestHeaders.Destination).toEqual(OC.webroot + '/remote.php/dav/trashbin/user/restore/' + files[i]);
); request.respond(200);
expect(fileList.isEmpty).toEqual(true); }
return promise.then(function() {
expect(fileList.isEmpty).toEqual(true);
});
}); });
}); });
}); });

View File

@ -1,10 +1,10 @@
default: default:
autoload: autoload:
'': %paths.base%/../features/bootstrap '': "%paths.base%/../features/bootstrap"
suites: suites:
default: default:
paths: paths:
- %paths.base%/../features - "%paths.base%/../features"
contexts: contexts:
- FeatureContext: - FeatureContext:
baseUrl: http://localhost:8080/ocs/ baseUrl: http://localhost:8080/ocs/
@ -27,7 +27,7 @@ default:
ocPath: ../../ ocPath: ../../
federation: federation:
paths: paths:
- %paths.base%/../federation_features - "%paths.base%/../federation_features"
contexts: contexts:
- FederationContext: - FederationContext:
baseUrl: http://localhost:8080/ocs/ baseUrl: http://localhost:8080/ocs/
@ -37,7 +37,7 @@ default:
regular_user_password: 123456 regular_user_password: 123456
capabilities: capabilities:
paths: paths:
- %paths.base%/../capabilities_features - "%paths.base%/../capabilities_features"
contexts: contexts:
- CapabilitiesContext: - CapabilitiesContext:
baseUrl: http://localhost:8080/ocs/ baseUrl: http://localhost:8080/ocs/
@ -47,7 +47,7 @@ default:
regular_user_password: 123456 regular_user_password: 123456
sharees: sharees:
paths: paths:
- %paths.base%/../sharees_features - "%paths.base%/../sharees_features"
contexts: contexts:
- ShareesContext: - ShareesContext:
baseUrl: http://localhost:8080/ocs/ baseUrl: http://localhost:8080/ocs/
@ -57,7 +57,7 @@ default:
regular_user_password: 123456 regular_user_password: 123456
setup: setup:
paths: paths:
- %paths.base%/../setup_features - "%paths.base%/../setup_features"
contexts: contexts:
- SetupContext: - SetupContext:
baseUrl: http://localhost:8080/ocs/ baseUrl: http://localhost:8080/ocs/
@ -67,7 +67,7 @@ default:
regular_user_password: 123456 regular_user_password: 123456
filesdrop: filesdrop:
paths: paths:
- %paths.base%/../filesdrop_features - "%paths.base%/../filesdrop_features"
contexts: contexts:
- FilesDropContext: - FilesDropContext:
baseUrl: http://localhost:8080 baseUrl: http://localhost:8080
@ -77,7 +77,7 @@ default:
regular_user_password: 123456 regular_user_password: 123456
ldap: ldap:
paths: paths:
- %paths.base%/../ldap_features - "%paths.base%/../ldap_features"
contexts: contexts:
- LDAPContext: - LDAPContext:
baseUrl: http://localhost:8080 baseUrl: http://localhost:8080
@ -87,7 +87,7 @@ default:
regular_user_password: what_for regular_user_password: what_for
remoteapi: remoteapi:
paths: paths:
- %paths.base%/../remoteapi_features - "%paths.base%/../remoteapi_features"
contexts: contexts:
- FeatureContext: - FeatureContext:
baseUrl: http://localhost:8080/ocs/ baseUrl: http://localhost:8080/ocs/
@ -100,4 +100,4 @@ default:
extensions: extensions:
jarnaiz\JUnitFormatter\JUnitFormatterExtension: jarnaiz\JUnitFormatter\JUnitFormatterExtension:
filename: report.xml filename: report.xml
outputDir: %paths.base%/../output/ outputDir: "%paths.base%/../output/"

View File

@ -20,8 +20,6 @@
* *
*/ */
use GuzzleHttp\Client;
use GuzzleHttp\Message\ResponseInterface;
use PHPUnit\Framework\Assert; use PHPUnit\Framework\Assert;
require __DIR__ . '/../../vendor/autoload.php'; require __DIR__ . '/../../vendor/autoload.php';
@ -30,16 +28,46 @@ require __DIR__ . '/../../vendor/autoload.php';
* Trashbin functions * Trashbin functions
*/ */
trait Trashbin { trait Trashbin {
use WebDav;
/** /**
* @When User :user empties trashbin * @When User :user empties trashbin
* @param string $user user * @param string $user user
*/ */
public function emptyTrashbin($user) { public function emptyTrashbin($user) {
$this->asAn($user); $client = $this->getSabreClient($user);
$body = new \Behat\Gherkin\Node\TableNode([['allfiles', 'true'], ['dir', '%2F']]); $response = $client->request('DELETE', $this->makeSabrePath($user, 'trash', 'trashbin'));
$this->sendingToWithDirectUrl('POST', "/index.php/apps/files_trashbin/ajax/delete.php", $body); Assert::assertEquals(204, $response['statusCode']);
$this->theHTTPStatusCodeShouldBe('200'); }
private function findFullTrashname($user, $name) {
$rootListing = $this->listTrashbinFolder($user, '/');
foreach ($rootListing as $href => $rootItem) {
if ($rootItem['{http://nextcloud.org/ns}trashbin-filename'] === $name) {
return basename($href);
}
}
return null;
}
/**
* Get the full /startofpath.dxxxx/rest/of/path from /startofpath/rest/of/path
*/
private function getFullTrashPath($user, $path) {
if ($path !== '' && $path !== '/') {
$parts = explode('/', $path);
$fullName = $this->findFullTrashname($user, $parts[1]);
if ($fullName === null) {
Assert::fail("cant find $path in trash");
return '/dummy_full_path_not_found';
}
$parts[1] = $fullName;
$path = implode('/', $parts);
}
return $path;
} }
/** /**
@ -49,74 +77,92 @@ trait Trashbin {
* @param string $path path * @param string $path path
* @return array response * @return array response
*/ */
public function listTrashbinFolder($user, $path){ public function listTrashbinFolder($user, $path) {
$this->asAn($user); $path = $this->getFullTrashPath($user, $path);
$params = '?dir=' . rawurlencode('/' . trim($path, '/')); $client = $this->getSabreClient($user);
$this->sendingToWithDirectUrl('GET', '/index.php/apps/files_trashbin/ajax/list.php' . $params, null);
$this->theHTTPStatusCodeShouldBe('200');
$response = json_decode($this->response->getBody(), true); $results = $client->propfind($this->makeSabrePath($user, 'trash' . $path, 'trashbin'), [
'{http://nextcloud.org/ns}trashbin-filename',
return $response['data']['files']; '{http://nextcloud.org/ns}trashbin-original-location',
'{http://nextcloud.org/ns}trashbin-deletion-time'
], 1);
$results = array_filter($results, function (array $item) {
return isset($item['{http://nextcloud.org/ns}trashbin-filename']);
});
if ($path !== '' && $path !== '/') {
array_shift($results);
}
return $results;
} }
/** /**
* @Then /^as "([^"]*)" the (file|folder|entry) "([^"]*)" exists in trash$/ * @Then /^user "([^"]*)" in trash folder "([^"]*)" should have the following elements$/
* @param string $user * @param string $user
* @param string $entryText * @param string $folder
* @param string $path * @param \Behat\Gherkin\Node\TableNode|null $expectedElements
*/ */
public function asTheFileOrFolderExistsInTrash($user, $entryText, $path) { public function checkTrashContents($user, $folder, $expectedElements) {
$path = trim($path, '/'); $elementList = $this->listTrashbinFolder($user, $folder);
$sections = explode('/', $path, 2); $trashContent = array_filter(array_map(function (array $item) {
return $item['{http://nextcloud.org/ns}trashbin-filename'];
$firstEntry = $this->findFirstTrashedEntry($user, trim($sections[0], '/')); }, $elementList));
if ($expectedElements instanceof \Behat\Gherkin\Node\TableNode) {
Assert::assertNotNull($firstEntry); $elementRows = $expectedElements->getRows();
$elementsSimplified = $this->simplifyArray($elementRows);
// query was on the main element ? foreach ($elementsSimplified as $expectedElement) {
if (count($sections) === 1) { $expectedElement = ltrim($expectedElement, '/');
// already found, return if (array_search($expectedElement, $trashContent) === false) {
return; Assert::fail("$expectedElement" . " is not in trash listing");
} }
$subdir = trim(dirname($sections[1]), '/');
if ($subdir !== '' && $subdir !== '.') {
$subdir = $firstEntry . '/' . $subdir;
} else {
$subdir = $firstEntry;
}
$listing = $this->listTrashbinFolder($user, $subdir);
$checkedName = basename($path);
$found = false;
foreach ($listing as $entry) {
if ($entry['name'] === $checkedName) {
$found = true;
break;
} }
} }
Assert::assertTrue($found);
} }
/** /**
* Finds the first trashed entry matching the given name * @Then /^as "([^"]*)" the (file|folder) "([^"]*)" exists in trash$/
* * @param string $user
* @param string $name * @param string $type
* @return string|null real entry name with timestamp suffix or null if not found * @param string $file
*/ */
private function findFirstTrashedEntry($user, $name) { public function checkTrashContains($user, $type, $file) {
$listing = $this->listTrashbinFolder($user, '/'); $parent = dirname($file);
if ($parent === '.') {
foreach ($listing as $entry) { $parent = '/';
if ($entry['name'] === $name) {
return $entry['name'] . '.d' . ((int)$entry['mtime'] / 1000);
}
} }
$name = basename($file);
$elementList = $this->listTrashbinFolder($user, $parent);
$trashContent = array_filter(array_map(function (array $item) {
return $item['{http://nextcloud.org/ns}trashbin-filename'];
}, $elementList));
return null; Assert::assertArraySubset([$name], array_values($trashContent));
}
/**
* @Then /^user "([^"]*)" in trash folder "([^"]*)" should have (\d+) elements?$/
* @param string $user
* @param string $folder
* @param \Behat\Gherkin\Node\TableNode|null $expectedElements
*/
public function checkTrashSize($user, $folder, $expectedCount) {
$elementList = $this->listTrashbinFolder($user, $folder);
Assert::assertEquals($expectedCount, count($elementList));
}
/**
* @When /^user "([^"]*)" in restores "([^"]*)" from trash$/
* @param string $user
* @param string $file
*/
public function restoreFromTrash($user, $file) {
$file = $this->getFullTrashPath($user, $file);
$url = $this->makeSabrePath($user, 'trash' . $file, 'trashbin');
$client = $this->getSabreClient($user);
$response = $client->request('MOVE', $url, null, [
'Destination' => $this->makeSabrePath($user, 'restore/' . basename($file), 'trashbin'),
]);
Assert::assertEquals(201, $response['statusCode']);
return;
} }
} }

View File

@ -423,8 +423,12 @@ trait WebDav {
return $parsedResponse; return $parsedResponse;
} }
public function makeSabrePath($user, $path) { public function makeSabrePath($user, $path, $type = 'files') {
return $this->encodePath($this->getDavFilesPath($user) . $path); if ($type === 'files') {
return $this->encodePath($this->getDavFilesPath($user) . $path);
} else {
return $this->encodePath($this->davPath . '/' . $type . '/' . $user . '/' . $path);
}
} }
public function getSabreClient($user) { public function getSabreClient($user) {

View File

@ -1,7 +1,7 @@
Feature: sharing Feature: sharing
Background: Background:
Given using api version "1" Given using api version "1"
Given using old dav path Given using new dav path
# See sharing-v1-part2.feature # See sharing-v1-part2.feature
@ -295,7 +295,7 @@ Feature: sharing
And user "user0" exists And user "user0" exists
And User "user0" deletes file "/textfile0.txt" And User "user0" deletes file "/textfile0.txt"
When User "user0" empties trashbin When User "user0" empties trashbin
Then the HTTP status code should be "200" Then the HTTP status code should be "204"
Scenario: orphaned shares Scenario: orphaned shares
Given As an "admin" Given As an "admin"
@ -392,4 +392,4 @@ Feature: sharing
And folder "/shared" of user "user0" is shared with user "user1" And folder "/shared" of user "user0" is shared with user "user1"
When User "user1" moved file "/textfile0.txt" to "/shared/shared_file.txt" When User "user1" moved file "/textfile0.txt" to "/shared/shared_file.txt"
Then as "user1" the file "/shared/shared_file.txt" exists Then as "user1" the file "/shared/shared_file.txt" exists
And as "user0" the file "/shared/shared_file.txt" exists And as "user0" the file "/shared/shared_file.txt" exists

View File

@ -1,7 +1,7 @@
Feature: trashbin Feature: trashbin
Background: Background:
Given using api version "1" Given using api version "1"
And using old dav path And using new dav path
And As an "admin" And As an "admin"
And app "files_trashbin" is enabled And app "files_trashbin" is enabled
@ -9,5 +9,73 @@ Feature: trashbin
Given As an "admin" Given As an "admin"
And user "user0" exists And user "user0" exists
When User "user0" deletes file "/textfile0.txt" When User "user0" deletes file "/textfile0.txt"
Then as "user0" the file "/textfile0.txt" exists in trash Then user "user0" in trash folder "/" should have 1 element
And user "user0" in trash folder "/" should have the following elements
| textfile0.txt |
Scenario: clearing the trashbin
Given As an "admin"
And user "user0" exists
When User "user0" deletes file "/textfile0.txt"
And User "user0" empties trashbin
Then user "user0" in trash folder "/" should have 0 elements
Scenario: restoring file from trashbin
Given As an "admin"
And user "user0" exists
When User "user0" deletes file "/textfile0.txt"
And user "user0" in restores "/textfile0.txt" from trash
Then user "user0" in trash folder "/" should have 0 elements
And as "user0" the file "/textfile0.txt" exists
Scenario: deleting and restoring a folder
Given As an "admin"
And user "user0" exists
When User "user0" created a folder "/testfolder"
And User "user0" moves file "/textfile0.txt" to "/testfolder/textfile0.txt"
And as "user0" the file "/testfolder/textfile0.txt" exists
And User "user0" deletes file "/testfolder"
And user "user0" in trash folder "/" should have 1 element
And user "user0" in trash folder "/" should have the following elements
| testfolder |
And user "user0" in trash folder "/testfolder" should have 1 element
And user "user0" in trash folder "/testfolder" should have the following elements
| textfile0.txt |
And user "user0" in restores "/testfolder" from trash
Then user "user0" in trash folder "/" should have 0 elements
And as "user0" the file "/testfolder/textfile0.txt" exists
Scenario: deleting a file from a subfolder and restoring it moves it back to the subfolder
Given As an "admin"
And user "user0" exists
When User "user0" created a folder "/testfolder"
And User "user0" moves file "/textfile0.txt" to "/testfolder/textfile0.txt"
And as "user0" the file "/testfolder/textfile0.txt" exists
And User "user0" deletes file "/testfolder/textfile0.txt"
And user "user0" in trash folder "/" should have 1 element
And user "user0" in trash folder "/" should have the following elements
| textfile0.txt |
And user "user0" in restores "/textfile0.txt" from trash
Then user "user0" in trash folder "/" should have 0 elements
And as "user0" the file "/textfile0.txt" does not exist
And as "user0" the file "/testfolder/textfile0.txt" exists
Scenario: deleting and a folder and restoring a file inside it
Given As an "admin"
And user "user0" exists
When User "user0" created a folder "/testfolder"
And User "user0" moves file "/textfile0.txt" to "/testfolder/textfile0.txt"
And as "user0" the file "/testfolder/textfile0.txt" exists
And User "user0" deletes file "/testfolder"
And user "user0" in trash folder "/" should have 1 element
And user "user0" in trash folder "/" should have the following elements
| testfolder |
And user "user0" in trash folder "/testfolder" should have 1 element
And user "user0" in trash folder "/testfolder" should have the following elements
| textfile0.txt |
And user "user0" in restores "/testfolder/textfile0.txt" from trash
Then user "user0" in trash folder "/" should have 1 elements
And user "user0" in trash folder "/testfolder" should have 0 element
And as "user0" the file "/textfile0.txt" exists

View File

@ -61,6 +61,7 @@
} }
this._client = new dav.Client(clientOptions); this._client = new dav.Client(clientOptions);
this._client.xhrProvider = _.bind(this._xhrProvider, this); this._client.xhrProvider = _.bind(this._xhrProvider, this);
this._fileInfoParsers = [];
}; };
Client.NS_OWNCLOUD = 'http://owncloud.org/ns'; Client.NS_OWNCLOUD = 'http://owncloud.org/ns';
@ -390,7 +391,7 @@
// extend the parsed data using the custom parsers // extend the parsed data using the custom parsers
_.each(this._fileInfoParsers, function(parserFunction) { _.each(this._fileInfoParsers, function(parserFunction) {
_.extend(data, parserFunction(response) || {}); _.extend(data, parserFunction(response, data) || {});
}); });
return new FileInfo(data); return new FileInfo(data);