Skip to content

Commit

Permalink
COPY does not delete the destination and by that allows to keep versi…
Browse files Browse the repository at this point in the history
…ons - related to #18307 but needs to be addressed for MOVE
  • Loading branch information
DeepDiver1975 committed Nov 15, 2017
1 parent 954ef7f commit 3c87ff4
Show file tree
Hide file tree
Showing 8 changed files with 284 additions and 7 deletions.
3 changes: 3 additions & 0 deletions apps/dav/lib/Connector/Sabre/Node.php
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,9 @@ public function changeLock($type) {
$this->fileView->changeLock($this->path, $type);
}

/**
* @return \OCP\Files\FileInfo
*/
public function getFileInfo() {
return $this->info;
}
Expand Down
3 changes: 3 additions & 0 deletions apps/dav/lib/Connector/Sabre/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@

namespace OCA\DAV\Connector\Sabre;

use OCA\DAV\DAV\CopyPlugin;

/**
* Class \OCA\DAV\Connector\Sabre\Server
*
Expand All @@ -40,5 +42,6 @@ public function __construct($treeOrNode = null) {
parent::__construct($treeOrNode);
self::$exposeVersion = false;
$this->enablePropfindDepthInfinity = true;
$this->addPlugin(new CopyPlugin());
}
}
95 changes: 95 additions & 0 deletions apps/dav/lib/DAV/CopyPlugin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php
/**
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @copyright Copyright (c) 2017, 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 <http://www.gnu.org/licenses/>
*
*/


namespace OCA\DAV\DAV;

use OCA\DAV\Connector\Sabre\File;
use OCA\DAV\Files\ICopySource;
use Sabre\DAV\IFile;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;

/**
* Class CopyPlugin - adds own implementation of the COPY method.
* This is necessary because we don't want the target to be deleted before the move.
*
* Deleting the target will kill the versions which is the wrong behavior.
*
* @package OCA\DAV\DAV
*/
class CopyPlugin extends ServerPlugin {

/** @var Server */
private $server;

/**
* @param Server $server
*/
function initialize(Server $server) {
$this->server = $server;
$server->on('method:COPY', [$this, 'httpCopy'], 90);
}

/**
* WebDAV HTTP COPY method
*
* This method copies one uri to a different uri, and works much like the MOVE request
* A lot of the actual request processing is done in getCopyMoveInfo
*
* @param RequestInterface $request
* @param ResponseInterface $response
* @return bool
*/
function httpCopy(RequestInterface $request, ResponseInterface $response) {

$path = $request->getPath();

$copyInfo = $this->server->getCopyAndMoveInfo($request);
$sourceNode = $this->server->tree->getNodeForPath($path);
$destinationNode = $copyInfo['destinationNode'];
if (!$copyInfo['destinationExists'] || !$destinationNode instanceof File || !$sourceNode instanceof IFile) {
return true;
}

if (!$this->server->emit('beforeBind', [$copyInfo['destination']])) return false;

$copySuccess = false;
if ($sourceNode instanceof ICopySource) {
$copySuccess = $sourceNode->copy($destinationNode->getFileInfo()->getPath());
}
if (!$copySuccess) {
$destinationNode->put($sourceNode->get());
}

$this->server->emit('afterBind', [$copyInfo['destination']]);

$response->setHeader('Content-Length', '0');
$response->setStatus(204);

// Sending back false will interrupt the event chain and tell the server
// we've handled this method.
return false;
}

}
43 changes: 43 additions & 0 deletions apps/dav/lib/Files/ICopySource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php
/**
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @copyright Copyright (c) 2017, 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 <http://www.gnu.org/licenses/>
*
*/


namespace OCA\DAV\Files;


/**
* Interface ICopySource
* This interface allows special handling of copy operations based on the copy source.
* This gives the developer the freedom to implement a more efficient copy operation.
*
* @package OCA\DAV\Files
*/
interface ICopySource {

/**
* Copies the source to the given destination.
* If the operation was not successful false is returned.
*
* @param string $destinationPath
* @return boolean
*/
public function copy($destinationPath);
}
6 changes: 6 additions & 0 deletions apps/dav/lib/Meta/MetaFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@

use Sabre\DAV\File;

/**
* Class MetaFile
* This is a Sabre based implementation of a file living in the /meta resource.
*
* @package OCA\DAV\Meta
*/
class MetaFile extends File {

/** @var \OCP\Files\File */
Expand Down
10 changes: 8 additions & 2 deletions apps/dav/lib/Meta/MetaFolder.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@
use OCP\Files\Node;
use Sabre\DAV\Collection;

/**
* Class MetaFolder
* This is a Sabre based implementation of a folder living in the /meta resource.
*
* @package OCA\DAV\Meta
*/
class MetaFolder extends Collection {

/** @var Folder */
Expand All @@ -48,7 +54,7 @@ public function __construct(Folder $folder) {
function getChildren() {
$nodes = $this->folder->getDirectoryListing();
return array_map(function($node) {
return static::nodeFactory($node);
return $this->nodeFactory($node);
}, $nodes);
}

Expand All @@ -59,7 +65,7 @@ function getName() {
return $this->folder->getName();
}

public static function nodeFactory(Node $node) {
private function nodeFactory(Node $node) {
if ($node instanceof Folder) {
return new MetaFolder($node);
}
Expand Down
11 changes: 6 additions & 5 deletions apps/dav/lib/Meta/RootCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,8 @@
namespace OCA\DAV\Meta;


use OCP\Files\File;
use OCP\Files\Folder;
use OC\Files\Meta\MetaFileIdNode;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
use OCP\Files\NotFoundException;
use Sabre\DAV\Collection;
use Sabre\DAV\Exception\MethodNotAllowed;
Expand All @@ -52,8 +50,11 @@ public function __construct(IRootFolder $rootFolder) {
public function getChild($name) {
try {
$child = $this->rootFolder->get("meta/$name");
return MetaFolder::nodeFactory($child);
} catch (NotFoundException $ex) {
if (!$child instanceof MetaFileIdNode) {
throw new NotFound();
}
return new MetaFolder($child);
} catch (NotFoundException $exception) {
throw new NotFound();
}
}
Expand Down
120 changes: 120 additions & 0 deletions apps/dav/tests/unit/DAV/CopyPluginTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?php
/**
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @copyright Copyright (c) 2017, 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 <http://www.gnu.org/licenses/>
*
*/


namespace OCA\DAV\Tests\unit\DAV;


use OCA\DAV\Connector\Sabre\Directory;
use OCA\DAV\Connector\Sabre\File;
use OCA\DAV\DAV\CopyPlugin;
use Sabre\DAV\ICollection;
use Sabre\DAV\IFile;
use Sabre\DAV\Server;
use Sabre\DAV\Tree;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
use Test\TestCase;

class CopyPluginTest extends TestCase {

/** @var Server | \PHPUnit_Framework_MockObject_MockObject */
private $server;
/** @var CopyPlugin */
private $plugin;
/** @var Tree | \PHPUnit_Framework_MockObject_MockObject */
private $tree;
/** @var RequestInterface | \PHPUnit_Framework_MockObject_MockObject */
private $request;
/** @var ResponseInterface | \PHPUnit_Framework_MockObject_MockObject */
private $response;

public function setUp() {
parent::setUp();
$this->plugin = new CopyPlugin();

$this->server = $this->createMock(Server::class);
$this->tree = $this->createMock(Tree::class);
$this->server->tree = $this->tree;
/** @var RequestInterface | \PHPUnit_Framework_MockObject_MockObject $request */
$this->request = $this->createMock(RequestInterface::class);
/** @var ResponseInterface | \PHPUnit_Framework_MockObject_MockObject $response */
$this->response = $this->createMock( ResponseInterface::class);

$this->plugin->initialize($this->server);
}

/**
* @dataProvider providesSourceAndDestinations
* @param bool $destinationExists
* @param $destinationNode
* @param $sourceNode
*/
public function testCopyPluginReturnTrue($destinationExists, $destinationNode, $sourceNode) {

$this->tree->expects($this->once())->method('getNodeForPath')->willReturn($sourceNode);
$this->server->expects($this->once())->method('getCopyAndMoveInfo')->willReturn([
'destinationExists' => $destinationExists,
'destinationNode' => $destinationNode
]);

$returnValue = $this->plugin->httpCopy($this->request, $this->response);
$this->assertTrue($returnValue);
}

public function providesSourceAndDestinations() {
return [
'destination does not exist' => [false, null, null],
'destination is not a File' => [true, $this->createMock(Directory::class), $this->createMock(IFile::class)],
'source is not a IFile' => [true, $this->createMock(File::class), $this->createMock(ICollection::class)],
];
}

public function testCopyPluginReturnFalse() {

$destinationNode = $this->createMock(File::class);
$sourceNode = $this->createMock(IFile::class);

$this->tree->expects($this->once())->method('getNodeForPath')->willReturn($sourceNode);
$this->server->expects($this->once())->method('getCopyAndMoveInfo')->willReturn([
'destinationExists' => true,
'destinationNode' => $destinationNode,
'destination' => 'destination.txt'
]);

// make sure the plugin properly emits beforeBind and afterBind
$this->server->expects($this->exactly(2))->method('emit')->withConsecutive(
['beforeBind', ['destination.txt']], ['afterBind', ['destination.txt']])->willReturn(true);

// make sure the file content is actually copied over
$sourceNode->expects($this->once())->method('get')->willReturn('123456');
$destinationNode->expects($this->once())->method('put')->with('123456');

// make sure http status and content length are properly set
$this->response->expects($this->once())->method('setHeader')->with('Content-Length', '0');
$this->response->expects($this->once())->method('setStatus')->with(204);

$returnValue = $this->plugin->httpCopy($this->request, $this->response);
$this->assertFalse($returnValue);
}


}

0 comments on commit 3c87ff4

Please sign in to comment.