-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Add listener and interfaces to allow versions migration across storage #44187
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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); | ||
|
||
| } elseif ($target instanceof Folder) { | ||
| /** @var Folder $source */ | ||
| foreach ($target->getDirectoryListing() as $targetChild) { | ||
| if ($event instanceof NodeCopiedEvent) { | ||
| $sourceChild = $source->get($targetChild->getName()); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
|
||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * 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(); | ||
| } | ||
| } | ||
| } | ||
| 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; | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.