Skip to content

Commit 452a63e

Browse files
committed
feat(files_versions): Add listener and interfaces to allow versions migration across storages
Signed-off-by: Louis Chemineau <[email protected]>
1 parent bdd4ac2 commit 452a63e

File tree

14 files changed

+350
-20
lines changed

14 files changed

+350
-20
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: 3 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,8 @@ 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+
112115
$context->registerEventListener(NodeCreatedEvent::class, FileEventsListener::class);
113116
$context->registerEventListener(BeforeNodeTouchedEvent::class, FileEventsListener::class);
114117
$context->registerEventListener(NodeTouchedEvent::class, FileEventsListener::class);

apps/files_versions/lib/Listener/FileEventsListener.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,14 @@ 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+
306+
// If same storage, nothing to do
307+
if ($sourceBackend !== $targetBackend) {
308+
return;
309+
}
310+
303311
$oldPath = $this->getPathForNode($source);
304312
$newPath = $this->getPathForNode($target);
305313
Storage::renameOrCopy($oldPath, $newPath, 'rename');
@@ -333,6 +341,15 @@ public function pre_renameOrCopy_hook(Node $source, Node $target): void {
333341
$manager = Filesystem::getMountManager();
334342
$mount = $manager->find($absOldPath);
335343
$internalPath = $mount->getInternalPath($absOldPath);
344+
345+
$sourceBackend = $this->versionManager->getBackendForStorage($source->getStorage());
346+
$targetBackend = $this->versionManager->getBackendForStorage($target->getParent()->getStorage());
347+
348+
// If same storage, nothing to do
349+
if ($sourceBackend !== $targetBackend) {
350+
return;
351+
}
352+
336353
if ($internalPath === '' and $mount instanceof MoveableMount) {
337354
return;
338355
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* @copyright Copyright (c) 2024 Louis Chmn <[email protected]>
7+
*
8+
* @author Louis Chmn <[email protected]>
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 OCA\Files_Versions\Versions\IVersionManager;
29+
use OCP\EventDispatcher\Event;
30+
use OCP\EventDispatcher\IEventListener;
31+
use OCP\Files\Events\Node\BeforeNodeRenamedEvent;
32+
use OCP\Files\File;
33+
use OCP\IUserSession;
34+
35+
/** @template-implements IEventListener<BeforeNodeRenamedEvent> */
36+
class VersionStorageMoveListener implements IEventListener {
37+
public function __construct(
38+
private IVersionManager $versionManager,
39+
private IUserSession $userSession,
40+
) {
41+
}
42+
43+
/**
44+
* @abstract Moves version across storages if necessary.
45+
* @param Event $event
46+
*/
47+
public function handle(Event $event): void {
48+
/** @var BeforeNodeRenamedEvent $event **/
49+
50+
$source = $event->getSource();
51+
$target = $event->getTarget();
52+
53+
if (!($target instanceof File)) {
54+
return;
55+
}
56+
57+
$user = $this->userSession->getUser() ?? $source->getOwner();
58+
59+
if ($user === null) {
60+
throw new Exception("Cannot move versions across storages without a user.");
61+
}
62+
63+
$this->versionManager->moveVersionsAcrossBackends($user, $source, $target);
64+
}
65+
}

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: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525
*/
2626
namespace OCA\Files_Versions\Versions;
2727

28+
use OCP\Files\Node;
29+
use OCP\Files\Storage\IStorage;
30+
use OCP\IUser;
31+
2832
/**
2933
* @since 15.0.0
3034
*/
@@ -37,4 +41,21 @@ interface IVersionManager extends IVersionBackend {
3741
* @since 15.0.0
3842
*/
3943
public function registerBackend(string $storageType, IVersionBackend $backend);
44+
45+
/**
46+
* @param IStorage $storage
47+
* @return IVersionBackend
48+
* @throws BackendNotFoundException
49+
*/
50+
public function getBackendForStorage(IStorage $storage): IVersionBackend;
51+
52+
/**
53+
* Move versions across different backends
54+
*
55+
* @param IUser $user
56+
* @param Node $source
57+
* @param Node $target
58+
* @since 29.0.0
59+
*/
60+
public function moveVersionsAcrossBackends(IUser $user, Node $source, Node $target): void;
4061
}
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 <[email protected]>
7+
*
8+
* @author Louis Chmn <[email protected]>
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 $node): void;
50+
}

apps/files_versions/lib/Versions/LegacyVersionsBackend.php

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
namespace OCA\Files_Versions\Versions;
2929

30+
use Exception;
3031
use OC\Files\View;
3132
use OCA\DAV\Connector\Sabre\Exception\Forbidden;
3233
use OCA\Files_Sharing\ISharedStorage;
@@ -46,7 +47,7 @@
4647
use OCP\IUserManager;
4748
use OCP\IUserSession;
4849

49-
class LegacyVersionsBackend implements IVersionBackend, IDeletableVersionBackend, INeedSyncVersionBackend, IMetadataVersionBackend {
50+
class LegacyVersionsBackend implements IVersionBackend, IDeletableVersionBackend, INeedSyncVersionBackend, IMetadataVersionBackend, IVersionsImporterBackend {
5051
public function __construct(
5152
private IRootFolder $rootFolder,
5253
private IUserManager $userManager,
@@ -296,4 +297,69 @@ public function setMetadataValue(Node $node, int $revision, string $key, string
296297
$versionEntity->setMetadataValue($key, $value);
297298
$this->versionsMapper->update($versionEntity);
298299
}
300+
301+
302+
/**
303+
* @inheritdoc
304+
*/
305+
public function importVersionsForFile(IUser $user, Node $source, Node $target, array $versions): void {
306+
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
307+
$relativePath = $userFolder->getRelativePath($target->getPath());
308+
309+
if ($relativePath === null) {
310+
throw new \Exception('Target does not have a relative path' . $target->getPath());
311+
}
312+
313+
$userView = new View('/' . $user->getUID());
314+
// create all parent folders
315+
// Storage::createMissingDirectories($relativePath, $userView);
316+
Storage::scheduleExpire($user->getUID(), $relativePath);
317+
318+
foreach ($versions as $version) {
319+
// 1. Move the file to the new location
320+
if ($version->getTimestamp() !== $source->getMTime()) {
321+
$backend = $version->getBackend();
322+
$versionFile = $backend->getVersionFile($user, $source, $version->getRevisionId());
323+
$newVersionPath = 'files_versions/' . $relativePath . '.v' . $version->getTimestamp();
324+
325+
$versionContent = $versionFile->fopen('r');
326+
if ($versionContent === false) {
327+
throw new Exception('Fail to open version file.');
328+
}
329+
330+
$userView->file_put_contents($newVersionPath, $versionContent);
331+
$userView->getFileInfo($newVersionPath);
332+
}
333+
334+
// 2. Create the entity in the database
335+
$versionEntity = new VersionEntity();
336+
$versionEntity->setFileId($source->getId());
337+
$versionEntity->setTimestamp($version->getTimestamp());
338+
$versionEntity->setSize($version->getSize());
339+
$versionEntity->setMimetype($this->mimeTypeLoader->getId($version->getMimetype()));
340+
if ($version instanceof IMetadataVersion) {
341+
$versionEntity->setMetadata($version->getMetadata());
342+
}
343+
$this->versionsMapper->insert($versionEntity);
344+
}
345+
}
346+
347+
/**
348+
* @inheritdoc
349+
*/
350+
public function clearVersionsForFile(IUser $user, Node $node): void {
351+
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
352+
353+
$relativePath = $userFolder->getRelativePath($node->getPath());
354+
if ($relativePath === null) {
355+
throw new Exception("Relative path not found for node with path: " . $node->getPath());
356+
}
357+
358+
$versions = Storage::getVersions($user->getUID(), $relativePath);
359+
foreach ($versions as $v) {
360+
Storage::deleteRevision($relativePath, (int)$v['version']);
361+
}
362+
363+
$this->versionsMapper->deleteAllVersionsForFileId($node->getId());
364+
}
299365
}

apps/files_versions/lib/Versions/Version.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ public function getUser(): IUser {
7979
return $this->user;
8080
}
8181

82+
public function getMetadata(): array {
83+
return $this->metadata;
84+
}
85+
8286
public function getMetadataValue(string $key): ?string {
8387
return $this->metadata[$key] ?? null;
8488
}

0 commit comments

Comments
 (0)