Skip to content

Commit

Permalink
Add listener and interfaces to allow versions migration accros storages
Browse files Browse the repository at this point in the history
Signed-off-by: Louis Chemineau <louis@chmn.me>
  • Loading branch information
artonge committed Mar 14, 2024
1 parent a2e752c commit ebd56af
Show file tree
Hide file tree
Showing 10 changed files with 248 additions and 3 deletions.
2 changes: 2 additions & 0 deletions apps/files_versions/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
'OCA\\Files_Versions\\Listener\\LoadAdditionalListener' => $baseDir . '/../lib/Listener/LoadAdditionalListener.php',
'OCA\\Files_Versions\\Listener\\LoadSidebarListener' => $baseDir . '/../lib/Listener/LoadSidebarListener.php',
'OCA\\Files_Versions\\Listener\\VersionAuthorListener' => $baseDir . '/../lib/Listener/VersionAuthorListener.php',
'OCA\\Files_Versions\\Listener\\VersionStorageMoveListener' => $baseDir . '/../lib/Listener/VersionStorageMoveListener.php',
'OCA\\Files_Versions\\Migration\\Version1020Date20221114144058' => $baseDir . '/../lib/Migration/Version1020Date20221114144058.php',
'OCA\\Files_Versions\\Sabre\\Plugin' => $baseDir . '/../lib/Sabre/Plugin.php',
'OCA\\Files_Versions\\Sabre\\RestoreFolder' => $baseDir . '/../lib/Sabre/RestoreFolder.php',
Expand All @@ -41,6 +42,7 @@
'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\\IVersionsImporterBackend' => $baseDir . '/../lib/Versions/IVersionsImporterBackend.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',
Expand Down
2 changes: 2 additions & 0 deletions apps/files_versions/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class ComposerStaticInitFiles_Versions
'OCA\\Files_Versions\\Listener\\LoadAdditionalListener' => __DIR__ . '/..' . '/../lib/Listener/LoadAdditionalListener.php',
'OCA\\Files_Versions\\Listener\\LoadSidebarListener' => __DIR__ . '/..' . '/../lib/Listener/LoadSidebarListener.php',
'OCA\\Files_Versions\\Listener\\VersionAuthorListener' => __DIR__ . '/..' . '/../lib/Listener/VersionAuthorListener.php',
'OCA\\Files_Versions\\Listener\\VersionStorageMoveListener' => __DIR__ . '/..' . '/../lib/Listener/VersionStorageMoveListener.php',
'OCA\\Files_Versions\\Migration\\Version1020Date20221114144058' => __DIR__ . '/..' . '/../lib/Migration/Version1020Date20221114144058.php',
'OCA\\Files_Versions\\Sabre\\Plugin' => __DIR__ . '/..' . '/../lib/Sabre/Plugin.php',
'OCA\\Files_Versions\\Sabre\\RestoreFolder' => __DIR__ . '/..' . '/../lib/Sabre/RestoreFolder.php',
Expand All @@ -56,6 +57,7 @@ class ComposerStaticInitFiles_Versions
'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\\IVersionsImporterBackend' => __DIR__ . '/..' . '/../lib/Versions/IVersionsImporterBackend.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',
Expand Down
8 changes: 6 additions & 2 deletions apps/files_versions/lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
use OCA\Files_Versions\Listener\LoadAdditionalListener;
use OCA\Files_Versions\Listener\LoadSidebarListener;
use OCA\Files_Versions\Listener\VersionAuthorListener;
use OCA\Files_Versions\Listener\VersionStorageMoveListener;
use OCA\Files_Versions\Versions\IVersionManager;
use OCA\Files_Versions\Versions\VersionManager;
use OCP\Accounts\IAccountManager;
Expand Down Expand Up @@ -109,19 +110,22 @@ public function register(IRegistrationContext $context): void {
$context->registerEventListener(LoadAdditionalScriptsEvent::class, LoadAdditionalListener::class);
$context->registerEventListener(LoadSidebar::class, LoadSidebarListener::class);

$context->registerEventListener(BeforeNodeRenamedEvent::class, VersionStorageMoveListener::class);

$context->registerEventListener(NodeCreatedEvent::class, FileEventsListener::class);
$context->registerEventListener(BeforeNodeTouchedEvent::class, FileEventsListener::class);
$context->registerEventListener(NodeTouchedEvent::class, FileEventsListener::class);
$context->registerEventListener(BeforeNodeWrittenEvent::class, FileEventsListener::class);
$context->registerEventListener(NodeWrittenEvent::class, FileEventsListener::class);
$context->registerEventListener(BeforeNodeDeletedEvent::class, FileEventsListener::class);
$context->registerEventListener(NodeDeletedEvent::class, FileEventsListener::class);
// TODO: what will be the impact of moving the version across storage on this listener?
$context->registerEventListener(NodeRenamedEvent::class, FileEventsListener::class);
$context->registerEventListener(NodeCopiedEvent::class, FileEventsListener::class);
$context->registerEventListener(BeforeNodeRenamedEvent::class, FileEventsListener::class);
$context->registerEventListener(BeforeNodeCopiedEvent::class, FileEventsListener::class);
$context->registerEventListener(NodeWrittenEvent::class, MetadataFileEvents::class);

$context->registerEventListener(NodeWrittenEvent::class, VersionAuthorListener::class);
}

public function boot(IBootContext $context): void {
Expand Down
58 changes: 58 additions & 0 deletions apps/files_versions/lib/Listener/VersionStorageMoveListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

declare(strict_types=1);

/**
* @author Eduardo Morales emoral435@gmail.com>
*
* @license GNU AGPL-3.0-or-later
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Files_Versions\Listener;

use OCA\Files_Versions\Versions\IVersionManager;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Files\Events\Node\BeforeNodeRenamedEvent;
use OCP\Files\File;
use OCP\IUserSession;

/** @template-implements IEventListener<BeforeNodeRenamedEvent> */
class VersionStorageMoveListener implements IEventListener {
public function __construct(
private IVersionManager $versionManager,
private IUserSession $userSession,
) {
}

/**
* @abstract Moves version across storages if necessary.
* @param Event $event
*/
public function handle(Event $event): void {
/** @var BeforeNodeRenamedEvent $event **/

$source = $event->getSource();
$target = $event->getTarget();

if (!($target instanceof File)) {
return;
}

$user = $this->userSession->getUser();

$this->versionManager->moveVersionsAcrossBackends($user, $source, $target);

Check notice

Code scanning / Psalm

PossiblyNullArgument Note

Argument 1 of OCA\Files_Versions\Versions\IVersionManager::moveVersionsAcrossBackends cannot be null, possibly null value provided
}
}
8 changes: 8 additions & 0 deletions apps/files_versions/lib/Versions/IMetadataVersion.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@
* @since 29.0.0
*/
interface IMetadataVersion {
/**
* retrieves the all the metadata
*
* @return string[]
* @since 29.0.0
*/
public function getMetadata(): array;

/**
* retrieves the metadata value from our $key param
*
Expand Down
13 changes: 13 additions & 0 deletions apps/files_versions/lib/Versions/IVersionManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
*/
namespace OCA\Files_Versions\Versions;

use OCP\Files\Node;
use OCP\IUser;

/**
* @since 15.0.0
*/
Expand All @@ -37,4 +40,14 @@ interface IVersionManager extends IVersionBackend {
* @since 15.0.0
*/
public function registerBackend(string $storageType, IVersionBackend $backend);

/**
* Move versions across different backends
*
* @param IUser $user
* @param Node $source
* @param Node $target
* @since 29.0.0
*/
public function moveVersionsAcrossBackends(IUser $user, Node $source, Node $target): void;
}
54 changes: 54 additions & 0 deletions apps/files_versions/lib/Versions/IVersionsImporterBackend.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);

/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author 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 29.0.0
*/
interface IVersionsImporterBackend {
/**
* Get all versions for a file
*
* @param IUser $user
* @param FileInfo $target
* @param IVersion[] $versions
* @since 29.0.0
*/
public function importVersionsForFile(IUser $user, FileInfo $target, array $versions): void;

/**
* Clear all versions for a file
*
* @param IUser $user
* @param FileInfo $node
* @since 29.0.0
*/
public function clearVersionsForFile(IUser $user, FileInfo $node): void;
}
65 changes: 64 additions & 1 deletion apps/files_versions/lib/Versions/LegacyVersionsBackend.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
use OCP\IUserManager;
use OCP\IUserSession;

class LegacyVersionsBackend implements IVersionBackend, IDeletableVersionBackend, INeedSyncVersionBackend, IMetadataVersionBackend {
class LegacyVersionsBackend implements IVersionBackend, IDeletableVersionBackend, INeedSyncVersionBackend, IMetadataVersionBackend, IVersionsImporterBackend {
public function __construct(
private IRootFolder $rootFolder,
private IUserManager $userManager,
Expand Down Expand Up @@ -173,6 +173,21 @@ public function createVersion(IUser $user, FileInfo $file) {
$userView->getFileInfo('files_versions/' . $relativePath . '.v' . $file->getMtime());
}

public function writeVersion(IUser $user, int $revision, stream $fileContent) {

Check failure

Code scanning / Psalm

UndefinedClass Error

Class, interface or enum named OCA\Files_Versions\Versions\stream does not exist

Check notice

Code scanning / Psalm

MissingReturnType Note

Method OCA\Files_Versions\Versions\LegacyVersionsBackend::writeVersion does not have a return type, expecting void

Check failure on line 176 in apps/files_versions/lib/Versions/LegacyVersionsBackend.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

UndefinedClass

apps/files_versions/lib/Versions/LegacyVersionsBackend.php:176:59: UndefinedClass: Class, interface or enum named OCA\Files_Versions\Versions\stream does not exist (see https://psalm.dev/019)
$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) {
if (!$this->currentUserHasPermissions($version->getSourceFile(), \OCP\Constants::PERMISSION_UPDATE)) {
throw new Forbidden('You cannot restore this version because you do not have update permissions on the source file.');
Expand Down Expand Up @@ -296,4 +311,52 @@ public function setMetadataValue(Node $node, int $revision, string $key, string
$versionEntity->setMetadataValue($key, $value);
$this->versionsMapper->update($versionEntity);
}

public function importVersionsForFile(IUser $user, FileInfo $target, array $versions): void {
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
$relativePath = $userFolder->getRelativePath($target->getPath());
$userView = new View('/' . $user->getUID());
// create all parent folders
Storage::createMissingDirectories($relativePath, $userView);

Check notice

Code scanning / Psalm

PossiblyNullArgument Note

Argument 1 of OCA\Files_Versions\Storage::createMissingDirectories cannot be null, possibly null value provided
Storage::scheduleExpire($user->getUID(), $relativePath);

Check notice

Code scanning / Psalm

PossiblyNullArgument Note

Argument 2 of OCA\Files_Versions\Storage::scheduleExpire cannot be null, possibly null value provided

foreach ($versions as $version) {
// 1. Move the file to the new location
$versionPath = $version->get();

Check failure

Code scanning / Psalm

UndefinedInterfaceMethod Error

Method OCA\Files_Versions\Versions\IVersion::get does not exist

Check failure on line 325 in apps/files_versions/lib/Versions/LegacyVersionsBackend.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

UndefinedInterfaceMethod

apps/files_versions/lib/Versions/LegacyVersionsBackend.php:325:29: UndefinedInterfaceMethod: Method OCA\Files_Versions\Versions\IVersion::get does not exist (see https://psalm.dev/181)
$newVersionPath = 'files_versions/' . $relativePath . '.v' . $version->getTimestamp();

Check notice

Code scanning / Psalm

PossiblyNullOperand Note

Cannot concatenate with a possibly null null|string
$userView->rename($versionPath, $newVersionPath);
// ensure the file is scanned
$userView->getFileInfo($newVersionPath);

// $destinationMount = $userView->getMount()->getStorage()
// $originalMount = $version->getVersionPath();
// $originalVersionCache = $originalMount->getStorage()->getCache();
// $revision = $version->getTimestamp();

// $versionInternalPath = $versionFolder->getInternalPath() . '/' . $revision;
// $originalVersionInternalPath = $target->getInternalPath();

// $destinationMount->getStorage()->moveFromStorage($originalMount->getStorage(), $originalVersionInternalPath, $versionInternalPath);
// $destinationMount->getStorage()->getCache()->moveFromCache($originalVersionCache, $originalVersionCache->get($originalVersionInternalPath), $versionInternalPath);

// 2. Create the entity in the database
$versionEntity = new VersionEntity();
$versionEntity->setFileId($target->getId());

Check notice

Code scanning / Psalm

PossiblyNullArgument Note

Argument 1 of setFileId cannot be null, possibly null value provided
$versionEntity->setTimestamp($version->getTimestamp());
$versionEntity->setSize($version->getSize());
$versionEntity->setMimetype($this->mimeTypeLoader->getId($version->getMimetype()));
if ($version instanceof IMetadataVersion) {
$versionEntity->setMetadata($version->getMetadata());
}
$this->versionsMapper->insert($versionEntity);
}
}

public function clearVersionsForFile(IUser $user, FileInfo $node): void {
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
$relativePath = $userFolder->getRelativePath($node->getPath());

Storage::delete($relativePath);
$this->versionsMapper->deleteAllVersionsForFileId($node->getId());

Check notice

Code scanning / Psalm

PossiblyNullArgument Note

Argument 1 of OCA\Files_Versions\Db\VersionsMapper::deleteAllVersionsForFileId cannot be null, possibly null value provided
}
}
4 changes: 4 additions & 0 deletions apps/files_versions/lib/Versions/Version.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ public function getUser(): IUser {
return $this->user;
}

public function getMetadata(): array {
return $this->metadata;
}

public function getMetadataValue(string $key): ?string {
return $this->metadata[$key] ?? null;
}
Expand Down
37 changes: 37 additions & 0 deletions apps/files_versions/lib/Versions/VersionManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -204,4 +204,41 @@ private static function handleAppLocks(callable $callback): ?bool {
}
}

/**
* TODO: test
* - share to share - Should not move
* - share to home - Should not move
* - home to share - Should not move
* - home to home - Should not move
* - groupfolder to home - Should move
* - home to groupfolder - Should move
* - groupfolder to share - Should move
* - share to groupfolder - Should move
* - home to s3 - Should not move
* - s3 to home - Should move
* - s3 to groupfolder - Should move
* - groupfolder to s3 - Should not move
*/
public function moveVersionsAcrossBackends(IUser $user, Node $source, Node $target): void {
$sourceStorage = $source->getStorage();
// Look at the parent because the node itself does not exist yet.
$targetStorage = $target->getParent()->getStorage();

// If same storage, nothing to do
if ($sourceStorage === $targetStorage) {
return;
}

$sourceBackend = $this->getBackendForStorage($sourceStorage);
$targetBackend = $this->getBackendForStorage($targetStorage);

if ($targetBackend instanceof IVersionsImporterBackend) {
$versions = $sourceBackend->getVersionsForFile($user, $source);
$targetBackend->importVersionsForFile($user, $target, $versions);
}

if ($sourceBackend instanceof IVersionsImporterBackend) {
$sourceBackend->clearVersionsForFile($user, $source);
}
}
}

0 comments on commit ebd56af

Please sign in to comment.