diff options
Diffstat (limited to 'lib/private/Migration')
-rw-r--r-- | lib/private/Migration/BackgroundRepair.php | 74 | ||||
-rw-r--r-- | lib/private/Migration/ConsoleOutput.php | 45 | ||||
-rw-r--r-- | lib/private/Migration/Exceptions/AttributeException.php | 17 | ||||
-rw-r--r-- | lib/private/Migration/MetadataManager.php | 168 | ||||
-rw-r--r-- | lib/private/Migration/NullOutput.php | 36 | ||||
-rw-r--r-- | lib/private/Migration/SimpleOutput.php | 44 |
6 files changed, 271 insertions, 113 deletions
diff --git a/lib/private/Migration/BackgroundRepair.php b/lib/private/Migration/BackgroundRepair.php index 579ba494e58..d542b82d5e1 100644 --- a/lib/private/Migration/BackgroundRepair.php +++ b/lib/private/Migration/BackgroundRepair.php @@ -1,38 +1,17 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Daniel Kesselberg <mail@danielkesselberg.de> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Robin Appelman <robin@icewind.nl> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @license AGPL-3.0 - * - * 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/> - * + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OC\Migration; +use OC\Repair; +use OCP\App\IAppManager; use OCP\AppFramework\Utility\ITimeFactory; use OCP\BackgroundJob\IJobList; use OCP\BackgroundJob\TimedJob; -use OCP\EventDispatcher\IEventDispatcher; -use OC\NeedsUpdateException; -use OC\Repair; -use OC_App; use Psr\Log\LoggerInterface; /** @@ -41,24 +20,22 @@ use Psr\Log\LoggerInterface; * @package OC\Migration */ class BackgroundRepair extends TimedJob { - private IJobList $jobList; - private LoggerInterface $logger; - private IEventDispatcher $dispatcher; - - public function __construct(IEventDispatcher $dispatcher, ITimeFactory $time, LoggerInterface $logger, IJobList $jobList) { + public function __construct( + private Repair $repair, + ITimeFactory $time, + private LoggerInterface $logger, + private IJobList $jobList, + private IAppManager $appManager, + ) { parent::__construct($time); - $this->dispatcher = $dispatcher; - $this->logger = $logger; - $this->jobList = $jobList; $this->setInterval(15 * 60); } /** * @param array $argument * @throws \Exception - * @throws \OC\NeedsUpdateException */ - protected function run($argument) { + protected function run($argument): void { if (!isset($argument['app']) || !isset($argument['step'])) { // remove the job - we can never execute it $this->jobList->remove($this, $this->argument); @@ -66,18 +43,12 @@ class BackgroundRepair extends TimedJob { } $app = $argument['app']; - try { - $this->loadApp($app); - } catch (NeedsUpdateException $ex) { - // as long as the app is not yet done with it's offline migration - // we better not start with the live migration - return; - } + $this->appManager->loadApp($app); $step = $argument['step']; - $repair = new Repair([], $this->dispatcher, \OC::$server->get(LoggerInterface::class)); + $this->repair->setRepairSteps([]); try { - $repair->addStep($step); + $this->repair->addStep($step); } catch (\Exception $ex) { $this->logger->error($ex->getMessage(), [ 'app' => 'migration', @@ -90,18 +61,9 @@ class BackgroundRepair extends TimedJob { } // execute the repair step - $repair->run(); + $this->repair->run(); // remove the job once executed successfully $this->jobList->remove($this, $this->argument); } - - /** - * @codeCoverageIgnore - * @param $app - * @throws NeedsUpdateException - */ - protected function loadApp($app) { - OC_App::loadApp($app); - } } diff --git a/lib/private/Migration/ConsoleOutput.php b/lib/private/Migration/ConsoleOutput.php index 9e3396f2a75..31412bf4ff0 100644 --- a/lib/private/Migration/ConsoleOutput.php +++ b/lib/private/Migration/ConsoleOutput.php @@ -1,23 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2017, ownCloud GmbH - * - * @author Morris Jobke <hey@morrisjobke.de> - * - * @license AGPL-3.0 - * - * 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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2015 ownCloud GmbH + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OC\Migration; @@ -34,34 +20,35 @@ use Symfony\Component\Console\Output\OutputInterface; * @package OC\Migration */ class ConsoleOutput implements IOutput { - /** @var OutputInterface */ - private $output; + private ?ProgressBar $progressBar = null; - /** @var ProgressBar */ - private $progressBar; + public function __construct( + private OutputInterface $output, + ) { + } - public function __construct(OutputInterface $output) { - $this->output = $output; + public function debug(string $message): void { + $this->output->writeln($message, OutputInterface::VERBOSITY_VERBOSE); } /** * @param string $message */ - public function info($message) { + public function info($message): void { $this->output->writeln("<info>$message</info>"); } /** * @param string $message */ - public function warning($message) { + public function warning($message): void { $this->output->writeln("<comment>$message</comment>"); } /** * @param int $max */ - public function startProgress($max = 0) { + public function startProgress($max = 0): void { if (!is_null($this->progressBar)) { $this->progressBar->finish(); } @@ -73,7 +60,7 @@ class ConsoleOutput implements IOutput { * @param int $step * @param string $description */ - public function advance($step = 1, $description = '') { + public function advance($step = 1, $description = ''): void { if (is_null($this->progressBar)) { $this->progressBar = new ProgressBar($this->output); $this->progressBar->start(); @@ -84,7 +71,7 @@ class ConsoleOutput implements IOutput { } } - public function finishProgress() { + public function finishProgress(): void { if (is_null($this->progressBar)) { return; } diff --git a/lib/private/Migration/Exceptions/AttributeException.php b/lib/private/Migration/Exceptions/AttributeException.php new file mode 100644 index 00000000000..3daf99032ad --- /dev/null +++ b/lib/private/Migration/Exceptions/AttributeException.php @@ -0,0 +1,17 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OC\Migration\Exceptions; + +use Exception; + +/** + * @since 30.0.0 + */ +class AttributeException extends Exception { +} diff --git a/lib/private/Migration/MetadataManager.php b/lib/private/Migration/MetadataManager.php new file mode 100644 index 00000000000..f4cb95342b4 --- /dev/null +++ b/lib/private/Migration/MetadataManager.php @@ -0,0 +1,168 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OC\Migration; + +use OC\DB\Connection; +use OC\DB\MigrationService; +use OC\Migration\Exceptions\AttributeException; +use OCP\App\IAppManager; +use OCP\Migration\Attributes\GenericMigrationAttribute; +use OCP\Migration\Attributes\MigrationAttribute; +use Psr\Log\LoggerInterface; +use ReflectionClass; + +/** + * Helps managing DB Migrations' Metadata + * + * @since 30.0.0 + */ +class MetadataManager { + public function __construct( + private readonly IAppManager $appManager, + private readonly Connection $connection, + private readonly LoggerInterface $logger, + ) { + } + + /** + * We get all migrations from an app (or 'core'), and + * for each migration files we extract its attributes + * + * @param string $appId + * + * @return array<string, MigrationAttribute[]> + * @since 30.0.0 + */ + public function extractMigrationAttributes(string $appId): array { + $ms = new MigrationService($appId, $this->connection); + + $metadata = []; + foreach ($ms->getAvailableVersions() as $version) { + $metadata[$version] = []; + $class = new ReflectionClass($ms->createInstance($version)); + $attributes = $class->getAttributes(); + foreach ($attributes as $attribute) { + $item = $attribute->newInstance(); + if ($item instanceof MigrationAttribute) { + $metadata[$version][] = $item; + } + } + } + + return $metadata; + } + + /** + * convert direct data from release metadata into a list of Migrations' Attribute + * + * @param array<array-key, array<array-key, array>> $metadata + * @param bool $filterKnownMigrations ignore metadata already done in local instance + * + * @return array{apps: array<array-key, array<string, MigrationAttribute[]>>, core: array<string, MigrationAttribute[]>} + * @since 30.0.0 + */ + public function getMigrationsAttributesFromReleaseMetadata( + array $metadata, + bool $filterKnownMigrations = false, + ): array { + $appsAttributes = []; + foreach (array_keys($metadata['apps']) as $appId) { + if ($filterKnownMigrations && !$this->appManager->isEnabledForAnyone($appId)) { + continue; // if not interested and app is not installed + } + + $done = ($filterKnownMigrations) ? $this->getKnownMigrations($appId) : []; + $appsAttributes[$appId] = $this->parseMigrations($metadata['apps'][$appId] ?? [], $done); + } + + $done = ($filterKnownMigrations) ? $this->getKnownMigrations('core') : []; + return [ + 'core' => $this->parseMigrations($metadata['core'] ?? [], $done), + 'apps' => $appsAttributes + ]; + } + + /** + * returns list of installed apps that does not support migrations metadata (yet) + * + * @param array<array-key, array<array-key, array>> $metadata + * + * @return string[] + * @since 30.0.0 + */ + public function getUnsupportedApps(array $metadata): array { + return array_values(array_diff($this->appManager->getEnabledApps(), array_keys($metadata['apps']))); + } + + /** + * convert raw data to a list of MigrationAttribute + * + * @param array $migrations + * @param array $ignoreMigrations + * + * @return array<string, MigrationAttribute[]> + */ + private function parseMigrations(array $migrations, array $ignoreMigrations = []): array { + $parsed = []; + foreach (array_keys($migrations) as $entry) { + if (in_array($entry, $ignoreMigrations)) { + continue; + } + + $parsed[$entry] = []; + foreach ($migrations[$entry] as $item) { + try { + $parsed[$entry][] = $this->createAttribute($item); + } catch (AttributeException $e) { + $this->logger->warning('exception while trying to create attribute', ['exception' => $e, 'item' => json_encode($item)]); + $parsed[$entry][] = new GenericMigrationAttribute($item); + } + } + } + + return $parsed; + } + + /** + * returns migrations already done + * + * @param string $appId + * + * @return array + * @throws \Exception + */ + private function getKnownMigrations(string $appId): array { + $ms = new MigrationService($appId, $this->connection); + return $ms->getMigratedVersions(); + } + + /** + * generate (deserialize) a MigrationAttribute from a serialized version + * + * @param array $item + * + * @return MigrationAttribute + * @throws AttributeException + */ + private function createAttribute(array $item): MigrationAttribute { + $class = $item['class'] ?? ''; + $namespace = 'OCP\Migration\Attributes\\'; + if (!str_starts_with($class, $namespace) + || !ctype_alpha(substr($class, strlen($namespace)))) { + throw new AttributeException('class name does not looks valid'); + } + + try { + $attribute = new $class($item['table'] ?? ''); + return $attribute->import($item); + } catch (\Error) { + throw new AttributeException('cannot import Attribute'); + } + } +} diff --git a/lib/private/Migration/NullOutput.php b/lib/private/Migration/NullOutput.php new file mode 100644 index 00000000000..8db7b950af8 --- /dev/null +++ b/lib/private/Migration/NullOutput.php @@ -0,0 +1,36 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OC\Migration; + +use OCP\Migration\IOutput; + +/** + * Class NullOutput + * + * A simple IOutput that discards all output + * + * @package OC\Migration + */ +class NullOutput implements IOutput { + public function debug(string $message): void { + } + + public function info($message): void { + } + + public function warning($message): void { + } + + public function startProgress($max = 0): void { + } + + public function advance($step = 1, $description = ''): void { + } + + public function finishProgress(): void { + } +} diff --git a/lib/private/Migration/SimpleOutput.php b/lib/private/Migration/SimpleOutput.php index f97bcb767f8..b7a07cc6ff2 100644 --- a/lib/private/Migration/SimpleOutput.php +++ b/lib/private/Migration/SimpleOutput.php @@ -1,23 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2017, ownCloud GmbH - * - * @author Joas Schilling <coding@schilljs.com> - * - * @license AGPL-3.0 - * - * 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/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2015 ownCloud GmbH + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OC\Migration; @@ -33,19 +19,21 @@ use Psr\Log\LoggerInterface; * @package OC\Migration */ class SimpleOutput implements IOutput { - private LoggerInterface $logger; - private $appName; + public function __construct( + private LoggerInterface $logger, + private $appName, + ) { + } - public function __construct(LoggerInterface $logger, $appName) { - $this->logger = $logger; - $this->appName = $appName; + public function debug(string $message): void { + $this->logger->debug($message, ['app' => $this->appName]); } /** * @param string $message * @since 9.1.0 */ - public function info($message) { + public function info($message): void { $this->logger->info($message, ['app' => $this->appName]); } @@ -53,7 +41,7 @@ class SimpleOutput implements IOutput { * @param string $message * @since 9.1.0 */ - public function warning($message) { + public function warning($message): void { $this->logger->warning($message, ['app' => $this->appName]); } @@ -61,7 +49,7 @@ class SimpleOutput implements IOutput { * @param int $max * @since 9.1.0 */ - public function startProgress($max = 0) { + public function startProgress($max = 0): void { } /** @@ -69,12 +57,12 @@ class SimpleOutput implements IOutput { * @param string $description * @since 9.1.0 */ - public function advance($step = 1, $description = '') { + public function advance($step = 1, $description = ''): void { } /** * @since 9.1.0 */ - public function finishProgress() { + public function finishProgress(): void { } } |