]> source.dussan.org Git - nextcloud-server.git/commitdiff
Integrate migrator
authorChristopher Ng <chrng8@gmail.com>
Wed, 23 Feb 2022 05:27:49 +0000 (05:27 +0000)
committerChristopher Ng <chrng8@gmail.com>
Wed, 2 Mar 2022 01:59:15 +0000 (01:59 +0000)
Signed-off-by: Christopher Ng <chrng8@gmail.com>
apps/dav/appinfo/info.xml
apps/dav/composer/composer/autoload_classmap.php
apps/dav/composer/composer/autoload_static.php
apps/dav/lib/AppInfo/Application.php
apps/dav/lib/Command/ExportCalendars.php [deleted file]
apps/dav/lib/Command/ImportCalendar.php [deleted file]
apps/dav/lib/UserMigration/CalendarMigrator.php
apps/dav/tests/integration/UserMigration/CalendarMigratorTest.php

index 88c4ee03ac37389fcd9dacd43964d03f19e8d6d2..8462ed1816d4255c83abf7b0df21a3ca26d87515 100644 (file)
@@ -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>
index bf0fe9cbf750214d7a32f1aa735072148a40d4f9..f4b178148175d381cc0187995c69b25244c7a755 100644 (file)
@@ -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',
index 18bfd64296029e20eb2fa2284d9ec45d5030ee45..d164ab2b1ce712d50d68c18118e8ef0964fc6d49 100644 (file)
@@ -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',
index 99521b61e8b2f5a30f9449b6bdfa1f4056647cb4..f29161d69760ac7cce8f8fbb1c04425c86fef601 100644 (file)
@@ -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 (file)
index 770d6ed..0000000
+++ /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 (file)
index 193ecc3..0000000
+++ /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;
-       }
-}
index c1252d7517aea105d89e00a1b87f9f2cf01d7712..6a36a7204e181a52654b7e26e20d162670c39a7b 100644 (file)
@@ -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\"");
+                       }
                }
        }
 }
index 1f7bf6300943d5794b3796ed0d09fb67a20f5856..e2eb0c75d9bc067c9f0b3c13d57b4dfbac994ba8 100644 (file)
@@ -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),
                );