Compare commits

...

2 Commits

  1. 6
      appinfo/info.xml
  2. 7
      composer.json
  3. 1871
      composer.lock
  4. 107
      lib/AppInfo/Application.php
  5. 214
      lib/Migration/BackgroundJob.php
  6. 218
      lib/Migration/Notifier.php
  7. 156
      lib/Migration/Utils.php

@ -6,12 +6,11 @@
<summary>Migrate user data on login</summary>
<description>Migrate user data on login</description>
<version>1.0.0</version>
<version>2.0.0</version>
<licence>agpl</licence>
<author>Florian Charlaix</author>
<namespace>FirstRunMigrate</namespace>
<default_enable/>
<types>
<logging/>
@ -24,6 +23,7 @@
<repository>https://git.open-dsi.fr/nextcloud-extension/firstrunmigrate.git</repository>
<dependencies>
<nextcloud min-version="25" max-version="25" />
<php min-version="8.2" max-version="8.2" />
<nextcloud min-version="28" max-version="28" />
</dependencies>
</info>

@ -6,14 +6,17 @@
"optimize-autoloader": true,
"classmap-authoritative": true,
"platform": {
"php": "7.4"
"php": "8.2"
}
},
"require": {
"php": "^8.2"
},
"require-dev": {
"phpunit/phpunit": "^9.5",
"nextcloud/coding-standard": "^1.0.0",
"vimeo/psalm": "^4.3.2",
"nextcloud/ocp": "dev-stable25"
"nextcloud/ocp": "dev-stable28"
},
"scripts": {
"lint": "find . -name \\*.php -not -path './vendor/*' -not -path './build/*' -print0 | xargs -0 -n1 php -l",

1871
composer.lock generated

File diff suppressed because it is too large Load Diff

@ -28,77 +28,68 @@ use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\EventDispatcher\IEventDispatcher;
use Symfony\Component\EventDispatcher\GenericEvent as OldGenericEvent;
use OCP\EventDispatcher\GenericEvent;
use OCP\IUser;
use OCP\User\Events\UserFirstTimeLoggedInEvent;
use Psr\Log\LoggerInterface;
use OCP\BackgroundJob\IJobList;
use OCP\Notification\IManager as INotificationManager;
use OCA\FirstRunMigrate\Migration\Notifier;
use OCA\FirstRunMigrate\Migration\Utils;
class Application extends App implements IBootstrap {
public const APP_ID = 'firstrunmigrate';
public const APP_ID = 'firstrunmigrate';
public const MIGRATION_JOB = 'OCA\FirstRunMigrate\Migration\BackgroundJob';
public function __construct() {
parent::__construct('firstrunmigrate');
}
public function __construct() {
parent::__construct('firstrunmigrate');
}
public function boot(IBootContext $context): void {
$context->injectFn([$this, 'registerHooks']);
public function boot(IBootContext $context): void {
$context->injectFn([$this, 'registerHooks']);
}
$notificationManager = \OC::$server->get(INotificationManager::class);
$notificationManager->registerNotifierService(Notifier::class);
}
public function register(IRegistrationContext $context): void {
$context->registerNotifierService(Notifier::class);
}
public function register(IRegistrationContext $context): void {
}
public function registerHooks(IEventDispatcher $dispatcher, IJobList $jobList, LoggerInterface $logger) {
// first time login event setup
$dispatcher->addListener(UserFirstTimeLoggedInEvent::class,
function (UserFirstTimeLoggedInEvent $event) use ($jobList, $logger) {
$logger->debug("Trigger of::firstLogin");
public function registerHooks(IEventDispatcher $dispatcher, IJobList $jobList, LoggerInterface $logger) {
$user = $event->getUser();
$uid = $user->getUID();
// first time login event setup
$dispatcher->addListener(IUser::class . '::firstLogin', function ($event) use ($jobList, $logger) {
if ($event instanceof GenericEvent || $event instanceof OldGenericEvent) {
$logger->debug("Trigger of::firstLogin");
$logger->debug("Subject $uid set, checking migration ID");
// If the user dont have an ID, skip all the migrations
if (Utils::getUserId($user) === null) {
$logger->info("$uid don't have a migration ID");
return;
}
/**
* @var IUser
*/
$user = $event->getSubject();
$uid = $user->getUID();
$logger->debug("Checking $uid migrations");
if (Utils::isMigrationData()){
$logger->debug("Scheduling $uid data migrations");
$jobList->add(self::MIGRATION_JOB, ['uid' => $uid, 'type' => 'data']);
Utils::setMigrationStatus('data', 'scheduled', $user);
} else {
$logger->info("$uid don't have data migrations");
}
$logger->debug("Subject $uid set, checking migration ID");
// If the user dont have an ID, skip all the migrations
if (Utils::getUserId($user) === null) {
$logger->info("$uid don't have a migration ID");
return;
}
if (Utils::isMigrationGroups()) {
$logger->debug("Scheduling $uid groups migrations");
$jobList->add(self::MIGRATION_JOB, ['uid' => $uid, 'type' => 'group']);
Utils::setMigrationStatus('group', 'scheduled', $user);
} else {
$logger->info("$uid don't have groups migrations");
}
$logger->debug("Checking $uid migrations");
if (Utils::isMigrationData()){
$logger->debug("Scheduling $uid data migrations");
$jobList->add('OCA\FirstRunMigrate\Migration\BackgroundJob', ['uid' => $uid, 'type' => 'data']);
Utils::setMigrationStatus('data', 'scheduled', $user);
} else {
$logger->info("$uid don't have data migrations");
}
if (Utils::isMigrationGroups()) {
$logger->debug("Scheduling $uid groups migrations");
$jobList->add('OCA\FirstRunMigrate\Migration\BackgroundJob', ['uid' => $uid, 'type' => 'group']);
Utils::setMigrationStatus('group', 'scheduled', $user);
} else {
$logger->info("$uid don't have groups migrations");
}
if (Utils::isMigrationQuotas()) {
$logger->debug("Scheduling $uid quota migrations");
$jobList->add('OCA\FirstRunMigrate\Migration\BackgroundJob', ['uid' => $uid, 'type' => 'quota']);
Utils::setMigrationStatus('quota', 'scheduled', $user);
} else {
$logger->info("$uid don't have quota migrations");
}
}
});
}
if (Utils::isMigrationQuotas()) {
$logger->debug("Scheduling $uid quota migrations");
$jobList->add(self::MIGRATION_JOB, ['uid' => $uid, 'type' => 'quota']);
Utils::setMigrationStatus('quota', 'scheduled', $user);
} else {
$logger->info("$uid don't have quota migrations");
}
});
}
}

@ -21,7 +21,8 @@
namespace OCA\FirstRunMigrate\Migration;
use OC\BackgroundJob\QueuedJob;
use OCP\BackgroundJob\QueuedJob;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\IConfig;
use OC_Util;
use OCA\FirstRunMigrate\Migration\Utils;
@ -31,107 +32,112 @@ use OCP\IUserManager;
use Psr\Log\LoggerInterface;
class BackgroundJob extends QueuedJob {
/** @var IConfig */
protected $config;
/** @var LoggerInterface */
protected $logger;
/** @var IUserManager */
protected $userManager;
/** @var IGroupManager */
protected $groupManager;
/**
* BackgroundJob constructor.
*
* @param INotificationManager $notificationManager
*/
public function __construct(IConfig $config, IUserManager $userManager, IGroupManager $groupManager, LoggerInterface $logger) {
$this->config = $config;
$this->logger = $logger;
$this->userManager = $userManager;
$this->groupManager = $groupManager;
}
/**
* @param array $argument
*/
protected function run($argument) {
$this->logger->debug("Starting job {$this->getId()} with args " . json_encode($argument));
$uid = $argument['uid'];
$user = $this->userManager->get($uid);
$type = $argument['type'];
switch ($type) {
case 'data':
$this->data($user);
break;
case 'group':
$this->group($user);
break;
case 'quota':
$this->quota($user);
break;
}
}
private function data(IUser $user) {
$this->logger->debug("{$this->getId()}: Migrating datas");
$uid = $user->getUID();
Utils::setMigrationStatus('data', 'started', $user);
if ($migrate_dir = Utils::getUserMigrationDir($user)) {
// Trigger creation of user home and /files folder
$userFolder = \OC::$server->getUserFolder($uid);
$quota = $user->getQuota();
$user->setQuota('none');
OC_Util::copyr($migrate_dir, $userFolder);
$user->setQuota($quota);
// update the file cache
$userFolder->getStorage()->getScanner()->scan('', \OC\Files\Cache\Scanner::SCAN_RECURSIVE);
} else {
$this->logger->info("{$this->getId()}: No dir to migrate");
}
Utils::setMigrationStatus('data', 'finished', $user);
}
private function group(IUser $user) {
$this->logger->debug("{$this->getId()}: Migrating groups");
Utils::setMigrationStatus('group', 'started', $user);
if ($groups = Utils::getUserMigrationGroups($user)) {
foreach ($groups as $group) {
if ($this->groupManager->groupExists($group)) {
$group = $this->groupManager->get($group);
} else {
$group = $this->groupManager->createGroup($group);
}
$group->addUser($user);
}
} else {
$this->logger->info("{$this->getId()}: No groups to migrate");
}
Utils::setMigrationStatus('group', 'finished', $user);
}
private function quota(IUser $user) {
$this->logger->debug("{$this->getId()}: Migrating quota");
Utils::setMigrationStatus('quota', 'started', $user);
if ($quota = Utils::getUserMigrationQuota($user)) {
$user->setQuota($quota);
} else {
$this->logger->info("{$this->getId()}: No quota to migrate");
}
Utils::setMigrationStatus('quota', 'finished', $user);
}
/** @var IConfig */
protected $config;
/** @var LoggerInterface */
protected $logger;
/** @var IUserManager */
protected $userManager;
/** @var IGroupManager */
protected $groupManager;
/**
* BackgroundJob constructor.
*
* @param INotificationManager $notificationManager
*/
public function __construct(ITimeFactory $timeFactory, IConfig $config,
IUserManager $userManager, IGroupManager $groupManager, LoggerInterface $logger) {
parent::__construct($timeFactory);
$this->config = $config;
$this->logger = $logger;
$this->userManager = $userManager;
$this->groupManager = $groupManager;
}
/**
* @param array $argument
*/
protected function run($argument) {
$this->logger->debug("Starting job {$this->getId()} with args " . json_encode($argument));
$uid = $argument['uid'];
$user = $this->userManager->get($uid);
$type = $argument['type'];
switch ($type) {
case 'data':
$this->data($user);
break;
case 'group':
$this->group($user);
break;
case 'quota':
$this->quota($user);
break;
default:
throw new \Exception("Invalid migration type");
}
}
private function data(IUser $user) {
$this->logger->debug("{$this->getId()}: Migrating datas");
$uid = $user->getUID();
Utils::setMigrationStatus('data', 'started', $user);
if ($migrate_dir = Utils::getUserMigrationDir($user)) {
// Trigger creation of user home and /files folder
/** @disregard P1007 */
$userFolder = \OC::$server->getUserFolder($uid);
$quota = $user->getQuota();
$user->setQuota('none');
OC_Util::copyr($migrate_dir, $userFolder);
$user->setQuota($quota);
// update the file cache
$userFolder->getStorage()->getScanner()->scan('', \OC\Files\Cache\Scanner::SCAN_RECURSIVE);
} else {
$this->logger->info("{$this->getId()}: No dir to migrate");
}
Utils::setMigrationStatus('data', 'finished', $user);
}
private function group(IUser $user) {
$this->logger->debug("{$this->getId()}: Migrating groups");
Utils::setMigrationStatus('group', 'started', $user);
if ($groups = Utils::getUserMigrationGroups($user)) {
foreach ($groups as $group) {
if ($this->groupManager->groupExists($group)) {
$group = $this->groupManager->get($group);
} else {
$group = $this->groupManager->createGroup($group);
}
$group->addUser($user);
}
} else {
$this->logger->info("{$this->getId()}: No groups to migrate");
}
Utils::setMigrationStatus('group', 'finished', $user);
}
private function quota(IUser $user) {
$this->logger->debug("{$this->getId()}: Migrating quota");
Utils::setMigrationStatus('quota', 'started', $user);
if ($quota = Utils::getUserMigrationQuota($user)) {
$user->setQuota($quota);
} else {
$this->logger->info("{$this->getId()}: No quota to migrate");
}
Utils::setMigrationStatus('quota', 'finished', $user);
}
}

@ -30,126 +30,128 @@ use OCP\Notification\IManager as INotificationManager;
class Notifier implements INotifier {
/** @var IFactory */
protected $factory;
/** @var INotificationManager */
protected $notificationManager;
/** @var IFactory */
protected $factory;
/** @var IURLGenerator */
protected $url;
/** @var INotificationManager */
protected $notificationManager;
public function __construct(IFactory $factory, INotificationManager $notificationManager, IURLGenerator $urlGenerator) {
$this->factory = $factory;
$this->notificationManager = $notificationManager;
$this->url = $urlGenerator;
}
/** @var IURLGenerator */
protected $url;
/**
* Identifier of the notifier, only use [a-z0-9_]
*
* @return string
* @since 17.0.0
*/
public function getID(): string {
return Application::APP_ID;
}
public function __construct(IFactory $factory, INotificationManager $notificationManager,
IURLGenerator $urlGenerator) {
$this->factory = $factory;
$this->notificationManager = $notificationManager;
$this->url = $urlGenerator;
}
/**
* Human readable name describing the notifier
*
* @return string
* @since 17.0.0
*/
public function getName(): string {
return $this->factory->get($this->getID())->t('First run migrate');
}
/**
* Identifier of the notifier, only use [a-z0-9_]
*
* @return string
* @since 17.0.0
*/
public function getID(): string {
return Application::APP_ID;
}
/**
* @param INotification $notification
* @param string $languageCode The code of the language that should be used to prepare the notification
* @return INotification
* @throws \InvalidArgumentException When the notification was not prepared by a notifier
* @since 9.0.0
*/
public function prepare(INotification $notification, string $languageCode): INotification {
if ($notification->getApp() !== $this->getID()) {
// Not my app => throw
throw new \InvalidArgumentException();
}
/**
* Human readable name describing the notifier
*
* @return string
* @since 17.0.0
*/
public function getName(): string {
return $this->factory->get($this->getID())->t('First run migrate');
}
/**
* @param INotification $notification
* @param string $languageCode The code of the language that should be used to prepare the notification
* @return INotification
* @throws \InvalidArgumentException When the notification was not prepared by a notifier
* @since 9.0.0
*/
public function prepare(INotification $notification, string $languageCode): INotification {
if ($notification->getApp() !== $this->getID()) {
// Not my app => throw
throw new \InvalidArgumentException();
}
$l = $this->factory->get($this->getID(), $languageCode);
$notify_subject = $notification->getSubject();
$user = $notification->getUser();
$notify_subject = $notification->getSubject();
$user = $notification->getUser();
$subject = null;
$message = null;
switch ($notify_subject) {
case 'data_scheduled':
$subject = $l->t('Data migration scheduled');
$message = $l->t('Your data will be soon available');
break;
case 'data_started':
$subject = $l->t('Data migration started');
$message = $l->t('Your data will be soon available');
break;
case 'data_finished':
$message = null;
switch ($notify_subject) {
case 'data_scheduled':
$subject = $l->t('Data migration scheduled');
$message = $l->t('Your data will be soon available');
break;
case 'data_started':
$subject = $l->t('Data migration started');
$message = $l->t('Your data will be soon available');
break;
case 'data_finished':
$subject = $l->t('Data migration complete');
$message = $l->t('Your data are available !');
break;
case 'group_scheduled':
$subject = $l->t('Groups migration scheduled');
$message = $l->t('Your group folders will be soon available');
break;
case 'group_started':
$subject = $l->t('Groups migration started');
$message = $l->t('Your group folders will be soon available');
break;
case 'group_finished':
$subject = $l->t('Groups migration complete');
$message = $l->t('Your group folders are available !');
break;
case 'quota_scheduled':
$subject = $l->t('Quota migration scheduled');
break;
case 'quota_started':
$subject = $l->t('Quota migration started');
break;
case 'quota_finished':
$subject = $l->t('Quota migration complete');
break;
default:
// Unknown subject => Unknown notification => throw
throw new \InvalidArgumentException();
}
$message = $l->t('Your data are available !');
break;
case 'group_scheduled':
$subject = $l->t('Groups migration scheduled');
$message = $l->t('Your group folders will be soon available');
break;
case 'group_started':
$subject = $l->t('Groups migration started');
$message = $l->t('Your group folders will be soon available');
break;
case 'group_finished':
$subject = $l->t('Groups migration complete');
$message = $l->t('Your group folders are available !');
break;
case 'quota_scheduled':
$subject = $l->t('Quota migration scheduled');
break;
case 'quota_started':
$subject = $l->t('Quota migration started');
break;
case 'quota_finished':
$subject = $l->t('Quota migration complete');
break;
default:
// Unknown subject => Unknown notification => throw
throw new \InvalidArgumentException();
}
$icon = null;
$old_notification = $this->notificationManager->createNotification()
->setApp(Application::APP_ID)->setUser($user);
switch ($notification->getObjectId()) {
case 'scheduled':
$icon = $this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/recent.svg'));
break;
case 'started':
$icon = $this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/change.svg'));
$old_notification->setObject($notification->getObjectType(), 'scheduled');
break;
case 'finished':
$icon = $this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/checkmark.svg'));
$old_notification->setObject($notification->getObjectType(), 'started');
break;
default:
$icon = $this->url->getAbsoluteURL($this->url->imagePath(Application::APP_ID, 'app.svg'));
}
$icon = null;
$old_notification = $this->notificationManager->createNotification()->setApp(Application::APP_ID)->setUser($user);
switch ($notification->getObjectId()) {
case 'scheduled':
$icon = $this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/recent.svg'));
break;
case 'started':
$icon = $this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/change.svg'));
$old_notification->setObject($notification->getObjectType(), 'scheduled');
break;
case 'finished':
$icon = $this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/checkmark.svg'));
$old_notification->setObject($notification->getObjectType(), 'started');
break;
default:
$icon = $this->url->getAbsoluteURL($this->url->imagePath(Application::APP_ID, 'app.svg'));
}
if ($old_notification->getObjectId()) {
$this->notificationManager->markProcessed($old_notification);
}
if ($old_notification->getObjectId()) {
$this->notificationManager->markProcessed($old_notification);
}
$notification->setParsedSubject($subject);
if (!empty($message)) {
$notification->setParsedMessage($message);
}
$notification->setIcon($icon);
if (!empty($message)) {
$notification->setParsedMessage($message);
}
$notification->setIcon($icon);
return $notification;
}
}
}

@ -8,94 +8,94 @@ use OCA\FirstRunMigrate\AppInfo\Application;
use OCP\Notification\IManager as INotificationManager;
class Utils {
static public function setMigrationStatus(string $type, string $status, IUser $user) {
/** @var INotificationManager */
$notificationManager = \OC::$server->get(INotificationManager::class);
/** @var IConfig */
$config = \OC::$server->get(IConfig::class);
$uid = $user->getUID();
public static function setMigrationStatus(string $type, string $status, IUser $user) {
/** @var INotificationManager */
$notificationManager = \OC::$server->get(INotificationManager::class);
/** @var IConfig */
$config = \OC::$server->get(IConfig::class);
$uid = $user->getUID();
$config->setUserValue($uid, Application::APP_ID, $type . '_status', $status);
$config->setUserValue($uid, Application::APP_ID, $status . '_date', (new \DateTime())->getTimestamp());
$notification = $notificationManager->createNotification();
$notification->setApp(Application::APP_ID)
->setUser($uid)
->setDateTime(new \DateTime())
->setObject($type, $status)
->setSubject($type . '_' .$status);
$notificationManager->notify($notification);
$config->setUserValue($uid, Application::APP_ID, $status . '_date', (new \DateTime())->getTimestamp());
$notification = $notificationManager->createNotification();
$notification->setApp(Application::APP_ID)
->setUser($uid)
->setDateTime(new \DateTime())
->setObject($type, $status)
->setSubject($type . '_' .$status);
$notificationManager->notify($notification);
}
static public function getUserId(IUser $user) : ?string {
/** @var IConfig */
$config = \OC::$server->get(IConfig::class);
public static function getUserId(IUser $user) : ?string {
/** @var IConfig */
$config = \OC::$server->get(IConfig::class);
return $config->getUserValue($user->getUID(), 'settings', 'email', null);
}
return $config->getUserValue($user->getUID(), 'settings', 'email', null);
}
private static function getMigrationDataDir() : ?string {
/** @var IConfig */
$config = \OC::$server->get(IConfig::class);
return $config->getSystemValue('firstrunmigrate_dir', null);
}
public static function isMigrationData() : bool {
return ($file = self::getMigrationDataDir()) && file_exists($file);
}
public static function getUserMigrationDir(IUser $user) : ?string {
$dir = self::getMigrationDataDir() . '/' . self::getUserId($user);
if (file_exists($dir)) {
return $dir;
} else {
return null;
}
}
static private function getMigrationDataDir() : ?string {
/** @var IConfig */
$config = \OC::$server->get(IConfig::class);
private static function getMigrationGroupsFile() : ?string {
/** @var IConfig */
$config = \OC::$server->get(IConfig::class);
return $config->getSystemValue('firstrunmigrate_dir', null);
}
return $config->getSystemValue('firstrunmigrate_groups', null);
}
public static function isMigrationGroups() : bool {
return ($file = self::getMigrationGroupsFile()) && file_exists($file);
}
static public function isMigrationData() : bool {
return ($file = self::getMigrationDataDir()) && file_exists($file);
}
public static function getUserMigrationGroups(IUser $user) : ?array {
$groups = json_decode(file_get_contents(self::getMigrationGroupsFile()), true);
$id = self::getUserId($user);
if (array_key_exists($id, $groups)) {
return $groups[$id];
} else {
return null;
}
}
static public function getUserMigrationDir(IUser $user) : ?string {
$dir = self::getMigrationDataDir() . '/' . self::getUserId($user);
private static function getMigrationQuotasFile() : ?string {
/** @var IConfig */
$config = \OC::$server->get(IConfig::class);
if (file_exists($dir)) {
return $dir;
} else {
return null;
}
return $config->getSystemValue('firstrunmigrate_quotas', null);
}
static private function getMigrationGroupsFile() : ?string {
/** @var IConfig */
$config = \OC::$server->get(IConfig::class);
return $config->getSystemValue('firstrunmigrate_groups', null);
}
static public function isMigrationGroups() : bool {
return ($file = self::getMigrationGroupsFile()) && file_exists($file);
}
static public function getUserMigrationGroups(IUser $user) : ?array {
$groups = json_decode(file_get_contents(self::getMigrationGroupsFile()), true);
$id = self::getUserId($user);
if (array_key_exists($id, $groups)) {
return $groups[$id];
} else {
return null;
}
}
static private function getMigrationQuotasFile() : ?string {
/** @var IConfig */
$config = \OC::$server->get(IConfig::class);
return $config->getSystemValue('firstrunmigrate_quotas', null);
}
static public function isMigrationQuotas() : bool {
return ($file = self::getMigrationQuotasFile()) && file_exists($file);
}
static public function getUserMigrationQuota(IUser $user) : ?string {
$quotas = json_decode(file_get_contents(self::getMigrationQuotasFile()), true);
$id = self::getUserId($user);
if (array_key_exists($id, $quotas)) {
return $quotas[$id];
} else {
return null;
}
}
public static function isMigrationQuotas() : bool {
return ($file = self::getMigrationQuotasFile()) && file_exists($file);
}
public static function getUserMigrationQuota(IUser $user) : ?string {
$quotas = json_decode(file_get_contents(self::getMigrationQuotasFile()), true);
$id = self::getUserId($user);
if (array_key_exists($id, $quotas)) {
return $quotas[$id];
} else {
return null;
}
}
}

Loading…
Cancel
Save