Skip to content

Commit 14ef689

Browse files
committed
feat(files_versions): Add listener and interfaces to allow versions migration across storages
Signed-off-by: Louis Chemineau <louis@chmn.me>
1 parent 1a55084 commit 14ef689

16 files changed

+494
-36
lines changed

apps/files_versions/composer/composer/autoload_classmap.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
'OCA\\Files_Versions\\Listener\\LoadAdditionalListener' => $baseDir . '/../lib/Listener/LoadAdditionalListener.php',
2323
'OCA\\Files_Versions\\Listener\\LoadSidebarListener' => $baseDir . '/../lib/Listener/LoadSidebarListener.php',
2424
'OCA\\Files_Versions\\Listener\\VersionAuthorListener' => $baseDir . '/../lib/Listener/VersionAuthorListener.php',
25+
'OCA\\Files_Versions\\Listener\\VersionStorageMoveListener' => $baseDir . '/../lib/Listener/VersionStorageMoveListener.php',
2526
'OCA\\Files_Versions\\Migration\\Version1020Date20221114144058' => $baseDir . '/../lib/Migration/Version1020Date20221114144058.php',
2627
'OCA\\Files_Versions\\Sabre\\Plugin' => $baseDir . '/../lib/Sabre/Plugin.php',
2728
'OCA\\Files_Versions\\Sabre\\RestoreFolder' => $baseDir . '/../lib/Sabre/RestoreFolder.php',
@@ -41,6 +42,7 @@
4142
'OCA\\Files_Versions\\Versions\\IVersion' => $baseDir . '/../lib/Versions/IVersion.php',
4243
'OCA\\Files_Versions\\Versions\\IVersionBackend' => $baseDir . '/../lib/Versions/IVersionBackend.php',
4344
'OCA\\Files_Versions\\Versions\\IVersionManager' => $baseDir . '/../lib/Versions/IVersionManager.php',
45+
'OCA\\Files_Versions\\Versions\\IVersionsImporterBackend' => $baseDir . '/../lib/Versions/IVersionsImporterBackend.php',
4446
'OCA\\Files_Versions\\Versions\\LegacyVersionsBackend' => $baseDir . '/../lib/Versions/LegacyVersionsBackend.php',
4547
'OCA\\Files_Versions\\Versions\\Version' => $baseDir . '/../lib/Versions/Version.php',
4648
'OCA\\Files_Versions\\Versions\\VersionManager' => $baseDir . '/../lib/Versions/VersionManager.php',

apps/files_versions/composer/composer/autoload_static.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class ComposerStaticInitFiles_Versions
3737
'OCA\\Files_Versions\\Listener\\LoadAdditionalListener' => __DIR__ . '/..' . '/../lib/Listener/LoadAdditionalListener.php',
3838
'OCA\\Files_Versions\\Listener\\LoadSidebarListener' => __DIR__ . '/..' . '/../lib/Listener/LoadSidebarListener.php',
3939
'OCA\\Files_Versions\\Listener\\VersionAuthorListener' => __DIR__ . '/..' . '/../lib/Listener/VersionAuthorListener.php',
40+
'OCA\\Files_Versions\\Listener\\VersionStorageMoveListener' => __DIR__ . '/..' . '/../lib/Listener/VersionStorageMoveListener.php',
4041
'OCA\\Files_Versions\\Migration\\Version1020Date20221114144058' => __DIR__ . '/..' . '/../lib/Migration/Version1020Date20221114144058.php',
4142
'OCA\\Files_Versions\\Sabre\\Plugin' => __DIR__ . '/..' . '/../lib/Sabre/Plugin.php',
4243
'OCA\\Files_Versions\\Sabre\\RestoreFolder' => __DIR__ . '/..' . '/../lib/Sabre/RestoreFolder.php',
@@ -56,6 +57,7 @@ class ComposerStaticInitFiles_Versions
5657
'OCA\\Files_Versions\\Versions\\IVersion' => __DIR__ . '/..' . '/../lib/Versions/IVersion.php',
5758
'OCA\\Files_Versions\\Versions\\IVersionBackend' => __DIR__ . '/..' . '/../lib/Versions/IVersionBackend.php',
5859
'OCA\\Files_Versions\\Versions\\IVersionManager' => __DIR__ . '/..' . '/../lib/Versions/IVersionManager.php',
60+
'OCA\\Files_Versions\\Versions\\IVersionsImporterBackend' => __DIR__ . '/..' . '/../lib/Versions/IVersionsImporterBackend.php',
5961
'OCA\\Files_Versions\\Versions\\LegacyVersionsBackend' => __DIR__ . '/..' . '/../lib/Versions/LegacyVersionsBackend.php',
6062
'OCA\\Files_Versions\\Versions\\Version' => __DIR__ . '/..' . '/../lib/Versions/Version.php',
6163
'OCA\\Files_Versions\\Versions\\VersionManager' => __DIR__ . '/..' . '/../lib/Versions/VersionManager.php',

apps/files_versions/lib/AppInfo/Application.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
use OCA\Files_Versions\Listener\LoadAdditionalListener;
3838
use OCA\Files_Versions\Listener\LoadSidebarListener;
3939
use OCA\Files_Versions\Listener\VersionAuthorListener;
40+
use OCA\Files_Versions\Listener\VersionStorageMoveListener;
4041
use OCA\Files_Versions\Versions\IVersionManager;
4142
use OCA\Files_Versions\Versions\VersionManager;
4243
use OCP\Accounts\IAccountManager;
@@ -109,6 +110,11 @@ public function register(IRegistrationContext $context): void {
109110
$context->registerEventListener(LoadAdditionalScriptsEvent::class, LoadAdditionalListener::class);
110111
$context->registerEventListener(LoadSidebar::class, LoadSidebarListener::class);
111112

113+
$context->registerEventListener(BeforeNodeRenamedEvent::class, VersionStorageMoveListener::class);
114+
$context->registerEventListener(NodeRenamedEvent::class, VersionStorageMoveListener::class);
115+
$context->registerEventListener(BeforeNodeCopiedEvent::class, VersionStorageMoveListener::class);
116+
$context->registerEventListener(NodeCopiedEvent::class, VersionStorageMoveListener::class);
117+
112118
$context->registerEventListener(NodeCreatedEvent::class, FileEventsListener::class);
113119
$context->registerEventListener(BeforeNodeTouchedEvent::class, FileEventsListener::class);
114120
$context->registerEventListener(NodeTouchedEvent::class, FileEventsListener::class);

apps/files_versions/lib/Listener/FileEventsListener.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,13 @@ public function pre_remove_hook(Node $node): void {
300300
* of the stored versions along the actual file
301301
*/
302302
public function rename_hook(Node $source, Node $target): void {
303+
$sourceBackend = $this->versionManager->getBackendForStorage($source->getParent()->getStorage());
304+
$targetBackend = $this->versionManager->getBackendForStorage($target->getStorage());
305+
// If different backends, do nothing.
306+
if ($sourceBackend !== $targetBackend) {
307+
return;
308+
}
309+
303310
$oldPath = $this->getPathForNode($source);
304311
$newPath = $this->getPathForNode($target);
305312
Storage::renameOrCopy($oldPath, $newPath, 'rename');
@@ -312,6 +319,13 @@ public function rename_hook(Node $source, Node $target): void {
312319
* the stored versions to the new location
313320
*/
314321
public function copy_hook(Node $source, Node $target): void {
322+
$sourceBackend = $this->versionManager->getBackendForStorage($source->getParent()->getStorage());
323+
$targetBackend = $this->versionManager->getBackendForStorage($target->getStorage());
324+
// If different backends, do nothing.
325+
if ($sourceBackend !== $targetBackend) {
326+
return;
327+
}
328+
315329
$oldPath = $this->getPathForNode($source);
316330
$newPath = $this->getPathForNode($target);
317331
Storage::renameOrCopy($oldPath, $newPath, 'copy');
@@ -325,6 +339,13 @@ public function copy_hook(Node $source, Node $target): void {
325339
*
326340
*/
327341
public function pre_renameOrCopy_hook(Node $source, Node $target): void {
342+
$sourceBackend = $this->versionManager->getBackendForStorage($source->getStorage());
343+
$targetBackend = $this->versionManager->getBackendForStorage($target->getParent()->getStorage());
344+
// If different backends, do nothing.
345+
if ($sourceBackend !== $targetBackend) {
346+
return;
347+
}
348+
328349
// if we rename a movable mount point, then the versions don't have
329350
// to be renamed
330351
$oldPath = $this->getPathForNode($source);
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* @copyright Copyright (c) 2024 Louis Chmn <louis@chmn.me>
7+
*
8+
* @author Louis Chmn <louis@chmn.me>
9+
*
10+
* @license GNU AGPL-3.0-or-later
11+
*
12+
* This code is free software: you can redistribute it and/or modify
13+
* it under the terms of the GNU Affero General Public License, version 3,
14+
* as published by the Free Software Foundation.
15+
*
16+
* This program is distributed in the hope that it will be useful,
17+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
18+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19+
* GNU Affero General Public License for more details.
20+
*
21+
* You should have received a copy of the GNU Affero General Public License, version 3,
22+
* along with this program. If not, see <http://www.gnu.org/licenses/>
23+
*
24+
*/
25+
namespace OCA\Files_Versions\Listener;
26+
27+
use Exception;
28+
use OC\Files\Node\NonExistingFile;
29+
use OCA\Files_Versions\Versions\IVersionBackend;
30+
use OCA\Files_Versions\Versions\IVersionManager;
31+
use OCA\Files_Versions\Versions\IVersionsImporterBackend;
32+
use OCP\EventDispatcher\Event;
33+
use OCP\EventDispatcher\IEventListener;
34+
use OCP\Files\Events\Node\AbstractNodesEvent;
35+
use OCP\Files\Events\Node\BeforeNodeRenamedEvent;
36+
use OCP\Files\Events\Node\NodeCopiedEvent;
37+
use OCP\Files\Events\Node\NodeRenamedEvent;
38+
use OCP\Files\File;
39+
use OCP\Files\Folder;
40+
use OCP\Files\Node;
41+
use OCP\Files\Storage\IStorage;
42+
use OCP\IUser;
43+
use OCP\IUserSession;
44+
45+
/** @template-implements IEventListener<Event> */
46+
class VersionStorageMoveListener implements IEventListener {
47+
/** @var File[] */
48+
private array $movedNodes = [];
49+
50+
public function __construct(
51+
private IVersionManager $versionManager,
52+
private IUserSession $userSession,
53+
) {
54+
}
55+
56+
/**
57+
* @abstract Moves version across storages if necessary.
58+
* @throws Exception No user in session
59+
*/
60+
public function handle(Event $event): void {
61+
if (!($event instanceof AbstractNodesEvent)) {
62+
return;
63+
}
64+
65+
$source = $event->getSource();
66+
$target = $event->getTarget();
67+
68+
$sourceStorage = $this->getNodeStorage($source);
69+
$targetStorage = $this->getNodeStorage($target);
70+
71+
$sourceBackend = $this->versionManager->getBackendForStorage($sourceStorage);
72+
$targetBackend = $this->versionManager->getBackendForStorage($targetStorage);
73+
74+
// If same backend, nothing to do.
75+
if ($sourceBackend === $targetBackend) {
76+
return;
77+
}
78+
79+
$user = $this->userSession->getUser() ?? $source->getOwner();
80+
81+
if ($user === null) {
82+
throw new Exception("Cannot move versions across storages without a user.");
83+
}
84+
85+
if ($event instanceof BeforeNodeRenamedEvent) {
86+
$this->recursivelyPrepareMove($source);
87+
} elseif ($event instanceof NodeRenamedEvent || $event instanceof NodeCopiedEvent) {
88+
$this->recursivelyHandleMoveOrCopy($event, $user, $source, $target, $sourceBackend, $targetBackend);
89+
}
90+
}
91+
92+
/**
93+
* Store all sub files in this->movedNodes so their info can be used after the operation.
94+
*/
95+
private function recursivelyPrepareMove(Node $source): void {
96+
if ($source instanceof File) {
97+
$this->movedNodes[$source->getId()] = $source;
98+
} elseif ($source instanceof Folder) {
99+
foreach ($source->getDirectoryListing() as $child) {
100+
$this->recursivelyPrepareMove($child);
101+
}
102+
}
103+
}
104+
105+
/**
106+
* Call handleMoveOrCopy on each sub files
107+
* @param NodeRenamedEvent|NodeCopiedEvent $event
108+
*/
109+
private function recursivelyHandleMoveOrCopy(Event $event, IUser $user, Node $source, Node $target, IVersionBackend $sourceBackend, IVersionBackend $targetBackend): void {
110+
if ($target instanceof File) {
111+
/** @var File $source */
112+
$this->handleMoveOrCopy($event, $user, $source, $target, $sourceBackend, $targetBackend);
113+
} elseif ($target instanceof Folder) {
114+
/** @var Folder $source */
115+
foreach ($target->getDirectoryListing() as $targetChild) {
116+
if ($event instanceof NodeRenamedEvent) {
117+
$sourceChild = $this->movedNodes[$target->getId()];
118+
} else {
119+
$sourceChild = $source->get($targetChild->getName());
120+
}
121+
$this->recursivelyHandleMoveOrCopy($event, $user, $sourceChild, $targetChild, $sourceBackend, $targetBackend);
122+
}
123+
}
124+
}
125+
126+
/**
127+
* Called only during NodeRenamedEvent or NodeCopiedEvent
128+
* Will send the source node versions to the new backend, and then delete them from the old backend.
129+
* @param NodeRenamedEvent|NodeCopiedEvent $event
130+
*/
131+
private function handleMoveOrCopy(Event $event, IUser $user, File $source, File $target, IVersionBackend $sourceBackend, IVersionBackend $targetBackend): void {
132+
if ($targetBackend instanceof IVersionsImporterBackend) {
133+
$versions = $sourceBackend->getVersionsForFile($user, $source);
134+
$targetBackend->importVersionsForFile($user, $source, $target, $versions);
135+
}
136+
137+
if ($event instanceof NodeRenamedEvent && $sourceBackend instanceof IVersionsImporterBackend) {
138+
$sourceBackend->clearVersionsForFile($user, $source, $target);
139+
}
140+
}
141+
142+
private function getNodeStorage(Node $node): IStorage {
143+
if ($node instanceof NonExistingFile) {
144+
return $node->getParent()->getStorage();
145+
} else {
146+
return $node->getStorage();
147+
}
148+
}
149+
}

apps/files_versions/lib/Versions/IMetadataVersion.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@
2828
* @since 29.0.0
2929
*/
3030
interface IMetadataVersion {
31+
/**
32+
* retrieves the all the metadata
33+
*
34+
* @return string[]
35+
* @since 29.0.0
36+
*/
37+
public function getMetadata(): array;
38+
3139
/**
3240
* retrieves the metadata value from our $key param
3341
*

apps/files_versions/lib/Versions/IVersionManager.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
*/
2626
namespace OCA\Files_Versions\Versions;
2727

28+
use OCP\Files\Storage\IStorage;
29+
2830
/**
2931
* @since 15.0.0
3032
*/
@@ -37,4 +39,10 @@ interface IVersionManager extends IVersionBackend {
3739
* @since 15.0.0
3840
*/
3941
public function registerBackend(string $storageType, IVersionBackend $backend);
42+
43+
/**
44+
* @throws BackendNotFoundException
45+
* @since 29.0.0
46+
*/
47+
public function getBackendForStorage(IStorage $storage): IVersionBackend;
4048
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* @copyright Copyright (c) 2024 Louis Chmn <louis@chmn.me>
7+
*
8+
* @author Louis Chmn <louis@chmn.me>
9+
*
10+
* @license GNU AGPL version 3 or any later version
11+
*
12+
* This program is free software: you can redistribute it and/or modify
13+
* it under the terms of the GNU Affero General Public License as
14+
* published by the Free Software Foundation, either version 3 of the
15+
* License, or (at your option) any later version.
16+
*
17+
* This program is distributed in the hope that it will be useful,
18+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
19+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20+
* GNU Affero General Public License for more details.
21+
*
22+
* You should have received a copy of the GNU Affero General Public License
23+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
24+
*
25+
*/
26+
namespace OCA\Files_Versions\Versions;
27+
28+
use OCP\Files\Node;
29+
use OCP\IUser;
30+
31+
/**
32+
* @since 29.0.0
33+
*/
34+
interface IVersionsImporterBackend {
35+
/**
36+
* Import the given versions for the target file.
37+
*
38+
* @param Node $target - The target is not yet created.
39+
* @param IVersion[] $versions
40+
* @since 29.0.0
41+
*/
42+
public function importVersionsForFile(IUser $user, Node $source, Node $target, array $versions): void;
43+
44+
/**
45+
* Clear all versions for a file
46+
*
47+
* @since 29.0.0
48+
*/
49+
public function clearVersionsForFile(IUser $user, Node $source, Node $target): void;
50+
}

0 commit comments

Comments
 (0)