From d93f77b4e09f0b6538977de75e7df9f69eeb624d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Wed, 13 Jun 2018 16:26:15 +0200 Subject: [PATCH 1/6] Introduce AMD style MOVE operation --- .../Migrations/Version20180622095921.php | 34 ++++ apps/dav/appinfo/info.xml | 2 +- apps/dav/lib/Capabilities.php | 23 ++- apps/dav/lib/Connector/Sabre/Server.php | 1 + apps/dav/lib/DAV/LazyOpsPlugin.php | 169 ++++++++++++++++++ apps/dav/lib/JobStatus/Entity/JobStatus.php | 42 +++++ .../lib/JobStatus/Entity/JobStatusMapper.php | 47 +++++ apps/dav/lib/JobStatus/Home.php | 68 +++++++ apps/dav/lib/JobStatus/JobStatus.php | 79 ++++++++ apps/dav/lib/JobStatus/RootCollection.php | 43 +++++ apps/dav/lib/RootCollection.php | 6 +- apps/dav/lib/Server.php | 18 +- apps/dav/tests/unit/DAV/LazyOpsPluginTest.php | 161 +++++++++++++++++ .../JobStatus/Entity/JobStatusMapperTest.php | 87 +++++++++ apps/dav/tests/unit/JobStatus/HomeTest.php | 78 ++++++++ .../tests/unit/JobStatus/JobStatusTest.php | 70 ++++++++ apps/files/js/file-upload.js | 37 +++- config/config.sample.php | 5 + core/js/files/client.js | 6 +- 19 files changed, 964 insertions(+), 12 deletions(-) create mode 100644 apps/dav/appinfo/Migrations/Version20180622095921.php create mode 100644 apps/dav/lib/DAV/LazyOpsPlugin.php create mode 100644 apps/dav/lib/JobStatus/Entity/JobStatus.php create mode 100644 apps/dav/lib/JobStatus/Entity/JobStatusMapper.php create mode 100644 apps/dav/lib/JobStatus/Home.php create mode 100644 apps/dav/lib/JobStatus/JobStatus.php create mode 100644 apps/dav/lib/JobStatus/RootCollection.php create mode 100644 apps/dav/tests/unit/DAV/LazyOpsPluginTest.php create mode 100644 apps/dav/tests/unit/JobStatus/Entity/JobStatusMapperTest.php create mode 100644 apps/dav/tests/unit/JobStatus/HomeTest.php create mode 100644 apps/dav/tests/unit/JobStatus/JobStatusTest.php diff --git a/apps/dav/appinfo/Migrations/Version20180622095921.php b/apps/dav/appinfo/Migrations/Version20180622095921.php new file mode 100644 index 000000000000..6939fc0b9805 --- /dev/null +++ b/apps/dav/appinfo/Migrations/Version20180622095921.php @@ -0,0 +1,34 @@ +hasTable("${prefix}dav_job_status")) { + return; + } + $table = $schema->createTable("${prefix}dav_job_status"); + $table->addColumn('id', Type::BIGINT, [ + 'autoincrement' => true, + 'notnull' => true, + 'length' => 20, + ]); + $table->addColumn('uuid', Type::GUID, [ + 'notnull' => true, + ]); + $table->addColumn('user_id', Type::STRING, [ + 'notnull' => true, + 'length' => 64, + ]); + $table->addColumn('status_info', Type::STRING, [ + 'notnull' => true, + 'length' => 4000, + ]); + $table->setPrimaryKey(['id']); + $table->addUniqueIndex(['uuid']); + } +} diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml index bfe8a1d39949..378a4d7537f1 100644 --- a/apps/dav/appinfo/info.xml +++ b/apps/dav/appinfo/info.xml @@ -5,7 +5,7 @@ ownCloud WebDAV endpoint AGPL owncloud.org - 0.3.2 + 0.3.3 true diff --git a/apps/dav/lib/Capabilities.php b/apps/dav/lib/Capabilities.php index c8a241d18573..f28e454c9f16 100644 --- a/apps/dav/lib/Capabilities.php +++ b/apps/dav/lib/Capabilities.php @@ -22,17 +22,36 @@ namespace OCA\DAV; use OCP\Capabilities\ICapability; +use OCP\IConfig; class Capabilities implements ICapability { + /** @var IConfig */ + private $config; + + /** + * Capabilities constructor. + * + * @param IConfig $config + */ + public function __construct(IConfig $config) { + $this->config = $config; + } + public function getCapabilities() { - return [ + $cap = [ 'dav' => [ 'chunking' => '1.0', 'zsync' => '1.0', 'reports' => [ 'search-files', - ], + ] ] ]; + + if ($this->config->getSystemValue('dav.enable.async', true)) { + $cap['async'] = '1.0'; + } + + return $cap; } } diff --git a/apps/dav/lib/Connector/Sabre/Server.php b/apps/dav/lib/Connector/Sabre/Server.php index 52255cd903e5..eb21e2e7cf88 100644 --- a/apps/dav/lib/Connector/Sabre/Server.php +++ b/apps/dav/lib/Connector/Sabre/Server.php @@ -37,6 +37,7 @@ class Server extends \Sabre\DAV\Server { /** * @see \Sabre\DAV\Server + * @param null $treeOrNode * @throws \Sabre\DAV\Exception */ public function __construct($treeOrNode = null) { diff --git a/apps/dav/lib/DAV/LazyOpsPlugin.php b/apps/dav/lib/DAV/LazyOpsPlugin.php new file mode 100644 index 000000000000..a626c2a9402c --- /dev/null +++ b/apps/dav/lib/DAV/LazyOpsPlugin.php @@ -0,0 +1,169 @@ + + * + * @copyright Copyright (c) 2018, ownCloud GmbH + * @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 + * + */ + +namespace OCA\DAV\DAV; + +use OCA\DAV\JobStatus\Entity\JobStatus; +use OCA\DAV\JobStatus\Entity\JobStatusMapper; +use OCP\ILogger; +use OCP\IURLGenerator; +use OCP\IUserSession; +use OCP\Shutdown\IShutdownManager; +use Sabre\DAV\Exception; +use Sabre\DAV\Server; +use Sabre\DAV\ServerPlugin; +use Sabre\DAV\UUIDUtil; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\Response; +use Sabre\HTTP\ResponseInterface; + +/** + * Class LazyOpsPlugin + * + * @package OCA\DAV\DAV + */ +class LazyOpsPlugin extends ServerPlugin { + + /** @var Server */ + private $server; + /** @var string */ + private $jobId; + /** @var JobStatus */ + private $entity; + /** @var IUserSession */ + private $userSession; + /** @var IURLGenerator */ + private $urlGenerator; + /** @var IShutdownManager */ + private $shutdownManager; + /** @var ILogger */ + private $logger; + /** @var JobStatusMapper */ + private $mapper; + + public function __construct(IUserSession $userSession, + IURLGenerator $urlGenerator, + IShutdownManager $shutdownManager, + JobStatusMapper $jobStatusMapper, + ILogger $logger) { + $this->userSession = $userSession; + $this->urlGenerator = $urlGenerator; + $this->shutdownManager = $shutdownManager; + $this->logger = $logger; + $this->mapper = $jobStatusMapper; + } + + /** + * @param Server $server + */ + public function initialize(Server $server) { + $this->server = $server; + $server->on('method:MOVE', [$this, 'httpMove'], 90); + } + + /** + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + * @throws Exception\NotAuthenticated + */ + public function httpMove(RequestInterface $request, ResponseInterface $response) { + if (!$request->getHeader('OC-LazyOps')) { + return true; + } + + $this->jobId = UUIDUtil::getUUID(); + $this->setJobStatus([ + 'status' => 'init' + ]); + $userId = $this->getUserId(); + $location = $this->urlGenerator + ->linkTo('', 'remote.php') . "/dav/job-status/{$userId}/{$this->jobId}"; + + $response->setStatus(202); + $response->setHeader('Connection', 'close'); + $response->setHeader('OC-JobStatus-Location', $location); + + $this->shutdownManager->register(function () use ($request, $response) { + return $this->afterResponse($request, $response); + }, IShutdownManager::HIGH); + + return false; + } + + public function afterResponse(RequestInterface $request, ResponseInterface $response) { + if (!$request->getHeader('OC-LazyOps')) { + return true; + } + + \flush(); + $request->removeHeader('OC-LazyOps'); + $responseDummy = new Response(); + try { + $this->setJobStatus([ + 'status' => 'started' + ]); + $this->server->emit('method:MOVE', [$request, $responseDummy]); + + $this->setJobStatus([ + 'status' => 'finished', + 'fileId' => $response->getHeader('OC-FileId'), + 'ETag' => $response->getHeader('ETag') + ]); + } catch (\Exception $ex) { + $this->logger->logException($ex); + + $this->setJobStatus([ + 'status' => 'error', + 'errorCode' => $ex instanceof Exception ? $ex->getHTTPCode() : 500, + 'errorMessage' => $ex->getMessage() + ]); + } + return false; + } + + private function setJobStatus(array $status) { + if ($this->entity === null) { + $userId = $this->getUserId(); + + $this->entity = new JobStatus(); + $this->entity->setStatusInfo(\json_encode($status)); + $this->entity->setUserId($userId); + $this->entity->setUuid($this->jobId); + $this->mapper->insert($this->entity); + } else { + $this->entity->setStatusInfo(\json_encode($status)); + $this->mapper->update($this->entity); + } + } + + /** + * @return string + * @throws Exception\NotAuthenticated + */ + private function getUserId() { + $user = $this->userSession->getUser(); + if ($user === null) { + throw new Exception\NotAuthenticated(); + } + return $user->getUID(); + } +} diff --git a/apps/dav/lib/JobStatus/Entity/JobStatus.php b/apps/dav/lib/JobStatus/Entity/JobStatus.php new file mode 100644 index 000000000000..f5e1975bb066 --- /dev/null +++ b/apps/dav/lib/JobStatus/Entity/JobStatus.php @@ -0,0 +1,42 @@ + + * + * @copyright Copyright (c) 2018, ownCloud GmbH + * @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 + * + */ + +namespace OCA\DAV\JobStatus\Entity; + +use OCP\AppFramework\Db\Entity; + +/** + * Class JobStatus + * + * @method string getUuid() + * @method void setUuid(string $uuid) + * @method string getUserId() + * @method void setUserId(string $userId) + * @method string getStatusInfo() + * @method void setStatusInfo(string $statusInfo) + * + * @package OCA\DAV\JobStatus\Entity + */ +class JobStatus extends Entity { + protected $uuid; + protected $userId; + protected $statusInfo; +} diff --git a/apps/dav/lib/JobStatus/Entity/JobStatusMapper.php b/apps/dav/lib/JobStatus/Entity/JobStatusMapper.php new file mode 100644 index 000000000000..7b5c4b40fcc4 --- /dev/null +++ b/apps/dav/lib/JobStatus/Entity/JobStatusMapper.php @@ -0,0 +1,47 @@ + + * + * @copyright Copyright (c) 2018, ownCloud GmbH + * @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 + * + */ + +namespace OCA\DAV\JobStatus\Entity; + +use OCP\AppFramework\Db\Mapper; +use OCP\IDBConnection; + +class JobStatusMapper extends Mapper { + public function __construct(IDBConnection $db) { + parent::__construct($db, 'dav_job_status'); + } + + /** + * @param string $userId + * @param string $jobId + * @return JobStatus + * @throws \OCP\AppFramework\Db\DoesNotExistException + * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException + */ + public function findByUserIdAndJobId($userId, $jobId) { + $query = $this->db->getQueryBuilder(); + $query->select('*') + ->from($this->getTableName()) + ->where($query->expr()->eq('uuid', $query->createNamedParameter($jobId))) + ->andWhere($query->expr()->eq('user_id', $query->createNamedParameter($userId))); + return $this->mapRowToEntity($this->findOneQuery($query->getSQL(), $query->getParameters())); + } +} diff --git a/apps/dav/lib/JobStatus/Home.php b/apps/dav/lib/JobStatus/Home.php new file mode 100644 index 000000000000..7765d5a79cb6 --- /dev/null +++ b/apps/dav/lib/JobStatus/Home.php @@ -0,0 +1,68 @@ + + * + * @copyright Copyright (c) 2018, ownCloud GmbH + * @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 + * + */ +namespace OCA\DAV\JobStatus; + +use OCA\DAV\DAV\LazyOpsPlugin; +use OCA\DAV\JobStatus\Entity\JobStatusMapper; +use OCP\AppFramework\Db\DoesNotExistException; +use Sabre\DAV\Collection; +use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\Exception\MethodNotAllowed; +use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\SimpleFile; +use Sabre\HTTP\URLUtil; + +class Home extends Collection { + /** @var array */ + private $principalInfo; + /** @var JobStatusMapper */ + private $mapper; + + /** + * Home constructor. + * + * @param array $principalInfo + * @param JobStatusMapper $mapper + */ + public function __construct($principalInfo, JobStatusMapper $mapper) { + $this->principalInfo = $principalInfo; + $this->mapper = $mapper; + } + + public function getChild($name) { + try { + $entity = $this->mapper->findByUserIdAndJobId($this->getName(), $name); + + return new JobStatus($this->getName(), $name, $this->mapper, $entity); + } catch (DoesNotExistException $ex) { + throw new NotFound(); + } + } + + public function getChildren() { + throw new MethodNotAllowed('Listing members of this collection is disabled'); + } + + public function getName() { + list(, $name) = URLUtil::splitPath($this->principalInfo['uri']); + return $name; + } +} diff --git a/apps/dav/lib/JobStatus/JobStatus.php b/apps/dav/lib/JobStatus/JobStatus.php new file mode 100644 index 000000000000..8d7f10a79828 --- /dev/null +++ b/apps/dav/lib/JobStatus/JobStatus.php @@ -0,0 +1,79 @@ + + * + * @copyright Copyright (c) 2018, ownCloud GmbH + * @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 + * + */ +namespace OCA\DAV\JobStatus; + +use OCA\DAV\JobStatus\Entity\JobStatus as JobStatusEntity; +use OCA\DAV\JobStatus\Entity\JobStatusMapper; +use Sabre\DAV\File; + +class JobStatus extends File { + + /** @var string */ + private $jobId; + /** @var string */ + private $userId; + /** @var string */ + private $data; + /** @var JobStatusMapper */ + private $mapper; + /** @var JobStatusEntity */ + private $entity; + + public function __construct($userId, $jobId, + JobStatusMapper $mapper, + JobStatusEntity $entity) { + $this->userId = $userId; + $this->jobId = $jobId; + $this->mapper = $mapper; + $this->entity = $entity; + } + + /** + * Returns the name of the node. + * + * This is used to generate the url. + * + * @return string + */ + public function getName() { + return $this->jobId; + } + + public function get() { + if ($this->entity === null) { + $this->entity = $this->mapper + ->findByUserIdAndJobId($this->userId, $this->jobId); + } + return $this->entity->getStatusInfo(); + } + + public function getETag() { + return '"' . \sha1($this->get()) . '"'; + } + + public function getSize() { + return \strlen($this->get()); + } + + public function refreshStatus() { + $this->entity = null; + } +} diff --git a/apps/dav/lib/JobStatus/RootCollection.php b/apps/dav/lib/JobStatus/RootCollection.php new file mode 100644 index 000000000000..a953fc7906c7 --- /dev/null +++ b/apps/dav/lib/JobStatus/RootCollection.php @@ -0,0 +1,43 @@ + + * + * @copyright Copyright (c) 2018, ownCloud GmbH + * @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 + * + */ +namespace OCA\DAV\JobStatus; + +use OCA\DAV\JobStatus\Entity\JobStatusMapper; +use Sabre\DAVACL\AbstractPrincipalCollection; + +class RootCollection extends AbstractPrincipalCollection { + + /** + * @inheritdoc + */ + public function getChildForPrincipal(array $principalInfo) { + /** @var JobStatusMapper $mapper */ + $mapper = \OC::$server->query(JobStatusMapper::class); + return new Home($principalInfo, $mapper); + } + + /** + * @inheritdoc + */ + public function getName() { + return 'job-status'; + } +} diff --git a/apps/dav/lib/RootCollection.php b/apps/dav/lib/RootCollection.php index 51d3a0f1b690..faa8e3fba63d 100644 --- a/apps/dav/lib/RootCollection.php +++ b/apps/dav/lib/RootCollection.php @@ -91,6 +91,9 @@ public function __construct() { $avatarCollection = new Avatars\RootCollection($userPrincipalBackend, 'principals/users'); $avatarCollection->disableListing = $disableListing; + $queueCollection = new JobStatus\RootCollection($userPrincipalBackend, 'principals/users'); + $queueCollection->disableListing = $disableListing; + $children = [ new SimpleCollection('principals', [ $userPrincipals, @@ -106,7 +109,8 @@ public function __construct() { $systemTagRelationsCollection, $uploadCollection, $avatarCollection, - new \OCA\DAV\Meta\RootCollection(\OC::$server->getRootFolder()) + new \OCA\DAV\Meta\RootCollection(\OC::$server->getRootFolder()), + $queueCollection ]; parent::__construct('root', $children); diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php index d5e9ffbbe39d..a3f2bbee5b08 100644 --- a/apps/dav/lib/Server.php +++ b/apps/dav/lib/Server.php @@ -47,10 +47,12 @@ use OCA\DAV\Connector\Sabre\ValidateRequestPlugin; use OCA\DAV\DAV\FileCustomPropertiesBackend; use OCA\DAV\DAV\FileCustomPropertiesPlugin; +use OCA\DAV\DAV\LazyOpsPlugin; use OCA\DAV\DAV\MiscCustomPropertiesBackend; use OCA\DAV\DAV\PublicAuth; use OCA\DAV\Files\BrowserErrorPagePlugin; use OCA\DAV\Files\PreviewPlugin; +use OCA\DAV\JobStatus\Entity\JobStatusMapper; use OCA\DAV\SystemTag\SystemTagPlugin; use OCA\DAV\Upload\ChunkingPlugin; use OCP\IRequest; @@ -73,6 +75,8 @@ class Server { * * @param IRequest $request * @param string $baseUri + * @throws \OCP\AppFramework\QueryException + * @throws \Sabre\DAV\Exception */ public function __construct(IRequest $request, $baseUri) { $this->request = $request; @@ -84,6 +88,17 @@ public function __construct(IRequest $request, $baseUri) { $tree = new \OCA\DAV\Tree($root); $this->server = new \OCA\DAV\Connector\Sabre\Server($tree); + $config = \OC::$server->getConfig(); + if ($config->getSystemValue('dav.enable.async', true)) { + $this->server->addPlugin(new LazyOpsPlugin( + \OC::$server->getUserSession(), + \OC::$server->getURLGenerator(), + \OC::$server->getShutdownHandler(), + \OC::$server->query(JobStatusMapper::class), + \OC::$server->getLogger() + )); + } + // Backends $authBackend = new Auth( \OC::$server->getSession(), @@ -97,7 +112,6 @@ public function __construct(IRequest $request, $baseUri) { $this->server->httpRequest->setUrl($this->request->getRequestUri()); $this->server->setBaseUri($this->baseUri); - $config = \OC::$server->getConfig(); $this->server->addPlugin(new MaintenancePlugin($config)); $this->server->addPlugin(new ValidateRequestPlugin('dav')); $this->server->addPlugin(new BlockLegacyClientPlugin($config)); @@ -285,7 +299,7 @@ public function exec() { } /** - * @param string[] $subTree + * @param string[] $subTrees * @return bool */ private function isRequestForSubtree(array $subTrees) { diff --git a/apps/dav/tests/unit/DAV/LazyOpsPluginTest.php b/apps/dav/tests/unit/DAV/LazyOpsPluginTest.php new file mode 100644 index 000000000000..65fd6b48610d --- /dev/null +++ b/apps/dav/tests/unit/DAV/LazyOpsPluginTest.php @@ -0,0 +1,161 @@ + + * + * @copyright Copyright (c) 2018, ownCloud GmbH + * @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 + * + */ + +namespace OCA\DAV\Tests\unit\DAV; + +use OCA\DAV\DAV\LazyOpsPlugin; +use OCA\DAV\JobStatus\Entity\JobStatus; +use OCA\DAV\JobStatus\Entity\JobStatusMapper; +use OCP\ILogger; +use OCP\IURLGenerator; +use OCP\IUser; +use OCP\IUserSession; +use OCP\Shutdown\IShutdownManager; +use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\Server; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; +use Test\TestCase; + +class LazyOpsPluginTest extends TestCase { + + /** @var LazyOpsPlugin */ + private $plugin; + /** @var ILogger */ + private $logger; + /** @var JobStatusMapper | \PHPUnit_Framework_MockObject_MockObject */ + private $jobStatusMapper; + /** @var IShutdownManager | \PHPUnit_Framework_MockObject_MockObject */ + private $shutdownManager; + /** @var IURLGenerator | \PHPUnit_Framework_MockObject_MockObject */ + private $urlGenerator; + /** @var IUserSession | \PHPUnit_Framework_MockObject_MockObject */ + private $userSession; + + public function setUp() { + parent::setUp(); + + $this->userSession = $this->createMock(IUserSession::class); + $this->urlGenerator= $this->createMock(IURLGenerator::class); + $this->shutdownManager = $this->createMock(IShutdownManager::class); + $this->jobStatusMapper = $this->createMock(JobStatusMapper::class); + $this->logger = $this->createMock(ILogger::class); + $this->plugin = new LazyOpsPlugin($this->userSession, $this->urlGenerator, + $this->shutdownManager, $this->jobStatusMapper, $this->logger); + + $user = $this->createMock(IUser::class); + $user->method('getUID')->willReturn('alice'); + $this->userSession->method('getUser')->willReturn($user); + } + + public function testInit() { + $server = $this->createMock(Server::class); + $server->expects(self::once())->method('on')->with('method:MOVE'); + $this->plugin->initialize($server); + } + + public function testMoveWithoutLazyOpsHeader() { + $request = $this->createMock(RequestInterface::class); + $response= $this->createMock(ResponseInterface::class); + $response->expects($this->never())->method('setStatus'); + $this->plugin->httpMove($request, $response); + } + + public function testMoveWithLazyOpsHeader() { + $request = $this->createMock(RequestInterface::class); + $request->method('getHeader')->with('OC-LazyOps')->willReturn(true); + $response= $this->createMock(ResponseInterface::class); + $response->expects($this->once())->method('setStatus')->with(202); + $response->expects($this->exactly(2))->method('setHeader')->withConsecutive( + ['Connection', 'close'], + ['OC-JobStatus-Location', self::stringStartsWith('/remote.php/dav/job-status/alice/')] + ); + + $this->urlGenerator->expects(self::once())->method('linkTo')->willReturn('/remote.php'); + + $this->jobStatusMapper->expects(self::once()) + ->method('insert')->willReturnCallback(function (JobStatus $entity) { + self::assertEquals('alice', $entity->getUserId()); + self::assertEquals('{"status":"init"}', $entity->getStatusInfo()); + }); + + $this->shutdownManager->expects(self::once())->method('register'); + + $this->plugin->httpMove($request, $response); + } + + public function testAfterResponseProcessing() { + $request = $this->createMock(RequestInterface::class); + $request->method('getHeader')->with('OC-LazyOps')->willReturn(true); + $request->expects(self::once())->method('removeHeader')->with('OC-LazyOps')->willReturn(true); + + $response= $this->createMock(ResponseInterface::class); + $response->expects($this->exactly(2))->method('getHeader')->withConsecutive( + ['OC-FileId'], + ['ETag'] + )->willReturnOnConsecutiveCalls( + 'oc1234', + '"abcdef"' + ); + + $this->jobStatusMapper->expects(self::once()) + ->method('insert')->willReturnCallback(function (JobStatus $entity) { + self::assertEquals('alice', $entity->getUserId()); + self::assertEquals('{"status":"started"}', $entity->getStatusInfo()); + }); + $this->jobStatusMapper->expects(self::once()) + ->method('update')->willReturnCallback(function (JobStatus $entity) { + self::assertEquals('alice', $entity->getUserId()); + self::assertEquals('{"status":"finished","fileId":"oc1234","ETag":"\"abcdef\""}', $entity->getStatusInfo()); + }); + + $server = $this->createMock(Server::class); + $server->expects(self::once())->method('emit')->with('method:MOVE'); + + $this->plugin->initialize($server); + $this->plugin->afterResponse($request, $response); + } + + public function testAfterResponseProcessingThrowingAnException() { + $request = $this->createMock(RequestInterface::class); + $request->method('getHeader')->with('OC-LazyOps')->willReturn(true); + $request->expects(self::once())->method('removeHeader')->with('OC-LazyOps')->willReturn(true); + + $response= $this->createMock(ResponseInterface::class); + + $this->jobStatusMapper->expects(self::once()) + ->method('insert')->willReturnCallback(function (JobStatus $entity) { + self::assertEquals('alice', $entity->getUserId()); + self::assertEquals('{"status":"started"}', $entity->getStatusInfo()); + }); + $this->jobStatusMapper->expects(self::once()) + ->method('update')->willReturnCallback(function (JobStatus $entity) { + self::assertEquals('alice', $entity->getUserId()); + self::assertEquals('{"status":"error","errorCode":404,"errorMessage":""}', $entity->getStatusInfo()); + }); + + $server = $this->createMock(Server::class); + $server->expects(self::once())->method('emit')->willThrowException(new NotFound()); + + $this->plugin->initialize($server); + $this->plugin->afterResponse($request, $response); + } +} diff --git a/apps/dav/tests/unit/JobStatus/Entity/JobStatusMapperTest.php b/apps/dav/tests/unit/JobStatus/Entity/JobStatusMapperTest.php new file mode 100644 index 000000000000..649e99ffdc5c --- /dev/null +++ b/apps/dav/tests/unit/JobStatus/Entity/JobStatusMapperTest.php @@ -0,0 +1,87 @@ + + * + * @copyright Copyright (c) 2018, ownCloud GmbH + * @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 + * + */ + +namespace OCA\DAV\Tests\Unit\JobStatus\Entity; + +use Doctrine\DBAL\Exception\UniqueConstraintViolationException; +use OCA\DAV\JobStatus\Entity\JobStatus; +use OCA\DAV\JobStatus\Entity\JobStatusMapper; +use OCP\IDBConnection; +use Sabre\DAV\UUIDUtil; +use Test\TestCase; + +/** + * Class JobStatusMapperTest + * + * @package OCA\DAV\Tests\Unit\JobStatus\Entity + * @group DB + */ +class JobStatusMapperTest extends TestCase { + /** @var JobStatusMapper */ + private $mapper; + /** @var IDBConnection */ + private $database; + /** @var JobStatus */ + private $testJobStatus; + + public function setUp() { + parent::setUp(); + $this->database = \OC::$server->getDatabaseConnection(); + $this->mapper = new JobStatusMapper($this->database); + + // test entity + $this->testJobStatus = new JobStatus(); + $this->testJobStatus->setUserId('xxx'); + $this->testJobStatus->setUuid(UUIDUtil::getUUID()); + $this->testJobStatus->setStatusInfo(\json_encode([])); + } + + public function tearDown() { + parent::tearDown(); + if ($this->mapper !== null && $this->testJobStatus !== null) { + $this->mapper->delete($this->testJobStatus); + } + } + + /** + * @expectedException \Doctrine\DBAL\Exception\UniqueConstraintViolationException + */ + public function testInsert() { + $this->mapper->insert($this->testJobStatus); + $this->assertNotNull($this->testJobStatus->getId()); + // below throws exception due to unique constraint violation + $this->mapper->insert($this->testJobStatus); + } + + /** + * @depends testInsert + */ + public function testQuery() { + $this->mapper->insert($this->testJobStatus); + $entity = $this->mapper->findByUserIdAndJobId($this->testJobStatus->getUserId(), + $this->testJobStatus->getUuid()); + $this->assertInstanceOf(JobStatus::class, $entity); + $this->assertEquals($this->testJobStatus->getId(), $entity->getId()); + $this->assertEquals($this->testJobStatus->getUserId(), $entity->getUserId()); + $this->assertEquals($this->testJobStatus->getUuid(), $entity->getUuid()); + $this->assertEquals($this->testJobStatus->getStatusInfo(), $entity->getStatusInfo()); + } +} diff --git a/apps/dav/tests/unit/JobStatus/HomeTest.php b/apps/dav/tests/unit/JobStatus/HomeTest.php new file mode 100644 index 000000000000..b38f2c5bfca2 --- /dev/null +++ b/apps/dav/tests/unit/JobStatus/HomeTest.php @@ -0,0 +1,78 @@ + + * + * @copyright Copyright (c) 2018, ownCloud GmbH + * @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 + * + */ + +namespace OCA\DAV\Tests\Unit\JobStatus; + +use OCA\DAV\JobStatus\Entity\JobStatus as JobStatusEntity; +use OCA\DAV\JobStatus\Entity\JobStatusMapper; +use OCA\DAV\JobStatus\Home; +use OCA\DAV\JobStatus\JobStatus; +use OCP\AppFramework\Db\DoesNotExistException; +use Test\TestCase; + +/** + * Class HomeTest + * + * @package OCA\DAV\Tests\Unit\JobStatus + */ +class HomeTest extends TestCase { + public function testGetName() { + /** @var JobStatusMapper | \PHPUnit_Framework_MockObject_MockObject $mapper */ + $mapper = $this->createMock(JobStatusMapper::class); + $home = new Home(['uri' => 'principals/users/user1'], $mapper); + $this->assertEquals('user1', $home->getName()); + } + + /** + * @expectedException \Sabre\DAV\Exception\MethodNotAllowed + */ + public function testGetChildren() { + /** @var JobStatusMapper | \PHPUnit_Framework_MockObject_MockObject $mapper */ + $mapper = $this->createMock(JobStatusMapper::class); + $home = new Home(['uri' => 'principals/users/user1'], $mapper); + $home->getChildren(); + } + + public function testGetChild() { + /** @var JobStatusMapper | \PHPUnit_Framework_MockObject_MockObject $mapper */ + $mapper = $this->createMock(JobStatusMapper::class); + + $jobStatusEntity = new JobStatusEntity(); + $mapper->method('findByUserIdAndJobId')->willReturn($jobStatusEntity); + $home = new Home(['uri' => 'principals/users/user1'], $mapper); + $child = $home->getChild('1234567890'); + $this->assertInstanceOf(JobStatus::class, $child); + $this->assertEquals('1234567890', $child->getName()); + } + + /** + * @expectedException \Sabre\DAV\Exception\NotFound + */ + public function testGetChildNotFound() { + /** @var JobStatusMapper | \PHPUnit_Framework_MockObject_MockObject $mapper */ + $mapper = $this->createMock(JobStatusMapper::class); + + $ex = new DoesNotExistException(''); + $mapper->method('findByUserIdAndJobId')->willThrowException($ex); + $home = new Home(['uri' => 'principals/users/user1'], $mapper); + $home->getChild('1234567890'); + } +} diff --git a/apps/dav/tests/unit/JobStatus/JobStatusTest.php b/apps/dav/tests/unit/JobStatus/JobStatusTest.php new file mode 100644 index 000000000000..4dc1abe1e4ee --- /dev/null +++ b/apps/dav/tests/unit/JobStatus/JobStatusTest.php @@ -0,0 +1,70 @@ + + * + * @copyright Copyright (c) 2018, ownCloud GmbH + * @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 + * + */ + +namespace OCA\DAV\Tests\Unit\JobStatus; + +use OCA\DAV\JobStatus\Entity\JobStatus as JobStatusEntity; +use OCA\DAV\JobStatus\Entity\JobStatusMapper; +use OCA\DAV\JobStatus\JobStatus; +use Test\TestCase; + +/** + * Class JobStatusTest + * + * @package OCA\DAV\Tests\Unit\JobStatus + */ +class JobStatusTest extends TestCase { + + /** @var JobStatus */ + private $jobStatus; + /** @var JobStatusEntity */ + private $jobStatusEntity; + /** @var JobStatusMapper | \PHPUnit_Framework_MockObject_MockObject */ + private $mapper; + + protected function setUp() { + parent::setUp(); + + $this->mapper = $this->createMock(JobStatusMapper::class); + + $this->jobStatusEntity = new JobStatusEntity(); + $this->mapper->method('findByUserIdAndJobId')->willReturn($this->jobStatusEntity); + + $this->jobStatus = new JobStatus('user1', '1234567890', $this->mapper, $this->jobStatusEntity); + } + + public function testGetChild() { + $this->assertEquals('1234567890', $this->jobStatus->getName()); + } + + public function testGet() { + $this->jobStatusEntity->setStatusInfo('abc'); + $this->assertEquals('abc', $this->jobStatus->get()); + $this->assertEquals('"a9993e364706816aba3e25717850c26c9cd0d89d"', $this->jobStatus->getETag()); + $this->assertEquals(3, $this->jobStatus->getSize()); + + $this->jobStatus->refreshStatus(); + $newJobStatusEntity = new JobStatusEntity(); + $newJobStatusEntity->setStatusInfo('def'); + $this->mapper->method('findByUserIdAndJobId')->willReturn($newJobStatusEntity); + $this->assertEquals('abc', $this->jobStatus->get()); + } +} diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js index 3fab0d0b14e4..22ff12dd42ec 100644 --- a/apps/files/js/file-upload.js +++ b/apps/files/js/file-upload.js @@ -307,15 +307,46 @@ OC.FileUpload.prototype = { } if (size) { headers['OC-Total-Length'] = size; - } + headers['OC-LazyOps'] = 1; + + var doneDeferred = $.Deferred(); - return this.uploader.davClient.move( + this.uploader.davClient.move( 'uploads/' + uid + '/' + this.getId() + '/.file', 'files/' + uid + '/' + OC.joinPaths(this.getFullPath(), this.getFileName()), true, headers - ); + ).then(function (status, response) { + if (status === 202) { + function poll() { + $.ajax(response.xhr.getResponseHeader('oc-jobstatus-location')).then(function(data) { + var obj = JSON.parse(data); + if (obj.status === 'finished') { + doneDeferred.resolve(status, response); + } + if (obj.status === 'error') { + OC.Notification.show(obj.errorMessage); + doneDeferred.reject(status, response); + } + if (obj.status === 'started' || obj.status === 'initial') { + // call it again after some short delay + setTimeout(poll, 1000); + } + }) + } + + // start the polling + poll(); + } else { + doneDeferred.resolve(status, response); + } + + }).fail( function(status, response) { + doneDeferred.reject(status, response); + }); + + return doneDeferred.promise(); }, _deleteChunkFolder: function() { diff --git a/config/config.sample.php b/config/config.sample.php index 2c87b1b8ce1f..becacf9cadb1 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -1460,4 +1460,9 @@ */ 'smb.logging.enable' => false, + +/** + * Async dav extensions can be enabled or disabled. + */ +'dav.enable.async' => true, ); diff --git a/core/js/files/client.js b/core/js/files/client.js index 66bcf7b3ce23..d6d5dda27bff 100644 --- a/core/js/files/client.js +++ b/core/js/files/client.js @@ -672,7 +672,7 @@ ).then( function(result) { if (self._isSuccessStatus(result.status)) { - deferred.resolve(result.status); + deferred.resolve(result.status, result); } else { result = _.extend(result, self._getSabreException(result)); deferred.reject(result.status, result); @@ -697,7 +697,7 @@ ).then( function(result) { if (self._isSuccessStatus(result.status)) { - deferred.resolve(result.status); + deferred.resolve(result.status, result); } else { result = _.extend(result, self._getSabreException(result)); deferred.reject(result.status, result); @@ -740,7 +740,7 @@ ).then( function(result) { if (self._isSuccessStatus(result.status)) { - deferred.resolve(result.status); + deferred.resolve(result.status, result); } else { result = _.extend(result, self._getSabreException(result)); deferred.reject(result.status, result); From d13f208f6530b8b2d88e783bd7f8b2a391659ad9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Thu, 21 Jun 2018 12:57:39 +0200 Subject: [PATCH 2/6] Introduce IShutdownManager which allows us to order the shutdown functions --- lib/base.php | 12 +- lib/private/Log/ErrorHandler.php | 4 +- lib/private/Log/Syslog.php | 4 +- lib/private/Server.php | 11 + lib/private/Shutdown/ShutDownManager.php | 65 ++++ lib/public/IServerContainer.php | 7 + lib/public/Shutdown/IShutdownManager.php | 47 +++ tests/lib/ServerTest.php | 360 ++++++++++++++------- tests/lib/Shutdown/ShutdownManagerTest.php | 48 +++ 9 files changed, 427 insertions(+), 131 deletions(-) create mode 100644 lib/private/Shutdown/ShutDownManager.php create mode 100644 lib/public/Shutdown/IShutdownManager.php create mode 100644 tests/lib/Shutdown/ShutdownManagerTest.php diff --git a/lib/base.php b/lib/base.php index 1e590cf56829..c9e4787e58b0 100644 --- a/lib/base.php +++ b/lib/base.php @@ -677,10 +677,14 @@ public static function init() { } //make sure temporary files are cleaned up - $tmpManager = \OC::$server->getTempManager(); - \register_shutdown_function([$tmpManager, 'clean']); - $lockProvider = \OC::$server->getLockingProvider(); - \register_shutdown_function([$lockProvider, 'releaseAll']); + \OC::$server->getShutdownHandler()->register(function () { + $tmpManager = \OC::$server->getTempManager(); + $tmpManager->clean(); + }); + \OC::$server->getShutdownHandler()->register(function () { + $lockProvider = \OC::$server->getLockingProvider(); + $lockProvider->releaseAll(); + }); // Check whether the sample configuration has been copied if ($systemConfig->getValue('copied_sample_config', false)) { diff --git a/lib/private/Log/ErrorHandler.php b/lib/private/Log/ErrorHandler.php index b98ea597ed8e..ad5c70624b18 100644 --- a/lib/private/Log/ErrorHandler.php +++ b/lib/private/Log/ErrorHandler.php @@ -50,7 +50,9 @@ public static function register($debug=false) { } else { \set_error_handler([$handler, 'onError']); } - \register_shutdown_function([$handler, 'onShutdown']); + \OC::$server->getShutdownHandler()->register(function () use ($handler) { + $handler->onShutdown(); + }); \set_exception_handler([$handler, 'onException']); } diff --git a/lib/private/Log/Syslog.php b/lib/private/Log/Syslog.php index b80110729e69..950bf3d1836a 100644 --- a/lib/private/Log/Syslog.php +++ b/lib/private/Log/Syslog.php @@ -41,7 +41,9 @@ class Syslog { public static function init() { \openlog(\OC::$server->getSystemConfig()->getValue("syslog_tag", "ownCloud"), LOG_PID | LOG_CONS, LOG_USER); // Close at shutdown - \register_shutdown_function('closelog'); + \OC::$server->getShutdownHandler()->register(function () { + \closelog(); + }); } /** diff --git a/lib/private/Server.php b/lib/private/Server.php index 1c94a607e5b4..f884dade798b 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -82,6 +82,7 @@ use OC\Session\Memory; use OC\Settings\Panels\Helper; use OC\Settings\SettingsManager; +use OC\Shutdown\ShutDownManager; use OC\Tagging\TagMapper; use OC\Theme\ThemeService; use OC\User\AccountMapper; @@ -99,6 +100,7 @@ use OCP\ISession; use OCP\IUser; use OCP\Security\IContentSecurityPolicyManager; +use OCP\Shutdown\IShutdownManager; use OCP\Theme\IThemeService; use OCP\Util\UserSearch; use Symfony\Component\EventDispatcher\EventDispatcher; @@ -1660,4 +1662,13 @@ public function load(array $xmlPath, IUser $user = null) { } } } + + /** + * @return IShutdownManager + * @throws QueryException + * @since 11.0.0 + */ + public function getShutdownHandler() { + return $this->query(ShutDownManager::class); + } } diff --git a/lib/private/Shutdown/ShutDownManager.php b/lib/private/Shutdown/ShutDownManager.php new file mode 100644 index 000000000000..7286e178ddbb --- /dev/null +++ b/lib/private/Shutdown/ShutDownManager.php @@ -0,0 +1,65 @@ + + * + * @copyright Copyright (c) 2018, ownCloud GmbH + * @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 + * + */ + +namespace OC\Shutdown; + +use OCP\Shutdown\IShutdownManager; + +class ShutDownManager implements IShutdownManager { + + /** @var array[] */ + private $callbacks = []; + + /** + * ShutDownManager constructor. + * + * @codeCoverageIgnore + */ + public function __construct() { + \register_shutdown_function([$this, 'run']); + } + + /** + * Any kind of cleanup should be added with priority LOW. + * Anything which shall be processed before the cleanup routes better + * uses a value smaller then LOW + * + * @param \Closure $callback + * @param int $priority - the lower the number the higher is the priority + * @return void + * @since 11.0.0 + */ + public function register(\Closure $callback, $priority = self::LOW) { + if (!isset($this->callbacks[$priority])) { + $this->callbacks[$priority] = []; + } + $this->callbacks[$priority][] = $callback; + } + + public function run() { + \ksort($this->callbacks); + foreach ($this->callbacks as $callbacks) { + foreach ($callbacks as $callback) { + $callback(); + } + } + } +} diff --git a/lib/public/IServerContainer.php b/lib/public/IServerContainer.php index 7c9c0afddc57..e54667a6eb14 100644 --- a/lib/public/IServerContainer.php +++ b/lib/public/IServerContainer.php @@ -43,6 +43,7 @@ // This means that they should be used by apps instead of the internal ownCloud classes namespace OCP; use OCP\Security\IContentSecurityPolicyManager; +use OCP\Shutdown\IShutdownManager; use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** @@ -536,4 +537,10 @@ public function getDateTimeFormatter(); * @since 10.0.3 */ public function getThemeService(); + + /** + * @return IShutdownManager + * @since 11.0.0 + */ + public function getShutdownHandler(); } diff --git a/lib/public/Shutdown/IShutdownManager.php b/lib/public/Shutdown/IShutdownManager.php new file mode 100644 index 000000000000..939904a029e4 --- /dev/null +++ b/lib/public/Shutdown/IShutdownManager.php @@ -0,0 +1,47 @@ + + * + * @copyright Copyright (c) 2018, ownCloud GmbH + * @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 + * + */ + +namespace OCP\Shutdown; + +/** + * Interface IShutdownManager + * + * @package OCP\Shutdown + * @since 10.0.10 + */ +interface IShutdownManager { + const HIGH = 10; + const LOW = 1000; + + /** + * Any kind of cleanup should be added with priority LOW. + * Anything which shall be processed before the cleanup routes better + * uses a value smaller then LOW. You can use HIGH if you like. + * In case two callbacks have the same prio they will be executed in the + * order in which they have been registered. + * + * @param \Closure $callback - a simple function with no arguments and no return + * @param int $priority - the lower the number the higher is the priority + * @return void + * @since 10.0.10 + */ + public function register(\Closure $callback, $priority = self::LOW); +} diff --git a/tests/lib/ServerTest.php b/tests/lib/ServerTest.php index 66ea8c603c45..a7c3085e7d11 100644 --- a/tests/lib/ServerTest.php +++ b/tests/lib/ServerTest.php @@ -26,7 +26,101 @@ use OC\Config; use OC\Server; +use OC\Shutdown\ShutDownManager; use OCA\Comments\Dav\CommentsPlugin; +use OCP\Shutdown\IShutdownManager; +use OCP\Comments\ICommentsManager; +use Test\Comments\FakeFactory; +use OC\Comments\ManagerFactory; +use OCP\IEventSource; +use OCP\ICertificateManager; +use OC\Security\CertificateManager; +use OCP\SystemTag\ISystemTagObjectMapper; +use OCP\SystemTag\ISystemTagManager; +use OC\Security\TrustedDomainHelper; +use OCP\ITempManager; +use OC\TempManager; +use OCP\ITagManager; +use OC\TagManager; +use OCP\AppFramework\Db\Mapper; +use OC\Tagging\TagMapper; +use OCP\IUserSession; +use OC\User\Session; +use OCP\IUserManager; +use OC\User\Manager; +use OCP\IURLGenerator; +use OC\URLGenerator; +use OC\SystemConfig; +use OCP\Share\IManager; +use OCP\Security\ISecureRandom; +use OC\Security\SecureRandom; +use OCP\ISearch; +use OC\Search; +use OCP\Route\IRouter; +use OCP\Files\Folder; +use OCP\Files\IRootFolder; +use OC\Files\Node\Root; +use OCP\IRequest; +use OC\AppFramework\Http\Request; +use OCP\Diagnostics\IQueryLogger; +use OCP\IPreview; +use OC\PreviewManager; +use OCP\ICache; +use OC\Cache\File; +use OCP\INavigationManager; +use OC\NavigationManager; +use OCP\Files\Config\IMountProviderCollection; +use OC\Files\Config\MountProviderCollection; +use OCP\ICacheFactory; +use OC\Memcache\Factory; +use OCP\Mail\IMailer; +use OC\Mail\Mailer; +use OCP\ILogger; +use OC\Log; +use OCP\Lock\ILockingProvider; +use OCP\L10N\IFactory; +use OCP\BackgroundJob\IJobList; +use OC\BackgroundJob\JobList; +use OC\Files\Type\Detection; +use OCP\Files\IMimeTypeDetector; +use bantu\IniGetWrapper\IniGetWrapper; +use OCP\Http\Client\IClientService; +use OC\Http\Client\ClientService; +use OC\HTTPHelper; +use OCP\Security\IHasher; +use OC\Security\Hasher; +use OCP\IGroupManager; +use OCP\Diagnostics\IEventLogger; +use OCP\Encryption\Keys\IStorage; +use OC\Encryption\Keys\Storage; +use OCP\Encryption\IFile; +use OCP\IDb; +use OC\AppFramework\Db\Db; +use OCP\IDateTimeZone; +use OC\DateTimeZone; +use OCP\IDateTimeFormatter; +use OC\DateTimeFormatter; +use OCP\IDBConnection; +use OC\DB\Connection; +use OC\Security\CSRF\CsrfTokenManager; +use OC\Session\CryptoWrapper; +use OCP\Security\ICrypto; +use OC\Security\Crypto; +use OC\Security\CSP\ContentSecurityPolicyManager; +use OC\ContactsManager; +use OC\CapabilitiesManager; +use OCP\IAvatarManager; +use OC\AvatarManager; +use OCP\Command\IBus; +use OC\Command\AsyncBus; +use OCP\App\IAppManager; +use OC\App\AppManager; +use OCP\IHelper; +use OC\AppHelper; +use OCP\IAppConfig; +use OC\AppConfig; +use OCP\IConfig; +use OC\AllConfig; /** * Class Server @@ -35,7 +129,7 @@ * * @package Test */ -class ServerTest extends \Test\TestCase { +class ServerTest extends TestCase { /** @var Server */ protected $server; @@ -47,122 +141,124 @@ public function setUp() { public function dataTestQuery() { return [ - ['ActivityManager', '\OC\Activity\Manager'], - ['ActivityManager', '\OCP\Activity\IManager'], - ['AllConfig', '\OC\AllConfig'], - ['AllConfig', '\OCP\IConfig'], - ['AppConfig', '\OC\AppConfig'], - ['AppConfig', '\OCP\IAppConfig'], - ['AppHelper', '\OC\AppHelper'], - ['AppHelper', '\OCP\IHelper'], - ['AppManager', '\OC\App\AppManager'], - ['AppManager', '\OCP\App\IAppManager'], - ['AsyncCommandBus', '\OC\Command\AsyncBus'], - ['AsyncCommandBus', '\OCP\Command\IBus'], - ['AvatarManager', '\OC\AvatarManager'], - ['AvatarManager', '\OCP\IAvatarManager'], - - ['CapabilitiesManager', '\OC\CapabilitiesManager'], - ['ContactsManager', '\OC\ContactsManager'], - ['ContactsManager', '\OCP\Contacts\IManager'], - ['ContentSecurityPolicyManager', '\OC\Security\CSP\ContentSecurityPolicyManager'], - ['CommentsManager', '\OCP\Comments\ICommentsManager'], - ['Crypto', '\OC\Security\Crypto'], - ['Crypto', '\OCP\Security\ICrypto'], - ['CryptoWrapper', '\OC\Session\CryptoWrapper'], - ['CsrfTokenManager', '\OC\Security\CSRF\CsrfTokenManager'], - - ['DatabaseConnection', '\OC\DB\Connection'], - ['DatabaseConnection', '\OCP\IDBConnection'], - ['DateTimeFormatter', '\OC\DateTimeFormatter'], - ['DateTimeFormatter', '\OCP\IDateTimeFormatter'], - ['DateTimeZone', '\OC\DateTimeZone'], - ['DateTimeZone', '\OCP\IDateTimeZone'], - ['Db', '\OC\AppFramework\Db\Db'], - ['Db', '\OCP\IDb'], - - ['EncryptionFileHelper', '\OC\Encryption\File'], - ['EncryptionFileHelper', '\OCP\Encryption\IFile'], - ['EncryptionKeyStorage', '\OC\Encryption\Keys\Storage'], - ['EncryptionKeyStorage', '\OCP\Encryption\Keys\IStorage'], - ['EncryptionManager', '\OC\Encryption\Manager'], - ['EncryptionManager', '\OCP\Encryption\IManager'], - ['EventLogger', '\OCP\Diagnostics\IEventLogger'], - - ['GroupManager', '\OC\Group\Manager'], - ['GroupManager', '\OCP\IGroupManager'], - - ['Hasher', '\OC\Security\Hasher'], - ['Hasher', '\OCP\Security\IHasher'], - ['HTTPHelper', '\OC\HTTPHelper'], - ['HttpClientService', '\OC\Http\Client\ClientService'], - ['HttpClientService', '\OCP\Http\Client\IClientService'], - - ['IniWrapper', '\bantu\IniGetWrapper\IniGetWrapper'], - ['MimeTypeDetector', '\OCP\Files\IMimeTypeDetector'], - ['MimeTypeDetector', '\OC\Files\Type\Detection'], - - ['JobList', '\OC\BackgroundJob\JobList'], - ['JobList', '\OCP\BackgroundJob\IJobList'], - - ['L10NFactory', '\OC\L10N\Factory'], - ['L10NFactory', '\OCP\L10N\IFactory'], - ['LockingProvider', '\OCP\Lock\ILockingProvider'], - ['Logger', '\OC\Log'], - ['Logger', '\OCP\ILogger'], - - ['Mailer', '\OC\Mail\Mailer'], - ['Mailer', '\OCP\Mail\IMailer'], - ['MemCacheFactory', '\OC\Memcache\Factory'], - ['MemCacheFactory', '\OCP\ICacheFactory'], - ['MountConfigManager', '\OC\Files\Config\MountProviderCollection'], - ['MountConfigManager', '\OCP\Files\Config\IMountProviderCollection'], - - ['NavigationManager', '\OC\NavigationManager'], - ['NavigationManager', '\OCP\INavigationManager'], - ['NotificationManager', '\OC\Notification\Manager'], - ['NotificationManager', '\OCP\Notification\IManager'], - ['UserCache', '\OC\Cache\File'], - ['UserCache', '\OCP\ICache'], - - ['PreviewManager', '\OC\PreviewManager'], - ['PreviewManager', '\OCP\IPreview'], - - ['QueryLogger', '\OCP\Diagnostics\IQueryLogger'], - - ['Request', '\OC\AppFramework\Http\Request'], - ['Request', '\OCP\IRequest'], - ['RootFolder', '\OC\Files\Node\Root'], - ['RootFolder', '\OC\Files\Node\Folder'], - ['RootFolder', '\OCP\Files\IRootFolder'], - ['RootFolder', '\OCP\Files\Folder'], - ['Router', '\OCP\Route\IRouter'], - - ['Search', '\OC\Search'], - ['Search', '\OCP\ISearch'], - ['SecureRandom', '\OC\Security\SecureRandom'], - ['SecureRandom', '\OCP\Security\ISecureRandom'], - ['ShareManager', '\OC\Share20\Manager'], - ['ShareManager', '\OCP\Share\IManager'], - ['SystemConfig', '\OC\SystemConfig'], - - ['URLGenerator', '\OC\URLGenerator'], - ['URLGenerator', '\OCP\IURLGenerator'], - ['UserManager', '\OC\User\Manager'], - ['UserManager', '\OCP\IUserManager'], - ['UserSession', '\OC\User\Session'], - ['UserSession', '\OCP\IUserSession'], - - ['TagMapper', '\OC\Tagging\TagMapper'], - ['TagMapper', '\OCP\AppFramework\Db\Mapper'], - ['TagManager', '\OC\TagManager'], - ['TagManager', '\OCP\ITagManager'], - ['TempManager', '\OC\TempManager'], - ['TempManager', '\OCP\ITempManager'], - ['TrustedDomainHelper', '\OC\Security\TrustedDomainHelper'], - - ['SystemTagManager', '\OCP\SystemTag\ISystemTagManager'], - ['SystemTagObjectMapper', '\OCP\SystemTag\ISystemTagObjectMapper'], + ['ActivityManager', \OC\Activity\Manager::class], + ['ActivityManager', \OCP\Activity\IManager::class], + ['AllConfig', AllConfig::class], + ['AllConfig', IConfig::class], + ['AppConfig', AppConfig::class], + ['AppConfig', IAppConfig::class], + ['AppHelper', AppHelper::class], + ['AppHelper', IHelper::class], + ['AppManager', AppManager::class], + ['AppManager', IAppManager::class], + ['AsyncCommandBus', AsyncBus::class], + ['AsyncCommandBus', IBus::class], + ['AvatarManager', AvatarManager::class], + ['AvatarManager', IAvatarManager::class], + + ['CapabilitiesManager', CapabilitiesManager::class], + ['ContactsManager', ContactsManager::class], + ['ContactsManager', \OCP\Contacts\IManager::class], + ['ContentSecurityPolicyManager', ContentSecurityPolicyManager::class], + ['CommentsManager', ICommentsManager::class], + ['Crypto', Crypto::class], + ['Crypto', ICrypto::class], + ['CryptoWrapper', CryptoWrapper::class], + ['CsrfTokenManager', CsrfTokenManager::class], + + ['DatabaseConnection', Connection::class], + ['DatabaseConnection', IDBConnection::class], + ['DateTimeFormatter', DateTimeFormatter::class], + ['DateTimeFormatter', IDateTimeFormatter::class], + ['DateTimeZone', DateTimeZone::class], + ['DateTimeZone', IDateTimeZone::class], + ['Db', Db::class], + ['Db', IDb::class], + + ['EncryptionFileHelper', \OC\Encryption\File::class], + ['EncryptionFileHelper', IFile::class], + ['EncryptionKeyStorage', Storage::class], + ['EncryptionKeyStorage', IStorage::class], + ['EncryptionManager', \OC\Encryption\Manager::class], + ['EncryptionManager', \OCP\Encryption\IManager::class], + ['EventLogger', IEventLogger::class], + + ['GroupManager', \OC\Group\Manager::class], + ['GroupManager', IGroupManager::class], + + ['Hasher', Hasher::class], + ['Hasher', IHasher::class], + ['HTTPHelper', HTTPHelper::class], + ['HttpClientService', ClientService::class], + ['HttpClientService', IClientService::class], + + ['IniWrapper', IniGetWrapper::class], + ['MimeTypeDetector', IMimeTypeDetector::class], + ['MimeTypeDetector', Detection::class], + + ['JobList', JobList::class], + ['JobList', IJobList::class], + + ['L10NFactory', \OC\L10N\Factory::class], + ['L10NFactory', IFactory::class], + ['LockingProvider', ILockingProvider::class], + ['Logger', Log::class], + ['Logger', ILogger::class], + + ['Mailer', Mailer::class], + ['Mailer', IMailer::class], + ['MemCacheFactory', Factory::class], + ['MemCacheFactory', ICacheFactory::class], + ['MountConfigManager', MountProviderCollection::class], + ['MountConfigManager', IMountProviderCollection::class], + + ['NavigationManager', NavigationManager::class], + ['NavigationManager', INavigationManager::class], + ['NotificationManager', \OC\Notification\Manager::class], + ['NotificationManager', \OCP\Notification\IManager::class], + ['UserCache', File::class], + ['UserCache', ICache::class], + + ['PreviewManager', PreviewManager::class], + ['PreviewManager', IPreview::class], + + ['QueryLogger', IQueryLogger::class], + + ['Request', Request::class], + ['Request', IRequest::class], + ['RootFolder', Root::class], + ['RootFolder', \OC\Files\Node\Folder::class], + ['RootFolder', IRootFolder::class], + ['RootFolder', Folder::class], + ['Router', IRouter::class], + + ['Search', Search::class], + ['Search', ISearch::class], + ['SecureRandom', SecureRandom::class], + ['SecureRandom', ISecureRandom::class], + ['ShareManager', \OC\Share20\Manager::class], + ['ShareManager', IManager::class], + ['SystemConfig', SystemConfig::class], + + ['URLGenerator', URLGenerator::class], + ['URLGenerator', IURLGenerator::class], + ['UserManager', Manager::class], + ['UserManager', IUserManager::class], + ['UserSession', Session::class], + ['UserSession', IUserSession::class], + + ['TagMapper', TagMapper::class], + ['TagMapper', Mapper::class], + ['TagManager', TagManager::class], + ['TagManager', ITagManager::class], + ['TempManager', TempManager::class], + ['TempManager', ITempManager::class], + ['TrustedDomainHelper', TrustedDomainHelper::class], + + ['SystemTagManager', ISystemTagManager::class], + ['SystemTagObjectMapper', ISystemTagObjectMapper::class], + + [ShutDownManager::class, IShutdownManager::class] ]; } @@ -171,35 +267,49 @@ public function dataTestQuery() { * * @param string $serviceName * @param string $instanceOf + * @throws \OCP\AppFramework\QueryException */ public function testQuery($serviceName, $instanceOf) { - $this->assertInstanceOf($instanceOf, $this->server->query($serviceName), 'Service "' . $serviceName . '"" did not return the right class'); + $this->assertInstanceOf($instanceOf, + $this->server->query($serviceName), + 'Service "' . $serviceName . '"" did not return the right class'); } public function testGetCertificateManager() { - $this->assertInstanceOf('\OC\Security\CertificateManager', $this->server->getCertificateManager('test'), 'service returned by "getCertificateManager" did not return the right class'); - $this->assertInstanceOf('\OCP\ICertificateManager', $this->server->getCertificateManager('test'), 'service returned by "getCertificateManager" did not return the right class'); + $this->assertInstanceOf(CertificateManager::class, + $this->server->getCertificateManager('test'), + 'service returned by "getCertificateManager" did not return the right class'); + $this->assertInstanceOf(ICertificateManager::class, + $this->server->getCertificateManager('test'), + 'service returned by "getCertificateManager" did not return the right class'); } public function testCreateEventSource() { - $this->assertInstanceOf('\OC_EventSource', $this->server->createEventSource(), 'service returned by "createEventSource" did not return the right class'); - $this->assertInstanceOf('\OCP\IEventSource', $this->server->createEventSource(), 'service returned by "createEventSource" did not return the right class'); + $this->assertInstanceOf(\OC_EventSource::class, + $this->server->createEventSource(), + 'service returned by "createEventSource" did not return the right class'); + $this->assertInstanceOf(IEventSource::class, + $this->server->createEventSource(), + 'service returned by "createEventSource" did not return the right class'); } public function testOverwriteDefaultCommentsManager() { $config = $this->server->getConfig(); - $defaultManagerFactory = $config->getSystemValue('comments.managerFactory', '\OC\Comments\ManagerFactory'); + $defaultManagerFactory = $config->getSystemValue('comments.managerFactory', ManagerFactory::class); - $config->setSystemValue('comments.managerFactory', '\Test\Comments\FakeFactory'); + $config->setSystemValue('comments.managerFactory', FakeFactory::class); $manager = $this->server->getCommentsManager(); - $this->assertInstanceOf('\OCP\Comments\ICommentsManager', $manager); + $this->assertInstanceOf(ICommentsManager::class, $manager); $config->setSystemValue('comments.managerFactory', $defaultManagerFactory); } /** * @dataProvider providesServiceLoader + * @param $xmlPath + * @param $expects + * @throws \Exception */ public function testServiceLoader($xmlPath, $expects) { if ($expects === false) { diff --git a/tests/lib/Shutdown/ShutdownManagerTest.php b/tests/lib/Shutdown/ShutdownManagerTest.php new file mode 100644 index 000000000000..2e0f4db555b7 --- /dev/null +++ b/tests/lib/Shutdown/ShutdownManagerTest.php @@ -0,0 +1,48 @@ + + * + * @copyright Copyright (c) 2018, ownCloud GmbH + * @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 + * + */ + +namespace Tests\Shutdown; + +use OC\Shutdown\ShutDownManager; +use Test\TestCase; + +class ShutdownManagerTest extends TestCase { + public function testOrderedRun() { + /** @var ShutDownManager $manager */ + $manager = $this->getMockBuilder(ShutDownManager::class) + ->disableOriginalConstructor() + ->setMethods(['__construct']) + ->getMock(); + $callOrder = ''; + $manager->register(function () use (&$callOrder) { + $callOrder .= 'b'; + }, 20); + $manager->register(function () use (&$callOrder) { + $callOrder .= 'c'; + }, 30); + $manager->register(function () use (&$callOrder) { + $callOrder .= 'a'; + }, 10); + + $manager->run(); + $this->assertEquals('abc', $callOrder); + } +} From 1b9f93407fb0571ebf8fafaa0477dde1ab5da644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Thu, 23 Aug 2018 12:19:55 +0200 Subject: [PATCH 3/6] Fix dav app version in AppsListTest --- tests/Core/Command/Apps/AppsListTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Core/Command/Apps/AppsListTest.php b/tests/Core/Command/Apps/AppsListTest.php index cd830277f3fa..445c9b94dd46 100644 --- a/tests/Core/Command/Apps/AppsListTest.php +++ b/tests/Core/Command/Apps/AppsListTest.php @@ -58,9 +58,9 @@ public function testCommandInput($input, $expectedOutput) { public function providesAppIds() { return [ [[], '- files: 1.5.1'], - [['--shipped' => 'true'], '- dav: 0.3.2'], + [['--shipped' => 'true'], '- dav: 0.3.3'], [['--shipped' => 'false'], '- testing:'], - [['search-pattern' => 'dav'], '- dav: 0.3.2'] + [['search-pattern' => 'dav'], '- dav: 0.3.3'] ]; } } From 3029b9ef8d37b62b8bac68bcde182245fb96e4da Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Tue, 28 Aug 2018 10:12:38 +0200 Subject: [PATCH 4/6] dav.enable.async defaults to false --- apps/dav/lib/Capabilities.php | 2 +- apps/dav/lib/Server.php | 2 +- config/config.sample.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/dav/lib/Capabilities.php b/apps/dav/lib/Capabilities.php index f28e454c9f16..71a63c525230 100644 --- a/apps/dav/lib/Capabilities.php +++ b/apps/dav/lib/Capabilities.php @@ -48,7 +48,7 @@ public function getCapabilities() { ] ]; - if ($this->config->getSystemValue('dav.enable.async', true)) { + if ($this->config->getSystemValue('dav.enable.async', false)) { $cap['async'] = '1.0'; } diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php index a3f2bbee5b08..5067ca89d573 100644 --- a/apps/dav/lib/Server.php +++ b/apps/dav/lib/Server.php @@ -89,7 +89,7 @@ public function __construct(IRequest $request, $baseUri) { $this->server = new \OCA\DAV\Connector\Sabre\Server($tree); $config = \OC::$server->getConfig(); - if ($config->getSystemValue('dav.enable.async', true)) { + if ($config->getSystemValue('dav.enable.async', false)) { $this->server->addPlugin(new LazyOpsPlugin( \OC::$server->getUserSession(), \OC::$server->getURLGenerator(), diff --git a/config/config.sample.php b/config/config.sample.php index becacf9cadb1..2d8757f86adb 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -1464,5 +1464,5 @@ /** * Async dav extensions can be enabled or disabled. */ -'dav.enable.async' => true, +'dav.enable.async' => false, ); From cf327dd5fa2deed0947b3398b380497372801f23 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Tue, 28 Aug 2018 10:13:01 +0200 Subject: [PATCH 5/6] Fix PHPDoc @since versions, increase dav minor version --- apps/dav/appinfo/info.xml | 2 +- apps/files/js/file-upload.js | 8 +++++--- lib/private/Server.php | 2 +- lib/private/Shutdown/ShutDownManager.php | 2 +- lib/public/IServerContainer.php | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml index 378a4d7537f1..23f3ba59eecf 100644 --- a/apps/dav/appinfo/info.xml +++ b/apps/dav/appinfo/info.xml @@ -5,7 +5,7 @@ ownCloud WebDAV endpoint AGPL owncloud.org - 0.3.3 + 0.4.0 true diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js index 22ff12dd42ec..2cdaf15ebb4b 100644 --- a/apps/files/js/file-upload.js +++ b/apps/files/js/file-upload.js @@ -318,8 +318,10 @@ OC.FileUpload.prototype = { true, headers ).then(function (status, response) { + // a 202 response means the server is performing the final MOVE in an async manner, + // so we need to poll its status if (status === 202) { - function poll() { + var poll = function() { $.ajax(response.xhr.getResponseHeader('oc-jobstatus-location')).then(function(data) { var obj = JSON.parse(data); if (obj.status === 'finished') { @@ -333,8 +335,8 @@ OC.FileUpload.prototype = { // call it again after some short delay setTimeout(poll, 1000); } - }) - } + }); + }; // start the polling poll(); diff --git a/lib/private/Server.php b/lib/private/Server.php index f884dade798b..087972d9f3c4 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -1666,7 +1666,7 @@ public function load(array $xmlPath, IUser $user = null) { /** * @return IShutdownManager * @throws QueryException - * @since 11.0.0 + * @since 10.0.10 */ public function getShutdownHandler() { return $this->query(ShutDownManager::class); diff --git a/lib/private/Shutdown/ShutDownManager.php b/lib/private/Shutdown/ShutDownManager.php index 7286e178ddbb..c658da9d2966 100644 --- a/lib/private/Shutdown/ShutDownManager.php +++ b/lib/private/Shutdown/ShutDownManager.php @@ -45,7 +45,7 @@ public function __construct() { * @param \Closure $callback * @param int $priority - the lower the number the higher is the priority * @return void - * @since 11.0.0 + * @since 10.0.10 */ public function register(\Closure $callback, $priority = self::LOW) { if (!isset($this->callbacks[$priority])) { diff --git a/lib/public/IServerContainer.php b/lib/public/IServerContainer.php index e54667a6eb14..2c461fd5d8af 100644 --- a/lib/public/IServerContainer.php +++ b/lib/public/IServerContainer.php @@ -540,7 +540,7 @@ public function getThemeService(); /** * @return IShutdownManager - * @since 11.0.0 + * @since 10.0.10 */ public function getShutdownHandler(); } From f25fcbffb8a9ce3cc67ac6d61784a2ac7357a526 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Wed, 29 Aug 2018 11:39:31 +0200 Subject: [PATCH 6/6] Fix dav app version in unit tests --- tests/Core/Command/Apps/AppsListTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Core/Command/Apps/AppsListTest.php b/tests/Core/Command/Apps/AppsListTest.php index 445c9b94dd46..78f46ac26cfc 100644 --- a/tests/Core/Command/Apps/AppsListTest.php +++ b/tests/Core/Command/Apps/AppsListTest.php @@ -58,9 +58,9 @@ public function testCommandInput($input, $expectedOutput) { public function providesAppIds() { return [ [[], '- files: 1.5.1'], - [['--shipped' => 'true'], '- dav: 0.3.3'], + [['--shipped' => 'true'], '- dav: 0.4.0'], [['--shipped' => 'false'], '- testing:'], - [['search-pattern' => 'dav'], '- dav: 0.3.3'] + [['search-pattern' => 'dav'], '- dav: 0.4.0'] ]; } }