Skip to content
Snippets Groups Projects
Unverified Commit 4403d771 authored by Robin Appelman's avatar Robin Appelman
Browse files

modular versions api


Allows apps to register version backends for storage types
The existing versions backend is wrapped in a "legacy" backend.

Signed-off-by: default avatarRobin Appelman <robin@icewind.nl>
parent 4a642fc0
No related branches found
No related tags found
No related merge requests found
Showing
with 781 additions and 120 deletions
......@@ -41,4 +41,8 @@
<collection>OCA\Files_Versions\Sabre\RootCollection</collection>
</collections>
</sabre>
<versions>
<backend for="OCP\Files\Storage\IStorage">OCA\Files_Versions\Versions\LegacyVersionsBackend</backend>
</versions>
</info>
......@@ -23,4 +23,11 @@ return array(
'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',
'OCA\\Files_Versions\\Versions\\BackendNotFoundException' => $baseDir . '/../lib/Versions/BackendNotFoundException.php',
'OCA\\Files_Versions\\Versions\\IVersion' => $baseDir . '/../lib/Versions/IVersion.php',
'OCA\\Files_Versions\\Versions\\IVersionBackend' => $baseDir . '/../lib/Versions/IVersionBackend.php',
'OCA\\Files_Versions\\Versions\\IVersionManager' => $baseDir . '/../lib/Versions/IVersionManager.php',
'OCA\\Files_Versions\\Versions\\LegacyVersionsBackend' => $baseDir . '/../lib/Versions/LegacyVersionsBackend.php',
'OCA\\Files_Versions\\Versions\\Version' => $baseDir . '/../lib/Versions/Version.php',
'OCA\\Files_Versions\\Versions\\VersionManager' => $baseDir . '/../lib/Versions/VersionManager.php',
);
......@@ -38,6 +38,13 @@ class ComposerStaticInitFiles_Versions
'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',
'OCA\\Files_Versions\\Versions\\BackendNotFoundException' => __DIR__ . '/..' . '/../lib/Versions/BackendNotFoundException.php',
'OCA\\Files_Versions\\Versions\\IVersion' => __DIR__ . '/..' . '/../lib/Versions/IVersion.php',
'OCA\\Files_Versions\\Versions\\IVersionBackend' => __DIR__ . '/..' . '/../lib/Versions/IVersionBackend.php',
'OCA\\Files_Versions\\Versions\\IVersionManager' => __DIR__ . '/..' . '/../lib/Versions/IVersionManager.php',
'OCA\\Files_Versions\\Versions\\LegacyVersionsBackend' => __DIR__ . '/..' . '/../lib/Versions/LegacyVersionsBackend.php',
'OCA\\Files_Versions\\Versions\\Version' => __DIR__ . '/..' . '/../lib/Versions/Version.php',
'OCA\\Files_Versions\\Versions\\VersionManager' => __DIR__ . '/..' . '/../lib/Versions/VersionManager.php',
);
public static function getInitializer(ClassLoader $loader)
......
......@@ -24,9 +24,10 @@
namespace OCA\Files_Versions\AppInfo;
use OCA\DAV\Connector\Sabre\Principal;
use OCA\Files_Versions\Versions\IVersionManager;
use OCA\Files_Versions\Versions\VersionManager;
use OCP\AppFramework\App;
use OCA\Files_Versions\Expiration;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\AppFramework\IAppContainer;
use OCA\Files_Versions\Capabilities;
class Application extends App {
......@@ -43,14 +44,45 @@ class Application extends App {
/*
* Register $principalBackend for the DAV collection
*/
$container->registerService('principalBackend', function () {
$container->registerService('principalBackend', function (IAppContainer $c) {
$server = $c->getServer();
return new Principal(
\OC::$server->getUserManager(),
\OC::$server->getGroupManager(),
\OC::$server->getShareManager(),
\OC::$server->getUserSession(),
\OC::$server->getConfig()
$server->getUserManager(),
$server->getGroupManager(),
$server->getShareManager(),
$server->getUserSession(),
$server->getConfig()
);
});
$container->registerService(IVersionManager::class, function(IAppContainer $c) {
return new VersionManager();
});
$this->registerVersionBackends();
}
public function registerVersionBackends() {
$server = $this->getContainer()->getServer();
$logger = $server->getLogger();
$appManager = $server->getAppManager();
/** @var IVersionManager $versionManager */
$versionManager = $this->getContainer()->getServer()->query(IVersionManager::class);
foreach($appManager->getInstalledApps() as $app) {
$appInfo = $appManager->getAppInfo($app);
if (isset($appInfo['versions'])) {
$backends = $appInfo['versions'];
foreach($backends as $backend) {
$class = $backend['@value'];
$for = $backend['@attributes']['for'];
try {
$backendObject = $server->query($class);
$versionManager->registerBackend($for, $backendObject);
} catch (\Exception $e) {
$logger->logException($e);
}
}
}
}
}
}
......@@ -21,45 +21,53 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Controller;
use OCA\Files_Versions\Versions\IVersionManager;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\FileDisplayResponse;
use OCP\Files\File;
use OCP\Files\Folder;
use OCP\Files\IMimeTypeDetector;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\IPreview;
use OCP\IRequest;
use OCP\IUserSession;
class PreviewController extends Controller {
/** @var IRootFolder */
private $rootFolder;
/** @var string */
private $userId;
/** @var IUserSession */
private $userSession;
/** @var IMimeTypeDetector */
private $mimeTypeDetector;
/** @var IVersionManager */
private $versionManager;
/** @var IPreview */
private $previewManager;
public function __construct($appName,
IRequest $request,
IRootFolder $rootFolder,
$userId,
IMimeTypeDetector $mimeTypeDetector,
IPreview $previewManager) {
public function __construct(
$appName,
IRequest $request,
IRootFolder $rootFolder,
IUserSession $userSession,
IMimeTypeDetector $mimeTypeDetector,
IVersionManager $versionManager,
IPreview $previewManager
) {
parent::__construct($appName, $request);
$this->rootFolder = $rootFolder;
$this->userId = $userId;
$this->userSession = $userSession;
$this->mimeTypeDetector = $mimeTypeDetector;
$this->versionManager = $versionManager;
$this->previewManager = $previewManager;
}
......@@ -79,20 +87,17 @@ class PreviewController extends Controller {
$y = 44,
$version = ''
) {
if($file === '' || $version === '' || $x === 0 || $y === 0) {
if ($file === '' || $version === '' || $x === 0 || $y === 0) {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
try {
$userFolder = $this->rootFolder->getUserFolder($this->userId);
/** @var Folder $versionFolder */
$versionFolder = $userFolder->getParent()->get('files_versions');
$mimeType = $this->mimeTypeDetector->detectPath($file);
$file = $versionFolder->get($file.'.v'.$version);
/** @var File $file */
$f = $this->previewManager->getPreview($file, $x, $y, true, IPreview::MODE_FILL, $mimeType);
return new FileDisplayResponse($f, Http::STATUS_OK, ['Content-Type' => $f->getMimeType()]);
$user = $this->userSession->getUser();
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
$file = $userFolder->get($file);
$versionFile = $this->versionManager->getVersionFile($user, $file, (int)$version);
$preview = $this->previewManager->getPreview($versionFile, $x, $y, true, IPreview::MODE_FILL, $versionFile->getMimetype());
return new FileDisplayResponse($preview, Http::STATUS_OK, ['Content-Type' => $preview->getMimeType()]);
} catch (NotFoundException $e) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
} catch (\InvalidArgumentException $e) {
......
......@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace OCA\Files_Versions\Sabre;
use OCP\IUser;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\ICollection;
use Sabre\DAV\IMoveTarget;
......@@ -31,14 +32,6 @@ 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();
}
......@@ -80,7 +73,8 @@ class RestoreFolder implements ICollection, IMoveTarget {
return false;
}
return $sourceNode->rollBack();
$sourceNode->rollBack();
return true;
}
}
......@@ -20,10 +20,13 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Sabre;
use OCA\Files_Versions\Versions\IVersionManager;
use OCP\Files\IRootFolder;
use OCP\IConfig;
use OCP\IUserManager;
use Sabre\DAV\INode;
use Sabre\DAVACL\AbstractPrincipalCollection;
use Sabre\DAVACL\PrincipalBackend;
......@@ -33,12 +36,24 @@ class RootCollection extends AbstractPrincipalCollection {
/** @var IRootFolder */
private $rootFolder;
public function __construct(PrincipalBackend\BackendInterface $principalBackend,
IRootFolder $rootFolder,
IConfig $config) {
/** @var IUserManager */
private $userManager;
/** @var IVersionManager */
private $versionManager;
public function __construct(
PrincipalBackend\BackendInterface $principalBackend,
IRootFolder $rootFolder,
IConfig $config,
IUserManager $userManager,
IVersionManager $versionManager
) {
parent::__construct($principalBackend, 'principals/users');
$this->rootFolder = $rootFolder;
$this->userManager = $userManager;
$this->versionManager = $versionManager;
$this->disableListing = !$config->getSystemValue('debug', false);
}
......@@ -54,12 +69,12 @@ class RootCollection extends AbstractPrincipalCollection {
* @return INode
*/
public function getChildForPrincipal(array $principalInfo) {
list(,$name) = \Sabre\Uri\split($principalInfo['uri']);
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);
return new VersionHome($principalInfo, $this->rootFolder, $this->userManager, $this->versionManager);
}
public function getName() {
......
......@@ -21,11 +21,15 @@ declare(strict_types=1);
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Sabre;
use OCA\Files_Versions\Storage;
use OCA\Files_Versions\Versions\IVersion;
use OCA\Files_Versions\Versions\IVersionManager;
use OCP\Files\File;
use OCP\Files\Folder;
use OCP\IUser;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\ICollection;
......@@ -37,13 +41,17 @@ class VersionCollection implements ICollection {
/** @var File */
private $file;
/** @var string */
private $userId;
/** @var IUser */
private $user;
/** @var IVersionManager */
private $versionManager;
public function __construct(Folder $userFolder, File $file, string $userId) {
public function __construct(Folder $userFolder, File $file, IUser $user, IVersionManager $versionManager) {
$this->userFolder = $userFolder;
$this->file = $file;
$this->userId = $userId;
$this->user = $user;
$this->versionManager = $versionManager;
}
public function createFile($name, $data = null) {
......@@ -68,10 +76,10 @@ class VersionCollection implements ICollection {
}
public function getChildren(): array {
$versions = Storage::getVersions($this->userId, $this->userFolder->getRelativePath($this->file->getPath()));
$versions = $this->versionManager->getVersionsForFile($this->user, $this->file);
return array_map(function (array $data) {
return new VersionFile($data, $this->userFolder->getParent());
return array_map(function (IVersion $version) {
return new VersionFile($version, $this->versionManager);
}, $versions);
}
......
......@@ -21,26 +21,26 @@ declare(strict_types=1);
* 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 OCA\Files_Versions\Versions\IVersion;
use OCA\Files_Versions\Versions\IVersionManager;
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 IVersion */
private $version;
/** @var Folder */
private $userRoot;
/** @var IVersionManager */
private $versionManager;
public function __construct(array $data, Folder $userRoot) {
$this->data = $data;
$this->userRoot = $userRoot;
public function __construct(IVersion $version, IVersionManager $versionManager) {
$this->version = $version;
$this->versionManager = $versionManager;
}
public function put($data) {
......@@ -49,27 +49,22 @@ class VersionFile implements IFile {
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']);
return $this->versionManager->read($this->version);
} catch (NotFoundException $e) {
throw new NotFound();
}
return $version->fopen('rb');
}
public function getContentType(): string {
return $this->data['mimetype'];
return $this->version->getMimeType();
}
public function getETag(): string {
return $this->data['version'];
return (string)$this->version->getRevisionId();
}
public function getSize(): int {
return $this->data['size'];
return $this->version->getSize();
}
public function delete() {
......@@ -77,7 +72,7 @@ class VersionFile implements IFile {
}
public function getName(): string {
return $this->data['version'];
return (string)$this->version->getRevisionId();
}
public function setName($name) {
......@@ -85,10 +80,10 @@ class VersionFile implements IFile {
}
public function getLastModified(): int {
return (int)$this->data['version'];
return $this->version->getTimestamp();
}
public function rollBack(): bool {
return Storage::rollback($this->data['path'], $this->data['version']);
public function rollBack() {
$this->versionManager->rollback($this->version);
}
}
......@@ -20,9 +20,13 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Sabre;
use OC\User\NoUserException;
use OCA\Files_Versions\Versions\IVersionManager;
use OCP\Files\IRootFolder;
use OCP\IUserManager;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\ICollection;
......@@ -34,9 +38,25 @@ class VersionHome implements ICollection {
/** @var IRootFolder */
private $rootFolder;
public function __construct(array $principalInfo, IRootFolder $rootFolder) {
/** @var IUserManager */
private $userManager;
/** @var IVersionManager */
private $versionManager;
public function __construct(array $principalInfo, IRootFolder $rootFolder, IUserManager $userManager, IVersionManager $versionManager) {
$this->principalInfo = $principalInfo;
$this->rootFolder = $rootFolder;
$this->userManager = $userManager;
$this->versionManager = $versionManager;
}
private function getUser() {
list(, $name) = \Sabre\Uri\split($this->principalInfo['uri']);
$user = $this->userManager->get($name);
if (!$user) {
throw new NoUserException();
}
}
public function delete() {
......@@ -44,8 +64,7 @@ class VersionHome implements ICollection {
}
public function getName(): string {
list(,$name) = \Sabre\Uri\split($this->principalInfo['uri']);
return $name;
return $this->getUser()->getUID();
}
public function setName($name) {
......@@ -61,22 +80,22 @@ class VersionHome implements ICollection {
}
public function getChild($name) {
list(,$userId) = \Sabre\Uri\split($this->principalInfo['uri']);
$user = $this->getUser();
if ($name === 'versions') {
return new VersionRoot($userId, $this->rootFolder);
return new VersionRoot($user, $this->rootFolder, $this->versionManager);
}
if ($name === 'restore') {
return new RestoreFolder($userId);
return new RestoreFolder();
}
}
public function getChildren() {
list(,$userId) = \Sabre\Uri\split($this->principalInfo['uri']);
$user = $this->getUser();
return [
new VersionRoot($userId, $this->rootFolder),
new RestoreFolder($userId),
new VersionRoot($user, $this->rootFolder, $this->versionManager),
new RestoreFolder(),
];
}
......
......@@ -23,23 +23,29 @@ declare(strict_types=1);
*/
namespace OCA\Files_Versions\Sabre;
use OCA\Files_Versions\Versions\IVersionManager;
use OCP\Files\File;
use OCP\Files\IRootFolder;
use OCP\IUser;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\ICollection;
class VersionRoot implements ICollection {
/** @var string */
private $userId;
/** @var IUser */
private $user;
/** @var IRootFolder */
private $rootFolder;
public function __construct(string $userId, IRootFolder $rootFolder) {
$this->userId = $userId;
/** @var IVersionManager */
private $versionManager;
public function __construct(IUser $user, IRootFolder $rootFolder, IVersionManager $versionManager) {
$this->user = $user;
$this->rootFolder = $rootFolder;
$this->versionManager = $versionManager;
}
public function delete() {
......@@ -63,7 +69,7 @@ class VersionRoot implements ICollection {
}
public function getChild($name) {
$userFolder = $this->rootFolder->getUserFolder($this->userId);
$userFolder = $this->rootFolder->getUserFolder($this->user->getUID());
$fileId = (int)$name;
$nodes = $userFolder->getById($fileId);
......@@ -78,7 +84,7 @@ class VersionRoot implements ICollection {
throw new NotFound();
}
return new VersionCollection($userFolder, $node, $this->userId);
return new VersionCollection($userFolder, $node, $this->user, $this->versionManager);
}
public function getChildren(): array {
......
......@@ -48,6 +48,7 @@ use OC\Files\View;
use OCA\Files_Versions\AppInfo\Application;
use OCA\Files_Versions\Command\Expire;
use OCA\Files_Versions\Events\CreateVersionEvent;
use OCA\Files_Versions\Versions\IVersionManager;
use OCP\Files\NotFoundException;
use OCP\Lock\ILockingProvider;
use OCP\User;
......@@ -178,10 +179,10 @@ class Storage {
list($uid, $filename) = self::getUidAndFilename($filename);
$files_view = new View('/'.$uid .'/files');
$users_view = new View('/'.$uid);
$eventDispatcher = \OC::$server->getEventDispatcher();
$id = $files_view->getFileInfo($filename)->getId();
$fileInfo = $files_view->getFileInfo($filename);
$id = $fileInfo->getId();
$nodes = \OC::$server->getRootFolder()->getById($id);
foreach ($nodes as $node) {
$event = new CreateVersionEvent($node);
......@@ -192,20 +193,16 @@ class Storage {
}
// no use making versions for empty files
if ($files_view->filesize($filename) === 0) {
if ($fileInfo->getSize() === 0) {
return false;
}
// create all parent folders
self::createMissingDirectories($filename, $users_view);
self::scheduleExpire($uid, $filename);
/** @var IVersionManager $versionManager */
$versionManager = \OC::$server->query(IVersionManager::class);
$userManager = \OC::$server->getUserManager();
$user = $userManager->get($uid);
// store a new version of a file
$mtime = $users_view->filemtime('files/' . $filename);
$users_view->copy('files/' . $filename, 'files_versions/' . $filename . '.v' . $mtime);
// call getFileInfo to enforce a file cache entry for the new version
$users_view->getFileInfo('files_versions/' . $filename . '.v' . $mtime);
$versionManager->createVersion($user, $fileInfo);
}
......@@ -695,7 +692,7 @@ class Storage {
* @param string $uid owner of the file
* @param string $fileName file/folder for which to schedule expiration
*/
private static function scheduleExpire($uid, $fileName) {
public static function scheduleExpire($uid, $fileName) {
// let the admin disable auto expire
$expiration = self::getExpiration();
if ($expiration->isEnabled()) {
......@@ -833,7 +830,7 @@ class Storage {
* "files" folder
* @param View $view view on data/user/
*/
private static function createMissingDirectories($filename, $view) {
public static function createMissingDirectories($filename, $view) {
$dirname = Filesystem::normalizePath(dirname($filename));
$dirParts = explode('/', $dirname);
$dir = "/files_versions";
......
<?php
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.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\Versions;
class BackendNotFoundException extends \Exception {
}
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.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\Versions;
use OCP\Files\FileInfo;
use OCP\IUser;
/**
* @since 15.0.0
*/
interface IVersion {
/**
* @return IVersionBackend
* @since 15.0.0
*/
public function getBackend(): IVersionBackend;
/**
* Get the file info of the source file
*
* @return FileInfo
* @since 15.0.0
*/
public function getSourceFile(): FileInfo;
/**
* Get the id of the revision for the file
*
* @return int
* @since 15.0.0
*/
public function getRevisionId(): int;
/**
* Get the timestamp this version was created
*
* @return int
* @since 15.0.0
*/
public function getTimestamp(): int;
/**
* Get the size of this version
*
* @return int
* @since 15.0.0
*/
public function getSize(): int;
/**
* Get the name of the source file at the time of making this version
*
* @return string
* @since 15.0.0
*/
public function getSourceFileName(): string;
/**
* Get the mimetype of this version
*
* @return string
* @since 15.0.0
*/
public function getMimeType(): string;
/**
* Get the path of this version
*
* @return string
* @since 15.0.0
*/
public function getVersionPath(): string;
/**
* @return IUser
* @since 15.0.0
*/
public function getUser(): IUser;
}
<?php declare(strict_types=1);
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.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\Versions;
use OCP\Files\File;
use OCP\Files\FileInfo;
use OCP\Files\NotFoundException;
use OCP\Files\SimpleFS\ISimpleFile;
use OCP\IUser;
/**
* @since 15.0.0
*/
interface IVersionBackend {
/**
* Get all versions for a file
*
* @param IUser $user
* @param FileInfo $file
* @return IVersion[]
* @since 15.0.0
*/
public function getVersionsForFile(IUser $user, FileInfo $file): array;
/**
* Create a new version for a file
*
* @param IUser $user
* @param FileInfo $file
* @since 15.0.0
*/
public function createVersion(IUser $user, FileInfo $file);
/**
* Restore this version
*
* @param IVersion $version
* @since 15.0.0
*/
public function rollback(IVersion $version);
/**
* Open the file for reading
*
* @param IVersion $version
* @return resource
* @throws NotFoundException
* @since 15.0.0
*/
public function read(IVersion $version);
/**
* Get the preview for a specific version of a file
*
* @param IUser $user
* @param FileInfo $sourceFile
* @param int $revision
* @return ISimpleFile
* @since 15.0.0
*/
public function getVersionFile(IUser $user, FileInfo $sourceFile, int $revision): File;
}
<?php declare(strict_types=1);
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.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\Versions;
/**
* @since 15.0.0
*/
interface IVersionManager extends IVersionBackend {
/**
* Register a new backend
*
* @param string $storageType
* @param IVersionBackend $backend
* @since 15.0.0
*/
public function registerBackend(string $storageType, IVersionBackend $backend);
}
<?php declare(strict_types=1);
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.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\Versions;
use OC\Files\View;
use OCA\Files_Versions\Storage;
use OCP\Files\File;
use OCP\Files\FileInfo;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\IUser;
class LegacyVersionsBackend implements IVersionBackend {
/** @var IRootFolder */
private $rootFolder;
public function __construct(IRootFolder $rootFolder) {
$this->rootFolder = $rootFolder;
}
public function getVersionsForFile(IUser $user, FileInfo $file): array {
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
$versions = Storage::getVersions($user->getUID(), $userFolder->getRelativePath($file->getPath()));
return array_map(function (array $data) use ($file, $user) {
return new Version(
(int)$data['version'],
(int)$data['version'],
$data['name'],
(int)$data['size'],
$data['mimetype'],
$data['path'],
$file,
$this,
$user
);
}, $versions);
}
public function createVersion(IUser $user, FileInfo $file) {
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
$relativePath = $userFolder->getRelativePath($file->getPath());
$userView = new View('/' . $user->getUID());
// create all parent folders
Storage::createMissingDirectories($relativePath, $userView);
Storage::scheduleExpire($user->getUID(), $relativePath);
// store a new version of a file
$userView->copy('files/' . $relativePath, 'files_versions/' . $relativePath . '.v' . $file->getMtime());
// ensure the file is scanned
$userView->getFileInfo('files_versions/' . $relativePath . '.v' . $file->getMtime());
}
public function rollback(IVersion $version) {
return Storage::rollback($version->getVersionPath(), $version->getRevisionId());
}
private function getVersionFolder(IUser $user): Folder {
$userRoot = $this->rootFolder->getUserFolder($user->getUID())
->getParent();
try {
/** @var Folder $folder */
$folder = $userRoot->get('files_versions');
return $folder;
} catch (NotFoundException $e) {
return $userRoot->newFolder('files_versions');
}
}
public function read(IVersion $version) {
$versions = $this->getVersionFolder($version->getUser());
/** @var File $file */
$file = $versions->get($version->getVersionPath() . '.v' . $version->getRevisionId());
return $file->fopen('r');
}
public function getVersionFile(IUser $user, FileInfo $sourceFile, int $revision): File {
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
$versionFolder = $this->getVersionFolder($user);
/** @var File $file */
$file = $versionFolder->get($userFolder->getRelativePath($sourceFile->getPath()) . '.v' . $revision);
return $file;
}
}
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.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\Versions;
use OCP\Files\FileInfo;
use OCP\IUser;
class Version implements IVersion {
/** @var int */
private $timestamp;
/** @var int */
private $revisionId;
/** @var string */
private $name;
/** @var int */
private $size;
/** @var string */
private $mimetype;
/** @var string */
private $path;
/** @var FileInfo */
private $sourceFileInfo;
/** @var IVersionBackend */
private $backend;
/** @var IUser */
private $user;
public function __construct(
int $timestamp,
int $revisionId,
string $name,
int $size,
string $mimetype,
string $path,
FileInfo $sourceFileInfo,
IVersionBackend $backend,
IUser $user
) {
$this->timestamp = $timestamp;
$this->revisionId = $revisionId;
$this->name = $name;
$this->size = $size;
$this->mimetype = $mimetype;
$this->path = $path;
$this->sourceFileInfo = $sourceFileInfo;
$this->backend = $backend;
$this->user = $user;
}
public function getBackend(): IVersionBackend {
return $this->backend;
}
public function getSourceFile(): FileInfo {
return $this->sourceFileInfo;
}
public function getRevisionId(): int {
return $this->revisionId;
}
public function getTimestamp(): int {
return $this->timestamp;
}
public function getSize(): int {
return $this->size;
}
public function getSourceFileName(): string {
return $this->name;
}
public function getMimeType(): string {
return $this->mimetype;
}
public function getVersionPath(): string {
return $this->path;
}
public function getUser(): IUser {
return $this->user;
}
}
<?php declare(strict_types=1);
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.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\Versions;
use OCP\Files\File;
use OCP\Files\FileInfo;
use OCP\Files\Storage\IStorage;
use OCP\IUser;
class VersionManager implements IVersionManager {
/** @var IVersionBackend[] */
private $backends = [];
public function registerBackend(string $storageType, IVersionBackend $backend) {
$this->backends[$storageType] = $backend;
}
/**
* @return IVersionBackend[]
*/
private function getBackends(): array {
return $this->backends;
}
/**
* @param IStorage $storage
* @return IVersionBackend
* @throws BackendNotFoundException
*/
public function getBackendForStorage(IStorage $storage): IVersionBackend {
$fullType = get_class($storage);
$backends = $this->getBackends();
$foundType = array_reduce(array_keys($backends), function ($type, $registeredType) use ($storage) {
if (
$storage->instanceOfStorage($registeredType) &&
($type === '' || is_subclass_of($registeredType, $type))
) {
return $registeredType;
} else {
return $type;
}
}, '');
if ($foundType === '') {
throw new BackendNotFoundException("Version backend for $fullType not found");
} else {
return $backends[$foundType];
}
}
public function getVersionsForFile(IUser $user, FileInfo $file): array {
$backend = $this->getBackendForStorage($file->getStorage());
return $backend->getVersionsForFile($user, $file);
}
public function createVersion(IUser $user, FileInfo $file) {
$backend = $this->getBackendForStorage($file->getStorage());
$backend->createVersion($user, $file);
}
public function rollback(IVersion $version) {
$backend = $version->getBackend();
return $backend->rollback($version);
}
public function read(IVersion $version) {
$backend = $version->getBackend();
return $backend->read($version);
}
public function getVersionFile(IUser $user, FileInfo $sourceFile, int $revision): File {
$backend = $this->getBackendForStorage($sourceFile->getStorage());
return $backend->getVersionFile($user, $sourceFile, $revision);
}
}
......@@ -20,9 +20,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Tests\Controller;
use OC\User\User;
use OCA\Files_Versions\Controller\PreviewController;
use OCA\Files_Versions\Versions\IVersionManager;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\FileDisplayResponse;
......@@ -34,6 +37,8 @@ use OCP\Files\NotFoundException;
use OCP\Files\SimpleFS\ISimpleFile;
use OCP\IPreview;
use OCP\IRequest;
use OCP\IUser;
use OCP\IUserSession;
use Test\TestCase;
class PreviewControllerTest extends TestCase {
......@@ -50,23 +55,39 @@ class PreviewControllerTest extends TestCase {
/** @var IPreview|\PHPUnit_Framework_MockObject_MockObject */
private $previewManager;
/** @var PreviewController */
/** @var PreviewController|\PHPUnit_Framework_MockObject_MockObject */
private $controller;
/** @var IUserSession|\PHPUnit_Framework_MockObject_MockObject */
private $userSession;
/** @var IVersionManager|\PHPUnit_Framework_MockObject_MockObject */
private $versionManager;
public function setUp() {
parent::setUp();
$this->rootFolder = $this->createMock(IRootFolder::class);
$this->userId = 'user';
$user = $this->createMock(IUser::class);
$user->expects($this->any())
->method('getUID')
->willReturn($this->userId);
$this->mimeTypeDetector = $this->createMock(IMimeTypeDetector::class);
$this->previewManager = $this->createMock(IPreview::class);
$this->userSession = $this->createMock(IUserSession::class);
$this->userSession->expects($this->any())
->method('getUser')
->willReturn($user);
$this->versionManager = $this->createMock(IVersionManager::class);
$this->controller = new PreviewController(
'files_versions',
$this->createMock(IRequest::class),
$this->rootFolder,
$this->userId,
$this->userSession,
$this->mimeTypeDetector,
$this->versionManager,
$this->previewManager
);
}
......@@ -102,24 +123,23 @@ class PreviewControllerTest extends TestCase {
public function testValidPreview() {
$userFolder = $this->createMock(Folder::class);
$userRoot = $this->createMock(Folder::class);
$versions = $this->createMock(Folder::class);
$this->rootFolder->method('getUserFolder')
->with($this->userId)
->willReturn($userFolder);
$userFolder->method('getParent')
->willReturn($userRoot);
$userRoot->method('get')
->with('files_versions')
->willReturn($versions);
$this->mimeTypeDetector->method('detectPath')
->with($this->equalTo('file'))
->willReturn('myMime');
$sourceFile = $this->createMock(File::class);
$userFolder->method('get')
->with('file')
->willReturn($sourceFile);
$file = $this->createMock(File::class);
$versions->method('get')
->with($this->equalTo('file.v42'))
$file->method('getMimetype')
->willReturn('myMime');
$this->versionManager->method('getVersionFile')
->willReturn($file);
$preview = $this->createMock(ISimpleFile::class);
......@@ -138,24 +158,23 @@ class PreviewControllerTest extends TestCase {
public function testVersionNotFound() {
$userFolder = $this->createMock(Folder::class);
$userRoot = $this->createMock(Folder::class);
$versions = $this->createMock(Folder::class);
$this->rootFolder->method('getUserFolder')
->with($this->userId)
->willReturn($userFolder);
$userFolder->method('getParent')
->willReturn($userRoot);
$userRoot->method('get')
->with('files_versions')
->willReturn($versions);
$sourceFile = $this->createMock(File::class);
$userFolder->method('get')
->with('file')
->willReturn($sourceFile);
$this->mimeTypeDetector->method('detectPath')
->with($this->equalTo('file'))
->willReturn('myMime');
$file = $this->createMock(File::class);
$versions->method('get')
->with($this->equalTo('file.v42'))
$this->versionManager->method('getVersionFile')
->willThrowException(new NotFoundException());
$res = $this->controller->getPreview('file', 10, 10, '42');
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment