Skip to content

Commit

Permalink
feat(files_versions): Add listener and interfaces to allow versions m…
Browse files Browse the repository at this point in the history
…igration across storages

Signed-off-by: Louis Chemineau <louis@chmn.me>
  • Loading branch information
artonge committed Mar 25, 2024
1 parent 16e4a93 commit d3b22fd
Show file tree
Hide file tree
Showing 15 changed files with 474 additions and 35 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
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.
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());
// 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
134 changes: 134 additions & 0 deletions apps/files_versions/lib/Listener/VersionStorageMoveListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<?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\BeforeNodeCopiedEvent;
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 Node[] */
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 || $event instanceof BeforeNodeCopiedEvent) {
$this->recursivelyPrepareMoveOrCopy($source);
} elseif ($event instanceof NodeRenamedEvent || $event instanceof NodeCopiedEvent) {
$this->recursivelyHandleMoveOrCopy($event, $user, $target, $sourceBackend, $targetBackend);
}
}

private function recursivelyPrepareMoveOrCopy(Node $source): void {
if ($source instanceof File) {
$this->movedNodes[$source->getId()] = $source;
} elseif ($source instanceof Folder) {
foreach ($source->getDirectoryListing() as $child) {
$this->recursivelyPrepareMoveOrCopy($child);
}
}
}

private function recursivelyHandleMoveOrCopy(Event $event, IUser $user, Node $target, IVersionBackend $sourceBackend, IVersionBackend $targetBackend): void {
if ($target instanceof File) {
$originalSource = $this->movedNodes[$target->getId()];
$this->handleMoveOrCopy($event, $user, $originalSource, $target, $sourceBackend, $targetBackend);

Check notice

Code scanning / Psalm

ArgumentTypeCoercion Note

Argument 3 of OCA\Files_Versions\Listener\VersionStorageMoveListener::handleMoveOrCopy expects OCP\Files\File, but parent type OCP\Files\Node provided
} else {
/** @var Folder $target */
foreach ($target->getDirectoryListing() as $child) {
$this->recursivelyHandleMoveOrCopy($event, $user, $child, $sourceBackend, $targetBackend);
}
}
}

// Called only during NodeRenamedEvent or NodeCopiedEvent
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 <louis@chmn.me>
*
* @author 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;

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

/**
* @since 29.0.0
*/
interface IVersionsImporterBackend {
/**
* Import the given versions for the target file.
*
* @param Node $target - The target is not yet created.
* @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

0 comments on commit d3b22fd

Please sign in to comment.