|
@@ -0,0 +1,154 @@
|
|
|
+<?php
|
|
|
+
|
|
|
+declare(strict_types=1);
|
|
|
+
|
|
|
+/**
|
|
|
+ * @copyright Copyright (c) 2024 Louis Chmn <louis@chmn.me>
|
|
|
+ *
|
|
|
+ * @author Louis Chmn <louis@chmn.me>
|
|
|
+ *
|
|
|
+ * @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) {
|
|
|
+ $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());
|
|
|
+ } 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();
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|