From 7d2d8aa87c7c99889d66877608d0c07aa3a457aa Mon Sep 17 00:00:00 2001 From: Christopher Ng Date: Fri, 4 Feb 2022 20:41:22 +0000 Subject: [PATCH 01/12] Run exports and imports of registered migrators Signed-off-by: Christopher Ng --- lib/ExportDestination.php | 1 + lib/IExportDestination.php | 54 ------------------------- lib/IImportSource.php | 60 ---------------------------- lib/ImportSource.php | 1 + lib/Service/UserMigrationService.php | 43 ++++++++++++++++++-- 5 files changed, 41 insertions(+), 118 deletions(-) delete mode 100644 lib/IExportDestination.php delete mode 100644 lib/IImportSource.php diff --git a/lib/ExportDestination.php b/lib/ExportDestination.php index 97fb18bb..dd351b36 100644 --- a/lib/ExportDestination.php +++ b/lib/ExportDestination.php @@ -29,6 +29,7 @@ use OCP\Files\File; use OCP\Files\Folder; use OCP\ITempManager; +use OCP\UserMigration\IExportDestination; use ZipStreamer\COMPR; use ZipStreamer\ZipStreamer; diff --git a/lib/IExportDestination.php b/lib/IExportDestination.php deleted file mode 100644 index 74912ec4..00000000 --- a/lib/IExportDestination.php +++ /dev/null @@ -1,54 +0,0 @@ - - * - * @author Côme Chilliet - * - * @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 . - * - */ - -namespace OCA\UserMigration; - -use OCP\Files\Folder; - -interface IExportDestination { - /** - * Adds a file to the export - * - * @param string $path Full path to the file in the export archive. Parent directories will be created if needed. - * @param string $content The full content of the file. - * @return bool whether the file was successfully added. - */ - public function addFile(string $path, string $content): bool; - - /** - * Copy a folder to the export - * - * @param Folder $folder folder to copy to the export archive. - * @param string $destinationPath Full path to the folder in the export archive. Parent directories will be created if needed. - * @return bool whether the folder was successfully added. - */ - public function copyFolder(Folder $folder, string $destinationPath): bool; - - /** - * Called after export is complete - */ - public function close(): void; -} diff --git a/lib/IImportSource.php b/lib/IImportSource.php deleted file mode 100644 index df43144c..00000000 --- a/lib/IImportSource.php +++ /dev/null @@ -1,60 +0,0 @@ - - * - * @author Côme Chilliet - * - * @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 . - * - */ - -namespace OCA\UserMigration; - -use OCP\Files\Folder; - -interface IImportSource { - /** - * Reads a file from the export - * - * @param string $path Full path to the file in the export archive. - * @return string The full content of the file. - */ - public function getFileContents(string $path): string; - - /** - * Reads a file from the export as a stream - * - * @param string $path Full path to the file in the export archive. - * @return resource A stream resource to read from to get the file content. - */ - public function getFileAsStream(string $path); - - /** - * Copy files from the export to a Folder - * - * Folder $destination folder to copy into - * string $sourcePath path in the export archive - */ - public function copyToFolder(Folder $destination, string $sourcePath): bool; - - /** - * Called after import is complete - */ - public function close(): void; -} diff --git a/lib/ImportSource.php b/lib/ImportSource.php index e2a074ab..ae16fc4f 100644 --- a/lib/ImportSource.php +++ b/lib/ImportSource.php @@ -29,6 +29,7 @@ use OC\Archive\Archive; use OC\Archive\ZIP; use OCP\Files\Folder; +use OCP\UserMigration\IImportSource; class ImportSource implements IImportSource { private Archive $archive; diff --git a/lib/Service/UserMigrationService.php b/lib/Service/UserMigrationService.php index 39b5f938..8a7e2a46 100644 --- a/lib/Service/UserMigrationService.php +++ b/lib/Service/UserMigrationService.php @@ -5,6 +5,7 @@ /** * @copyright Copyright (c) 2022 Côme Chilliet * + * @author Christopher Ng * @author Côme Chilliet * * @license GNU AGPL version 3 or any later version @@ -26,12 +27,11 @@ namespace OCA\UserMigration\Service; +use OC\AppFramework\Bootstrap\Coordinator; +use OC\Files\Filesystem; use OCA\UserMigration\Exception\UserMigrationException; use OCA\UserMigration\ExportDestination; -use OCA\UserMigration\IExportDestination; -use OCA\UserMigration\IImportSource; use OCA\UserMigration\ImportSource; -use OC\Files\Filesystem; use OCP\Accounts\IAccountManager; use OCP\Files\IRootFolder; use OCP\IConfig; @@ -39,6 +39,10 @@ use OCP\IUser; use OCP\IUserManager; use OCP\Security\ISecureRandom; +use OCP\UserMigration\IExportDestination; +use OCP\UserMigration\IImportSource; +use OCP\UserMigration\IMigrator; +use Psr\Container\ContainerInterface; use Symfony\Component\Console\Output\NullOutput; use Symfony\Component\Console\Output\OutputInterface; @@ -53,18 +57,26 @@ class UserMigrationService { protected IUserManager $userManager; + protected ContainerInterface $container; + + protected Coordinator $coordinator; + public function __construct( IRootFolder $rootFolder, IConfig $config, IAccountManager $accountManager, ITempManager $tempManager, - IUserManager $userManager + IUserManager $userManager, + ContainerInterface $container, + Coordinator $coordinator ) { $this->root = $rootFolder; $this->config = $config; $this->accountManager = $accountManager; $this->tempManager = $tempManager; $this->userManager = $userManager; + $this->container = $container; + $this->coordinator = $coordinator; } /** @@ -113,6 +125,17 @@ public function export(IUser $user, ?OutputInterface $output = null): string { $output ); + // Run exports of registered migrators + $context = $this->coordinator->getRegistrationContext(); + + if ($context !== null) { + foreach ($context->getUserMigrators() as $migratorRegistration) { + /** @var IMigrator $migrator */ + $migrator = $this->container->get($migratorRegistration->getService()); + $migrator->export($user, $exportDestination, $output); + } + } + $exportDestination->close(); $output->writeln("Export saved in ".$exportDestination->getPath()); return $exportDestination->getPath(); @@ -131,6 +154,18 @@ public function import(string $path, ?OutputInterface $output = null): void { $this->importAccountInformation($user, $importSource, $output); $this->importAppsSettings($user, $importSource, $output); $this->importFiles($user, $importSource, $output); + + // Run imports of registered migrators + $context = $this->coordinator->getRegistrationContext(); + + if ($context !== null) { + foreach ($context->getUserMigrators() as $migratorRegistration) { + /** @var IMigrator $migrator */ + $migrator = $this->container->get($migratorRegistration->getService()); + $migrator->import($user, $importSource, $output); + } + } + $uid = $user->getUID(); $output->writeln("Successfully imported $uid from $path"); } finally { From dffce83a5c7889cc14109172f88903baec7fe05d Mon Sep 17 00:00:00 2001 From: Christopher Ng Date: Wed, 9 Feb 2022 02:08:03 +0000 Subject: [PATCH 02/12] Extend export destination as defined by the public interface Signed-off-by: Christopher Ng --- lib/ExportDestination.php | 10 +++++++++- lib/Service/UserMigrationService.php | 8 ++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/ExportDestination.php b/lib/ExportDestination.php index dd351b36..16f9b15c 100644 --- a/lib/ExportDestination.php +++ b/lib/ExportDestination.php @@ -54,7 +54,7 @@ public function __construct(ITempManager $tempManager, string $uid) { /** * {@inheritDoc} */ - public function addFile(string $path, string $content): bool { + public function addFileContents(string $path, string $content): bool { $stream = fopen('php://temp', 'r+'); fwrite($stream, $content); rewind($stream); @@ -62,6 +62,14 @@ public function addFile(string $path, string $content): bool { return true; } + /** + * {@inheritDoc} + */ + public function addFileAsStream(string $path, $stream): bool { + $this->streamer->addFileFromStream($stream, $path); + return true; + } + /** * {@inheritDoc} */ diff --git a/lib/Service/UserMigrationService.php b/lib/Service/UserMigrationService.php index 8a7e2a46..c8dbdfb0 100644 --- a/lib/Service/UserMigrationService.php +++ b/lib/Service/UserMigrationService.php @@ -219,7 +219,7 @@ protected function exportUserInformation(IUser $user, 'enabled' => $user->isEnabled(), ]; - if ($exportDestination->addFile("user.json", json_encode($userinfo)) === false) { + if ($exportDestination->addFileContents("user.json", json_encode($userinfo)) === false) { throw new UserMigrationException("Could not export user information."); } } @@ -253,7 +253,7 @@ protected function exportAccountInformation(IUser $user, OutputInterface $output): void { $output->writeln("Exporting account information in account.json…"); - if ($exportDestination->addFile("account.json", json_encode($this->accountManager->getAccount($user))) === false) { + if ($exportDestination->addFileContents("account.json", json_encode($this->accountManager->getAccount($user))) === false) { throw new UserMigrationException("Could not export account information."); } } @@ -282,7 +282,7 @@ protected function exportVersions(string $uid, \OC_App::getAppVersions() ); - if ($exportDestination->addFile("versions.json", json_encode($versions)) === false) { + if ($exportDestination->addFileContents("versions.json", json_encode($versions)) === false) { throw new UserMigrationException("Could not export versions."); } } @@ -297,7 +297,7 @@ protected function exportAppsSettings(string $uid, $data = $this->config->getAllUserValues($uid); - if ($exportDestination->addFile("settings.json", json_encode($data)) === false) { + if ($exportDestination->addFileContents("settings.json", json_encode($data)) === false) { throw new UserMigrationException("Could not export settings."); } } From ebe7931802427e414556d0d4d90c81f7fb63a66d Mon Sep 17 00:00:00 2001 From: Christopher Ng Date: Tue, 15 Feb 2022 04:26:51 +0000 Subject: [PATCH 03/12] Suppress psalm errors Signed-off-by: Christopher Ng --- lib/Service/UserMigrationService.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/Service/UserMigrationService.php b/lib/Service/UserMigrationService.php index c8dbdfb0..730ba007 100644 --- a/lib/Service/UserMigrationService.php +++ b/lib/Service/UserMigrationService.php @@ -59,6 +59,10 @@ class UserMigrationService { protected ContainerInterface $container; + // Allow use of the private Coordinator class here to get and run registered migrators + /** + * @psalm-suppress UndefinedClass + */ protected Coordinator $coordinator; public function __construct( @@ -68,6 +72,9 @@ public function __construct( ITempManager $tempManager, IUserManager $userManager, ContainerInterface $container, + /** + * @psalm-suppress UndefinedClass + */ Coordinator $coordinator ) { $this->root = $rootFolder; @@ -76,6 +83,9 @@ public function __construct( $this->tempManager = $tempManager; $this->userManager = $userManager; $this->container = $container; + /** + * @psalm-suppress UndefinedClass + */ $this->coordinator = $coordinator; } @@ -126,6 +136,9 @@ public function export(IUser $user, ?OutputInterface $output = null): string { ); // Run exports of registered migrators + /** + * @psalm-suppress UndefinedClass + */ $context = $this->coordinator->getRegistrationContext(); if ($context !== null) { @@ -156,6 +169,9 @@ public function import(string $path, ?OutputInterface $output = null): void { $this->importFiles($user, $importSource, $output); // Run imports of registered migrators + /** + * @psalm-suppress UndefinedClass + */ $context = $this->coordinator->getRegistrationContext(); if ($context !== null) { From c1b931a16c0de6df88de9171f0c65f35235dca77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Tue, 15 Feb 2022 10:34:03 +0100 Subject: [PATCH 04/12] Add a stubs file for Coordinator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- lib/Service/UserMigrationService.php | 15 -------- psalm.xml | 3 ++ tests/stubs/stub.phpstub | 55 ++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 15 deletions(-) create mode 100644 tests/stubs/stub.phpstub diff --git a/lib/Service/UserMigrationService.php b/lib/Service/UserMigrationService.php index 730ba007..be34fb0d 100644 --- a/lib/Service/UserMigrationService.php +++ b/lib/Service/UserMigrationService.php @@ -60,9 +60,6 @@ class UserMigrationService { protected ContainerInterface $container; // Allow use of the private Coordinator class here to get and run registered migrators - /** - * @psalm-suppress UndefinedClass - */ protected Coordinator $coordinator; public function __construct( @@ -72,9 +69,6 @@ public function __construct( ITempManager $tempManager, IUserManager $userManager, ContainerInterface $container, - /** - * @psalm-suppress UndefinedClass - */ Coordinator $coordinator ) { $this->root = $rootFolder; @@ -83,9 +77,6 @@ public function __construct( $this->tempManager = $tempManager; $this->userManager = $userManager; $this->container = $container; - /** - * @psalm-suppress UndefinedClass - */ $this->coordinator = $coordinator; } @@ -136,9 +127,6 @@ public function export(IUser $user, ?OutputInterface $output = null): string { ); // Run exports of registered migrators - /** - * @psalm-suppress UndefinedClass - */ $context = $this->coordinator->getRegistrationContext(); if ($context !== null) { @@ -169,9 +157,6 @@ public function import(string $path, ?OutputInterface $output = null): void { $this->importFiles($user, $importSource, $output); // Run imports of registered migrators - /** - * @psalm-suppress UndefinedClass - */ $context = $this->coordinator->getRegistrationContext(); if ($context !== null) { diff --git a/psalm.xml b/psalm.xml index 5ef2b0fe..10580177 100644 --- a/psalm.xml +++ b/psalm.xml @@ -17,6 +17,9 @@ + + + diff --git a/tests/stubs/stub.phpstub b/tests/stubs/stub.phpstub new file mode 100644 index 00000000..68bb46f7 --- /dev/null +++ b/tests/stubs/stub.phpstub @@ -0,0 +1,55 @@ + + * + * @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 . + * + */ + +use OCP\UserMigration\IMigrator as IUserMigrator; + +namespace OC\AppFramework\Bootstrap { + class Coordinator { + public function getRegistrationContext(): ?RegistrationContext {} + } + + class RegistrationContext { + /** + * @return ServiceRegistration[] + */ + public function getUserMigrators(): array {} + } + + /** + * @psalm-immutable + * @template T + */ + class ServiceRegistration extends ARegistration { + /** + * @psalm-return class-string + */ + public function getService(): string {} + } + + /** + * @psalm-immutable + */ + abstract class ARegistration { + public function getAppId(): string {} + } +} From 4f7dde736ffe25893c0343f7472ffc21cec7b5ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Tue, 15 Feb 2022 10:52:19 +0100 Subject: [PATCH 05/12] Update baseline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- composer.json | 3 ++- composer.lock | 12 ++++++------ tests/psalm-baseline.xml | 5 +++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index 7cebbdc3..2457790a 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,8 @@ "cs:check": "php-cs-fixer fix --dry-run --diff", "lint": "find . -name \\*.php -not -path './vendor/*' -not -path './build/*' -not -path './node_modules/*' -print0 | xargs -0 -n1 php -l", "psalm": "psalm", - "psalm:fix": "psalm --alter --issues=InvalidReturnType,InvalidNullableReturnType,MissingParamType,InvalidFalsableReturnType" + "psalm:fix": "psalm --alter --issues=InvalidReturnType,InvalidNullableReturnType,MissingParamType,InvalidFalsableReturnType", + "psalm:update-baseline": "psalm --threads=1 --update-baseline" }, "config": { "optimize-autoloader": true, diff --git a/composer.lock b/composer.lock index fd819629..7691231c 100644 --- a/composer.lock +++ b/composer.lock @@ -179,16 +179,16 @@ "source": { "type": "git", "url": "https://github.com/ChristophWurst/nextcloud_composer.git", - "reference": "b664d5ed366d7dc1e9d7f81e62f09ec6c923430b" + "reference": "b21887763357434748b7793a67fdced7c35f88b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ChristophWurst/nextcloud_composer/zipball/b664d5ed366d7dc1e9d7f81e62f09ec6c923430b", - "reference": "b664d5ed366d7dc1e9d7f81e62f09ec6c923430b", + "url": "https://api.github.com/repos/ChristophWurst/nextcloud_composer/zipball/b21887763357434748b7793a67fdced7c35f88b5", + "reference": "b21887763357434748b7793a67fdced7c35f88b5", "shasum": "" }, "require": { - "php": "^7.3 || ~8.0.0", + "php": "^7.4 || ~8.0 || ~8.1", "psr/container": "^1.0", "psr/event-dispatcher": "^1.0", "psr/log": "^1.1" @@ -215,7 +215,7 @@ "issues": "https://github.com/ChristophWurst/nextcloud_composer/issues", "source": "https://github.com/ChristophWurst/nextcloud_composer/tree/master" }, - "time": "2022-02-03T01:08:28+00:00" + "time": "2022-02-15T01:22:02+00:00" }, { "name": "composer/package-versions-deprecated", @@ -4920,5 +4920,5 @@ "platform-overrides": { "php": "7.4" }, - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.1.0" } diff --git a/tests/psalm-baseline.xml b/tests/psalm-baseline.xml index a176c44e..5b6630ef 100644 --- a/tests/psalm-baseline.xml +++ b/tests/psalm-baseline.xml @@ -1,9 +1,10 @@ - + - + $read $stream + $stream From 245ad9da5de6a882b25789036d2f2b8ec6e6249a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Mon, 14 Feb 2022 16:22:24 +0100 Subject: [PATCH 06/12] Export migrator versions in the export archive and check compatibility on import MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- lib/Service/UserMigrationService.php | 69 +++++++++++++++++++++------- 1 file changed, 52 insertions(+), 17 deletions(-) diff --git a/lib/Service/UserMigrationService.php b/lib/Service/UserMigrationService.php index be34fb0d..cffcb594 100644 --- a/lib/Service/UserMigrationService.php +++ b/lib/Service/UserMigrationService.php @@ -27,11 +27,10 @@ namespace OCA\UserMigration\Service; -use OC\AppFramework\Bootstrap\Coordinator; -use OC\Files\Filesystem; use OCA\UserMigration\Exception\UserMigrationException; use OCA\UserMigration\ExportDestination; use OCA\UserMigration\ImportSource; +use OCA\UserMigration\Migrator\TMigratorBasicVersionHandling; use OCP\Accounts\IAccountManager; use OCP\Files\IRootFolder; use OCP\IConfig; @@ -42,11 +41,15 @@ use OCP\UserMigration\IExportDestination; use OCP\UserMigration\IImportSource; use OCP\UserMigration\IMigrator; +use OC\AppFramework\Bootstrap\Coordinator; +use OC\Files\Filesystem; use Psr\Container\ContainerInterface; use Symfony\Component\Console\Output\NullOutput; use Symfony\Component\Console\Output\OutputInterface; class UserMigrationService { + use TMigratorBasicVersionHandling; + protected IRootFolder $root; protected IConfig $config; @@ -93,6 +96,12 @@ public function export(IUser $user, ?OutputInterface $output = null): string { \OC::$server->getUserFolder($uid); Filesystem::initMountPoints($uid); + $context = $this->coordinator->getRegistrationContext(); + + if ($context === null) { + throw new UserMigrationException("Failed to get context"); + } + $exportDestination = new ExportDestination($this->tempManager, $uid); // copy the files @@ -127,14 +136,17 @@ public function export(IUser $user, ?OutputInterface $output = null): string { ); // Run exports of registered migrators - $context = $this->coordinator->getRegistrationContext(); - - if ($context !== null) { - foreach ($context->getUserMigrators() as $migratorRegistration) { - /** @var IMigrator $migrator */ - $migrator = $this->container->get($migratorRegistration->getService()); - $migrator->export($user, $exportDestination, $output); - } + $migratorVersions = [ + static::class => $this->getVersion(), + ]; + foreach ($context->getUserMigrators() as $migratorRegistration) { + /** @var IMigrator $migrator */ + $migrator = $this->container->get($migratorRegistration->getService()); + $migrator->export($user, $exportDestination, $output); + $migratorVersions[$migrator::class] = $migrator->getVersion(); + } + if ($exportDestination->addFileContents("migrator_versions.json", json_encode($migratorVersions)) === false) { + throw new UserMigrationException("Could not export user information."); } $exportDestination->close(); @@ -148,8 +160,33 @@ public function import(string $path, ?OutputInterface $output = null): void { $output->writeln("Importing from ${path}…"); $importSource = new ImportSource($path); + $context = $this->coordinator->getRegistrationContext(); + try { - // TODO check versions + if ($context === null) { + throw new UserMigrationException("Failed to get context"); + } + // TODO move this to ImportSource so that migrators can query the versions as well + $migrationVersions = json_decode($importSource->getFileContents("migrator_versions.json"), true, 512, JSON_THROW_ON_ERROR); + + if (!isset($migrationVersions[static::class])) { + throw new UserMigrationException("Cannot find version for main class ".static::class); + } elseif (!$this->canImport($migrationVersions[static::class])) { + throw new UserMigrationException("Version ${migrationVersions[static::class]} for main class ".static::class." is not compatible"); + } + + // Check versions + foreach ($context->getUserMigrators() as $migratorRegistration) { + /** @var IMigrator $migrator */ + $migrator = $this->container->get($migratorRegistration->getService()); + if (isset($migrationVersions[$migrator::class])) { + if (!$migrator->canImport($migrationVersions[$migrator::class])) { + throw new UserMigrationException("Version ${migrationVersions[$migrator::class]} for migrator ".$migrator::class." is not compatible"); + } + } else { + $output->writeln("No input data for migrator ".$migrator::class.", ignoring"); + } + } $user = $this->importUser($importSource, $output); $this->importAccountInformation($user, $importSource, $output); @@ -157,12 +194,10 @@ public function import(string $path, ?OutputInterface $output = null): void { $this->importFiles($user, $importSource, $output); // Run imports of registered migrators - $context = $this->coordinator->getRegistrationContext(); - - if ($context !== null) { - foreach ($context->getUserMigrators() as $migratorRegistration) { - /** @var IMigrator $migrator */ - $migrator = $this->container->get($migratorRegistration->getService()); + foreach ($context->getUserMigrators() as $migratorRegistration) { + /** @var IMigrator $migrator */ + $migrator = $this->container->get($migratorRegistration->getService()); + if (isset($migrationVersions[$migrator::class])) { $migrator->import($user, $importSource, $output); } } From 474c21b3d6061a49d52f42f7379b4abb455a8e7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Mon, 14 Feb 2022 16:09:35 +0100 Subject: [PATCH 07/12] Moved the trait to server repository MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- lib/Service/UserMigrationService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Service/UserMigrationService.php b/lib/Service/UserMigrationService.php index cffcb594..6364dd51 100644 --- a/lib/Service/UserMigrationService.php +++ b/lib/Service/UserMigrationService.php @@ -30,7 +30,6 @@ use OCA\UserMigration\Exception\UserMigrationException; use OCA\UserMigration\ExportDestination; use OCA\UserMigration\ImportSource; -use OCA\UserMigration\Migrator\TMigratorBasicVersionHandling; use OCP\Accounts\IAccountManager; use OCP\Files\IRootFolder; use OCP\IConfig; @@ -41,6 +40,7 @@ use OCP\UserMigration\IExportDestination; use OCP\UserMigration\IImportSource; use OCP\UserMigration\IMigrator; +use OCP\UserMigration\TMigratorBasicVersionHandling; use OC\AppFramework\Bootstrap\Coordinator; use OC\Files\Filesystem; use Psr\Container\ContainerInterface; From cc8393babfb613afad2c4f377cf28ae65401a553 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Tue, 15 Feb 2022 11:43:00 +0100 Subject: [PATCH 08/12] Adapt to version handling changes in core interfaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- lib/ExportDestination.php | 7 +++++++ lib/ImportSource.php | 15 +++++++++++++++ lib/Service/UserMigrationService.php | 27 ++++++++++----------------- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/lib/ExportDestination.php b/lib/ExportDestination.php index 16f9b15c..ced3ed2b 100644 --- a/lib/ExportDestination.php +++ b/lib/ExportDestination.php @@ -92,6 +92,13 @@ public function copyFolder(Folder $folder, string $destinationPath): bool { return true; } + /** + * {@inheritDoc} + */ + public function setMigratorVersions(array $versions): bool { + return $this->addFileContents("migrator_versions.json", json_encode($versions)); + } + /** * {@inheritDoc} */ diff --git a/lib/ImportSource.php b/lib/ImportSource.php index ae16fc4f..3a978ce0 100644 --- a/lib/ImportSource.php +++ b/lib/ImportSource.php @@ -36,6 +36,11 @@ class ImportSource implements IImportSource { private string $path; + /** + * @var ?array + */ + private ?array $migratorVersions = null; + public function __construct(string $path) { $this->path = $path; $this->archive = new ZIP($this->path); @@ -84,6 +89,16 @@ public function copyToFolder(Folder $destination, string $sourcePath): bool { return true; } + /** + * {@inheritDoc} + */ + public function getMigratorVersions(): array { + if ($this->migratorVersions === null) { + $this->migratorVersions = json_decode($this->getFileContents("migrator_versions.json"), true, 512, JSON_THROW_ON_ERROR); + } + return $this->migratorVersions; + } + /** * {@inheritDoc} */ diff --git a/lib/Service/UserMigrationService.php b/lib/Service/UserMigrationService.php index 6364dd51..de29919f 100644 --- a/lib/Service/UserMigrationService.php +++ b/lib/Service/UserMigrationService.php @@ -50,6 +50,8 @@ class UserMigrationService { use TMigratorBasicVersionHandling; + protected bool $mandatory = true; + protected IRootFolder $root; protected IConfig $config; @@ -143,9 +145,9 @@ public function export(IUser $user, ?OutputInterface $output = null): string { /** @var IMigrator $migrator */ $migrator = $this->container->get($migratorRegistration->getService()); $migrator->export($user, $exportDestination, $output); - $migratorVersions[$migrator::class] = $migrator->getVersion(); + $migratorVersions[get_class($migrator)] = $migrator->getVersion(); } - if ($exportDestination->addFileContents("migrator_versions.json", json_encode($migratorVersions)) === false) { + if ($exportDestination->setMigratorVersions($migratorVersions) === false) { throw new UserMigrationException("Could not export user information."); } @@ -166,25 +168,18 @@ public function import(string $path, ?OutputInterface $output = null): void { if ($context === null) { throw new UserMigrationException("Failed to get context"); } - // TODO move this to ImportSource so that migrators can query the versions as well - $migrationVersions = json_decode($importSource->getFileContents("migrator_versions.json"), true, 512, JSON_THROW_ON_ERROR); + $migratorVersions = $importSource->getMigratorVersions(); - if (!isset($migrationVersions[static::class])) { - throw new UserMigrationException("Cannot find version for main class ".static::class); - } elseif (!$this->canImport($migrationVersions[static::class])) { - throw new UserMigrationException("Version ${migrationVersions[static::class]} for main class ".static::class." is not compatible"); + if (!$this->canImport($importSource, $migratorVersions[static::class] ?? null)) { + throw new UserMigrationException("Version ${$migratorVersions[static::class]} for main class ".static::class." is not compatible"); } // Check versions foreach ($context->getUserMigrators() as $migratorRegistration) { /** @var IMigrator $migrator */ $migrator = $this->container->get($migratorRegistration->getService()); - if (isset($migrationVersions[$migrator::class])) { - if (!$migrator->canImport($migrationVersions[$migrator::class])) { - throw new UserMigrationException("Version ${migrationVersions[$migrator::class]} for migrator ".$migrator::class." is not compatible"); - } - } else { - $output->writeln("No input data for migrator ".$migrator::class.", ignoring"); + if (!$migrator->canImport($importSource, $migratorVersions[get_class($migrator)] ?? null)) { + throw new UserMigrationException("Version ".($migratorVersions[get_class($migrator)] ?? 'null')." for migrator ".get_class($migrator)." is not compatible"); } } @@ -197,9 +192,7 @@ public function import(string $path, ?OutputInterface $output = null): void { foreach ($context->getUserMigrators() as $migratorRegistration) { /** @var IMigrator $migrator */ $migrator = $this->container->get($migratorRegistration->getService()); - if (isset($migrationVersions[$migrator::class])) { - $migrator->import($user, $importSource, $output); - } + $migrator->import($user, $importSource, $output, $migratorVersions[get_class($migrator)] ?? null); } $uid = $user->getUID(); From 77c4fc8a4e34b9a1432f64c6121c7a9871a1bc2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Tue, 15 Feb 2022 16:26:23 +0100 Subject: [PATCH 09/12] Trait property default value cannot be overridden MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- lib/Service/UserMigrationService.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Service/UserMigrationService.php b/lib/Service/UserMigrationService.php index de29919f..af3ad4e3 100644 --- a/lib/Service/UserMigrationService.php +++ b/lib/Service/UserMigrationService.php @@ -50,8 +50,6 @@ class UserMigrationService { use TMigratorBasicVersionHandling; - protected bool $mandatory = true; - protected IRootFolder $root; protected IConfig $config; @@ -83,6 +81,8 @@ public function __construct( $this->userManager = $userManager; $this->container = $container; $this->coordinator = $coordinator; + + $this->mandatory = true; } /** From 60e60ec812103c4951045ad7de4e7edfb6dc0561 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Mon, 21 Feb 2022 16:39:58 +0100 Subject: [PATCH 10/12] Adapt to changes in core MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- lib/ImportSource.php | 8 ++++++++ lib/Service/UserMigrationService.php | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/ImportSource.php b/lib/ImportSource.php index 3a978ce0..9ad3bac4 100644 --- a/lib/ImportSource.php +++ b/lib/ImportSource.php @@ -99,6 +99,14 @@ public function getMigratorVersions(): array { return $this->migratorVersions; } + /** + * {@inheritDoc} + */ + public function getMigratorVersion(string $migrator): ?int { + $versions = $this->getMigratorVersions(); + return $versions[$migrator] ?? null; + } + /** * {@inheritDoc} */ diff --git a/lib/Service/UserMigrationService.php b/lib/Service/UserMigrationService.php index af3ad4e3..1a3d5a8f 100644 --- a/lib/Service/UserMigrationService.php +++ b/lib/Service/UserMigrationService.php @@ -178,8 +178,8 @@ public function import(string $path, ?OutputInterface $output = null): void { foreach ($context->getUserMigrators() as $migratorRegistration) { /** @var IMigrator $migrator */ $migrator = $this->container->get($migratorRegistration->getService()); - if (!$migrator->canImport($importSource, $migratorVersions[get_class($migrator)] ?? null)) { - throw new UserMigrationException("Version ".($migratorVersions[get_class($migrator)] ?? 'null')." for migrator ".get_class($migrator)." is not compatible"); + if (!$migrator->canImport($importSource)) { + throw new UserMigrationException("Version ".($importSource->getMigratorVersion(get_class($migrator)) ?? 'null')." for migrator ".get_class($migrator)." is not supported"); } } @@ -192,7 +192,7 @@ public function import(string $path, ?OutputInterface $output = null): void { foreach ($context->getUserMigrators() as $migratorRegistration) { /** @var IMigrator $migrator */ $migrator = $this->container->get($migratorRegistration->getService()); - $migrator->import($user, $importSource, $output, $migratorVersions[get_class($migrator)] ?? null); + $migrator->import($user, $importSource, $output); } $uid = $user->getUID(); From b6be1e1fa24c80e511ff957d8cc3bcdc6d6a0e83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Tue, 22 Feb 2022 15:44:40 +0100 Subject: [PATCH 11/12] update christophwurst/nextcloud MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- composer.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.lock b/composer.lock index 7691231c..b66ca597 100644 --- a/composer.lock +++ b/composer.lock @@ -179,12 +179,12 @@ "source": { "type": "git", "url": "https://github.com/ChristophWurst/nextcloud_composer.git", - "reference": "b21887763357434748b7793a67fdced7c35f88b5" + "reference": "8acf976cf96ca52a720bec554af52e109e7100a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ChristophWurst/nextcloud_composer/zipball/b21887763357434748b7793a67fdced7c35f88b5", - "reference": "b21887763357434748b7793a67fdced7c35f88b5", + "url": "https://api.github.com/repos/ChristophWurst/nextcloud_composer/zipball/8acf976cf96ca52a720bec554af52e109e7100a1", + "reference": "8acf976cf96ca52a720bec554af52e109e7100a1", "shasum": "" }, "require": { @@ -215,7 +215,7 @@ "issues": "https://github.com/ChristophWurst/nextcloud_composer/issues", "source": "https://github.com/ChristophWurst/nextcloud_composer/tree/master" }, - "time": "2022-02-15T01:22:02+00:00" + "time": "2022-02-22T10:38:34+00:00" }, { "name": "composer/package-versions-deprecated", From 82469c047a957418b385feb69e4bebf5a5dbc6fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Tue, 22 Feb 2022 16:58:52 +0100 Subject: [PATCH 12/12] Update baseline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No idea why psalm keeps thinking there is a signature mismatch with the interface, I was not able to reproduce on psalm.dev. Adding it to the baseline. Signed-off-by: Côme Chilliet --- composer.lock | 12 ++++++------ psalm.xml | 1 - tests/psalm-baseline.xml | 5 ++++- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/composer.lock b/composer.lock index b66ca597..c9739c3a 100644 --- a/composer.lock +++ b/composer.lock @@ -4694,16 +4694,16 @@ }, { "name": "vimeo/psalm", - "version": "4.20.0", + "version": "4.21.0", "source": { "type": "git", "url": "https://github.com/vimeo/psalm.git", - "reference": "f82a70e7edfc6cf2705e9374c8a0b6a974a779ed" + "reference": "d8bec4c7aaee111a532daec32fb09de5687053d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/f82a70e7edfc6cf2705e9374c8a0b6a974a779ed", - "reference": "f82a70e7edfc6cf2705e9374c8a0b6a974a779ed", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/d8bec4c7aaee111a532daec32fb09de5687053d1", + "reference": "d8bec4c7aaee111a532daec32fb09de5687053d1", "shasum": "" }, "require": { @@ -4794,9 +4794,9 @@ ], "support": { "issues": "https://github.com/vimeo/psalm/issues", - "source": "https://github.com/vimeo/psalm/tree/4.20.0" + "source": "https://github.com/vimeo/psalm/tree/4.21.0" }, - "time": "2022-02-03T17:03:47+00:00" + "time": "2022-02-18T04:34:15+00:00" }, { "name": "webmozart/assert", diff --git a/psalm.xml b/psalm.xml index 10580177..f56440fa 100644 --- a/psalm.xml +++ b/psalm.xml @@ -1,6 +1,5 @@ - + $read $stream $stream + + $versions +