diff --git a/apps/files_trashbin/ajax/delete.php b/apps/files_trashbin/ajax/delete.php deleted file mode 100644 index 5fb69d7f2e..0000000000 --- a/apps/files_trashbin/ajax/delete.php +++ /dev/null @@ -1,93 +0,0 @@ - - * @author Björn Schießle - * @author Lukas Reschke - * @author Robin Appelman - * @author Roeland Jago Douma - * @author Vincent Petry - * - * @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 - * - */ - -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))); -} diff --git a/apps/files_trashbin/ajax/isEmpty.php b/apps/files_trashbin/ajax/isEmpty.php deleted file mode 100644 index 73356047e7..0000000000 --- a/apps/files_trashbin/ajax/isEmpty.php +++ /dev/null @@ -1,33 +0,0 @@ - - * @author Lukas Reschke - * @author Morris Jobke - * - * @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 - * - */ - -\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))); - - diff --git a/apps/files_trashbin/ajax/list.php b/apps/files_trashbin/ajax/list.php deleted file mode 100644 index e72c063f42..0000000000 --- a/apps/files_trashbin/ajax/list.php +++ /dev/null @@ -1,48 +0,0 @@ - - * @author Lukas Reschke - * @author Vincent Petry - * - * @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 - * - */ -\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)); - diff --git a/apps/files_trashbin/ajax/undelete.php b/apps/files_trashbin/ajax/undelete.php deleted file mode 100644 index 348148b03b..0000000000 --- a/apps/files_trashbin/ajax/undelete.php +++ /dev/null @@ -1,98 +0,0 @@ - - * @author Björn Schießle - * @author Lukas Reschke - * @author Morris Jobke - * @author Robin Appelman - * @author Roeland Jago Douma - * @author Thomas Müller - * @author Vincent Petry - * - * @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 - * - */ - -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))); -} diff --git a/apps/files_trashbin/appinfo/routes.php b/apps/files_trashbin/appinfo/routes.php index 21b4bc2d8c..20d52adf3f 100644 --- a/apps/files_trashbin/appinfo/routes.php +++ b/apps/files_trashbin/appinfo/routes.php @@ -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'); - diff --git a/apps/files_trashbin/composer/composer/autoload_classmap.php b/apps/files_trashbin/composer/composer/autoload_classmap.php index 3713de530c..164d64333c 100644 --- a/apps/files_trashbin/composer/composer/autoload_classmap.php +++ b/apps/files_trashbin/composer/composer/autoload_classmap.php @@ -18,6 +18,7 @@ return array( 'OCA\\Files_Trashbin\\Expiration' => $baseDir . '/../lib/Expiration.php', 'OCA\\Files_Trashbin\\Helper' => $baseDir . '/../lib/Helper.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\\PropfindPlugin' => $baseDir . '/../lib/Sabre/PropfindPlugin.php', 'OCA\\Files_Trashbin\\Sabre\\RestoreFolder' => $baseDir . '/../lib/Sabre/RestoreFolder.php', diff --git a/apps/files_trashbin/composer/composer/autoload_static.php b/apps/files_trashbin/composer/composer/autoload_static.php index b00778741b..6ebb8c35f3 100644 --- a/apps/files_trashbin/composer/composer/autoload_static.php +++ b/apps/files_trashbin/composer/composer/autoload_static.php @@ -33,6 +33,7 @@ class ComposerStaticInitFiles_Trashbin 'OCA\\Files_Trashbin\\Expiration' => __DIR__ . '/..' . '/../lib/Expiration.php', 'OCA\\Files_Trashbin\\Helper' => __DIR__ . '/..' . '/../lib/Helper.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\\PropfindPlugin' => __DIR__ . '/..' . '/../lib/Sabre/PropfindPlugin.php', 'OCA\\Files_Trashbin\\Sabre\\RestoreFolder' => __DIR__ . '/..' . '/../lib/Sabre/RestoreFolder.php', diff --git a/apps/files_trashbin/js/app.js b/apps/files_trashbin/js/app.js index 7cdc157fe4..82e47d510b 100644 --- a/apps/files_trashbin/js/app.js +++ b/apps/files_trashbin/js/app.js @@ -17,12 +17,21 @@ OCA.Trashbin = {}; */ OCA.Trashbin.App = { _initialized: false, + /** @type {OC.Files.Client} */ + client: null, - initialize: function($el) { + initialize: function ($el) { if (this._initialized) { return; } 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(); this.fileList = new OCA.Trashbin.FileList( $('#app-content-trashbin'), { @@ -31,22 +40,24 @@ OCA.Trashbin.App = { scrollTo: urlParams.scrollto, config: OCA.Files.App.getFilesConfig(), multiSelectMenu: [ - { - name: 'restore', - displayName: t('files', 'Restore'), - iconClass: 'icon-history', - }, - { - name: 'delete', - displayName: t('files', 'Delete'), - iconClass: 'icon-delete', - } - ] + { + name: 'restore', + displayName: t('files', 'Restore'), + iconClass: 'icon-history', + }, + { + name: 'delete', + displayName: t('files', 'Delete'), + iconClass: 'icon-delete', + } + ], + client: this.client } ); }, - _createFileActions: function() { + _createFileActions: function () { + var client = this.client; var fileActions = new OCA.Files.FileActions(); fileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename, context) { var dir = context.fileList.getCurrentDirectory(); @@ -62,17 +73,19 @@ OCA.Trashbin.App = { mime: 'all', permissions: OC.PERMISSION_READ, iconClass: 'icon-history', - actionHandler: function(filename, context) { + actionHandler: function (filename, context) { var fileList = context.fileList; var tr = fileList.findFileEl(filename); - var deleteAction = tr.children("td.date").children(".action.delete"); - deleteAction.removeClass('icon-delete').addClass('icon-loading-small'); - $.post(OC.filePath('files_trashbin', 'ajax', 'undelete.php'), { - files: JSON.stringify([filename]), - dir: fileList.getCurrentDirectory() - }, - _.bind(fileList._removeCallback, fileList) - ); + fileList.showFileBusyState(tr, true); + var dir = context.fileList.getCurrentDirectory(); + client.move(OC.joinPaths('trash', dir, filename), OC.joinPaths('restore', filename), true) + .then( + fileList._removeCallback.bind(fileList, [filename]), + function () { + 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', permissions: OC.PERMISSION_READ, iconClass: 'icon-delete', - render: function(actionSpec, isDefault, context) { + render: function (actionSpec, isDefault, context) { var $actionLink = fileActions._makeActionLink(actionSpec, context); $actionLink.attr('original-title', t('files_trashbin', 'Delete permanently')); $actionLink.children('img').attr('alt', t('files_trashbin', 'Delete permanently')); context.$file.find('td:last').append($actionLink); return $actionLink; }, - actionHandler: function(filename, context) { + actionHandler: function (filename, context) { var fileList = context.fileList; $('.tipsy').remove(); var tr = fileList.findFileEl(filename); - var deleteAction = tr.children("td.date").children(".action.delete"); - deleteAction.removeClass('icon-delete').addClass('icon-loading-small'); - $.post(OC.filePath('files_trashbin', 'ajax', 'delete.php'), { - files: JSON.stringify([filename]), - dir: fileList.getCurrentDirectory() - }, - _.bind(fileList._removeCallback, fileList) - ); + fileList.showFileBusyState(tr, true); + var dir = context.fileList.getCurrentDirectory(); + client.remove(OC.joinPaths('trash', dir, filename)) + .then( + fileList._removeCallback.bind(fileList, [filename]), + function () { + fileList.showFileBusyState(tr, false); + OC.Notification.show(t('files_trashbin', 'Error while removing file from trashbin')); + } + ); } }); return fileActions; } }; -$(document).ready(function() { - $('#app-content-trashbin').one('show', function() { +$(document).ready(function () { + $('#app-content-trashbin').one('show', function () { var App = OCA.Trashbin.App; App.initialize($('#app-content-trashbin')); // force breadcrumb init diff --git a/apps/files_trashbin/js/filelist.js b/apps/files_trashbin/js/filelist.js index 324e4d8a7e..3159506fae 100644 --- a/apps/files_trashbin/js/filelist.js +++ b/apps/files_trashbin/js/filelist.js @@ -9,6 +9,8 @@ */ (function() { 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. @@ -36,17 +38,30 @@ * @param [options] map of options */ var FileList = function($el, options) { + this.client = options.client; this.initialize($el, options); }; FileList.prototype = _.extend({}, OCA.Files.FileList.prototype, /** @lends OCA.Trashbin.FileList.prototype */ { id: 'trashbin', appName: t('files_trashbin', 'Deleted files'), + /** @type {OC.Files.Client} */ + client: null, /** * @private */ 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); this.$el.find('.undelete').click('click', _.bind(this._onClickRestoreSelected, this)); @@ -91,23 +106,6 @@ 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) { var q = ''; if (params) { @@ -140,15 +138,10 @@ this.$el.find('#filestable th').toggleClass('hidden', !exists); }, - _removeCallback: function(result) { - if (result.status !== 'success') { - OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error')); - } - - var files = result.data.success; + _removeCallback: function(files) { var $el; 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.update(); @@ -158,97 +151,71 @@ _onClickRestoreSelected: function(event) { event.preventDefault(); var self = this; - var allFiles = this.$el.find('.select-all').is(':checked'); - var files = []; - var params = {}; - this.fileMultiSelectMenu.toggleLoading('restore', 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() - }; + var files = _.pluck(this.getSelectedFiles(), 'name'); + for (var i = 0; i < files.length; i++) { + var tr = this.findFileEl(files[i]); + this.showFileBusyState(tr, true); } - $.post(OC.filePath('files_trashbin', 'ajax', 'undelete.php'), - params, - function(result) { - if (allFiles) { - if (result.status !== 'success') { - OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error')); + this.fileMultiSelectMenu.toggleLoading('restore', true); + var restorePromises = files.map(function(file) { + return self.client.move(OC.joinPaths('trash', self.getCurrentDirectory(), file), OC.joinPaths('restore', file), true) + .then( + function() { + self._removeCallback([file]); } - self.hideMask(); - // simply remove all files - self.setFiles([]); - } - else { - self._removeCallback(result); - } + ); + }); + return Promise.all(restorePromises).then( + function() { self.fileMultiSelectMenu.toggleLoading('restore', false); + }, + function() { + OC.Notification.show(t('files_trashbin', 'Error while restoring files from trashbin')); } ); - event.preventDefault(); }, _onClickDeleteSelected: function(event) { event.preventDefault(); var self = this; var allFiles = this.$el.find('.select-all').is(':checked'); - var files = []; - var params = {}; - if (allFiles) { - params = { - allfiles: true, - dir: this.getCurrentDirectory() - }; - } - else { - files = _.pluck(this.getSelectedFiles(), 'name'); - params = { - files: JSON.stringify(files), - dir: this.getCurrentDirectory() - }; + var files = _.pluck(this.getSelectedFiles(), 'name'); + for (var i = 0; i < files.length; i++) { + var tr = this.findFileEl(files[i]); + this.showFileBusyState(tr, true); } - this.fileMultiSelectMenu.toggleLoading('delete', true); if (allFiles) { - this.showMask(); - } - else { - 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')); - } + return this.client.remove(OC.joinPaths('trash', this.getCurrentDirectory())) + .then( + function() { self.hideMask(); - // simply remove all files 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); + }, + function() { + OC.Notification.show(t('files_trashbin', 'Error while removing files from trashbin')); } - ); + ); + } }, _onClickFile: function(event) { @@ -277,6 +244,13 @@ 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 * @@ -290,39 +264,25 @@ if (this._reloadCall) { this._reloadCall.abort(); } - this._reloadCall = $.ajax({ - url: this.getAjaxUrl('list'), - data: { - dir : this.getCurrentDirectory(), - sort: this._sort, - sortdirection: this._sortDirection + this._reloadCall = this.client.getFolderContents( + 'trash/' + this.getCurrentDirectory(), { + includeParent: false, + properties: this._getWebdavProperties() } - }); + ); var callBack = this.reloadCallback.bind(this); return this._reloadCall.then(callBack, callBack); }, - reloadCallback: function(result) { + reloadCallback: function(status, result) { delete this._reloadCall; this.hideMask(); - if (!result || result.status === 'error') { - // 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) { + if (status === 401) { return false; } // Firewall Blocked request? - if (result.status === 403) { + if (status === 403) { // Go home this.changeDirectory('/'); OC.Notification.show(t('files', 'This operation is forbidden')); @@ -330,24 +290,24 @@ } // Did share service die or something else fail? - if (result.status === 500) { + if (status === 500) { // Go home this.changeDirectory('/'); OC.Notification.show(t('files', 'This directory is unavailable, please check the logs or contact the administrator')); return false; } - if (result.status === 404) { + if (status === 404) { // go back home this.changeDirectory('/'); return false; } // aborted ? - if (result.status === 0){ + if (status === 0){ return true; } - this.setFiles(result.data.files); + this.setFiles(result); return true; }, diff --git a/apps/files_trashbin/lib/Sabre/AbstractTrash.php b/apps/files_trashbin/lib/Sabre/AbstractTrash.php new file mode 100644 index 0000000000..43f9cc0274 --- /dev/null +++ b/apps/files_trashbin/lib/Sabre/AbstractTrash.php @@ -0,0 +1,69 @@ + + * + * @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 . + * + */ + +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(); + } +} diff --git a/apps/files_trashbin/lib/Sabre/ITrash.php b/apps/files_trashbin/lib/Sabre/ITrash.php index 6db9bccf0a..49c600c3f1 100644 --- a/apps/files_trashbin/lib/Sabre/ITrash.php +++ b/apps/files_trashbin/lib/Sabre/ITrash.php @@ -23,6 +23,8 @@ declare(strict_types=1); */ namespace OCA\Files_Trashbin\Sabre; +use OCP\Files\FileInfo; + interface ITrash { public function restore(): bool; @@ -35,4 +37,6 @@ interface ITrash { public function getSize(); public function getFileId(): int; + + public function getFileInfo(): FileInfo; } diff --git a/apps/files_trashbin/lib/Sabre/PropfindPlugin.php b/apps/files_trashbin/lib/Sabre/PropfindPlugin.php index 492035304b..19da79fd2a 100644 --- a/apps/files_trashbin/lib/Sabre/PropfindPlugin.php +++ b/apps/files_trashbin/lib/Sabre/PropfindPlugin.php @@ -25,6 +25,8 @@ declare(strict_types=1); namespace OCA\Files_Trashbin\Sabre; use OCA\DAV\Connector\Sabre\FilesPlugin; +use OCP\Constants; +use OCP\IPreview; use Sabre\DAV\INode; use Sabre\DAV\PropFind; use Sabre\DAV\Server; @@ -39,7 +41,13 @@ class PropfindPlugin extends ServerPlugin { /** @var Server */ private $server; - public function __construct() { + /** @var IPreview */ + private $previewManager; + + public function __construct( + IPreview $previewManager + ) { + $this->previewManager = $previewManager; } public function initialize(Server $server) { @@ -54,11 +62,11 @@ class PropfindPlugin extends ServerPlugin { return; } - $propFind->handle(self::TRASHBIN_FILENAME, function() use ($node) { + $propFind->handle(self::TRASHBIN_FILENAME, function () use ($node) { return $node->getFilename(); }); - $propFind->handle(self::TRASHBIN_ORIGINAL_LOCATION, function() use ($node) { + $propFind->handle(self::TRASHBIN_ORIGINAL_LOCATION, function () use ($node) { return $node->getOriginalLocation(); }); @@ -73,6 +81,28 @@ class PropfindPlugin extends ServerPlugin { $propFind->handle(FilesPlugin::FILEID_PROPERTYNAME, function () use ($node) { 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 ''; + }); } } diff --git a/apps/files_trashbin/lib/Sabre/TrashFile.php b/apps/files_trashbin/lib/Sabre/TrashFile.php index eba9eee641..840ca6a193 100644 --- a/apps/files_trashbin/lib/Sabre/TrashFile.php +++ b/apps/files_trashbin/lib/Sabre/TrashFile.php @@ -27,16 +27,13 @@ use OCP\Files\FileInfo; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\IFile; -class TrashFile implements IFile, ITrash { +class TrashFile extends AbstractTrash implements IFile, ITrash { /** @var string */ private $userId; - /** @var FileInfo */ - private $data; - public function __construct(string $userId, FileInfo $data) { $this->userId = $userId; - $this->data = $data; + parent::__construct($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'); } - 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() { \OCA\Files_Trashbin\Trashbin::delete($this->data->getName(), $this->userId, $this->getLastModified()); } @@ -71,29 +56,11 @@ class TrashFile implements IFile, ITrash { throw new Forbidden(); } - public function getLastModified(): int { - return $this->data->getMtime(); - } - public function restore(): bool { 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 { return $this->data['extraData']; } - - public function getDeletionTime(): int { - return $this->getLastModified(); - } - - public function getFileId(): int { - return $this->data->getId(); - } - - } diff --git a/apps/files_trashbin/lib/Sabre/TrashFolder.php b/apps/files_trashbin/lib/Sabre/TrashFolder.php index 6b7d71b80e..d884eefcc9 100644 --- a/apps/files_trashbin/lib/Sabre/TrashFolder.php +++ b/apps/files_trashbin/lib/Sabre/TrashFolder.php @@ -28,16 +28,13 @@ use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\ICollection; -class TrashFolder implements ICollection, ITrash { +class TrashFolder extends AbstractTrash implements ICollection, ITrash { /** @var string */ private $userId; - /** @var FileInfo */ - private $data; - public function __construct(string $root, string $userId, FileInfo $data) { $this->userId = $userId; - $this->data = $data; + parent::__construct($data); } public function createFile($name, $data = null) { @@ -100,31 +97,11 @@ class TrashFolder implements ICollection, ITrash { throw new Forbidden(); } - public function getLastModified(): int { - return $this->data->getMtime(); - } - public function restore(): bool { 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 { 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(); - } } diff --git a/apps/files_trashbin/lib/Sabre/TrashFolderFile.php b/apps/files_trashbin/lib/Sabre/TrashFolderFile.php index 921c98b02f..3e28d048b4 100644 --- a/apps/files_trashbin/lib/Sabre/TrashFolderFile.php +++ b/apps/files_trashbin/lib/Sabre/TrashFolderFile.php @@ -27,16 +27,13 @@ use OCP\Files\FileInfo; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\IFile; -class TrashFolderFile implements IFile, ITrash { +class TrashFolderFile extends AbstractTrash implements IFile, ITrash { /** @var string */ private $root; /** @var string */ private $userId; - /** @var FileInfo */ - private $data; - /** @var string */ private $location; @@ -46,8 +43,8 @@ class TrashFolderFile implements IFile, ITrash { string $location) { $this->root = $root; $this->userId = $userId; - $this->data = $data; $this->location = $location; + parent::__construct($data); } public function put($data) { @@ -58,51 +55,19 @@ class TrashFolderFile implements IFile, ITrash { 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() { \OCA\Files_Trashbin\Trashbin::delete($this->root . '/' . $this->getName(), $this->userId, null); } - public function getName(): string { - return $this->data->getName(); - } - public function setName($name) { throw new Forbidden(); } - public function getLastModified(): int { - return $this->data->getMtime(); - } - public function restore(): bool { 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 { return $this->location . '/' . $this->getFilename(); } - - public function getDeletionTime(): int { - return $this->getLastModified(); - } - - public function getFileId(): int { - return $this->data->getId(); - } } diff --git a/apps/files_trashbin/lib/Sabre/TrashFolderFolder.php b/apps/files_trashbin/lib/Sabre/TrashFolderFolder.php index 2fe75479c1..4ee9a0e2db 100644 --- a/apps/files_trashbin/lib/Sabre/TrashFolderFolder.php +++ b/apps/files_trashbin/lib/Sabre/TrashFolderFolder.php @@ -28,7 +28,7 @@ use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\ICollection; -class TrashFolderFolder implements ICollection, ITrash { +class TrashFolderFolder extends AbstractTrash implements ICollection, ITrash { /** @var string */ private $root; @@ -36,9 +36,6 @@ class TrashFolderFolder implements ICollection, ITrash { /** @var string */ private $userId; - /** @var FileInfo */ - private $data; - /** @var string */ private $location; @@ -48,8 +45,8 @@ class TrashFolderFolder implements ICollection, ITrash { string $location) { $this->root = $root; $this->userId = $userId; - $this->data = $data; $this->location = $location; + parent::__construct($data); } 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); } - public function getName(): string { - return $this->data->getName(); - - } - public function setName($name) { throw new Forbidden(); } - public function getLastModified(): int { - return $this->data->getMtime(); - } - public function restore(): bool { 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 { 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(); - } } diff --git a/apps/files_trashbin/tests/js/filelistSpec.js b/apps/files_trashbin/tests/js/filelistSpec.js index c5b1018856..e9b519ad1f 100644 --- a/apps/files_trashbin/tests/js/filelistSpec.js +++ b/apps/files_trashbin/tests/js/filelistSpec.js @@ -1,31 +1,38 @@ /** -* ownCloud -* -* @author Vincent Petry -* @copyright 2014 Vincent Petry -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE -* License as published by the Free Software Foundation; either -* version 3 of the License, or any later version. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU AFFERO GENERAL PUBLIC LICENSE for more details. -* -* You should have received a copy of the GNU Affero General Public -* License along with this library. If not, see . -* -*/ + * ownCloud + * + * @author Vincent Petry + * @copyright 2014 Vincent Petry + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see . + * + */ -describe('OCA.Trashbin.FileList tests', function() { - var testFiles, alertStub, notificationStub, fileList; +describe('OCA.Trashbin.FileList tests', function () { + var testFiles, alertStub, notificationStub, fileList, client; - beforeEach(function() { + beforeEach(function () { alertStub = sinon.stub(OC.dialogs, 'alert'); 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 $('#testArea').append( '
' + @@ -59,21 +66,24 @@ describe('OCA.Trashbin.FileList tests', function() { testFiles = [{ id: 1, type: 'file', - name: 'One.txt', + name: 'One.txt.d11111', + displayName: 'One.txt', mtime: 11111000, mimetype: 'text/plain', etag: 'abc' }, { id: 2, type: 'file', - name: 'Two.jpg', + name: 'Two.jpg.d22222', + displayName: 'Two.jpg', mtime: 22222000, mimetype: 'image/jpeg', etag: 'def', }, { id: 3, type: 'file', - name: 'Three.pdf', + name: 'Three.pdf.d33333', + displayName: 'Three.pdf', mtime: 33333000, mimetype: 'application/pdf', etag: '123', @@ -81,7 +91,8 @@ describe('OCA.Trashbin.FileList tests', function() { id: 4, type: 'dir', mtime: 99999000, - name: 'somedir', + name: 'somedir.d99999', + displayName: 'somedir', mimetype: 'httpd/unix-directory', etag: '456' }]; @@ -92,20 +103,21 @@ describe('OCA.Trashbin.FileList tests', function() { $('#app-content-trashbin'), { fileActions: fileActions, multiSelectMenu: [{ - name: 'restore', - displayName: t('files', 'Restore'), - iconClass: 'icon-history', - }, + name: 'restore', + displayName: t('files', 'Restore'), + iconClass: 'icon-history', + }, { name: 'delete', displayName: t('files', 'Delete'), iconClass: 'icon-delete', } - ] + ], + client: client } ); }); - afterEach(function() { + afterEach(function () { testFiles = undefined; fileList.destroy(); fileList = undefined; @@ -114,17 +126,17 @@ describe('OCA.Trashbin.FileList tests', function() { notificationStub.restore(); alertStub.restore(); }); - describe('Initialization', function() { - it('Sorts by mtime by default', function() { + describe('Initialization', function () { + it('Sorts by mtime by default', function () { expect(fileList._sort).toEqual('mtime'); 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); }); }); - describe('Breadcrumbs', function() { - beforeEach(function() { + describe('Breadcrumbs', function () { + beforeEach(function () { var data = { status: 'success', data: { @@ -133,13 +145,13 @@ describe('OCA.Trashbin.FileList tests', function() { } }; fakeServer.respondWith(/\/index\.php\/apps\/files_trashbin\/ajax\/list.php\?dir=%2Fsubdir/, [ - 200, { - "Content-Type": "application/json" - }, - JSON.stringify(data) + 200, { + "Content-Type": "application/json" + }, + 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); fakeServer.respond(); 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'); }); }); - describe('Rendering rows', function() { - it('renders rows with the correct data when in root', function() { + describe('Rendering rows', function () { + it('renders rows with the correct data when in root', function () { // dir listing is false when in root $('#dir').val('/'); fileList.setFiles(testFiles); @@ -174,7 +186,7 @@ describe('OCA.Trashbin.FileList tests', function() { 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').val('/'); fileList.setFiles(testFiles); @@ -196,11 +208,14 @@ describe('OCA.Trashbin.FileList tests', function() { 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').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 $tr = $rows.eq(0); expect($rows.length).toEqual(4); @@ -218,42 +233,42 @@ describe('OCA.Trashbin.FileList tests', function() { 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); }); }); - describe('File actions', function() { - describe('Deleting single files', function() { + describe('File actions', function () { + describe('Deleting single files', function () { // TODO: checks ajax call // TODO: checks spinner // TODO: remove item after delete // TODO: bring back item if delete failed }); - describe('Restoring single files', function() { + describe('Restoring single files', function () { // TODO: checks ajax call // TODO: checks spinner // TODO: remove item after restore // 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 }); - describe('loading file list', function() { + describe('loading file list', function () { // TODO: check that ajax URL is going through files_trashbin }); - describe('breadcrumbs', function() { + describe('breadcrumbs', function () { // TODO: test label + URL }); - describe('elementToFile', function() { + describe('elementToFile', function () { var $tr; - beforeEach(function() { + beforeEach(function () { fileList.setFiles(testFiles); $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); expect(fileInfo.id).toEqual(1); expect(fileInfo.name).toEqual('One.txt.d11111'); @@ -265,8 +280,8 @@ describe('OCA.Trashbin.FileList tests', function() { expect(fileInfo.type).toEqual('file'); }); }); - describe('Global Actions', function() { - beforeEach(function() { + describe('Global Actions', function () { + beforeEach(function () { fileList.setFiles(testFiles); fileList.findFileEl('One.txt.d11111').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(); }); - afterEach(function() { + afterEach(function () { fileList.$el.find('.actions-selected').click(); }); - describe('Delete', function() { - it('Shows trashbin actions', function() { + describe('Delete', function () { + it('Shows trashbin actions', function () { // visible because a few files were selected expect($('.selectedActions').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-restore').is(':visible')).toEqual(false); }); - it('Deletes selected files when "Delete" clicked', function() { + it('Deletes selected files when "Delete" clicked', function () { var request; - var $deleteLink = $('.selectedActions .filesSelectMenu .delete'); - $deleteLink.click(); - expect($deleteLink.find('.icon-loading-small').length).toEqual(1); - expect(fakeServer.requests.length).toEqual(1); - request = fakeServer.requests[0]; - expect(request.url).toEqual(OC.webroot + '/index.php/apps/files_trashbin/ajax/delete.php'); - expect(OC.parseQueryString(request.requestBody)) - .toEqual({'dir': '/', files: '["One.txt.d11111","Three.pdf.d33333","somedir.d99999"]'}); - fakeServer.requests[0].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({ - status: 'success', - data: { - success: [ - {filename: 'One.txt.d11111'}, - {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); + var promise = fileList._onClickDeleteSelected({ + preventDefault: function () { + } + }); + var files = ["One.txt.d11111", "Three.pdf.d33333", "somedir.d99999"]; + expect(fakeServer.requests.length).toEqual(files.length); + for (var i = 0; i < files.length; i++) { + request = fakeServer.requests[i]; + expect(request.url).toEqual(OC.webroot + '/remote.php/dav/trashbin/user/trash/' + files[i]); + request.respond(200); + } + return promise.then(function () { + 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; $('.select-all').click(); - $('.selectedActions .filesSelectMenu .delete').click(); + var promise = fileList._onClickDeleteSelected({ + preventDefault: function () { + } + }); expect(fakeServer.requests.length).toEqual(1); request = fakeServer.requests[0]; - expect(request.url).toEqual(OC.webroot + '/index.php/apps/files_trashbin/ajax/delete.php'); - expect(OC.parseQueryString(request.requestBody)) - .toEqual({'dir': '/', allfiles: 'true'}); - fakeServer.requests[0].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({status: 'success'}) - ); - expect(fileList.isEmpty).toEqual(true); + expect(request.url).toEqual(OC.webroot + '/remote.php/dav/trashbin/user/trash'); + request.respond(200); + return promise.then(function () { + expect(fileList.isEmpty).toEqual(true); + }); }); }); - describe('Restore', function() { - it('Restores selected files when "Restore" clicked', function() { + describe('Restore', function () { + it('Restores selected files when "Restore" clicked', function () { var request; - var $restoreLink = $('.selectedActions .filesSelectMenu .restore'); - $restoreLink.click(); - expect($restoreLink.find('.icon-loading-small').length).toEqual(1); - expect(fakeServer.requests.length).toEqual(1); - request = fakeServer.requests[0]; - expect(request.url).toEqual(OC.webroot + '/index.php/apps/files_trashbin/ajax/undelete.php'); - expect(OC.parseQueryString(request.requestBody)) - .toEqual({'dir': '/', files: '["One.txt.d11111","Three.pdf.d33333","somedir.d99999"]'}); - fakeServer.requests[0].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({ - status: 'success', - data: { - success: [ - {filename: 'One.txt.d11111'}, - {filename: 'Three.pdf.d33333'}, - {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); + var promise = fileList._onClickRestoreSelected({ + preventDefault: function () { + } + }); + var files = ["One.txt.d11111", "Three.pdf.d33333", "somedir.d99999"]; + expect(fakeServer.requests.length).toEqual(files.length); + for (var i = 0; i < files.length; i++) { + request = fakeServer.requests[i]; + expect(request.url).toEqual(OC.webroot + '/remote.php/dav/trashbin/user/trash/' + files[i]); + expect(request.requestHeaders.Destination).toEqual(OC.webroot + '/remote.php/dav/trashbin/user/restore/' + files[i]); + request.respond(200); + } + return promise.then(function() { + 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; $('.select-all').click(); - $('.selectedActions .filesSelectMenu .restore').click(); - expect(fakeServer.requests.length).toEqual(1); - request = fakeServer.requests[0]; - expect(request.url).toEqual(OC.webroot + '/index.php/apps/files_trashbin/ajax/undelete.php'); - expect(OC.parseQueryString(request.requestBody)) - .toEqual({'dir': '/', allfiles: 'true'}); - fakeServer.requests[0].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({status: 'success'}) - ); - expect(fileList.isEmpty).toEqual(true); + var promise = fileList._onClickRestoreSelected({ + preventDefault: function () { + } + }); + var files = ["One.txt.d11111", "Two.jpg.d22222", "Three.pdf.d33333", "somedir.d99999"]; + expect(fakeServer.requests.length).toEqual(files.length); + for (var i = 0; i < files.length; i++) { + request = fakeServer.requests[i]; + expect(request.url).toEqual(OC.webroot + '/remote.php/dav/trashbin/user/trash/' + files[i]); + expect(request.requestHeaders.Destination).toEqual(OC.webroot + '/remote.php/dav/trashbin/user/restore/' + files[i]); + request.respond(200); + } + return promise.then(function() { + expect(fileList.isEmpty).toEqual(true); + }); }); }); }); diff --git a/build/integration/config/behat.yml b/build/integration/config/behat.yml index 428d4d45b7..7fd3686b8b 100644 --- a/build/integration/config/behat.yml +++ b/build/integration/config/behat.yml @@ -1,10 +1,10 @@ default: autoload: - '': %paths.base%/../features/bootstrap + '': "%paths.base%/../features/bootstrap" suites: default: paths: - - %paths.base%/../features + - "%paths.base%/../features" contexts: - FeatureContext: baseUrl: http://localhost:8080/ocs/ @@ -27,7 +27,7 @@ default: ocPath: ../../ federation: paths: - - %paths.base%/../federation_features + - "%paths.base%/../federation_features" contexts: - FederationContext: baseUrl: http://localhost:8080/ocs/ @@ -37,7 +37,7 @@ default: regular_user_password: 123456 capabilities: paths: - - %paths.base%/../capabilities_features + - "%paths.base%/../capabilities_features" contexts: - CapabilitiesContext: baseUrl: http://localhost:8080/ocs/ @@ -47,7 +47,7 @@ default: regular_user_password: 123456 sharees: paths: - - %paths.base%/../sharees_features + - "%paths.base%/../sharees_features" contexts: - ShareesContext: baseUrl: http://localhost:8080/ocs/ @@ -57,7 +57,7 @@ default: regular_user_password: 123456 setup: paths: - - %paths.base%/../setup_features + - "%paths.base%/../setup_features" contexts: - SetupContext: baseUrl: http://localhost:8080/ocs/ @@ -67,7 +67,7 @@ default: regular_user_password: 123456 filesdrop: paths: - - %paths.base%/../filesdrop_features + - "%paths.base%/../filesdrop_features" contexts: - FilesDropContext: baseUrl: http://localhost:8080 @@ -77,7 +77,7 @@ default: regular_user_password: 123456 ldap: paths: - - %paths.base%/../ldap_features + - "%paths.base%/../ldap_features" contexts: - LDAPContext: baseUrl: http://localhost:8080 @@ -87,7 +87,7 @@ default: regular_user_password: what_for remoteapi: paths: - - %paths.base%/../remoteapi_features + - "%paths.base%/../remoteapi_features" contexts: - FeatureContext: baseUrl: http://localhost:8080/ocs/ @@ -100,4 +100,4 @@ default: extensions: jarnaiz\JUnitFormatter\JUnitFormatterExtension: filename: report.xml - outputDir: %paths.base%/../output/ + outputDir: "%paths.base%/../output/" diff --git a/build/integration/features/bootstrap/Trashbin.php b/build/integration/features/bootstrap/Trashbin.php index 49d547a5ed..8e4d0892bc 100644 --- a/build/integration/features/bootstrap/Trashbin.php +++ b/build/integration/features/bootstrap/Trashbin.php @@ -20,8 +20,6 @@ * */ -use GuzzleHttp\Client; -use GuzzleHttp\Message\ResponseInterface; use PHPUnit\Framework\Assert; require __DIR__ . '/../../vendor/autoload.php'; @@ -30,16 +28,46 @@ require __DIR__ . '/../../vendor/autoload.php'; * Trashbin functions */ trait Trashbin { + use WebDav; /** * @When User :user empties trashbin * @param string $user user */ public function emptyTrashbin($user) { - $this->asAn($user); - $body = new \Behat\Gherkin\Node\TableNode([['allfiles', 'true'], ['dir', '%2F']]); - $this->sendingToWithDirectUrl('POST', "/index.php/apps/files_trashbin/ajax/delete.php", $body); - $this->theHTTPStatusCodeShouldBe('200'); + $client = $this->getSabreClient($user); + $response = $client->request('DELETE', $this->makeSabrePath($user, 'trash', 'trashbin')); + Assert::assertEquals(204, $response['statusCode']); + } + + 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 * @return array response */ - public function listTrashbinFolder($user, $path){ - $this->asAn($user); - $params = '?dir=' . rawurlencode('/' . trim($path, '/')); - $this->sendingToWithDirectUrl('GET', '/index.php/apps/files_trashbin/ajax/list.php' . $params, null); - $this->theHTTPStatusCodeShouldBe('200'); + public function listTrashbinFolder($user, $path) { + $path = $this->getFullTrashPath($user, $path); + $client = $this->getSabreClient($user); - $response = json_decode($this->response->getBody(), true); - - return $response['data']['files']; + $results = $client->propfind($this->makeSabrePath($user, 'trash' . $path, 'trashbin'), [ + '{http://nextcloud.org/ns}trashbin-filename', + '{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 $entryText - * @param string $path + * @param string $folder + * @param \Behat\Gherkin\Node\TableNode|null $expectedElements */ - public function asTheFileOrFolderExistsInTrash($user, $entryText, $path) { - $path = trim($path, '/'); - $sections = explode('/', $path, 2); - - $firstEntry = $this->findFirstTrashedEntry($user, trim($sections[0], '/')); - - Assert::assertNotNull($firstEntry); - - // query was on the main element ? - if (count($sections) === 1) { - // already found, return - return; - } - - $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; + public function checkTrashContents($user, $folder, $expectedElements) { + $elementList = $this->listTrashbinFolder($user, $folder); + $trashContent = array_filter(array_map(function (array $item) { + return $item['{http://nextcloud.org/ns}trashbin-filename']; + }, $elementList)); + if ($expectedElements instanceof \Behat\Gherkin\Node\TableNode) { + $elementRows = $expectedElements->getRows(); + $elementsSimplified = $this->simplifyArray($elementRows); + foreach ($elementsSimplified as $expectedElement) { + $expectedElement = ltrim($expectedElement, '/'); + if (array_search($expectedElement, $trashContent) === false) { + Assert::fail("$expectedElement" . " is not in trash listing"); + } } } - - Assert::assertTrue($found); } /** - * Finds the first trashed entry matching the given name - * - * @param string $name - * @return string|null real entry name with timestamp suffix or null if not found + * @Then /^as "([^"]*)" the (file|folder) "([^"]*)" exists in trash$/ + * @param string $user + * @param string $type + * @param string $file */ - private function findFirstTrashedEntry($user, $name) { - $listing = $this->listTrashbinFolder($user, '/'); - - foreach ($listing as $entry) { - if ($entry['name'] === $name) { - return $entry['name'] . '.d' . ((int)$entry['mtime'] / 1000); - } + public function checkTrashContains($user, $type, $file) { + $parent = dirname($file); + if ($parent === '.') { + $parent = '/'; } + $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; } } diff --git a/build/integration/features/bootstrap/WebDav.php b/build/integration/features/bootstrap/WebDav.php index 37a398e2aa..b36cdfaea6 100644 --- a/build/integration/features/bootstrap/WebDav.php +++ b/build/integration/features/bootstrap/WebDav.php @@ -423,8 +423,12 @@ trait WebDav { return $parsedResponse; } - public function makeSabrePath($user, $path) { - return $this->encodePath($this->getDavFilesPath($user) . $path); + public function makeSabrePath($user, $path, $type = 'files') { + if ($type === 'files') { + return $this->encodePath($this->getDavFilesPath($user) . $path); + } else { + return $this->encodePath($this->davPath . '/' . $type . '/' . $user . '/' . $path); + } } public function getSabreClient($user) { diff --git a/build/integration/features/sharing-v1-part3.feature b/build/integration/features/sharing-v1-part3.feature index 44a41341a0..6ab7cfdf9a 100644 --- a/build/integration/features/sharing-v1-part3.feature +++ b/build/integration/features/sharing-v1-part3.feature @@ -1,7 +1,7 @@ Feature: sharing Background: Given using api version "1" - Given using old dav path + Given using new dav path # See sharing-v1-part2.feature @@ -295,7 +295,7 @@ Feature: sharing And user "user0" exists And User "user0" deletes file "/textfile0.txt" When User "user0" empties trashbin - Then the HTTP status code should be "200" + Then the HTTP status code should be "204" Scenario: orphaned shares Given As an "admin" @@ -392,4 +392,4 @@ Feature: sharing And folder "/shared" of user "user0" is shared with user "user1" When User "user1" moved file "/textfile0.txt" to "/shared/shared_file.txt" Then as "user1" the file "/shared/shared_file.txt" exists - And as "user0" the file "/shared/shared_file.txt" exists \ No newline at end of file + And as "user0" the file "/shared/shared_file.txt" exists diff --git a/build/integration/features/trashbin.feature b/build/integration/features/trashbin.feature index adb73f2b5e..3a9c29f7cb 100644 --- a/build/integration/features/trashbin.feature +++ b/build/integration/features/trashbin.feature @@ -1,7 +1,7 @@ Feature: trashbin Background: Given using api version "1" - And using old dav path + And using new dav path And As an "admin" And app "files_trashbin" is enabled @@ -9,5 +9,73 @@ Feature: trashbin Given As an "admin" And user "user0" exists 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 + diff --git a/core/js/files/client.js b/core/js/files/client.js index 9d3f5c4a3d..aa450df177 100644 --- a/core/js/files/client.js +++ b/core/js/files/client.js @@ -61,6 +61,7 @@ } this._client = new dav.Client(clientOptions); this._client.xhrProvider = _.bind(this._xhrProvider, this); + this._fileInfoParsers = []; }; Client.NS_OWNCLOUD = 'http://owncloud.org/ns'; @@ -390,7 +391,7 @@ // extend the parsed data using the custom parsers _.each(this._fileInfoParsers, function(parserFunction) { - _.extend(data, parserFunction(response) || {}); + _.extend(data, parserFunction(response, data) || {}); }); return new FileInfo(data);