From a75abb1f5509bcefb7cc8d1f5fec2ede90ad0872 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Thu, 28 May 2020 13:20:04 +0200 Subject: [PATCH] FE - Add file action to explicitly lock a file (but no folder) BE - Make sure href to the lockroot includes the base uri. --- .../Connector/Sabre/PublicDavLocksPlugin.php | 15 +++++++ apps/files/js/filelockplugin.js | 39 +++++++++++++++++-- apps/files/js/locktabview.js | 18 +++++++-- apps/files/lib/App.php | 12 ++++-- apps/files/tests/js/filelockpluginSpec.js | 9 +++-- changelog/unreleased/37460 | 3 ++ core/js/files/client.js | 37 ++++++++++++++++++ 7 files changed, 120 insertions(+), 13 deletions(-) create mode 100644 changelog/unreleased/37460 diff --git a/apps/dav/lib/Connector/Sabre/PublicDavLocksPlugin.php b/apps/dav/lib/Connector/Sabre/PublicDavLocksPlugin.php index c933e0d03da4..7545d4c9c6fc 100644 --- a/apps/dav/lib/Connector/Sabre/PublicDavLocksPlugin.php +++ b/apps/dav/lib/Connector/Sabre/PublicDavLocksPlugin.php @@ -22,6 +22,7 @@ namespace OCA\DAV\Connector\Sabre; use Sabre\DAV\Locks\Backend\BackendInterface; +use Sabre\DAV\Locks\LockInfo; use Sabre\DAV\PropFind; use Sabre\DAV\INode; use Sabre\DAV\Exception\MethodNotAllowed; @@ -85,4 +86,18 @@ public function httpUnlock(RequestInterface $request, ResponseInterface $respons throw new MethodNotAllowed('Locking not allowed from public endpoint'); } } + + /** + * Generates the response for successful LOCK requests. + * @todo this method can be removed once upstream released a new version with this fix https://github.com/sabre-io/dav/pull/1273 + * + * @return string + */ + protected function generateLockResponse(LockInfo $lockInfo) { + $contextUri = $this->server->getBaseUri(); + + return $this->server->xml->write('{DAV:}prop', [ + '{DAV:}lockdiscovery' => new LockDiscovery([$lockInfo]), + ], $contextUri); + } } diff --git a/apps/files/js/filelockplugin.js b/apps/files/js/filelockplugin.js index c6b0fe182655..6f3fa7391758 100644 --- a/apps/files/js/filelockplugin.js +++ b/apps/files/js/filelockplugin.js @@ -119,12 +119,9 @@ }).map(function(xmlvalue) { return parseLockNode(xmlvalue); }).value(); - } return data; }); - - }, /** @@ -154,6 +151,42 @@ } }); + if (oc_appconfig.files.enable_lock_file_action) { + fileList.fileActions.registerAction({ + name: 'lock', + mime: 'all', + displayName: t('files', 'Lock file'), + permissions: OC.PERMISSION_UPDATE, + type: OCA.Files.FileActions.TYPE_DROPDOWN, + iconClass: 'icon-lock-open', + actionHandler: function (filename, context) { + const file = context.fileInfoModel.getFullPath(); + context.fileInfoModel._filesClient.lock(file).then(function (result, response) { + const xml = response.xhr.responseXML; + const activelock = xml.getElementsByTagNameNS('DAV:', 'activelock'); + const lock = parseLockNode(activelock[0]); + context.fileInfoModel.set('activeLocks', [lock]); + }, function (error) { + console.log(error) + OC.Notification.show(t('files', 'Failed to lock.')); + }); + } + }); + + fileList.fileActions.addAdvancedFilter(function (actions, context) { + var $file = context.$file; + if (context.fileInfoModel && context.fileInfoModel.attributes.mimetype === 'httpd/unix-directory') { + delete (actions.lock); + return actions; + } + var isLocked = $file.data('activelocks'); + if (isLocked && isLocked.length > 0) { + delete (actions.lock); + } + return actions; + }); + } + }, renderLink: function () { diff --git a/apps/files/js/locktabview.js b/apps/files/js/locktabview.js index 20e2372cc36d..ddeeedb2171a 100644 --- a/apps/files/js/locktabview.js +++ b/apps/files/js/locktabview.js @@ -62,7 +62,6 @@ var self = this; var $target = $(event.target).closest('.lock-entry'); var lockIndex = parseInt($target.attr('data-index'), 10); - var currentLock = this.model.get('activeLocks')[lockIndex]; // FIXME: move to FileInfoModel @@ -75,7 +74,6 @@ // implicit clone of array else backbone doesn't fire change event var locks = _.without(self.model.get('activeLocks') || [], currentLock); self.model.set('activeLocks', locks); - self.render(); } else if (result.status === 403) { OC.Notification.show(t('files', 'Could not unlock, please contact the lock owner {owner}', {owner: currentLock.owner})); @@ -121,7 +119,21 @@ canDisplay: function(fileInfo) { // don't display if no lock is set return fileInfo && fileInfo.get('activeLocks') && fileInfo.get('activeLocks').length > 0; - } + }, + + setFileInfo: function(fileInfo) { + if (this.model !== fileInfo) { + this.model = fileInfo; + this.render(); + if (fileInfo) { + const self = this; + this.model.on('change', function(data) { + self.render(); + }); + } + } + }, + }); OCA.Files.LockTabView = LockTabView; diff --git a/apps/files/lib/App.php b/apps/files/lib/App.php index ce07aa099c95..96214642b4a3 100644 --- a/apps/files/lib/App.php +++ b/apps/files/lib/App.php @@ -44,13 +44,17 @@ public static function getNavigationManager() { } public static function extendJsConfig($array) { - $maxChunkSize = (int)(\OC::$server->getConfig()->getAppValue('files', 'max_chunk_size', (10 * 1024 * 1024))); - $uploadStallTimeout = (int)(\OC::$server->getConfig()->getAppValue('files', 'upload_stall_timeout', 60)); // in seconds - $uploadStallRetries = (int)(\OC::$server->getConfig()->getAppValue('files', 'upload_stall_retries', 100)); + $config = \OC::$server->getConfig(); + $maxChunkSize = (int)($config->getAppValue('files', 'max_chunk_size', (10 * 1024 * 1024))); + $uploadStallTimeout = (int)($config->getAppValue('files', 'upload_stall_timeout', 60)); // in seconds + $uploadStallRetries = (int)($config->getAppValue('files', 'upload_stall_retries', 100)); + $enableLockFileAction = (boolean)($config->getAppValue('files', 'enable_lock_file_action', false)); + $array['array']['oc_appconfig']['files'] = [ 'max_chunk_size' => $maxChunkSize, 'upload_stall_timeout' => $uploadStallTimeout, - 'upload_stall_retries' => $uploadStallRetries + 'upload_stall_retries' => $uploadStallRetries, + 'enable_lock_file_action' => $enableLockFileAction ]; } } diff --git a/apps/files/tests/js/filelockpluginSpec.js b/apps/files/tests/js/filelockpluginSpec.js index 57af6299650b..02626d4ee1d6 100644 --- a/apps/files/tests/js/filelockpluginSpec.js +++ b/apps/files/tests/js/filelockpluginSpec.js @@ -16,6 +16,9 @@ describe('OCA.Files.LockPlugin tests', function() { var currentUserStub; beforeEach(function() { + oc_appconfig = oc_appconfig || {}; + oc_appconfig.files = oc_appconfig.files || {}; + oc_appconfig.files.enable_lock_file_action = true; var $content = $('
'); $('#testArea').append($content); // dummy file list @@ -134,8 +137,8 @@ describe('OCA.Files.LockPlugin tests', function() { requestDeferred = new $.Deferred(); requestStub = sinon.stub(dav.Client.prototype, 'propFind').returns(requestDeferred.promise()); }); - afterEach(function() { - requestStub.restore(); + afterEach(function() { + requestStub.restore(); }); function makeLockXml(owner) { @@ -186,7 +189,7 @@ describe('OCA.Files.LockPlugin tests', function() { return xml; } - + it('parses lock information from response XML to JSON', function(done) { var xml = dav.Client.prototype.parseMultiStatus(makeLockXml('lock owner')); var promise = fileList.reload(); diff --git a/changelog/unreleased/37460 b/changelog/unreleased/37460 new file mode 100644 index 000000000000..3001ea5684d2 --- /dev/null +++ b/changelog/unreleased/37460 @@ -0,0 +1,3 @@ +Change: Add file action to lock a file + +https://github.com/owncloud/core/pull/37460 diff --git a/core/js/files/client.js b/core/js/files/client.js index e664d01569f5..752f3584901b 100644 --- a/core/js/files/client.js +++ b/core/js/files/client.js @@ -763,6 +763,43 @@ return promise; }, + lock: function(path, options) { + if (!path) { + throw 'Missing argument "path"'; + } + var self = this; + var deferred = $.Deferred(); + var promise = deferred.promise(); + + options = _.extend({ + 'pathIsUrl' : false + }, options); + + const lockBody = "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + "\n"; + + this._client.request( + 'LOCK', + options.pathIsUrl ? path : this._buildUrl(path), + {}, + lockBody + ).then( + function(result) { + if (self._isSuccessStatus(result.status)) { + deferred.resolve(result.status, result); + } else { + result = _.extend(result, self._getSabreException(result)); + deferred.reject(result.status, result); + } + } + ); + return promise; + }, + /** * Creates a directory *