diff options
author | Christopher Ng <chrng8@gmail.com> | 2022-02-23 05:27:49 +0000 |
---|---|---|
committer | Christopher Ng <chrng8@gmail.com> | 2022-03-02 01:59:15 +0000 |
commit | 302a67f6853065122ef8648cdbdefc1504e8d350 (patch) | |
tree | de3c6e69c8859252ebc3127d7f0831611c3251d9 /apps/dav | |
parent | 4c3d68381b6bb1847a2b5b22dbfc61a83c529df6 (diff) | |
download | nextcloud-server-302a67f6853065122ef8648cdbdefc1504e8d350.tar.gz nextcloud-server-302a67f6853065122ef8648cdbdefc1504e8d350.zip |
Integrate migrator
Signed-off-by: Christopher Ng <chrng8@gmail.com>
Diffstat (limited to 'apps/dav')
-rw-r--r-- | apps/dav/appinfo/info.xml | 2 | ||||
-rw-r--r-- | apps/dav/composer/composer/autoload_classmap.php | 2 | ||||
-rw-r--r-- | apps/dav/composer/composer/autoload_static.php | 2 | ||||
-rw-r--r-- | apps/dav/lib/AppInfo/Application.php | 3 | ||||
-rw-r--r-- | apps/dav/lib/Command/ExportCalendars.php | 83 | ||||
-rw-r--r-- | apps/dav/lib/Command/ImportCalendar.php | 94 | ||||
-rw-r--r-- | apps/dav/lib/UserMigration/CalendarMigrator.php | 140 | ||||
-rw-r--r-- | apps/dav/tests/integration/UserMigration/CalendarMigratorTest.php | 8 |
8 files changed, 74 insertions, 260 deletions
diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml index 88c4ee03ac3..8462ed1816d 100644 --- a/apps/dav/appinfo/info.xml +++ b/apps/dav/appinfo/info.xml @@ -56,8 +56,6 @@ <command>OCA\DAV\Command\SyncBirthdayCalendar</command> <command>OCA\DAV\Command\SyncSystemAddressBook</command> <command>OCA\DAV\Command\RemoveInvalidShares</command> - <command>OCA\DAV\Command\ExportCalendars</command> - <command>OCA\DAV\Command\ImportCalendar</command> </commands> <settings> diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php index bf0fe9cbf75..f4b17814817 100644 --- a/apps/dav/composer/composer/autoload_classmap.php +++ b/apps/dav/composer/composer/autoload_classmap.php @@ -124,8 +124,6 @@ return array( 'OCA\\DAV\\Command\\CreateAddressBook' => $baseDir . '/../lib/Command/CreateAddressBook.php', 'OCA\\DAV\\Command\\CreateCalendar' => $baseDir . '/../lib/Command/CreateCalendar.php', 'OCA\\DAV\\Command\\DeleteCalendar' => $baseDir . '/../lib/Command/DeleteCalendar.php', - 'OCA\\DAV\\Command\\ExportCalendars' => $baseDir . '/../lib/Command/ExportCalendars.php', - 'OCA\\DAV\\Command\\ImportCalendar' => $baseDir . '/../lib/Command/ImportCalendar.php', 'OCA\\DAV\\Command\\ListCalendars' => $baseDir . '/../lib/Command/ListCalendars.php', 'OCA\\DAV\\Command\\MoveCalendar' => $baseDir . '/../lib/Command/MoveCalendar.php', 'OCA\\DAV\\Command\\RemoveInvalidShares' => $baseDir . '/../lib/Command/RemoveInvalidShares.php', diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php index 18bfd642960..d164ab2b1ce 100644 --- a/apps/dav/composer/composer/autoload_static.php +++ b/apps/dav/composer/composer/autoload_static.php @@ -139,8 +139,6 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Command\\CreateAddressBook' => __DIR__ . '/..' . '/../lib/Command/CreateAddressBook.php', 'OCA\\DAV\\Command\\CreateCalendar' => __DIR__ . '/..' . '/../lib/Command/CreateCalendar.php', 'OCA\\DAV\\Command\\DeleteCalendar' => __DIR__ . '/..' . '/../lib/Command/DeleteCalendar.php', - 'OCA\\DAV\\Command\\ExportCalendars' => __DIR__ . '/..' . '/../lib/Command/ExportCalendars.php', - 'OCA\\DAV\\Command\\ImportCalendar' => __DIR__ . '/..' . '/../lib/Command/ImportCalendar.php', 'OCA\\DAV\\Command\\ListCalendars' => __DIR__ . '/..' . '/../lib/Command/ListCalendars.php', 'OCA\\DAV\\Command\\MoveCalendar' => __DIR__ . '/..' . '/../lib/Command/MoveCalendar.php', 'OCA\\DAV\\Command\\RemoveInvalidShares' => __DIR__ . '/..' . '/../lib/Command/RemoveInvalidShares.php', diff --git a/apps/dav/lib/AppInfo/Application.php b/apps/dav/lib/AppInfo/Application.php index 99521b61e8b..f29161d6976 100644 --- a/apps/dav/lib/AppInfo/Application.php +++ b/apps/dav/lib/AppInfo/Application.php @@ -80,6 +80,7 @@ use OCA\DAV\Listener\CardListener; use OCA\DAV\Search\ContactsSearchProvider; use OCA\DAV\Search\EventsSearchProvider; use OCA\DAV\Search\TasksSearchProvider; +use OCA\DAV\UserMigration\CalendarMigrator; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; @@ -165,6 +166,8 @@ class Application extends App implements IBootstrap { $context->registerNotifierService(Notifier::class); $context->registerCalendarProvider(CalendarProvider::class); + + $context->registerUserMigrator(CalendarMigrator::class); } public function boot(IBootContext $context): void { diff --git a/apps/dav/lib/Command/ExportCalendars.php b/apps/dav/lib/Command/ExportCalendars.php deleted file mode 100644 index 770d6edc425..00000000000 --- a/apps/dav/lib/Command/ExportCalendars.php +++ /dev/null @@ -1,83 +0,0 @@ -<?php - -declare(strict_types=1); - -/** - * @copyright 2022 Christopher Ng <chrng8@gmail.com> - * - * @author Christopher Ng <chrng8@gmail.com> - * - * @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\DAV\Command; - -use OC\Core\Command\Base; -use OCA\DAV\UserMigration\CalendarMigrator; -use OCA\DAV\UserMigration\CalendarMigratorException; -use OCP\IUser; -use OCP\IUserManager; -use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; - -class ExportCalendars extends Base { - - /** @var IUserManager */ - private $userManager; - - /** @var CalendarMigrator */ - private $calendarMigrator; - - public function __construct( - IUserManager $userManager, - CalendarMigrator $calendarMigrator - ) { - parent::__construct(); - $this->userManager = $userManager; - $this->calendarMigrator = $calendarMigrator; - } - - protected function configure() { - $this - ->setName('dav:export-calendars') - ->setDescription('Export the calendars of a user') - ->addArgument( - 'user', - InputArgument::REQUIRED, - 'User to export', - ); - } - - protected function execute(InputInterface $input, OutputInterface $output): int { - $user = $this->userManager->get($input->getArgument('user')); - - if (!$user instanceof IUser) { - $output->writeln('<error>User ' . $input->getArgument('user') . ' does not exist</error>'); - return 1; - } - - try { - $this->calendarMigrator->export($user, $output); - } catch (CalendarMigratorException $e) { - $output->writeln('<error>' . $e->getMessage() . '</error>'); - return $e->getCode() !== 0 ? (int)$e->getCode() : 1; - } - - return 0; - } -} diff --git a/apps/dav/lib/Command/ImportCalendar.php b/apps/dav/lib/Command/ImportCalendar.php deleted file mode 100644 index 193ecc3b29b..00000000000 --- a/apps/dav/lib/Command/ImportCalendar.php +++ /dev/null @@ -1,94 +0,0 @@ -<?php - -declare(strict_types=1); - -/** - * @copyright 2022 Christopher Ng <chrng8@gmail.com> - * - * @author Christopher Ng <chrng8@gmail.com> - * - * @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\DAV\Command; - -use OC\Core\Command\Base; -use OCA\DAV\UserMigration\CalendarMigrator; -use OCA\DAV\UserMigration\CalendarMigratorException; -use OCP\IUser; -use OCP\IUserManager; -use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; - -class ImportCalendar extends Base { - - /** @var IUserManager */ - private $userManager; - - /** @var CalendarMigrator */ - private $calendarMigrator; - - public function __construct( - IUserManager $userManager, - CalendarMigrator $calendarMigrator - ) { - parent::__construct(); - $this->userManager = $userManager; - $this->calendarMigrator = $calendarMigrator; - } - - protected function configure() { - $this - ->setName('dav:import-calendar') - ->setDescription('Import a calendar to a user\'s account') - ->addArgument( - 'user', - InputArgument::REQUIRED, - 'User to import the calendar for', - ) - ->addArgument( - 'path', - InputArgument::REQUIRED, - 'Path to the *.ics file', - ); - } - - protected function execute(InputInterface $input, OutputInterface $output): int { - $user = $this->userManager->get($input->getArgument('user')); - - [ - 'basename' => $filename, - 'dirname' => $srcDir, - ] = pathinfo($input->getArgument('path')); - - - if (!$user instanceof IUser) { - $output->writeln('<error>User ' . $input->getArgument('user') . ' does not exist</error>'); - return 1; - } - - try { - $this->calendarMigrator->import($user, $srcDir, $filename, $output); - } catch (CalendarMigratorException $e) { - $output->writeln('<error>' . $e->getMessage() . '</error>'); - return $e->getCode() !== 0 ? (int)$e->getCode() : 1; - } - - return 0; - } -} diff --git a/apps/dav/lib/UserMigration/CalendarMigrator.php b/apps/dav/lib/UserMigration/CalendarMigrator.php index c1252d7517a..6a36a7204e1 100644 --- a/apps/dav/lib/UserMigration/CalendarMigrator.php +++ b/apps/dav/lib/UserMigration/CalendarMigrator.php @@ -26,10 +26,7 @@ declare(strict_types=1); namespace OCA\DAV\UserMigration; -use function Safe\fopen; use function Safe\substr; -use OC\Files\Filesystem; -use OC\Files\View; use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\CalDAV\ICSExportPlugin\ICSExportPlugin; use OCA\DAV\CalDAV\Plugin as CalDAVPlugin; @@ -41,6 +38,10 @@ use OCP\Calendar\IManager as ICalendarManager; use OCP\Defaults; use OCP\IL10N; use OCP\IUser; +use OCP\UserMigration\IExportDestination; +use OCP\UserMigration\IImportSource; +use OCP\UserMigration\IMigrator; +use OCP\UserMigration\TMigratorBasicVersionHandling; use Sabre\DAV\Exception\BadRequest; use Sabre\DAV\Version as SabreDavVersion; use Sabre\VObject\Component as VObjectComponent; @@ -52,7 +53,9 @@ use Sabre\VObject\UUIDUtil; use Safe\Exceptions\FilesystemException; use Symfony\Component\Console\Output\OutputInterface; -class CalendarMigrator { +class CalendarMigrator implements IMigrator { + + use TMigratorBasicVersionHandling; private CalDavBackend $calDavBackend; @@ -67,11 +70,13 @@ class CalendarMigrator { private SabreDavServer $sabreDavServer; - public const USERS_URI_ROOT = 'principals/users/'; + private const USERS_URI_ROOT = 'principals/users/'; + + private const FILENAME_EXT = '.ics'; - public const FILENAME_EXT = '.ics'; + private const MIGRATED_URI_PREFIX = 'migrated-'; - public const MIGRATED_URI_PREFIX = 'migrated-'; + private const EXPORT_ROOT = 'calendars/'; public function __construct( CalDavBackend $calDavBackend, @@ -86,12 +91,15 @@ class CalendarMigrator { $this->defaults = $defaults; $this->l10n = $l10n; + // Override trait property + $this->mandatory = true; + $root = new RootCollection(); $this->sabreDavServer = new SabreDavServer(new CachingTree($root)); $this->sabreDavServer->addPlugin(new CalDAVPlugin()); } - public function getPrincipalUri(IUser $user): string { + private function getPrincipalUri(IUser $user): string { return CalendarMigrator::USERS_URI_ROOT . $user->getUID(); } @@ -101,7 +109,7 @@ class CalendarMigrator { * @throws CalendarMigratorException * @throws InvalidCalendarException */ - public function getCalendarExportData(IUser $user, ICalendar $calendar): array { + private function getCalendarExportData(IUser $user, ICalendar $calendar): array { $userId = $user->getUID(); $calendarId = $calendar->getKey(); $calendarInfo = $this->calDavBackend->getCalendarById($calendarId); @@ -157,7 +165,7 @@ class CalendarMigrator { * * @throws CalendarMigratorException */ - public function getCalendarExports(IUser $user): array { + private function getCalendarExports(IUser $user): array { $principalUri = $this->getPrincipalUri($user); return array_values(array_filter(array_map( @@ -174,7 +182,7 @@ class CalendarMigrator { ))); } - public function getUniqueCalendarUri(IUser $user, string $initialCalendarUri): string { + private function getUniqueCalendarUri(IUser $user, string $initialCalendarUri): string { $principalUri = $this->getPrincipalUri($user); $initialCalendarUri = substr($initialCalendarUri, 0, strlen(CalendarMigrator::MIGRATED_URI_PREFIX)) === CalendarMigrator::MIGRATED_URI_PREFIX ? $initialCalendarUri @@ -196,24 +204,11 @@ class CalendarMigrator { } /** - * @throws CalendarMigratorException + * {@inheritDoc} */ - protected function writeExport(IUser $user, string $data, string $destDir, string $filename, OutputInterface $output): void { - $userId = $user->getUID(); - - \OC::$server->getUserFolder($userId); - Filesystem::initMountPoints($userId); - - $view = new View(); + public function export(IUser $user, IExportDestination $exportDestination, OutputInterface $output): void { + $output->writeln("Exporting calendars…"); - if ($view->file_put_contents("$destDir/$filename", $data) === false) { - throw new CalendarMigratorException('Could not export calendar'); - } - - $output->writeln("<info>✅ Exported calendar of <$userId> into $destDir/$filename</info>"); - } - - public function export(IUser $user, OutputInterface $output): void { $userId = $user->getUID(); try { @@ -234,24 +229,16 @@ class CalendarMigrator { // Set filename to sanitized calendar name appended with the date $filename = preg_replace('/[^a-zA-Z0-9-_ ]/um', '', $name) . '_' . date('Y-m-d') . CalendarMigrator::FILENAME_EXT; - $this->writeExport( - $user, - $vCalendar->serialize(), - // TESTING directory does not automatically get created so just write to user directory, this will be put in a zip with all other user_migration data - // "/$userId/export/$appId", - "/$userId", - $filename, - $output, - ); + if ($exportDestination->addFileContents(CalendarMigrator::EXPORT_ROOT . $filename, $vCalendar->serialize()) === false) { + throw new CalendarMigratorException(); + } } } /** - * Return an associative array mapping Time Zone ID to VTimeZone component - * * @return array<string, VTimeZone> */ - public function getCalendarTimezones(VCalendar $vCalendar): array { + private function getCalendarTimezones(VCalendar $vCalendar): array { /** @var VTimeZone[] $calendarTimezones */ $calendarTimezones = array_values(array_filter( $vCalendar->getComponents(), @@ -270,7 +257,7 @@ class CalendarMigrator { /** * @return VTimeZone[] */ - public function getTimezonesForComponent(VCalendar $vCalendar, VObjectComponent $component): array { + private function getTimezonesForComponent(VCalendar $vCalendar, VObjectComponent $component): array { $componentTimezoneIds = []; foreach ($component->children() as $child) { @@ -290,7 +277,7 @@ class CalendarMigrator { ))); } - public function sanitizeComponent(VObjectComponent $component): VObjectComponent { + private function sanitizeComponent(VObjectComponent $component): VObjectComponent { // Operate on the component clone to prevent mutation of the original $componentClone = clone $component; @@ -310,7 +297,7 @@ class CalendarMigrator { /** * @return VObjectComponent[] */ - public function getRequiredImportComponents(VCalendar $vCalendar, VObjectComponent $component): array { + private function getRequiredImportComponents(VCalendar $vCalendar, VObjectComponent $component): array { $component = $this->sanitizeComponent($component); /** @var array<int, VTimeZone> $timezoneComponents */ $timezoneComponents = $this->getTimezonesForComponent($vCalendar, $component); @@ -320,7 +307,7 @@ class CalendarMigrator { ]; } - public function initCalendarObject(): VCalendar { + private function initCalendarObject(): VCalendar { $vCalendarObject = new VCalendar(); $vCalendarObject->PRODID = $this->sabreDavServer::$exposeVersion ? '-//SabreDAV//SabreDAV ' . SabreDavVersion::VERSION . '//EN' @@ -328,7 +315,7 @@ class CalendarMigrator { return $vCalendarObject; } - public function importCalendarObject(int $calendarId, VCalendar $vCalendarObject): void { + private function importCalendarObject(int $calendarId, VCalendar $vCalendarObject): void { try { $this->calDavBackend->createCalendarObject( $calendarId, @@ -345,7 +332,7 @@ class CalendarMigrator { /** * @throws CalendarMigratorException */ - public function importCalendar(IUser $user, string $filename, string $initialCalendarUri, VCalendar $vCalendar): void { + private function importCalendar(IUser $user, string $filename, string $initialCalendarUri, VCalendar $vCalendar): void { $principalUri = $this->getPrincipalUri($user); $calendarUri = $this->getUniqueCalendarUri($user, $initialCalendarUri); @@ -415,43 +402,50 @@ class CalendarMigrator { } /** + * {@inheritDoc} + * * @throws FilesystemException * @throws CalendarMigratorException */ - public function import(IUser $user, string $srcDir, string $filename, OutputInterface $output): void { - $userId = $user->getUID(); - - try { - /** @var VCalendar $vCalendar */ - $vCalendar = VObjectReader::read( - fopen("$srcDir/$filename", 'r'), - VObjectReader::OPTION_FORGIVING, - ); - } catch (FilesystemException $e) { - throw new FilesystemException("Failed to read file: \"$srcDir/$filename\""); + public function import(IUser $user, IImportSource $importSource, OutputInterface $output): void { + if ($importSource->getMigratorVersion(static::class) === null) { + $output->writeln('No version for ' . static::class . ', skipping import…'); + return; } - $problems = $vCalendar->validate(); - if (empty($problems)) { - $splitFilename = explode('_', $filename, 2); - if (count($splitFilename) !== 2) { - $output->writeln("<error>Invalid filename, filename must be of the format: \"<calendar_name>_YYYY-MM-DD" . CalendarMigrator::FILENAME_EXT . "\"</error>"); - throw new CalendarMigratorException(); + $output->writeln("Importing calendars…"); + + foreach ($importSource->getFolderListing(CalendarMigrator::EXPORT_ROOT) as $filename) { + try { + /** @var VCalendar $vCalendar */ + $vCalendar = VObjectReader::read( + $importSource->getFileAsStream(CalendarMigrator::EXPORT_ROOT . $filename), + VObjectReader::OPTION_FORGIVING, + ); + } catch (FilesystemException $e) { + throw new FilesystemException("Failed to read file: \"$filename\""); } - [$initialCalendarUri, $suffix] = $splitFilename; - $this->importCalendar( - $user, - $filename, - $initialCalendarUri, - $vCalendar, - ); + $problems = $vCalendar->validate(); + if (empty($problems)) { + $splitFilename = explode('_', $filename, 2); + if (count($splitFilename) !== 2) { + $output->writeln("<error>Invalid filename, expected filename of the format: \"<calendar_name>_YYYY-MM-DD" . CalendarMigrator::FILENAME_EXT . "\"</error>"); + throw new CalendarMigratorException(); + } + [$initialCalendarUri, $suffix] = $splitFilename; - $vCalendar->destroy(); + $this->importCalendar( + $user, + $filename, + $initialCalendarUri, + $vCalendar, + ); - $output->writeln("<info>✅ Imported calendar \"$filename\" into account of <$userId></info>"); - } else { - throw new CalendarMigratorException("Invalid data contained in \"$srcDir/$filename\""); + $vCalendar->destroy(); + } else { + throw new CalendarMigratorException("Invalid data contained in \"$filename\""); + } } } } diff --git a/apps/dav/tests/integration/UserMigration/CalendarMigratorTest.php b/apps/dav/tests/integration/UserMigration/CalendarMigratorTest.php index 1f7bf630094..e2eb0c75d9b 100644 --- a/apps/dav/tests/integration/UserMigration/CalendarMigratorTest.php +++ b/apps/dav/tests/integration/UserMigration/CalendarMigratorTest.php @@ -97,7 +97,7 @@ class CalendarMigratorTest extends TestCase { private function getSanitizedComponents(VCalendar $vCalendar): array { return array_map( // Elements of the serialized blob are sorted - fn (VObjectComponent $component) => $this->migrator->sanitizeComponent($component)->serialize(), + fn (VObjectComponent $component) => $this->invokePrivate($this->migrator, 'sanitizeComponent', [$component])->serialize(), $vCalendar->getComponents(), ); } @@ -111,9 +111,9 @@ class CalendarMigratorTest extends TestCase { $problems = $importCalendar->validate(); $this->assertEmpty($problems); - $this->migrator->importCalendar($user, $filename, $initialCalendarUri, $importCalendar); + $this->invokePrivate($this->migrator, 'importCalendar', [$user, $filename, $initialCalendarUri, $importCalendar]); - $calendarExports = $this->migrator->getCalendarExports($user); + $calendarExports = $this->invokePrivate($this->migrator, 'getCalendarExports', [$user]); $this->assertCount(1, $calendarExports); /** @var VCalendar $exportCalendar */ @@ -125,7 +125,7 @@ class CalendarMigratorTest extends TestCase { ); $this->assertEqualsCanonicalizing( - // Components are sanitized on import + // Components are expected to be sanitized on import $this->getSanitizedComponents($importCalendar), $this->getComponents($exportCalendar), ); |