Merge pull request #9353 from nextcloud/feature/4724/versions_dav

Add versions DAV endpoint
This commit is contained in:
Morris Jobke 2018-05-03 16:52:32 +02:00 committed by GitHub
commit dc5c4b2e1f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 570 additions and 1 deletions

View File

@ -8,7 +8,7 @@
This application automatically maintains older versions of files that are changed. When enabled, a hidden versions folder is provisioned in every users directory and is used to store old file versions. A user can revert to an older version through the web interface at any time, with the replaced file becoming a version. The app automatically manages the versions folder to ensure the user doesnt run out of Quota because of versions.
In addition to the expiry of versions, the versions app makes certain never to use more than 50% of the users currently available free space. If stored versions exceed this limit, the app will delete the oldest versions first until it meets this limit. More information is available in the Versions documentation.
</description>
<version>1.7.0</version>
<version>1.7.1</version>
<licence>agpl</licence>
<author>Frank Karlitschek</author>
<author>Bjoern Schiessle</author>
@ -16,6 +16,7 @@
<default_enable/>
<types>
<filesystem/>
<dav/>
</types>
<documentation>
<user>user-versions</user>
@ -34,4 +35,10 @@
<command>OCA\Files_Versions\Command\CleanUp</command>
<command>OCA\Files_Versions\Command\ExpireVersions</command>
</commands>
<sabre>
<collections>
<collection>OCA\Files_Versions\Sabre\RootCollection</collection>
</collections>
</sabre>
</info>

View File

@ -16,5 +16,11 @@ return array(
'OCA\\Files_Versions\\Events\\CreateVersionEvent' => $baseDir . '/../lib/Events/CreateVersionEvent.php',
'OCA\\Files_Versions\\Expiration' => $baseDir . '/../lib/Expiration.php',
'OCA\\Files_Versions\\Hooks' => $baseDir . '/../lib/Hooks.php',
'OCA\\Files_Versions\\Sabre\\RestoreFolder' => $baseDir . '/../lib/Sabre/RestoreFolder.php',
'OCA\\Files_Versions\\Sabre\\RootCollection' => $baseDir . '/../lib/Sabre/RootCollection.php',
'OCA\\Files_Versions\\Sabre\\VersionCollection' => $baseDir . '/../lib/Sabre/VersionCollection.php',
'OCA\\Files_Versions\\Sabre\\VersionFile' => $baseDir . '/../lib/Sabre/VersionFile.php',
'OCA\\Files_Versions\\Sabre\\VersionHome' => $baseDir . '/../lib/Sabre/VersionHome.php',
'OCA\\Files_Versions\\Sabre\\VersionRoot' => $baseDir . '/../lib/Sabre/VersionRoot.php',
'OCA\\Files_Versions\\Storage' => $baseDir . '/../lib/Storage.php',
);

View File

@ -31,6 +31,12 @@ class ComposerStaticInitFiles_Versions
'OCA\\Files_Versions\\Events\\CreateVersionEvent' => __DIR__ . '/..' . '/../lib/Events/CreateVersionEvent.php',
'OCA\\Files_Versions\\Expiration' => __DIR__ . '/..' . '/../lib/Expiration.php',
'OCA\\Files_Versions\\Hooks' => __DIR__ . '/..' . '/../lib/Hooks.php',
'OCA\\Files_Versions\\Sabre\\RestoreFolder' => __DIR__ . '/..' . '/../lib/Sabre/RestoreFolder.php',
'OCA\\Files_Versions\\Sabre\\RootCollection' => __DIR__ . '/..' . '/../lib/Sabre/RootCollection.php',
'OCA\\Files_Versions\\Sabre\\VersionCollection' => __DIR__ . '/..' . '/../lib/Sabre/VersionCollection.php',
'OCA\\Files_Versions\\Sabre\\VersionFile' => __DIR__ . '/..' . '/../lib/Sabre/VersionFile.php',
'OCA\\Files_Versions\\Sabre\\VersionHome' => __DIR__ . '/..' . '/../lib/Sabre/VersionHome.php',
'OCA\\Files_Versions\\Sabre\\VersionRoot' => __DIR__ . '/..' . '/../lib/Sabre/VersionRoot.php',
'OCA\\Files_Versions\\Storage' => __DIR__ . '/..' . '/../lib/Storage.php',
);

View File

@ -23,6 +23,7 @@
namespace OCA\Files_Versions\AppInfo;
use OCA\DAV\Connector\Sabre\Principal;
use OCP\AppFramework\App;
use OCA\Files_Versions\Expiration;
use OCP\AppFramework\Utility\ITimeFactory;
@ -48,5 +49,17 @@ class Application extends App {
$c->query(ITimeFactory::class)
);
});
/*
* Register $principalBackend for the DAV collection
*/
$container->registerService('principalBackend', function () {
return new Principal(
\OC::$server->getUserManager(),
\OC::$server->getGroupManager(),
\OC::$server->getShareManager(),
\OC::$server->getUserSession()
);
});
}
}

View File

@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Sabre;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\ICollection;
use Sabre\DAV\IMoveTarget;
use Sabre\DAV\INode;
class RestoreFolder implements ICollection, IMoveTarget {
/** @var string */
protected $userId;
public function __construct(string $userId) {
$this->userId = $userId;
}
public function createFile($name, $data = null) {
throw new Forbidden();
}
public function createDirectory($name) {
throw new Forbidden();
}
public function getChild($name) {
return null;
}
public function delete() {
throw new Forbidden();
}
public function getName() {
return 'restore';
}
public function setName($name) {
throw new Forbidden();
}
public function getLastModified(): int {
return 0;
}
public function getChildren(): array {
return [];
}
public function childExists($name): bool {
return false;
}
public function moveInto($targetName, $sourcePath, INode $sourceNode): bool {
if (!($sourceNode instanceof VersionFile)) {
return false;
}
return $sourceNode->rollBack();
}
}

View File

@ -0,0 +1,65 @@
<?php
/**
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Sabre;
use OCP\Files\IRootFolder;
use Sabre\DAV\INode;
use Sabre\DAVACL\AbstractPrincipalCollection;
use Sabre\DAVACL\PrincipalBackend;
class RootCollection extends AbstractPrincipalCollection {
/** @var IRootFolder */
private $rootFolder;
public function __construct(PrincipalBackend\BackendInterface $principalBackend,
IRootFolder $rootFolder) {
parent::__construct($principalBackend, 'principals/users');
$this->rootFolder = $rootFolder;
}
/**
* This method returns a node for a principal.
*
* The passed array contains principal information, and is guaranteed to
* at least contain a uri item. Other properties may or may not be
* supplied by the authentication backend.
*
* @param array $principalInfo
* @return INode
*/
public function getChildForPrincipal(array $principalInfo) {
list(,$name) = \Sabre\Uri\split($principalInfo['uri']);
$user = \OC::$server->getUserSession()->getUser();
if (is_null($user) || $name !== $user->getUID()) {
throw new \Sabre\DAV\Exception\Forbidden();
}
return new VersionHome($principalInfo, $this->rootFolder);
}
public function getName() {
return 'versions';
}
}

View File

@ -0,0 +1,102 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Sabre;
use OCA\Files_Versions\Storage;
use OCP\Files\File;
use OCP\Files\Folder;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\ICollection;
class VersionCollection implements ICollection {
/** @var Folder */
private $userFolder;
/** @var File */
private $file;
/** @var string */
private $userId;
public function __construct(Folder $userFolder, File $file, string $userId) {
$this->userFolder = $userFolder;
$this->file = $file;
$this->userId = $userId;
}
public function createFile($name, $data = null) {
throw new Forbidden();
}
public function createDirectory($name) {
throw new Forbidden();
}
public function getChild($name) {
/** @var VersionFile[] $versions */
$versions = $this->getChildren();
foreach ($versions as $version) {
if ($version->getName() === $name) {
return $version;
}
}
throw new NotFound();
}
public function getChildren(): array {
$versions = Storage::getVersions($this->userId, $this->userFolder->getRelativePath($this->file->getPath()));
return array_map(function (array $data) {
return new VersionFile($data, $this->userFolder->getParent());
}, $versions);
}
public function childExists($name): bool {
try {
$this->getChild($name);
return true;
} catch (NotFound $e) {
return false;
}
}
public function delete() {
throw new Forbidden();
}
public function getName(): string {
return (string)$this->file->getId();
}
public function setName($name) {
throw new Forbidden();
}
public function getLastModified(): int {
return 0;
}
}

View File

@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Sabre;
use OCA\Files_Versions\Storage;
use OCP\Files\File;
use OCP\Files\Folder;
use OCP\Files\NotFoundException;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\IFile;
class VersionFile implements IFile {
/** @var array */
private $data;
/** @var Folder */
private $userRoot;
public function __construct(array $data, Folder $userRoot) {
$this->data = $data;
$this->userRoot = $userRoot;
}
public function put($data) {
throw new Forbidden();
}
public function get() {
try {
/** @var Folder $versions */
$versions = $this->userRoot->get('files_versions');
/** @var File $version */
$version = $versions->get($this->data['path'].'.v'.$this->data['version']);
} catch (NotFoundException $e) {
throw new NotFound();
}
return $version->fopen('rb');
}
public function getContentType(): string {
return $this->data['mimetype'];
}
public function getETag(): string {
return $this->data['version'];
}
public function getSize(): int {
return $this->data['size'];
}
public function delete() {
throw new Forbidden();
}
public function getName(): string {
return $this->data['version'];
}
public function setName($name) {
throw new Forbidden();
}
public function getLastModified(): int {
return (int)$this->data['version'];
}
public function rollBack(): bool {
return Storage::rollback($this->data['path'], $this->data['version']);
}
}

View File

@ -0,0 +1,90 @@
<?php
/**
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Sabre;
use OCP\Files\IRootFolder;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\ICollection;
class VersionHome implements ICollection {
/** @var array */
private $principalInfo;
/** @var IRootFolder */
private $rootFolder;
public function __construct(array $principalInfo, IRootFolder $rootFolder) {
$this->principalInfo = $principalInfo;
$this->rootFolder = $rootFolder;
}
public function delete() {
throw new Forbidden();
}
public function getName(): string {
list(,$name) = \Sabre\Uri\split($this->principalInfo['uri']);
return $name;
}
public function setName($name) {
throw new Forbidden();
}
public function createFile($name, $data = null) {
throw new Forbidden();
}
public function createDirectory($name) {
throw new Forbidden();
}
public function getChild($name) {
list(,$userId) = \Sabre\Uri\split($this->principalInfo['uri']);
if ($name === 'versions') {
return new VersionRoot($userId, $this->rootFolder);
}
if ($name === 'restore') {
return new RestoreFolder($userId);
}
}
public function getChildren() {
list(,$userId) = \Sabre\Uri\split($this->principalInfo['uri']);
return [
new VersionRoot($userId, $this->rootFolder),
new RestoreFolder($userId),
];
}
public function childExists($name) {
return $name === 'versions' || $name === 'restore';
}
public function getLastModified() {
return 0;
}
}

View File

@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Sabre;
use OCP\Files\File;
use OCP\Files\IRootFolder;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\ICollection;
class VersionRoot implements ICollection {
/** @var string */
private $userId;
/** @var IRootFolder */
private $rootFolder;
public function __construct(string $userId, IRootFolder $rootFolder) {
$this->userId = $userId;
$this->rootFolder = $rootFolder;
}
public function delete() {
throw new Forbidden();
}
public function getName(): string {
return 'versions';
}
public function setName($name) {
throw new Forbidden();
}
public function createFile($name, $data = null) {
throw new Forbidden();
}
public function createDirectory($name) {
throw new Forbidden();
}
public function getChild($name) {
$userFolder = $this->rootFolder->getUserFolder($this->userId);
$fileId = (int)$name;
$nodes = $userFolder->getById($fileId);
if ($nodes === []) {
throw new NotFound();
}
$node = array_pop($nodes);
if (!$node instanceof File) {
throw new NotFound();
}
return new VersionCollection($userFolder, $node, $this->userId);
}
public function getChildren(): array {
return [];
}
public function childExists($name): bool {
try {
$this->getChild($name);
return true;
} catch (NotFound $e) {
return false;
}
}
public function getLastModified(): int {
return 0;
}
}