<collection>OCA\Files_Versions\Sabre\RootCollection</collection>
</collections>
</sabre>
+
+ <versions>
+ <backend for="OCP\Files\Storage\IStorage">OCA\Files_Versions\Versions\LegacyVersionsBackend</backend>
+ </versions>
</info>
'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',
);
'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)
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 {
/*
* 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);
+ }
+ }
+ }
+ }
}
}
* 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;
}
$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) {
namespace OCA\Files_Versions\Sabre;
+use OCP\IUser;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\ICollection;
use Sabre\DAV\IMoveTarget;
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();
}
return false;
}
- return $sourceNode->rollBack();
+ $sourceNode->rollBack();
+ return true;
}
}
* 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;
/** @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);
}
* @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() {
* 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;
/** @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) {
}
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);
}
* 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) {
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() {
}
public function getName(): string {
- return $this->data['version'];
+ return (string)$this->version->getRevisionId();
}
public function setName($name) {
}
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);
}
}
* 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;
/** @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() {
}
public function getName(): string {
- list(,$name) = \Sabre\Uri\split($this->principalInfo['uri']);
- return $name;
+ return $this->getUser()->getUID();
}
public function setName($name) {
}
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(),
];
}
*/
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() {
}
public function getChild($name) {
- $userFolder = $this->rootFolder->getUserFolder($this->userId);
+ $userFolder = $this->rootFolder->getUserFolder($this->user->getUID());
$fileId = (int)$name;
$nodes = $userFolder->getById($fileId);
throw new NotFound();
}
- return new VersionCollection($userFolder, $node, $this->userId);
+ return new VersionCollection($userFolder, $node, $this->user, $this->versionManager);
}
public function getChildren(): array {
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;
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);
}
// 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);
}
* @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()) {
* "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";
--- /dev/null
+<?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 {
+
+}
--- /dev/null
+<?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;
+}
--- /dev/null
+<?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;
+}
--- /dev/null
+<?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);
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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);
+ }
+}
* 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;
use OCP\Files\SimpleFS\ISimpleFile;
use OCP\IPreview;
use OCP\IRequest;
+use OCP\IUser;
+use OCP\IUserSession;
use Test\TestCase;
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
);
}
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);
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');
maxOccurs="1" />
<xs:element name="trash" type="trash" minOccurs="0"
maxOccurs="1" />
+ <xs:element name="versions" type="versions" minOccurs="0"
+ maxOccurs="1" />
</xs:sequence>
</xs:complexType>
<xs:unique name="uniqueNameL10n">
</xs:simpleContent>
</xs:complexType>
+ <xs:complexType name="versions">
+ <xs:sequence>
+ <xs:element name="backend" type="versions-backend" minOccurs="1"
+ maxOccurs="unbounded"/>
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="versions-backend">
+ <xs:simpleContent>
+ <xs:extension base="php-class">
+ <xs:attribute name="for" type="php-class" use="required"/>
+ </xs:extension>
+ </xs:simpleContent>
+ </xs:complexType>
+
<xs:simpleType name="php-class">
<xs:restriction base="xs:string">
<xs:pattern