diff options
author | Louis Chemineau <louis@chmn.me> | 2022-11-29 14:52:02 +0100 |
---|---|---|
committer | Louis (Rebase PR Action) <artonge@users.noreply.github.com> | 2023-01-26 10:12:23 +0000 |
commit | 629de6c8c97f9a455944046737421b927b1e2d9b (patch) | |
tree | 25dc4a6e6dd5a339d3207a8c8fb325ecb71c1cbf /apps/files_versions/lib | |
parent | e82bfba114aa291fe6bbe1de3488c685d12489a1 (diff) | |
download | nextcloud-server-629de6c8c97f9a455944046737421b927b1e2d9b.tar.gz nextcloud-server-629de6c8c97f9a455944046737421b927b1e2d9b.zip |
Support getting and patching version-label
Signed-off-by: Louis Chemineau <louis@chmn.me>
Diffstat (limited to 'apps/files_versions/lib')
-rw-r--r-- | apps/files_versions/lib/AppInfo/Application.php | 4 | ||||
-rw-r--r-- | apps/files_versions/lib/Capabilities.php | 19 | ||||
-rw-r--r-- | apps/files_versions/lib/Db/VersionEntity.php | 11 | ||||
-rw-r--r-- | apps/files_versions/lib/Hooks.php | 49 | ||||
-rw-r--r-- | apps/files_versions/lib/Sabre/Plugin.php | 30 | ||||
-rw-r--r-- | apps/files_versions/lib/Sabre/VersionFile.php | 20 | ||||
-rw-r--r-- | apps/files_versions/lib/Versions/INameableVersion.php | 37 | ||||
-rw-r--r-- | apps/files_versions/lib/Versions/INameableVersionBackend.php | 36 | ||||
-rw-r--r-- | apps/files_versions/lib/Versions/LegacyVersionsBackend.php | 102 | ||||
-rw-r--r-- | apps/files_versions/lib/Versions/Version.php | 12 | ||||
-rw-r--r-- | apps/files_versions/lib/Versions/VersionManager.php | 9 |
11 files changed, 294 insertions, 35 deletions
diff --git a/apps/files_versions/lib/AppInfo/Application.php b/apps/files_versions/lib/AppInfo/Application.php index a6eab420742..08c49f280af 100644 --- a/apps/files_versions/lib/AppInfo/Application.php +++ b/apps/files_versions/lib/AppInfo/Application.php @@ -47,10 +47,11 @@ use OCP\AppFramework\Bootstrap\IRegistrationContext; use OCP\Files\Events\Node\BeforeNodeCopiedEvent; use OCP\Files\Events\Node\BeforeNodeDeletedEvent; use OCP\Files\Events\Node\BeforeNodeRenamedEvent; -use OCP\Files\Events\Node\BeforeNodeWrittenEvent; use OCP\Files\Events\Node\NodeCopiedEvent; use OCP\Files\Events\Node\NodeDeletedEvent; use OCP\Files\Events\Node\NodeRenamedEvent; +use OCP\Files\Events\Node\BeforeNodeWrittenEvent; +use OCP\Files\Events\Node\NodeWrittenEvent; use OCP\IConfig; use OCP\IGroupManager; use OCP\IServerContainer; @@ -105,6 +106,7 @@ class Application extends App implements IBootstrap { $context->registerEventListener(LoadSidebar::class, LoadSidebarListener::class); $context->registerEventListener(BeforeNodeWrittenEvent::class, Hooks::class); + $context->registerEventListener(NodeWrittenEvent::class, Hooks::class); $context->registerEventListener(BeforeNodeDeletedEvent::class, Hooks::class); $context->registerEventListener(NodeDeletedEvent::class, Hooks::class); $context->registerEventListener(NodeRenamedEvent::class, Hooks::class); diff --git a/apps/files_versions/lib/Capabilities.php b/apps/files_versions/lib/Capabilities.php index b8602540ec8..031cabb83ec 100644 --- a/apps/files_versions/lib/Capabilities.php +++ b/apps/files_versions/lib/Capabilities.php @@ -24,19 +24,34 @@ */ namespace OCA\Files_Versions; +use OCP\App\IAppManager; use OCP\Capabilities\ICapability; +use OCP\IConfig; class Capabilities implements ICapability { - + private IConfig $config; + private IAppManager $appManager; + + public function __construct( + IConfig $config, + IAppManager $appManager + ) { + $this->config = $config; + $this->appManager = $appManager; + } + /** * Return this classes capabilities * * @return array */ public function getCapabilities() { + $groupFolderOrS3VersioningInstalled = $this->appManager->isInstalled('groupfolders') || !$this->appManager->isInstalled('groupfolders'); + return [ 'files' => [ - 'versioning' => true + 'versioning' => true, + 'version_labeling' => !$groupFolderOrS3VersioningInstalled && $this->config->getSystemValueBool('enable_version_labeling', true), ] ]; } diff --git a/apps/files_versions/lib/Db/VersionEntity.php b/apps/files_versions/lib/Db/VersionEntity.php index 5ef94215dd2..d5adbcfa104 100644 --- a/apps/files_versions/lib/Db/VersionEntity.php +++ b/apps/files_versions/lib/Db/VersionEntity.php @@ -59,7 +59,7 @@ class VersionEntity extends Entity implements JsonSerializable { $this->addType('metadata', Types::JSON); } - public function jsonSerialize() { + public function jsonSerialize(): array { return [ 'id' => $this->id, 'file_id' => $this->fileId, @@ -69,4 +69,13 @@ class VersionEntity extends Entity implements JsonSerializable { 'metadata' => $this->metadata, ]; } + + public function getLabel(): string { + return $this->metadata['label'] ?? ''; + } + + public function setLabel(string $label): void { + $this->metadata['label'] = $label; + $this->markFieldUpdated('metadata'); + } }
\ No newline at end of file diff --git a/apps/files_versions/lib/Hooks.php b/apps/files_versions/lib/Hooks.php index d4408190ea3..ebb0974c4ed 100644 --- a/apps/files_versions/lib/Hooks.php +++ b/apps/files_versions/lib/Hooks.php @@ -32,6 +32,8 @@ namespace OCA\Files_Versions; use OC\Files\Filesystem; use OC\Files\Mount\MoveableMount; use OC\Files\View; +use OCA\Files_Versions\Db\VersionEntity; +use OCA\Files_Versions\Db\VersionsMapper; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; use OCP\Files\Events\Node\BeforeNodeCopiedEvent; @@ -41,16 +43,28 @@ use OCP\Files\Events\Node\BeforeNodeWrittenEvent; use OCP\Files\Events\Node\NodeCopiedEvent; use OCP\Files\Events\Node\NodeDeletedEvent; use OCP\Files\Events\Node\NodeRenamedEvent; +use OCP\Files\Events\Node\NodeWrittenEvent; use OCP\Files\Folder; +use OCP\Files\IMimeTypeLoader; use OCP\Files\Node; class Hooks implements IEventListener { - public Folder $userFolder; + private Folder $userFolder; + private VersionsMapper $versionsMapper; + /** + * @var array<int, bool> + */ + private array $versionsCreated = []; + private IMimeTypeLoader $mimeTypeLoader; public function __construct( - Folder $userFolder + Folder $userFolder, + VersionsMapper $versionsMapper, + IMimeTypeLoader $mimeTypeLoader ) { $this->userFolder = $userFolder; + $this->versionsMapper = $versionsMapper; + $this->mimeTypeLoader = $mimeTypeLoader; } public function handle(Event $event): void { @@ -58,6 +72,10 @@ class Hooks implements IEventListener { $this->write_hook($event->getNode()); } + if ($event instanceof NodeWrittenEvent) { + $this->post_write_hook($event->getNode()); + } + if ($event instanceof BeforeNodeDeletedEvent) { $this->pre_remove_hook($event->getNode()); } @@ -88,9 +106,34 @@ class Hooks implements IEventListener { */ public function write_hook(Node $node): void { $path = $this->userFolder->getRelativePath($node->getPath()); - Storage::store($path); + $result = Storage::store($path); + + if ($result === false) { + return; + } + + // Store the result of the version creation so it can be used in post_write_hook. + $this->versionsCreated[$node->getId()] = true; } + /** + * listen to post_write event. + */ + public function post_write_hook(Node $node): void { + if (!array_key_exists($node->getId(), $this->versionsCreated)) { + return; + } + + unset($this->versionsCreated[$node->getId()]); + + $versionEntity = new VersionEntity(); + $versionEntity->setFileId($node->getId()); + $versionEntity->setTimestamp($node->getMTime()); + $versionEntity->setSize($node->getSize()); + $versionEntity->setMimetype($this->mimeTypeLoader->getId($node->getMimetype())); + $versionEntity->setMetadata([]); + $this->versionsMapper->insert($versionEntity); + } /** * Erase versions of deleted file diff --git a/apps/files_versions/lib/Sabre/Plugin.php b/apps/files_versions/lib/Sabre/Plugin.php index 5a127b4251d..4fd17194ba6 100644 --- a/apps/files_versions/lib/Sabre/Plugin.php +++ b/apps/files_versions/lib/Sabre/Plugin.php @@ -29,19 +29,23 @@ namespace OCA\Files_Versions\Sabre; use OC\AppFramework\Http\Request; use OCP\IRequest; use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\INode; +use Sabre\DAV\PropFind; +use Sabre\DAV\PropPatch; use Sabre\DAV\Server; use Sabre\DAV\ServerPlugin; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; class Plugin extends ServerPlugin { + private Server $server; + private IRequest $request; - /** @var Server */ - private $server; - /** @var IRequest */ - private $request; + public const VERSION_LABEL = '{http://nextcloud.org/ns}version-label'; - public function __construct(IRequest $request) { + public function __construct( + IRequest $request + ) { $this->request = $request; } @@ -49,6 +53,8 @@ class Plugin extends ServerPlugin { $this->server = $server; $server->on('afterMethod:GET', [$this, 'afterGet']); + $server->on('propFind', [$this, 'propFind']); + $server->on('propPatch', [$this, 'propPatch']); } public function afterGet(RequestInterface $request, ResponseInterface $response) { @@ -81,4 +87,18 @@ class Plugin extends ServerPlugin { . '; filename="' . rawurlencode($filename) . '"'); } } + + public function propFind(PropFind $propFind, INode $node): void { + if ($node instanceof VersionFile) { + $propFind->handle(self::VERSION_LABEL, fn() => $node->getLabel()); + } + } + + public function propPatch($path, PropPatch $propPatch): void { + $node = $this->server->tree->getNodeForPath($path); + + if ($node instanceof VersionFile) { + $propPatch->handle(self::VERSION_LABEL, fn ($label) => $node->setLabel($label)); + } + } } diff --git a/apps/files_versions/lib/Sabre/VersionFile.php b/apps/files_versions/lib/Sabre/VersionFile.php index b7c7e6db1a6..9018f75703d 100644 --- a/apps/files_versions/lib/Sabre/VersionFile.php +++ b/apps/files_versions/lib/Sabre/VersionFile.php @@ -26,6 +26,8 @@ declare(strict_types=1); */ namespace OCA\Files_Versions\Sabre; +use OCA\Files_Versions\Versions\INameableVersion; +use OCA\Files_Versions\Versions\INameableVersionBackend; use OCA\Files_Versions\Versions\IVersion; use OCA\Files_Versions\Versions\IVersionManager; use OCP\Files\NotFoundException; @@ -70,6 +72,7 @@ class VersionFile implements IFile { } public function delete() { + // TODO: implement version deletion throw new Forbidden(); } @@ -81,6 +84,23 @@ class VersionFile implements IFile { throw new Forbidden(); } + public function getLabel(): ?string { + if ($this->version instanceof INameableVersion) { + return $this->version->getLabel(); + } else { + return null; + } + } + + public function setLabel($label): bool { + if ($this->versionManager instanceof INameableVersionBackend) { + $this->versionManager->setVersionLabel($this->version, $label); + return true; + } else { + return false; + } + } + public function getLastModified(): int { return $this->version->getTimestamp(); } diff --git a/apps/files_versions/lib/Versions/INameableVersion.php b/apps/files_versions/lib/Versions/INameableVersion.php new file mode 100644 index 00000000000..b6ddb951e25 --- /dev/null +++ b/apps/files_versions/lib/Versions/INameableVersion.php @@ -0,0 +1,37 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2022 Louis Chmn <louis@chmn.me> + * + * @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 26.0.0 + */ +interface INameableVersion { + /** + * Get the user created label + * + * @return string + * @since 26.0.0 + */ + public function getLabel(): string; +} diff --git a/apps/files_versions/lib/Versions/INameableVersionBackend.php b/apps/files_versions/lib/Versions/INameableVersionBackend.php new file mode 100644 index 00000000000..4a8c094cf18 --- /dev/null +++ b/apps/files_versions/lib/Versions/INameableVersionBackend.php @@ -0,0 +1,36 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2022 Louis Chmn <louis@chmn.me> + * + * @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 26.0.0 + */ +interface INameableVersionBackend { + /** + * Set the label for a version. + * + * @since 26.0.0 + */ + public function setVersionLabel(IVersion $version, string $label): void; +} diff --git a/apps/files_versions/lib/Versions/LegacyVersionsBackend.php b/apps/files_versions/lib/Versions/LegacyVersionsBackend.php index 4ea0985e113..90c7cb930d5 100644 --- a/apps/files_versions/lib/Versions/LegacyVersionsBackend.php +++ b/apps/files_versions/lib/Versions/LegacyVersionsBackend.php @@ -28,25 +28,36 @@ namespace OCA\Files_Versions\Versions; use OC\Files\View; use OCA\Files_Sharing\SharedStorage; +use OCA\Files_Versions\Db\VersionEntity; +use OCA\Files_Versions\Db\VersionsMapper; use OCA\Files_Versions\Storage; use OCP\Files\File; use OCP\Files\FileInfo; use OCP\Files\Folder; +use OCP\Files\IMimeTypeLoader; use OCP\Files\IRootFolder; +use OCP\Files\Node; use OCP\Files\NotFoundException; use OCP\Files\Storage\IStorage; use OCP\IUser; use OCP\IUserManager; -class LegacyVersionsBackend implements IVersionBackend { - /** @var IRootFolder */ - private $rootFolder; - /** @var IUserManager */ - private $userManager; - - public function __construct(IRootFolder $rootFolder, IUserManager $userManager) { +class LegacyVersionsBackend implements IVersionBackend, INameableVersionBackend, IDeletableVersionBackend { + private IRootFolder $rootFolder; + private IUserManager $userManager; + private VersionsMapper $versionsMapper; + private IMimeTypeLoader $mimeTypeLoader; + + public function __construct( + IRootFolder $rootFolder, + IUserManager $userManager, + VersionsMapper $versionsMapper, + IMimeTypeLoader $mimeTypeLoader + ) { $this->rootFolder = $rootFolder; $this->userManager = $userManager; + $this->versionsMapper = $versionsMapper; + $this->mimeTypeLoader = $mimeTypeLoader; } public function useBackendForStorage(IStorage $storage): bool { @@ -63,21 +74,60 @@ class LegacyVersionsBackend implements IVersionBackend { $userFolder = $this->rootFolder->getUserFolder($user->getUID()); $nodes = $userFolder->getById($file->getId()); $file2 = array_pop($nodes); - $versions = Storage::getVersions($user->getUID(), $userFolder->getRelativePath($file2->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'], + + $versions = $this->getVersionsForFileFromDB($file2, $user); + + if (count($versions) > 0) { + return $versions; + } + + // Insert the entry in the DB for the current version. + if ($file2->getSize() > 0) { + $versionEntity = new VersionEntity(); + $versionEntity->setFileId($file2->getId()); + $versionEntity->setTimestamp($file2->getMTime()); + $versionEntity->setSize($file2->getSize()); + $versionEntity->setMimetype($this->mimeTypeLoader->getId($file2->getMimetype())); + $versionEntity->setMetadata([]); + $this->versionsMapper->insert($versionEntity); + } + + // Insert entries in the DB for existing versions. + $versionsOnFS = Storage::getVersions($user->getUID(), $userFolder->getRelativePath($file2->getPath())); + foreach ($versionsOnFS as $version) { + $versionEntity = new VersionEntity(); + $versionEntity->setFileId($file2->getId()); + $versionEntity->setTimestamp((int)$version['version']); + $versionEntity->setSize((int)$version['size']); + $versionEntity->setMimetype($this->mimeTypeLoader->getId($version['mimetype'])); + $versionEntity->setMetadata([]); + $this->versionsMapper->insert($versionEntity); + } + + return $this->getVersionsForFileFromDB($file2, $user); + } + + /** + * @return IVersion[] + */ + private function getVersionsForFileFromDB(Node $file, IUser $user): array { + $userFolder = $this->rootFolder->getUserFolder($user->getUID()); + + return array_map( + fn (VersionEntity $versionEntity) => new Version( + $versionEntity->getTimestamp(), + $versionEntity->getTimestamp(), + $file->getName(), + $versionEntity->getSize(), + $this->mimeTypeLoader->getMimetypeById($versionEntity->getMimetype()), + $userFolder->getRelativePath($file->getPath()), $file, $this, - $user - ); - }, $versions); + $user, + $versionEntity->getLabel(), + ), + $this->versionsMapper->findAllVersionsForFileId($file->getId()) + ); } public function createVersion(IUser $user, FileInfo $file) { @@ -125,4 +175,16 @@ class LegacyVersionsBackend implements IVersionBackend { $file = $versionFolder->get($userFolder->getRelativePath($sourceFile->getPath()) . '.v' . $revision); return $file; } + + public function setVersionLabel(IVersion $version, string $label): void { + $versionEntity = $this->versionsMapper->findVersionForFileId( + $version->getSourceFile()->getId(), + $version->getTimestamp(), + ); + if (trim($label) === '') { + $label = null; + } + $versionEntity->setLabel($label ?? ''); + $this->versionsMapper->update($versionEntity); + } } diff --git a/apps/files_versions/lib/Versions/Version.php b/apps/files_versions/lib/Versions/Version.php index 9979933ebc5..e87c2a593d7 100644 --- a/apps/files_versions/lib/Versions/Version.php +++ b/apps/files_versions/lib/Versions/Version.php @@ -28,7 +28,7 @@ namespace OCA\Files_Versions\Versions; use OCP\Files\FileInfo; use OCP\IUser; -class Version implements IVersion { +class Version implements IVersion, INameableVersion { /** @var int */ private $timestamp; @@ -38,6 +38,8 @@ class Version implements IVersion { /** @var string */ private $name; + private string $label; + /** @var int */ private $size; @@ -65,11 +67,13 @@ class Version implements IVersion { string $path, FileInfo $sourceFileInfo, IVersionBackend $backend, - IUser $user + IUser $user, + string $label = '' ) { $this->timestamp = $timestamp; $this->revisionId = $revisionId; $this->name = $name; + $this->label = $label; $this->size = $size; $this->mimetype = $mimetype; $this->path = $path; @@ -102,6 +106,10 @@ class Version implements IVersion { return $this->name; } + public function getLabel(): string { + return $this->label; + } + public function getMimeType(): string { return $this->mimetype; } diff --git a/apps/files_versions/lib/Versions/VersionManager.php b/apps/files_versions/lib/Versions/VersionManager.php index 4700f1b208b..4787de7fdac 100644 --- a/apps/files_versions/lib/Versions/VersionManager.php +++ b/apps/files_versions/lib/Versions/VersionManager.php @@ -30,7 +30,7 @@ use OCP\Files\FileInfo; use OCP\Files\Storage\IStorage; use OCP\IUser; -class VersionManager implements IVersionManager { +class VersionManager implements IVersionManager, INameableVersionBackend { /** @var (IVersionBackend[])[] */ private $backends = []; @@ -110,4 +110,11 @@ class VersionManager implements IVersionManager { public function useBackendForStorage(IStorage $storage): bool { return false; } + + public function setVersionLabel(IVersion $version, string $label): void { + $backend = $this->getBackendForStorage($version->getSourceFile()->getStorage()); + if ($backend instanceof INameableVersionBackend) { + $backend->setVersionLabel($version, $label); + } + } } |