From 72f5c539e887d2671641222d33848e28a936d771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Wed, 28 Oct 2015 14:52:45 +0100 Subject: [PATCH 1/4] Initial implementation of the new chunked upload - as specified in https://dragotin.wordpress.com/2015/06/22/owncloud-chunking-ng/ --- apps/dav/bin/chunkperf.php | 76 ++++++ apps/dav/lib/connector/sabre/file.php | 2 +- apps/dav/lib/rootcollection.php | 4 + apps/dav/lib/upload/assemblystream.php | 234 ++++++++++++++++++ apps/dav/lib/upload/futurefile.php | 103 ++++++++ apps/dav/lib/upload/rootcollection.php | 23 ++ apps/dav/lib/upload/uploadfolder.php | 61 +++++ apps/dav/lib/upload/uploadhome.php | 74 ++++++ .../tests/unit/upload/assemblystreamtest.php | 47 ++++ apps/dav/tests/unit/upload/futurefiletest.php | 89 +++++++ 10 files changed, 712 insertions(+), 1 deletion(-) create mode 100644 apps/dav/bin/chunkperf.php create mode 100644 apps/dav/lib/upload/assemblystream.php create mode 100644 apps/dav/lib/upload/futurefile.php create mode 100644 apps/dav/lib/upload/rootcollection.php create mode 100644 apps/dav/lib/upload/uploadfolder.php create mode 100644 apps/dav/lib/upload/uploadhome.php create mode 100644 apps/dav/tests/unit/upload/assemblystreamtest.php create mode 100644 apps/dav/tests/unit/upload/futurefiletest.php diff --git a/apps/dav/bin/chunkperf.php b/apps/dav/bin/chunkperf.php new file mode 100644 index 0000000000..a193f001a4 --- /dev/null +++ b/apps/dav/bin/chunkperf.php @@ -0,0 +1,76 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @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 + * + */ + +require '../../../../3rdparty/autoload.php'; + +if ($argc !== 6) { + echo "Invalid number of arguments" . PHP_EOL; + exit; +} + +/** + * @param \Sabre\DAV\Client $client + * @param $uploadUrl + * @return mixed + */ +function request($client, $method, $uploadUrl, $data = null, $headers = []) { + echo "$method $uploadUrl ... "; + $t0 = microtime(true); + $result = $client->request($method, $uploadUrl, $data, $headers); + $t1 = microtime(true); + echo $result['statusCode'] . " - " . ($t1 - $t0) . ' seconds' . PHP_EOL; + if (!in_array($result['statusCode'], [200, 201])) { + echo $result['body'] . PHP_EOL; + } + return $result; +} + +$baseUri = $argv[1]; +$userName = $argv[2]; +$password = $argv[3]; +$file = $argv[4]; +$chunkSize = $argv[5] * 1024 * 1024; + +$client = new \Sabre\DAV\Client([ + 'baseUri' => $baseUri, + 'userName' => $userName, + 'password' => $password +]); + +$transfer = uniqid('transfer', true); +$uploadUrl = "$baseUri/uploads/$userName/$transfer"; + +request($client, 'MKCOL', $uploadUrl); + +$size = filesize($file); +$stream = fopen($file, 'r'); + +$index = 0; +while(!feof($stream)) { + request($client, 'PUT', "$uploadUrl/$index", fread($stream, $chunkSize)); + $index++; +} + +$destination = pathinfo($file, PATHINFO_BASENAME); +//echo "Moving $uploadUrl/.file to it's final destination $baseUri/files/$userName/$destination" . PHP_EOL; +request($client, 'MOVE', "$uploadUrl/.file", null, [ + 'Destination' => "$baseUri/files/$userName/$destination" +]); diff --git a/apps/dav/lib/connector/sabre/file.php b/apps/dav/lib/connector/sabre/file.php index 6b698c6e5a..943e9150e7 100644 --- a/apps/dav/lib/connector/sabre/file.php +++ b/apps/dav/lib/connector/sabre/file.php @@ -143,7 +143,7 @@ class File extends Node implements IFile { // if content length is sent by client: // double check if the file was fully received // compare expected and actual size - if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['REQUEST_METHOD'] !== 'LOCK') { + if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['REQUEST_METHOD'] === 'PUT') { $expected = $_SERVER['CONTENT_LENGTH']; if ($count != $expected) { throw new BadRequest('expected filesize ' . $expected . ' got ' . $count); diff --git a/apps/dav/lib/rootcollection.php b/apps/dav/lib/rootcollection.php index ea796c0917..b6e1747e99 100644 --- a/apps/dav/lib/rootcollection.php +++ b/apps/dav/lib/rootcollection.php @@ -89,6 +89,9 @@ class RootCollection extends SimpleCollection { $systemAddressBookRoot = new AddressBookRoot(new SystemPrincipalBackend(), $systemCardDavBackend, 'principals/system'); $systemAddressBookRoot->disableListing = $disableListing; + $uploadCollection = new Upload\RootCollection($userPrincipalBackend, 'principals/users'); + $uploadCollection->disableListing = $disableListing; + $children = [ new SimpleCollection('principals', [ $userPrincipals, @@ -102,6 +105,7 @@ class RootCollection extends SimpleCollection { $systemTagCollection, $systemTagRelationsCollection, $commentsCollection, + $uploadCollection, ]; parent::__construct('root', $children); diff --git a/apps/dav/lib/upload/assemblystream.php b/apps/dav/lib/upload/assemblystream.php new file mode 100644 index 0000000000..4b80a591ce --- /dev/null +++ b/apps/dav/lib/upload/assemblystream.php @@ -0,0 +1,234 @@ +loadContext('assembly'); + + // sort the nodes + $nodes = $this->nodes; + // http://stackoverflow.com/a/10985500 + @usort($nodes, function(IFile $a, IFile $b) { + return strcmp($a->getName(), $b->getName()); + }); + $this->nodes = $nodes; + + // build additional information + $this->sortedNodes = []; + $start = 0; + foreach($this->nodes as $node) { + $size = $node->getSize(); + $name = $node->getName(); + $this->sortedNodes[$name] = ['node' => $node, 'start' => $start, 'end' => $start + $size]; + $start += $size; + $this->size = $start; + } + return true; + } + + /** + * @param string $offset + * @param int $whence + * @return bool + */ + public function stream_seek($offset, $whence = SEEK_SET) { + return false; + } + + /** + * @return int + */ + public function stream_tell() { + return $this->pos; + } + + /** + * @param int $count + * @return string + */ + public function stream_read($count) { + + list($node, $posInNode) = $this->getNodeForPosition($this->pos); + if (is_null($node)) { + return null; + } + $stream = $this->getStream($node); + + fseek($stream, $posInNode); + $data = fread($stream, $count); + $read = strlen($data); + + // update position + $this->pos += $read; + return $data; + } + + /** + * @param string $data + * @return int + */ + public function stream_write($data) { + return false; + } + + /** + * @param int $option + * @param int $arg1 + * @param int $arg2 + * @return bool + */ + public function stream_set_option($option, $arg1, $arg2) { + return false; + } + + /** + * @param int $size + * @return bool + */ + public function stream_truncate($size) { + return false; + } + + /** + * @return array + */ + public function stream_stat() { + return []; + } + + /** + * @param int $operation + * @return bool + */ + public function stream_lock($operation) { + return false; + } + + /** + * @return bool + */ + public function stream_flush() { + return false; + } + + /** + * @return bool + */ + public function stream_eof() { + return $this->pos >= $this->size; + } + + /** + * @return bool + */ + public function stream_close() { + return true; + } + + + /** + * Load the source from the stream context and return the context options + * + * @param string $name + * @return array + * @throws \Exception + */ + protected function loadContext($name) { + $context = stream_context_get_options($this->context); + if (isset($context[$name])) { + $context = $context[$name]; + } else { + throw new \BadMethodCallException('Invalid context, "' . $name . '" options not set'); + } + if (isset($context['nodes']) and is_array($context['nodes'])) { + $this->nodes = $context['nodes']; + } else { + throw new \BadMethodCallException('Invalid context, nodes not set'); + } + return $context; + } + + /** + * @param IFile[] $nodes + * @return resource + * + * @throws \BadMethodCallException + */ + public static function wrap(array $nodes) { + $context = stream_context_create([ + 'assembly' => [ + 'nodes' => $nodes] + ]); + stream_wrapper_register('assembly', '\OCA\DAV\Upload\AssemblyStream'); + try { + $wrapped = fopen('assembly://', 'r', null, $context); + } catch (\BadMethodCallException $e) { + stream_wrapper_unregister('assembly'); + throw $e; + } + stream_wrapper_unregister('assembly'); + return $wrapped; + } + + /** + * @param $pos + * @return IFile | null + */ + private function getNodeForPosition($pos) { + foreach($this->sortedNodes as $node) { + if ($pos >= $node['start'] && $pos < $node['end']) { + return [$node['node'], $pos - $node['start']]; + } + } + return null; + } + + /** + * @param IFile $node + * @return resource + */ + private function getStream(IFile $node) { + $data = $node->get(); + if (is_resource($data)) { + return $data; + } + + return fopen('data://text/plain,' . $data,'r'); + } + +} diff --git a/apps/dav/lib/upload/futurefile.php b/apps/dav/lib/upload/futurefile.php new file mode 100644 index 0000000000..aca81afc05 --- /dev/null +++ b/apps/dav/lib/upload/futurefile.php @@ -0,0 +1,103 @@ +root = $root; + $this->name = $name; + } + + /** + * @inheritdoc + */ + function put($data) { + throw new Forbidden('Permission denied to put into this file'); + } + + /** + * @inheritdoc + */ + function get() { + $nodes = $this->root->getChildren(); + return AssemblyStream::wrap($nodes); + } + + /** + * @inheritdoc + */ + function getContentType() { + return 'application/octet-stream'; + } + + /** + * @inheritdoc + */ + function getETag() { + return $this->root->getETag(); + } + + /** + * @inheritdoc + */ + function getSize() { + $children = $this->root->getChildren(); + $sizes = array_map(function($node) { + /** @var IFile $node */ + return $node->getSize(); + }, $children); + + return array_sum($sizes); + } + + /** + * @inheritdoc + */ + function delete() { + $this->root->delete(); + } + + /** + * @inheritdoc + */ + function getName() { + return $this->name; + } + + /** + * @inheritdoc + */ + function setName($name) { + throw new Forbidden('Permission denied to rename this file'); + } + + /** + * @inheritdoc + */ + function getLastModified() { + return $this->root->getLastModified(); + } +} diff --git a/apps/dav/lib/upload/rootcollection.php b/apps/dav/lib/upload/rootcollection.php new file mode 100644 index 0000000000..673a373431 --- /dev/null +++ b/apps/dav/lib/upload/rootcollection.php @@ -0,0 +1,23 @@ +node = $node; + } + + function createFile($name, $data = null) { + // TODO: verify name - should be a simple number + $this->node->createFile($name, $data); + } + + function createDirectory($name) { + throw new Forbidden('Permission denied to create file (filename ' . $name . ')'); + } + + function getChild($name) { + if ($name === '.file') { + return new FutureFile($this->node, '.file'); + } + return $this->node->getChild($name); + } + + function getChildren() { + $children = $this->node->getChildren(); + $children[] = new FutureFile($this->node, '.file'); + return $children; + } + + function childExists($name) { + if ($name === '.file') { + return true; + } + return $this->node->childExists($name); + } + + function delete() { + $this->node->delete(); + } + + function getName() { + return $this->node->getName(); + } + + function setName($name) { + throw new Forbidden('Permission denied to rename this folder'); + } + + function getLastModified() { + return $this->node->getLastModified(); + } +} diff --git a/apps/dav/lib/upload/uploadhome.php b/apps/dav/lib/upload/uploadhome.php new file mode 100644 index 0000000000..ae4dcfa493 --- /dev/null +++ b/apps/dav/lib/upload/uploadhome.php @@ -0,0 +1,74 @@ +principalInfo = $principalInfo; + } + + function createFile($name, $data = null) { + throw new Forbidden('Permission denied to create file (filename ' . $name . ')'); + } + + function createDirectory($name) { + $this->impl()->createDirectory($name); + } + + function getChild($name) { + return new UploadFolder($this->impl()->getChild($name)); + } + + function getChildren() { + return array_map(function($node) { + return new UploadFolder($node); + }, $this->impl()->getChildren()); + } + + function childExists($name) { + return !is_null($this->getChild($name)); + } + + function delete() { + $this->impl()->delete(); + } + + function getName() { + return 'uploads'; + } + + function setName($name) { + throw new Forbidden('Permission denied to rename this folder'); + } + + function getLastModified() { + return $this->impl()->getLastModified(); + } + + /** + * @return Directory + */ + private function impl() { + $rootView = new View(); + $user = \OC::$server->getUserSession()->getUser(); + Filesystem::initMountPoints($user->getUID()); + if (!$rootView->file_exists('/' . $user->getUID() . '/uploads')) { + $rootView->mkdir('/' . $user->getUID() . '/uploads'); + } + $view = new View('/' . $user->getUID() . '/uploads'); + $rootInfo = $view->getFileInfo(''); + $impl = new Directory($view, $rootInfo); + return $impl; + } +} diff --git a/apps/dav/tests/unit/upload/assemblystreamtest.php b/apps/dav/tests/unit/upload/assemblystreamtest.php new file mode 100644 index 0000000000..373d525a9d --- /dev/null +++ b/apps/dav/tests/unit/upload/assemblystreamtest.php @@ -0,0 +1,47 @@ +assertEquals($expected, $content); + } + + function providesNodes() { + return[ + 'one node only' => ['1234567890', [ + $this->buildNode('0', '1234567890') + ]], + 'two nodes' => ['1234567890', [ + $this->buildNode('1', '67890'), + $this->buildNode('0', '12345') + ]] + ]; + } + + private function buildNode($name, $data) { + $node = $this->getMockBuilder('\Sabre\DAV\File') + ->setMethods(['getName', 'get', 'getSize']) + ->getMockForAbstractClass(); + + $node->expects($this->any()) + ->method('getName') + ->willReturn($name); + + $node->expects($this->any()) + ->method('get') + ->willReturn($data); + + $node->expects($this->any()) + ->method('getSize') + ->willReturn(strlen($data)); + + return $node; + } +} + diff --git a/apps/dav/tests/unit/upload/futurefiletest.php b/apps/dav/tests/unit/upload/futurefiletest.php new file mode 100644 index 0000000000..c0c14bf04d --- /dev/null +++ b/apps/dav/tests/unit/upload/futurefiletest.php @@ -0,0 +1,89 @@ +mockFutureFile(); + $this->assertEquals('application/octet-stream', $f->getContentType()); + } + + public function testGetETag() { + $f = $this->mockFutureFile(); + $this->assertEquals('1234567890', $f->getETag()); + } + + public function testGetName() { + $f = $this->mockFutureFile(); + $this->assertEquals('foo.txt', $f->getName()); + } + + public function testGetLastModified() { + $f = $this->mockFutureFile(); + $this->assertEquals(12121212, $f->getLastModified()); + } + + public function testGetSize() { + $f = $this->mockFutureFile(); + $this->assertEquals(0, $f->getSize()); + } + + public function testGet() { + $f = $this->mockFutureFile(); + $stream = $f->get(); + $this->assertTrue(is_resource($stream)); + } + + public function testDelete() { + $d = $this->getMockBuilder('OCA\DAV\Connector\Sabre\Directory') + ->disableOriginalConstructor() + ->setMethods(['delete']) + ->getMock(); + + $d->expects($this->once()) + ->method('delete'); + + $f = new \OCA\DAV\Upload\FutureFile($d, 'foo.txt'); + $f->delete(); + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + public function testPut() { + $f = $this->mockFutureFile(); + $f->put(''); + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + public function testSetName() { + $f = $this->mockFutureFile(); + $f->setName(''); + } + + /** + * @return \OCA\DAV\Upload\FutureFile + */ + private function mockFutureFile() { + $d = $this->getMockBuilder('OCA\DAV\Connector\Sabre\Directory') + ->disableOriginalConstructor() + ->setMethods(['getETag', 'getLastModified', 'getChildren']) + ->getMock(); + + $d->expects($this->any()) + ->method('getETag') + ->willReturn('1234567890'); + + $d->expects($this->any()) + ->method('getLastModified') + ->willReturn(12121212); + + $d->expects($this->any()) + ->method('getChildren') + ->willReturn([]); + + return new \OCA\DAV\Upload\FutureFile($d, 'foo.txt'); + } +} + From 629bc1812ee9def6ed78418d3d203a827e95f1d2 Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Fri, 11 Mar 2016 16:18:02 +0100 Subject: [PATCH 2/4] Add intergration tests for new chunking The same intergration tests as for the old endpoint. But now using the new chunking. We upload 3 chunks in different order and the result should be the same in all three. --- .../integration/features/bootstrap/WebDav.php | 32 +++++++++++++++++ .../features/webdav-related.feature | 36 +++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/build/integration/features/bootstrap/WebDav.php b/build/integration/features/bootstrap/WebDav.php index 2ef5f252f1..0ca2a411c7 100644 --- a/build/integration/features/bootstrap/WebDav.php +++ b/build/integration/features/bootstrap/WebDav.php @@ -373,5 +373,37 @@ trait WebDav { $this->makeDavRequest($user, 'PUT', $file, ['OC-Chunked' => '1'], $data); } + /** + * @Given user :user creates a new chunking upload with id :id + */ + public function userCreatesANewChunkingUploadWithId($user, $id) + { + $destination = '/uploads/'.$user.'/'.$id; + $this->makeDavRequest($user, 'MKCOL', $destination, []); + } + + /** + * @Given user :user uploads new chunk file :num with :data to id :id + */ + public function userUploadsNewChunkFileOfWithToId($user, $num, $data, $id) + { + $data = \GuzzleHttp\Stream\Stream::factory($data); + $destination = '/uploads/'.$user.'/'.$id.'/'.$num; + $this->makeDavRequest($user, 'PUT', $destination, [], $data); + } + + /** + * @Given user :user moves new chunk file with id :id to :dest + */ + public function userMovesNewChunkFileWithIdToMychunkedfile($user, $id, $dest) + { + $source = '/uploads/'.$user.'/'.$id.'/.file'; + $destination = substr($this->baseUrl, 0, -4) . $this->davPath . '/files/'.$user.$dest; + $this->makeDavRequest($user, 'MOVE', $source, [ + 'Destination' => $destination + ]); + } + + } diff --git a/build/integration/features/webdav-related.feature b/build/integration/features/webdav-related.feature index ee841f9eb5..6fc437773c 100644 --- a/build/integration/features/webdav-related.feature +++ b/build/integration/features/webdav-related.feature @@ -241,3 +241,39 @@ Feature: webdav-related | 0 | | 1 | | 3 | + + Scenario: Upload chunked file asc with new chunking + Given using dav path "remote.php/dav" + And user "user0" exists + And user "user0" creates a new chunking upload with id "chunking-42" + And user "user0" uploads new chunk file "1" with "AAAAA" to id "chunking-42" + And user "user0" uploads new chunk file "2" with "BBBBB" to id "chunking-42" + And user "user0" uploads new chunk file "3" with "CCCCC" to id "chunking-42" + And user "user0" moves new chunk file with id "chunking-42" to "/myChunkedFile.txt" + When As an "user0" + And Downloading file "/files/user0/myChunkedFile.txt" + Then Downloaded content should be "AAAAABBBBBCCCCC" + + Scenario: Upload chunked file desc with new chunking + Given using dav path "remote.php/dav" + And user "user0" exists + And user "user0" creates a new chunking upload with id "chunking-42" + And user "user0" uploads new chunk file "3" with "CCCCC" to id "chunking-42" + And user "user0" uploads new chunk file "2" with "BBBBB" to id "chunking-42" + And user "user0" uploads new chunk file "1" with "AAAAA" to id "chunking-42" + And user "user0" moves new chunk file with id "chunking-42" to "/myChunkedFile.txt" + When As an "user0" + And Downloading file "/files/user0/myChunkedFile.txt" + Then Downloaded content should be "AAAAABBBBBCCCCC" + + Scenario: Upload chunked file random with new chunking + Given using dav path "remote.php/dav" + And user "user0" exists + And user "user0" creates a new chunking upload with id "chunking-42" + And user "user0" uploads new chunk file "2" with "BBBBB" to id "chunking-42" + And user "user0" uploads new chunk file "3" with "CCCCC" to id "chunking-42" + And user "user0" uploads new chunk file "1" with "AAAAA" to id "chunking-42" + And user "user0" moves new chunk file with id "chunking-42" to "/myChunkedFile.txt" + When As an "user0" + And Downloading file "/files/user0/myChunkedFile.txt" + Then Downloaded content should be "AAAAABBBBBCCCCC" From bb2e68f72b439df797faabfe422339b9cc6f3cf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Tue, 12 Apr 2016 14:26:42 +0200 Subject: [PATCH 3/4] Fix chunk file move --- apps/dav/lib/connector/sabre/filesplugin.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/dav/lib/connector/sabre/filesplugin.php b/apps/dav/lib/connector/sabre/filesplugin.php index 8b54291793..b74fa7063b 100644 --- a/apps/dav/lib/connector/sabre/filesplugin.php +++ b/apps/dav/lib/connector/sabre/filesplugin.php @@ -28,17 +28,19 @@ namespace OCA\DAV\Connector\Sabre; use OC\Files\View; +use OCA\DAV\Upload\FutureFile; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\IFile; use \Sabre\DAV\PropFind; use \Sabre\DAV\PropPatch; +use Sabre\DAV\ServerPlugin; use Sabre\DAV\Tree; use \Sabre\HTTP\RequestInterface; use \Sabre\HTTP\ResponseInterface; use OCP\Files\StorageNotAvailableException; -class FilesPlugin extends \Sabre\DAV\ServerPlugin { +class FilesPlugin extends ServerPlugin { // namespace const NS_OWNCLOUD = 'http://owncloud.org/ns'; @@ -146,11 +148,17 @@ class FilesPlugin extends \Sabre\DAV\ServerPlugin { /** * Plugin that checks if a move can actually be performed. + * * @param string $source source path * @param string $destination destination path * @throws Forbidden + * @throws NotFound */ function checkMove($source, $destination) { + $sourceNode = $this->server->tree->getNodeForPath($source); + if ($sourceNode instanceof FutureFile) { + return; + } list($sourceDir,) = \Sabre\HTTP\URLUtil::splitPath($source); list($destinationDir,) = \Sabre\HTTP\URLUtil::splitPath($destination); From e21642ca31d69f2f9c5328c7eb804fb45a8c4c23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Tue, 12 Apr 2016 15:51:09 +0200 Subject: [PATCH 4/4] Fix unit test of file plugin --- apps/dav/lib/connector/sabre/filesplugin.php | 2 +- .../unit/connector/sabre/filesplugin.php | 33 +++++++++++-------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/apps/dav/lib/connector/sabre/filesplugin.php b/apps/dav/lib/connector/sabre/filesplugin.php index b74fa7063b..444e80d4ae 100644 --- a/apps/dav/lib/connector/sabre/filesplugin.php +++ b/apps/dav/lib/connector/sabre/filesplugin.php @@ -155,7 +155,7 @@ class FilesPlugin extends ServerPlugin { * @throws NotFound */ function checkMove($source, $destination) { - $sourceNode = $this->server->tree->getNodeForPath($source); + $sourceNode = $this->tree->getNodeForPath($source); if ($sourceNode instanceof FutureFile) { return; } diff --git a/apps/dav/tests/unit/connector/sabre/filesplugin.php b/apps/dav/tests/unit/connector/sabre/filesplugin.php index fb08ee170c..63ee5a53c1 100644 --- a/apps/dav/tests/unit/connector/sabre/filesplugin.php +++ b/apps/dav/tests/unit/connector/sabre/filesplugin.php @@ -23,6 +23,9 @@ namespace OCA\DAV\Tests\Unit\Connector\Sabre; use OCP\Files\StorageNotAvailableException; +use Sabre\DAV\PropFind; +use Sabre\DAV\PropPatch; +use Test\TestCase; /** * Copyright (c) 2015 Vincent Petry @@ -30,7 +33,7 @@ use OCP\Files\StorageNotAvailableException; * later. * See the COPYING-README file. */ -class FilesPlugin extends \Test\TestCase { +class FilesPlugin extends TestCase { const GETETAG_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::GETETAG_PROPERTYNAME; const FILEID_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::FILEID_PROPERTYNAME; const INTERNAL_FILEID_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::INTERNAL_FILEID_PROPERTYNAME; @@ -42,12 +45,12 @@ class FilesPlugin extends \Test\TestCase { const OWNER_DISPLAY_NAME_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME; /** - * @var \Sabre\DAV\Server + * @var \Sabre\DAV\Server | \PHPUnit_Framework_MockObject_MockObject */ private $server; /** - * @var \Sabre\DAV\Tree + * @var \Sabre\DAV\Tree | \PHPUnit_Framework_MockObject_MockObject */ private $tree; @@ -57,7 +60,7 @@ class FilesPlugin extends \Test\TestCase { private $plugin; /** - * @var \OC\Files\View + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject */ private $view; @@ -79,6 +82,7 @@ class FilesPlugin extends \Test\TestCase { /** * @param string $class + * @return \PHPUnit_Framework_MockObject_MockObject */ private function createTestNode($class) { $node = $this->getMockBuilder($class) @@ -111,9 +115,10 @@ class FilesPlugin extends \Test\TestCase { } public function testGetPropertiesForFile() { + /** @var \OCA\DAV\Connector\Sabre\File | \PHPUnit_Framework_MockObject_MockObject $node */ $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File'); - $propFind = new \Sabre\DAV\PropFind( + $propFind = new PropFind( '/dummyPath', array( self::GETETAG_PROPERTYNAME, @@ -165,11 +170,12 @@ class FilesPlugin extends \Test\TestCase { } public function testGetPropertiesForFileHome() { + /** @var \OCA\DAV\Files\FilesHome | \PHPUnit_Framework_MockObject_MockObject $node */ $node = $this->getMockBuilder('\OCA\DAV\Files\FilesHome') ->disableOriginalConstructor() ->getMock(); - $propFind = new \Sabre\DAV\PropFind( + $propFind = new PropFind( '/dummyPath', array( self::GETETAG_PROPERTYNAME, @@ -214,9 +220,10 @@ class FilesPlugin extends \Test\TestCase { } public function testGetPropertiesStorageNotAvailable() { + /** @var \OCA\DAV\Connector\Sabre\File | \PHPUnit_Framework_MockObject_MockObject $node */ $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File'); - $propFind = new \Sabre\DAV\PropFind( + $propFind = new PropFind( '/dummyPath', array( self::DOWNLOADURL_PROPERTYNAME, @@ -240,7 +247,7 @@ class FilesPlugin extends \Test\TestCase { $this->plugin = new \OCA\DAV\Connector\Sabre\FilesPlugin($this->tree, $this->view, true); $this->plugin->initialize($this->server); - $propFind = new \Sabre\DAV\PropFind( + $propFind = new PropFind( '/dummyPath', [ self::PERMISSIONS_PROPERTYNAME, @@ -248,6 +255,7 @@ class FilesPlugin extends \Test\TestCase { 0 ); + /** @var \OCA\DAV\Connector\Sabre\File | \PHPUnit_Framework_MockObject_MockObject $node */ $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File'); $node->expects($this->any()) ->method('getDavPermissions') @@ -262,9 +270,10 @@ class FilesPlugin extends \Test\TestCase { } public function testGetPropertiesForDirectory() { + /** @var \OCA\DAV\Connector\Sabre\Directory | \PHPUnit_Framework_MockObject_MockObject $node */ $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\Directory'); - $propFind = new \Sabre\DAV\PropFind( + $propFind = new PropFind( '/dummyPath', array( self::GETETAG_PROPERTYNAME, @@ -308,7 +317,7 @@ class FilesPlugin extends \Test\TestCase { ->will($this->returnValue(true)); // properties to set - $propPatch = new \Sabre\DAV\PropPatch(array( + $propPatch = new PropPatch(array( self::GETETAG_PROPERTYNAME => 'newetag', self::LASTMODIFIED_PROPERTYNAME => $testDate )); @@ -328,9 +337,7 @@ class FilesPlugin extends \Test\TestCase { } public function testUpdatePropsForbidden() { - $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File'); - - $propPatch = new \Sabre\DAV\PropPatch(array( + $propPatch = new PropPatch(array( self::OWNER_ID_PROPERTYNAME => 'user2', self::OWNER_DISPLAY_NAME_PROPERTYNAME => 'User Two', self::FILEID_PROPERTYNAME => 12345,