Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add listener and interfaces to allow versions migration across storage #44187

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
6 changes: 6 additions & 0 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,6 +110,11 @@ 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(NodeRenamedEvent::class, VersionStorageMoveListener::class);
$context->registerEventListener(BeforeNodeCopiedEvent::class, VersionStorageMoveListener::class);
$context->registerEventListener(NodeCopiedEvent::class, VersionStorageMoveListener::class);

$context->registerEventListener(NodeCreatedEvent::class, FileEventsListener::class);
$context->registerEventListener(BeforeNodeTouchedEvent::class, FileEventsListener::class);
$context->registerEventListener(NodeTouchedEvent::class, FileEventsListener::class);
Expand Down
21 changes: 21 additions & 0 deletions apps/files_versions/lib/Listener/FileEventsListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,13 @@ public function pre_remove_hook(Node $node): void {
* of the stored versions along the actual file
*/
public function rename_hook(Node $source, Node $target): void {
$sourceBackend = $this->versionManager->getBackendForStorage($source->getParent()->getStorage());
$targetBackend = $this->versionManager->getBackendForStorage($target->getStorage());
// If different backends, do nothing.
artonge marked this conversation as resolved.
Show resolved Hide resolved
if ($sourceBackend !== $targetBackend) {
return;
}

$oldPath = $this->getPathForNode($source);
$newPath = $this->getPathForNode($target);
Storage::renameOrCopy($oldPath, $newPath, 'rename');
Expand All @@ -312,6 +319,13 @@ public function rename_hook(Node $source, Node $target): void {
* the stored versions to the new location
*/
public function copy_hook(Node $source, Node $target): void {
$sourceBackend = $this->versionManager->getBackendForStorage($source->getParent()->getStorage());
$targetBackend = $this->versionManager->getBackendForStorage($target->getStorage());
artonge marked this conversation as resolved.
Show resolved Hide resolved
// If different backends, do nothing.
if ($sourceBackend !== $targetBackend) {
return;
}

$oldPath = $this->getPathForNode($source);
$newPath = $this->getPathForNode($target);
Storage::renameOrCopy($oldPath, $newPath, 'copy');
Expand All @@ -325,6 +339,13 @@ public function copy_hook(Node $source, Node $target): void {
*
*/
public function pre_renameOrCopy_hook(Node $source, Node $target): void {
$sourceBackend = $this->versionManager->getBackendForStorage($source->getStorage());
$targetBackend = $this->versionManager->getBackendForStorage($target->getParent()->getStorage());
// If different backends, do nothing.
if ($sourceBackend !== $targetBackend) {
return;
}

// if we rename a movable mount point, then the versions don't have
// to be renamed
$oldPath = $this->getPathForNode($source);
Expand Down
154 changes: 154 additions & 0 deletions apps/files_versions/lib/Listener/VersionStorageMoveListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
<?php

declare(strict_types=1);

/**
* @copyright Copyright (c) 2024 Louis Chmn <[email protected]>
*
* @author Louis Chmn <[email protected]>
*
* @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 Exception;
use OC\Files\Node\NonExistingFile;
use OCA\Files_Versions\Versions\IVersionBackend;
use OCA\Files_Versions\Versions\IVersionManager;
use OCA\Files_Versions\Versions\IVersionsImporterBackend;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Files\Events\Node\AbstractNodesEvent;
use OCP\Files\Events\Node\BeforeNodeRenamedEvent;
use OCP\Files\Events\Node\NodeCopiedEvent;
use OCP\Files\Events\Node\NodeRenamedEvent;
use OCP\Files\File;
use OCP\Files\Folder;
use OCP\Files\Node;
use OCP\Files\Storage\IStorage;
use OCP\IUser;
use OCP\IUserSession;

/** @template-implements IEventListener<Event> */
class VersionStorageMoveListener implements IEventListener {
/** @var File[] */
private array $movedNodes = [];

public function __construct(
private IVersionManager $versionManager,
private IUserSession $userSession,
) {
}

/**
* @abstract Moves version across storages if necessary.
* @throws Exception No user in session
*/
public function handle(Event $event): void {
if (!($event instanceof AbstractNodesEvent)) {
return;
}

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

$sourceStorage = $this->getNodeStorage($source);
$targetStorage = $this->getNodeStorage($target);

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

// If same backend, nothing to do.
if ($sourceBackend === $targetBackend) {
return;
}

$user = $this->userSession->getUser() ?? $source->getOwner();

if ($user === null) {
throw new Exception("Cannot move versions across storages without a user.");
}

if ($event instanceof BeforeNodeRenamedEvent) {
artonge marked this conversation as resolved.
Show resolved Hide resolved
$this->recursivelyPrepareMove($source);
} elseif ($event instanceof NodeRenamedEvent || $event instanceof NodeCopiedEvent) {
$this->recursivelyHandleMoveOrCopy($event, $user, $source, $target, $sourceBackend, $targetBackend);
}
}

/**
* Store all sub files in this->movedNodes so their info can be used after the operation.
*/
private function recursivelyPrepareMove(Node $source): void {
if ($source instanceof File) {
$this->movedNodes[$source->getId()] = $source;
} elseif ($source instanceof Folder) {
foreach ($source->getDirectoryListing() as $child) {
$this->recursivelyPrepareMove($child);
}
}
}

/**
* Call handleMoveOrCopy on each sub files
* @param NodeRenamedEvent|NodeCopiedEvent $event
*/
private function recursivelyHandleMoveOrCopy(Event $event, IUser $user, ?Node $source, Node $target, IVersionBackend $sourceBackend, IVersionBackend $targetBackend): void {
if ($target instanceof File) {
if ($event instanceof NodeRenamedEvent) {
$source = $this->movedNodes[$target->getId()];
}

/** @var File $source */
$this->handleMoveOrCopy($event, $user, $source, $target, $sourceBackend, $targetBackend);
Fixed Show fixed Hide fixed
} elseif ($target instanceof Folder) {
/** @var Folder $source */
foreach ($target->getDirectoryListing() as $targetChild) {
if ($event instanceof NodeCopiedEvent) {
$sourceChild = $source->get($targetChild->getName());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any reason for not using the same method as used for renames.

The "rename logic" gets the nodes from a getDirectoryListing per folder while this has to do a get for every child. So the "rename logic" should be significantly more efficient.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it failed in some way, let me finish testing on the groupfolder side, and I'll try it again.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, in case of copy, we need to do the tree walking to have the id of the source in any case, so it won't improve performances to cache the nodes.

} else {
$sourceChild = null;
}

$this->recursivelyHandleMoveOrCopy($event, $user, $sourceChild, $targetChild, $sourceBackend, $targetBackend);
Fixed Show fixed Hide fixed
}
}
}

/**
* Called only during NodeRenamedEvent or NodeCopiedEvent
* Will send the source node versions to the new backend, and then delete them from the old backend.
* @param NodeRenamedEvent|NodeCopiedEvent $event
*/
private function handleMoveOrCopy(Event $event, IUser $user, File $source, File $target, IVersionBackend $sourceBackend, IVersionBackend $targetBackend): void {
if ($targetBackend instanceof IVersionsImporterBackend) {
$versions = $sourceBackend->getVersionsForFile($user, $source);
$targetBackend->importVersionsForFile($user, $source, $target, $versions);
}

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

private function getNodeStorage(Node $node): IStorage {
if ($node instanceof NonExistingFile) {
return $node->getParent()->getStorage();
} else {
return $node->getStorage();
}
}
}
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
8 changes: 8 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,8 @@
*/
namespace OCA\Files_Versions\Versions;

use OCP\Files\Storage\IStorage;

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

/**
* @throws BackendNotFoundException
* @since 29.0.0
*/
public function getBackendForStorage(IStorage $storage): IVersionBackend;
}
50 changes: 50 additions & 0 deletions apps/files_versions/lib/Versions/IVersionsImporterBackend.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

declare(strict_types=1);

/**
* @copyright Copyright (c) 2024 Louis Chmn <[email protected]>
*
* @author Louis Chmn <[email protected]>
*
* @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\Node;
use OCP\IUser;

/**
* @since 29.0.0
*/
interface IVersionsImporterBackend {
/**
* Import the given versions for the target file.
*
* @param Node $source - The source might not exist anymore.
* @param IVersion[] $versions
* @since 29.0.0
*/
public function importVersionsForFile(IUser $user, Node $source, Node $target, array $versions): void;

/**
* Clear all versions for a file
*
* @since 29.0.0
*/
public function clearVersionsForFile(IUser $user, Node $source, Node $target): void;
}
Loading
Loading