diff --git a/lib/BackgroundJob/UserImportJob.php b/lib/BackgroundJob/UserImportJob.php new file mode 100644 index 00000000..c3f410d5 --- /dev/null +++ b/lib/BackgroundJob/UserImportJob.php @@ -0,0 +1,119 @@ + + * + * @author Christopher Ng + * + * @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\BackgroundJob; + +use OCA\UserMigration\AppInfo\Application; +use OCA\UserMigration\Db\UserImport; +use OCA\UserMigration\Db\UserImportMapper; +use OCA\UserMigration\Service\UserMigrationService; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\BackgroundJob\QueuedJob; +use OCP\IUser; +use OCP\IUserManager; +use OCP\Notification\IManager as NotificationManager; +use Psr\Log\LoggerInterface; + +class UserImportJob extends QueuedJob { + private IUserManager $userManager; + private UserMigrationService $migrationService; + private LoggerInterface $logger; + private NotificationManager $notificationManager; + private UserImportMapper $mapper; + + public function __construct( + ITimeFactory $timeFactory, + IUserManager $userManager, + UserMigrationService $migrationService, + LoggerInterface $logger, + NotificationManager $notificationManager, + UserImportMapper $mapper + ) { + parent::__construct($timeFactory); + + $this->userManager = $userManager; + $this->migrationService = $migrationService; + $this->logger = $logger; + $this->notificationManager = $notificationManager; + $this->mapper = $mapper; + } + + public function run($argument): void { + $id = $argument['id']; + + $import = $this->mapper->getById($id); + $user = $import->getSourceUser(); + $path = $import->getPath(); + + $userObject = $this->userManager->get($user); + + if (!$userObject instanceof IUser) { + $this->logger->error('Could not import: Unknown user ' . $user); + $this->failedNotication($import); + $this->mapper->delete($import); + return; + } + + try { + $import->setStatus(UserImport::STATUS_STARTED); + $this->mapper->update($import); + + $this->migrationService->import($path, $userObject); + $this->successNotification($import); + } catch (\Exception $e) { + $this->logger->error($e->getMessage(), ['exception' => $e]); + $this->failedNotication($import); + } finally { + $this->mapper->delete($import); + } + } + + private function failedNotication(UserImport $import): void { + // Send notification to user + $notification = $this->notificationManager->createNotification(); + $notification->setUser($import->getSourceUser()) + ->setApp(Application::APP_ID) + ->setDateTime($this->time->getDateTime()) + ->setSubject('importFailed', [ + 'sourceUser' => $import->getSourceUser(), + ]) + ->setObject('import', (string)$import->getId()); + $this->notificationManager->notify($notification); + } + + private function successNotification(UserImport $import): void { + // Send notification to user + $notification = $this->notificationManager->createNotification(); + $notification->setUser($import->getSourceUser()) + ->setApp(Application::APP_ID) + ->setDateTime($this->time->getDateTime()) + ->setSubject('importDone', [ + 'sourceUser' => $import->getSourceUser(), + ]) + ->setObject('import', (string)$import->getId()); + $this->notificationManager->notify($notification); + } +} diff --git a/lib/Db/UserImport.php b/lib/Db/UserImport.php new file mode 100644 index 00000000..2bb386ee --- /dev/null +++ b/lib/Db/UserImport.php @@ -0,0 +1,79 @@ + + * + * @author Christopher Ng + * + * @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\Db; + +use OCP\AppFramework\Db\Entity; + +/** + * @method void setSourceUser(string $uid) + * @method string getSourceUser() + * @method void setTargetUser(string $uid) + * @method string getTargetUser() + * @method void setPath(string $path) + * @method string getPath() + * @method void setMigrators(string $migrators) + * @method string getMigrators() + * @method void setStatus(int $status) + * @method int getStatus() + */ +class UserImport extends Entity { + public const STATUS_WAITING = 0; + public const STATUS_STARTED = 1; + + /** @var string */ + protected $sourceUser; + /** @var string */ + protected $targetUser; + /** @var string */ + protected $path; + /** @var string JSON encoded array */ + protected $migrators; + /** @var int */ + protected $status; + + public function __construct() { + $this->addType('sourceUser', 'string'); + $this->addType('targetUser', 'string'); + $this->addType('path', 'string'); + $this->addType('migrators', 'string'); + $this->addType('status', 'int'); + } + + /** + * Returns the migrators in an associative array + */ + public function getMigratorsArray(): ?array { + return json_decode($this->migrators, true); + } + + /** + * Set the migrators + */ + public function setMigratorsArray(?array $migrators): void { + $this->setMigrators(json_encode($migrators)); + } +} diff --git a/lib/Db/UserImportMapper.php b/lib/Db/UserImportMapper.php new file mode 100644 index 00000000..ac90fa14 --- /dev/null +++ b/lib/Db/UserImportMapper.php @@ -0,0 +1,74 @@ + + * + * @author Christopher Ng + * + * @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\Db; + +use OCP\AppFramework\Db\QBMapper; +use OCP\IDBConnection; + +class UserImportMapper extends QBMapper { + public const TABLE_NAME = 'user_import_jobs'; + + public function __construct(IDBConnection $db) { + parent::__construct($db, static::TABLE_NAME, UserImport::class); + } + + public function getById(int $id): UserImport { + $qb = $this->db->getQueryBuilder(); + + $qb->select('*') + ->from($this->getTableName()) + ->where( + $qb->expr()->eq('id', $qb->createNamedParameter($id)) + ); + + return $this->findEntity($qb); + } + + public function getBySourceUser(string $userId): UserImport { + $qb = $this->db->getQueryBuilder(); + + $qb->select('*') + ->from($this->getTableName()) + ->where( + $qb->expr()->eq('source_user', $qb->createNamedParameter($userId)) + ); + + return $this->findEntity($qb); + } + + public function getByTargetUser(string $userId): UserImport { + $qb = $this->db->getQueryBuilder(); + + $qb->select('*') + ->from($this->getTableName()) + ->where( + $qb->expr()->eq('target_user', $qb->createNamedParameter($userId)) + ); + + return $this->findEntity($qb); + } +} diff --git a/lib/Migration/Version00001Date20220412116131.php b/lib/Migration/Version00001Date20220412116131.php new file mode 100644 index 00000000..ed317469 --- /dev/null +++ b/lib/Migration/Version00001Date20220412116131.php @@ -0,0 +1,78 @@ + + * + * @author Christopher Ng + * + * @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\Migration; + +use Closure; +use OCA\UserMigration\Db\UserImportMapper; +use OCP\DB\ISchemaWrapper; +use OCP\DB\Types; +use OCP\Migration\IOutput; +use OCP\Migration\SimpleMigrationStep; + +class Version00001Date20220412116131 extends SimpleMigrationStep { + + /** + * @param IOutput $output + * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + * @return null|ISchemaWrapper + */ + public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + + $table = $schema->createTable(UserImportMapper::TABLE_NAME); + $table->addColumn('id', Types::BIGINT, [ + 'autoincrement' => true, + 'notnull' => true, + 'length' => 20, + 'unsigned' => true, + ]); + $table->addColumn('source_user', Types::STRING, [ + 'notnull' => true, + 'length' => 64, + ]); + $table->addColumn('target_user', Types::STRING, [ + 'notnull' => true, + 'length' => 64, + ]); + $table->addColumn('path', Types::STRING, [ + 'notnull' => true, + 'length' => 4000, + ]); + $table->addColumn('migrators', Types::STRING, [ + 'notnull' => false, + 'length' => 4000, + ]); + $table->addColumn('status', Types::SMALLINT, [ + 'notnull' => true, + ]); + $table->setPrimaryKey(['id']); + + return $schema; + } +} diff --git a/lib/Notification/Notifier.php b/lib/Notification/Notifier.php index 64d82121..c6e4375c 100644 --- a/lib/Notification/Notifier.php +++ b/lib/Notification/Notifier.php @@ -29,36 +29,28 @@ use OCA\UserMigration\AppInfo\Application; use OCA\UserMigration\ExportDestination; -use OCP\AppFramework\Utility\ITimeFactory; use OCP\Files\File; use OCP\Files\IRootFolder; use OCP\IURLGenerator; use OCP\IUser; use OCP\IUserManager; use OCP\L10N\IFactory; -use OCP\Notification\IManager; use OCP\Notification\INotification; use OCP\Notification\INotifier; class Notifier implements INotifier { protected IFactory $l10nFactory; protected IURLGenerator $urlGenerator; - private IManager $notificationManager; private IUserManager $userManager; - private ITimeFactory $timeFactory; private IRootFolder $root; public function __construct(IFactory $l10nFactory, IURLGenerator $urlGenerator, - IManager $notificationManager, IUserManager $userManager, - ITimeFactory $timeFactory, IRootFolder $root) { $this->l10nFactory = $l10nFactory; $this->urlGenerator = $urlGenerator; - $this->notificationManager = $notificationManager; $this->userManager = $userManager; - $this->timeFactory = $timeFactory; $this->root = $root; } @@ -85,6 +77,13 @@ public function prepare(INotification $notification, string $languageCode): INot return $this->handleExportFailed($notification, $languageCode); } + if ($notification->getSubject() === 'importDone') { + return $this->handleImportDone($notification, $languageCode); + } + if ($notification->getSubject() === 'importFailed') { + return $this->handleImportFailed($notification, $languageCode); + } + throw new \InvalidArgumentException('Unhandled subject'); } @@ -156,6 +155,69 @@ public function handleExportDone(INotification $notification, string $languageCo return $notification; } + public function handleImportFailed(INotification $notification, string $languageCode): INotification { + $l = $this->l10nFactory->get(Application::APP_ID, $languageCode); + $param = $notification->getSubjectParameters(); + + $sourceUser = $this->getUser($param['sourceUser']); + $notification->setRichSubject($l->t('User import failed')) + ->setParsedSubject($l->t('User import failed')) + ->setRichMessage( + $l->t('Your import of {user} failed.'), + [ + 'user' => [ + 'type' => 'user', + 'id' => $sourceUser->getUID(), + 'name' => $sourceUser->getDisplayName(), + ], + ]) + ->setParsedMessage( + str_replace( + ['{user}'], + [$sourceUser->getDisplayName()], + $l->t('Your import of {user} failed.') + ) + ); + return $notification; + } + + public function handleImportDone(INotification $notification, string $languageCode): INotification { + $l = $this->l10nFactory->get(Application::APP_ID, $languageCode); + $param = $notification->getSubjectParameters(); + + $sourceUser = $this->getUser($param['sourceUser']); + $path = $this->$param['path']; + $importFile = $this->getImportFile($sourceUser, $path); + + $notification->setRichSubject($l->t('User import done')) + ->setParsedSubject($l->t('User import done')) + ->setRichMessage( + $l->t('Your import of {user} has completed: {file}'), + [ + 'user' => [ + 'type' => 'user', + 'id' => $sourceUser->getUID(), + 'name' => $sourceUser->getDisplayName(), + ], + 'file' => [ + 'type' => 'file', + 'id' => $importFile->getId(), + 'name' => $importFile->getName(), + 'path' => $path, + 'link' => $this->urlGenerator->linkToRouteAbsolute('files.viewcontroller.showFile', ['fileid' => $importFile->getId()]), + ], + ]) + ->setParsedMessage( + str_replace( + ['{user}', '{file}'], + [$sourceUser->getDisplayName(), $path], + $l->t('Your import of {user} has completed: {link}') + ) + ); + + return $notification; + } + protected function getUser(string $userId): IUser { $user = $this->userManager->get($userId); if ($user instanceof IUser) { @@ -172,4 +234,13 @@ protected function getExportFile(IUser $user): File { } return $file; } + + protected function getImportFile(IUser $user, string $path): File { + $userFolder = $this->root->getUserFolder($user->getUID()); + $file = $userFolder->get($path); + if (!$file instanceof File) { + throw new \InvalidArgumentException("Import file \"$path\" does not exist"); + } + return $file; + } }