modular versions apitags/v15.0.0beta1
@@ -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"; |
@@ -0,0 +1,26 @@ | |||
<?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 { | |||
} |
@@ -0,0 +1,99 @@ | |||
<?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; | |||
} |
@@ -0,0 +1,81 @@ | |||
<?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; | |||
} |
@@ -0,0 +1,36 @@ | |||
<?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); | |||
} |
@@ -0,0 +1,105 @@ | |||
<?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; | |||
} | |||
} |
@@ -0,0 +1,113 @@ | |||
<?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; | |||
} | |||
} |
@@ -0,0 +1,93 @@ | |||
<?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'); |
@@ -63,6 +63,8 @@ | |||
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"> | |||
@@ -670,6 +672,21 @@ | |||
</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 |