]> source.dussan.org Git - nextcloud-server.git/commitdiff
Readd repair steps that are relevant when migrating from ownCloud
authorJulius Härtl <jus@bitgrid.net>
Mon, 7 Dec 2020 13:30:08 +0000 (14:30 +0100)
committerJulius Härtl <jus@bitgrid.net>
Mon, 14 Dec 2020 10:12:52 +0000 (11:12 +0100)
This reverts commit d9b1492e03ab9fe58bb87baaeba745790ca15c53.

Signed-off-by: Julius Härtl <jus@bitgrid.net>
13 files changed:
lib/composer/composer/autoload_classmap.php
lib/composer/composer/autoload_static.php
lib/private/Repair.php
lib/private/Repair/Owncloud/CleanPreviews.php [new file with mode: 0644]
lib/private/Repair/Owncloud/CleanPreviewsBackgroundJob.php [new file with mode: 0644]
lib/private/Repair/Owncloud/InstallCoreBundle.php [new file with mode: 0644]
lib/private/Repair/Owncloud/MoveAvatars.php [new file with mode: 0644]
lib/private/Repair/Owncloud/MoveAvatarsBackgroundJob.php [new file with mode: 0644]
lib/private/Repair/Owncloud/UpdateLanguageCodes.php [new file with mode: 0644]
tests/Test/Repair/Owncloud/CleanPreviewsBackgroundJobTest.php [new file with mode: 0644]
tests/Test/Repair/Owncloud/CleanPreviewsTest.php [new file with mode: 0644]
tests/Test/Repair/Owncloud/InstallCoreBundleTest.php [new file with mode: 0644]
tests/Test/Repair/Owncloud/UpdateLanguageCodesTest.php [new file with mode: 0644]

index 3db497983d0ffd7acd988f4b9e1e17c6c3134ea8..cd39dc849850d0e9898b7ad494e8f0506dae4cfc 100644 (file)
@@ -1250,7 +1250,14 @@ return array(
     'OC\\Repair\\ClearGeneratedAvatarCache' => $baseDir . '/lib/private/Repair/ClearGeneratedAvatarCache.php',
     'OC\\Repair\\Collation' => $baseDir . '/lib/private/Repair/Collation.php',
     'OC\\Repair\\MoveUpdaterStepFile' => $baseDir . '/lib/private/Repair/MoveUpdaterStepFile.php',
+    'OC\\Repair\\NC11\\CleanPreviews' => $baseDir . '/lib/private/Repair/NC11/CleanPreviews.php',
+    'OC\\Repair\\NC11\\CleanPreviewsBackgroundJob' => $baseDir . '/lib/private/Repair/NC11/CleanPreviewsBackgroundJob.php',
     'OC\\Repair\\NC11\\FixMountStorages' => $baseDir . '/lib/private/Repair/NC11/FixMountStorages.php',
+    'OC\\Repair\\NC11\\MoveAvatars' => $baseDir . '/lib/private/Repair/NC11/MoveAvatars.php',
+    'OC\\Repair\\NC11\\MoveAvatarsBackgroundJob' => $baseDir . '/lib/private/Repair/NC11/MoveAvatarsBackgroundJob.php',
+    'OC\\Repair\\NC12\\InstallCoreBundle' => $baseDir . '/lib/private/Repair/NC12/InstallCoreBundle.php',
+    'OC\\Repair\\NC12\\RepairIdentityProofKeyFolders' => $baseDir . '/lib/private/Repair/NC12/RepairIdentityProofKeyFolders.php',
+    'OC\\Repair\\NC12\\UpdateLanguageCodes' => $baseDir . '/lib/private/Repair/NC12/UpdateLanguageCodes.php',
     'OC\\Repair\\NC13\\AddLogRotateJob' => $baseDir . '/lib/private/Repair/NC13/AddLogRotateJob.php',
     'OC\\Repair\\NC14\\AddPreviewBackgroundCleanupJob' => $baseDir . '/lib/private/Repair/NC14/AddPreviewBackgroundCleanupJob.php',
     'OC\\Repair\\NC16\\AddClenupLoginFlowV2BackgroundJob' => $baseDir . '/lib/private/Repair/NC16/AddClenupLoginFlowV2BackgroundJob.php',
index 0dc5fcebdfb4f0512ea739b7c54aa3a0f2206181..d14a031f51b808d8d494318bc7963c70fc93c878 100644 (file)
@@ -1279,7 +1279,14 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
         'OC\\Repair\\ClearGeneratedAvatarCache' => __DIR__ . '/../../..' . '/lib/private/Repair/ClearGeneratedAvatarCache.php',
         'OC\\Repair\\Collation' => __DIR__ . '/../../..' . '/lib/private/Repair/Collation.php',
         'OC\\Repair\\MoveUpdaterStepFile' => __DIR__ . '/../../..' . '/lib/private/Repair/MoveUpdaterStepFile.php',
+        'OC\\Repair\\NC11\\CleanPreviews' => __DIR__ . '/../../..' . '/lib/private/Repair/NC11/CleanPreviews.php',
+        'OC\\Repair\\NC11\\CleanPreviewsBackgroundJob' => __DIR__ . '/../../..' . '/lib/private/Repair/NC11/CleanPreviewsBackgroundJob.php',
         'OC\\Repair\\NC11\\FixMountStorages' => __DIR__ . '/../../..' . '/lib/private/Repair/NC11/FixMountStorages.php',
+        'OC\\Repair\\NC11\\MoveAvatars' => __DIR__ . '/../../..' . '/lib/private/Repair/NC11/MoveAvatars.php',
+        'OC\\Repair\\NC11\\MoveAvatarsBackgroundJob' => __DIR__ . '/../../..' . '/lib/private/Repair/NC11/MoveAvatarsBackgroundJob.php',
+        'OC\\Repair\\NC12\\InstallCoreBundle' => __DIR__ . '/../../..' . '/lib/private/Repair/NC12/InstallCoreBundle.php',
+        'OC\\Repair\\NC12\\RepairIdentityProofKeyFolders' => __DIR__ . '/../../..' . '/lib/private/Repair/NC12/RepairIdentityProofKeyFolders.php',
+        'OC\\Repair\\NC12\\UpdateLanguageCodes' => __DIR__ . '/../../..' . '/lib/private/Repair/NC12/UpdateLanguageCodes.php',
         'OC\\Repair\\NC13\\AddLogRotateJob' => __DIR__ . '/../../..' . '/lib/private/Repair/NC13/AddLogRotateJob.php',
         'OC\\Repair\\NC14\\AddPreviewBackgroundCleanupJob' => __DIR__ . '/../../..' . '/lib/private/Repair/NC14/AddPreviewBackgroundCleanupJob.php',
         'OC\\Repair\\NC16\\AddClenupLoginFlowV2BackgroundJob' => __DIR__ . '/../../..' . '/lib/private/Repair/NC16/AddClenupLoginFlowV2BackgroundJob.php',
index 61857285f3962153aefff4b29d22225b3b4e03d3..f1fd6457418bc7960032de7dfd6d5990cd170bcd 100644 (file)
@@ -34,6 +34,7 @@
 
 namespace OC;
 
+use OC\App\AppStore\Bundles\BundleFetcher;
 use OC\Avatar\AvatarManager;
 use OC\Repair\AddCleanupUpdaterBackupsJob;
 use OC\Repair\CleanTags;
@@ -41,7 +42,11 @@ use OC\Repair\ClearFrontendCaches;
 use OC\Repair\ClearGeneratedAvatarCache;
 use OC\Repair\Collation;
 use OC\Repair\MoveUpdaterStepFile;
+use OC\Repair\Owncloud\CleanPreviews;
 use OC\Repair\NC11\FixMountStorages;
+use OC\Repair\Owncloud\MoveAvatars;
+use OC\Repair\Owncloud\InstallCoreBundle;
+use OC\Repair\Owncloud\UpdateLanguageCodes;
 use OC\Repair\NC13\AddLogRotateJob;
 use OC\Repair\NC14\AddPreviewBackgroundCleanupJob;
 use OC\Repair\NC16\AddClenupLoginFlowV2BackgroundJob;
@@ -148,7 +153,22 @@ class Repair implements IOutput {
                        new CleanTags(\OC::$server->getDatabaseConnection(), \OC::$server->getUserManager()),
                        new RepairInvalidShares(\OC::$server->getConfig(), \OC::$server->getDatabaseConnection()),
                        new MoveUpdaterStepFile(\OC::$server->getConfig()),
+                       new MoveAvatars(
+                               \OC::$server->getJobList(),
+                               \OC::$server->getConfig()
+                       ),
+                       new CleanPreviews(
+                               \OC::$server->getJobList(),
+                               \OC::$server->getUserManager(),
+                               \OC::$server->getConfig()
+                       ),
                        new FixMountStorages(\OC::$server->getDatabaseConnection()),
+                       new UpdateLanguageCodes(\OC::$server->getDatabaseConnection(), \OC::$server->getConfig()),
+                       new InstallCoreBundle(
+                               \OC::$server->query(BundleFetcher::class),
+                               \OC::$server->getConfig(),
+                               \OC::$server->query(Installer::class)
+                       ),
                        new AddLogRotateJob(\OC::$server->getJobList()),
                        new ClearFrontendCaches(\OC::$server->getMemCacheFactory(), \OC::$server->query(SCSSCacher::class), \OC::$server->query(JSCombiner::class)),
                        new ClearGeneratedAvatarCache(\OC::$server->getConfig(), \OC::$server->query(AvatarManager::class)),
diff --git a/lib/private/Repair/Owncloud/CleanPreviews.php b/lib/private/Repair/Owncloud/CleanPreviews.php
new file mode 100644 (file)
index 0000000..5c18345
--- /dev/null
@@ -0,0 +1,73 @@
+<?php
+/**
+ * @copyright 2016 Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @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 OC\Repair\Owncloud;
+
+use OCP\BackgroundJob\IJobList;
+use OCP\IConfig;
+use OCP\IUser;
+use OCP\IUserManager;
+use OCP\Migration\IOutput;
+use OCP\Migration\IRepairStep;
+
+class CleanPreviews implements IRepairStep {
+
+       /** @var IJobList */
+       private $jobList;
+
+       /** @var IUserManager */
+       private $userManager;
+
+       /** @var IConfig */
+       private $config;
+
+       /**
+        * MoveAvatars constructor.
+        *
+        * @param IJobList $jobList
+        * @param IUserManager $userManager
+        * @param IConfig $config
+        */
+       public function __construct(IJobList $jobList,
+                                                               IUserManager $userManager,
+                                                               IConfig $config) {
+               $this->jobList = $jobList;
+               $this->userManager = $userManager;
+               $this->config = $config;
+       }
+
+       /**
+        * @return string
+        */
+       public function getName() {
+               return 'Add preview cleanup background jobs';
+       }
+
+       public function run(IOutput $output) {
+               if (!$this->config->getAppValue('core', 'previewsCleanedUp', false)) {
+                       $this->userManager->callForSeenUsers(function (IUser $user) {
+                               $this->jobList->add(CleanPreviewsBackgroundJob::class, ['uid' => $user->getUID()]);
+                       });
+                       $this->config->setAppValue('core', 'previewsCleanedUp', '1');
+               }
+       }
+}
diff --git a/lib/private/Repair/Owncloud/CleanPreviewsBackgroundJob.php b/lib/private/Repair/Owncloud/CleanPreviewsBackgroundJob.php
new file mode 100644 (file)
index 0000000..e8d89c9
--- /dev/null
@@ -0,0 +1,132 @@
+<?php
+/**
+ * @copyright 2016 Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @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 OC\Repair\Owncloud;
+
+use OC\BackgroundJob\QueuedJob;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\IJobList;
+use OCP\Files\Folder;
+use OCP\Files\IRootFolder;
+use OCP\Files\NotFoundException;
+use OCP\Files\NotPermittedException;
+use OCP\ILogger;
+use OCP\IUserManager;
+
+class CleanPreviewsBackgroundJob extends QueuedJob {
+       /** @var IRootFolder */
+       private $rootFolder;
+
+       /** @var ILogger */
+       private $logger;
+
+       /** @var IJobList */
+       private $jobList;
+
+       /** @var ITimeFactory */
+       private $timeFactory;
+
+       /** @var IUserManager */
+       private $userManager;
+
+       /**
+        * CleanPreviewsBackgroundJob constructor.
+        *
+        * @param IRootFolder $rootFolder
+        * @param ILogger $logger
+        * @param IJobList $jobList
+        * @param ITimeFactory $timeFactory
+        * @param IUserManager $userManager
+        */
+       public function __construct(IRootFolder $rootFolder,
+                                                               ILogger $logger,
+                                                               IJobList $jobList,
+                                                               ITimeFactory $timeFactory,
+                                                               IUserManager $userManager) {
+               $this->rootFolder = $rootFolder;
+               $this->logger = $logger;
+               $this->jobList = $jobList;
+               $this->timeFactory = $timeFactory;
+               $this->userManager = $userManager;
+       }
+
+       public function run($arguments) {
+               $uid = $arguments['uid'];
+               if (!$this->userManager->userExists($uid)) {
+                       $this->logger->info('User no longer exists, skip user ' . $uid);
+                       return;
+               }
+               $this->logger->info('Started preview cleanup for ' . $uid);
+               $empty = $this->cleanupPreviews($uid);
+
+               if (!$empty) {
+                       $this->jobList->add(self::class, ['uid' => $uid]);
+                       $this->logger->info('New preview cleanup scheduled for ' . $uid);
+               } else {
+                       $this->logger->info('Preview cleanup done for ' . $uid);
+               }
+       }
+
+       /**
+        * @param $uid
+        * @return bool
+        */
+       private function cleanupPreviews($uid) {
+               try {
+                       $userFolder = $this->rootFolder->getUserFolder($uid);
+               } catch (NotFoundException $e) {
+                       return true;
+               }
+
+               $userRoot = $userFolder->getParent();
+
+               try {
+                       /** @var Folder $thumbnailFolder */
+                       $thumbnailFolder = $userRoot->get('thumbnails');
+               } catch (NotFoundException $e) {
+                       return true;
+               }
+
+               $thumbnails = $thumbnailFolder->getDirectoryListing();
+
+               $start = $this->timeFactory->getTime();
+               foreach ($thumbnails as $thumbnail) {
+                       try {
+                               $thumbnail->delete();
+                       } catch (NotPermittedException $e) {
+                               // Ignore
+                       }
+
+                       if (($this->timeFactory->getTime() - $start) > 15) {
+                               return false;
+                       }
+               }
+
+               try {
+                       $thumbnailFolder->delete();
+               } catch (NotPermittedException $e) {
+                       // Ignore
+               }
+
+               return true;
+       }
+}
diff --git a/lib/private/Repair/Owncloud/InstallCoreBundle.php b/lib/private/Repair/Owncloud/InstallCoreBundle.php
new file mode 100644 (file)
index 0000000..6d07ec9
--- /dev/null
@@ -0,0 +1,80 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @author Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @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 OC\Repair\Owncloud;
+
+use OC\App\AppStore\Bundles\BundleFetcher;
+use OC\Installer;
+use OCP\IConfig;
+use OCP\Migration\IOutput;
+use OCP\Migration\IRepairStep;
+
+class InstallCoreBundle implements IRepairStep {
+       /** @var BundleFetcher */
+       private $bundleFetcher;
+       /** @var IConfig */
+       private $config;
+       /** @var Installer */
+       private $installer;
+
+       /**
+        * @param BundleFetcher $bundleFetcher
+        * @param IConfig $config
+        * @param Installer $installer
+        */
+       public function __construct(BundleFetcher $bundleFetcher,
+                                                               IConfig $config,
+                                                               Installer $installer) {
+               $this->bundleFetcher = $bundleFetcher;
+               $this->config = $config;
+               $this->installer = $installer;
+       }
+
+       /**
+        * {@inheritdoc}
+        */
+       public function getName() {
+               return 'Install new core bundle components';
+       }
+
+       /**
+        * {@inheritdoc}
+        */
+       public function run(IOutput $output) {
+               $versionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0');
+
+               if (version_compare($versionFromBeforeUpdate, '12.0.0.14', '>')) {
+                       return;
+               }
+
+               $defaultBundle = $this->bundleFetcher->getDefaultInstallationBundle();
+               foreach ($defaultBundle as $bundle) {
+                       try {
+                               $this->installer->installAppBundle($bundle);
+                               $output->info('Successfully installed core app bundle.');
+                       } catch (\Exception $e) {
+                               $output->warning('Could not install core app bundle: ' . $e->getMessage());
+                       }
+               }
+       }
+}
diff --git a/lib/private/Repair/Owncloud/MoveAvatars.php b/lib/private/Repair/Owncloud/MoveAvatars.php
new file mode 100644 (file)
index 0000000..53f3097
--- /dev/null
@@ -0,0 +1,73 @@
+<?php
+/**
+ * @copyright 2016 Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @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 OC\Repair\Owncloud;
+
+use OCP\BackgroundJob\IJobList;
+use OCP\IConfig;
+use OCP\Migration\IOutput;
+use OCP\Migration\IRepairStep;
+
+class MoveAvatars implements IRepairStep {
+
+       /** @var IJobList */
+       private $jobList;
+
+       /** @var IConfig */
+       private $config;
+
+       /**
+        * MoveAvatars constructor.
+        *
+        * @param IJobList $jobList
+        * @param IConfig $config
+        */
+       public function __construct(IJobList $jobList,
+                                                               IConfig $config) {
+               $this->jobList = $jobList;
+               $this->config = $config;
+       }
+
+       /**
+        * @return string
+        */
+       public function getName() {
+               return 'Add move avatar background job';
+       }
+
+       public function run(IOutput $output) {
+               // only run once
+               if ($this->config->getAppValue('core', 'moveavatarsdone') === 'yes') {
+                       $output->info('Repair step already executed');
+                       return;
+               }
+               if ($this->config->getSystemValue('enable_avatars', true) === false) {
+                       $output->info('Avatars are disabled');
+               } else {
+                       $output->info('Add background job');
+                       $this->jobList->add(MoveAvatarsBackgroundJob::class);
+                       // if all were done, no need to redo the repair during next upgrade
+                       $this->config->setAppValue('core', 'moveavatarsdone', 'yes');
+               }
+       }
+}
diff --git a/lib/private/Repair/Owncloud/MoveAvatarsBackgroundJob.php b/lib/private/Repair/Owncloud/MoveAvatarsBackgroundJob.php
new file mode 100644 (file)
index 0000000..ecc2d6e
--- /dev/null
@@ -0,0 +1,147 @@
+<?php
+/**
+ * @copyright 2016 Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @author Joas Schilling <coding@schilljs.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @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 OC\Repair\Owncloud;
+
+use OC\BackgroundJob\QueuedJob;
+use OCP\Files\File;
+use OCP\Files\Folder;
+use OCP\Files\IAppData;
+use OCP\Files\IRootFolder;
+use OCP\Files\NotFoundException;
+use OCP\Files\SimpleFS\ISimpleFolder;
+use OCP\ILogger;
+use OCP\IUser;
+use OCP\IUserManager;
+
+class MoveAvatarsBackgroundJob extends QueuedJob {
+
+       /** @var IUserManager */
+       private $userManager;
+
+       /** @var IRootFolder */
+       private $rootFolder;
+
+       /** @var IAppData */
+       private $appData;
+
+       /** @var ILogger */
+       private $logger;
+
+       /**
+        * MoveAvatars constructor.
+        */
+       public function __construct() {
+               $this->userManager = \OC::$server->getUserManager();
+               $this->rootFolder = \OC::$server->getRootFolder();
+               $this->logger = \OC::$server->getLogger();
+               $this->appData = \OC::$server->getAppDataDir('avatar');
+       }
+
+       public function run($arguments) {
+               $this->logger->info('Started migrating avatars to AppData folder');
+               $this->moveAvatars();
+               $this->logger->info('All avatars migrated to AppData folder');
+       }
+
+       private function moveAvatars() {
+               try {
+                       $ownCloudAvatars = $this->rootFolder->get('avatars');
+               } catch (NotFoundException $e) {
+                       $this->logger->info('No legacy avatars available, skipping migration');
+                       return;
+               }
+
+               $counter = 0;
+               $this->userManager->callForSeenUsers(function (IUser $user) use ($counter, $ownCloudAvatars) {
+                       $uid = $user->getUID();
+
+                       \OC\Files\Filesystem::initMountPoints($uid);
+                       /** @var Folder $userFolder */
+                       $userFolder = $this->rootFolder->get($uid);
+
+                       try {
+                               $userData = $this->appData->getFolder($uid);
+                       } catch (NotFoundException $e) {
+                               $userData = $this->appData->newFolder($uid);
+                       }
+
+                       $foundAvatars = $this->copyAvatarsFromFolder($userFolder, $userData);
+
+                       // ownCloud migration?
+                       if ($foundAvatars === 0 && $ownCloudAvatars instanceof Folder) {
+                               $parts = $this->buildOwnCloudAvatarPath($uid);
+                               $userOwnCloudAvatar = $ownCloudAvatars;
+                               foreach ($parts as $part) {
+                                       try {
+                                               $userOwnCloudAvatar = $userOwnCloudAvatar->get($part);
+                                       } catch (NotFoundException $e) {
+                                               return;
+                                       }
+                               }
+
+                               $this->copyAvatarsFromFolder($userOwnCloudAvatar, $userData);
+                       }
+
+                       $counter++;
+                       if ($counter % 100 === 0) {
+                               $this->logger->info('{amount} avatars migrated', ['amount' => $counter]);
+                       }
+               });
+       }
+
+       /**
+        * @param Folder $source
+        * @param ISimpleFolder $target
+        * @return int
+        * @throws \OCP\Files\NotPermittedException
+        * @throws NotFoundException
+        */
+       protected function copyAvatarsFromFolder(Folder $source, ISimpleFolder $target) {
+               $foundAvatars = 0;
+               $avatars = $source->getDirectoryListing();
+               $regex = '/^avatar\.([0-9]+\.)?(jpg|png)$/';
+
+               foreach ($avatars as $avatar) {
+                       /** @var File $avatar */
+                       if (preg_match($regex, $avatar->getName())) {
+                               /*
+                                * This is not the most effective but it is the most abstract way
+                                * to handle this. Avatars should be small anyways.
+                                */
+                               $newAvatar = $target->newFile($avatar->getName());
+                               $newAvatar->putContent($avatar->getContent());
+                               $avatar->delete();
+                               $foundAvatars++;
+                       }
+               }
+
+               return $foundAvatars;
+       }
+
+       protected function buildOwnCloudAvatarPath($userId) {
+               $avatar = substr_replace(substr_replace(md5($userId), '/', 4, 0), '/', 2, 0);
+               return explode('/', $avatar);
+       }
+}
diff --git a/lib/private/Repair/Owncloud/UpdateLanguageCodes.php b/lib/private/Repair/Owncloud/UpdateLanguageCodes.php
new file mode 100644 (file)
index 0000000..b7da0b0
--- /dev/null
@@ -0,0 +1,90 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Morris Jobke <hey@morrisjobke.de>
+ *
+ * @author Joas Schilling <coding@schilljs.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ *
+ * @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 OC\Repair\Owncloud;
+
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IConfig;
+use OCP\IDBConnection;
+use OCP\Migration\IOutput;
+use OCP\Migration\IRepairStep;
+
+class UpdateLanguageCodes implements IRepairStep {
+       /** @var IDBConnection */
+       private $connection;
+
+       /** @var IConfig */
+       private $config;
+
+       /**
+        * @param IDBConnection $connection
+        * @param IConfig $config
+        */
+       public function __construct(IDBConnection $connection,
+                                                               IConfig $config) {
+               $this->connection = $connection;
+               $this->config = $config;
+       }
+
+       /**
+        * {@inheritdoc}
+        */
+       public function getName() {
+               return 'Repair language codes';
+       }
+
+       /**
+        * {@inheritdoc}
+        */
+       public function run(IOutput $output) {
+               $versionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0');
+
+               if (version_compare($versionFromBeforeUpdate, '12.0.0.13', '>')) {
+                       return;
+               }
+
+               $languages = [
+                       'bg_BG' => 'bg',
+                       'cs_CZ' => 'cs',
+                       'fi_FI' => 'fi',
+                       'hu_HU' => 'hu',
+                       'nb_NO' => 'nb',
+                       'sk_SK' => 'sk',
+                       'th_TH' => 'th',
+               ];
+
+               foreach ($languages as $oldCode => $newCode) {
+                       $qb = $this->connection->getQueryBuilder();
+
+                       $affectedRows = $qb->update('preferences')
+                               ->set('configvalue', $qb->createNamedParameter($newCode))
+                               ->where($qb->expr()->eq('appid', $qb->createNamedParameter('core')))
+                               ->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter('lang')))
+                               ->andWhere($qb->expr()->eq('configvalue', $qb->createNamedParameter($oldCode), IQueryBuilder::PARAM_STR))
+                               ->execute();
+
+                       $output->info('Changed ' . $affectedRows . ' setting(s) from "' . $oldCode . '" to "' . $newCode . '" in preferences table.');
+               }
+       }
+}
diff --git a/tests/Test/Repair/Owncloud/CleanPreviewsBackgroundJobTest.php b/tests/Test/Repair/Owncloud/CleanPreviewsBackgroundJobTest.php
new file mode 100644 (file)
index 0000000..267f01c
--- /dev/null
@@ -0,0 +1,245 @@
+<?php
+/**
+ * @copyright 2016, Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @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 Test\Repair\Owncloud;
+
+use OC\Repair\Owncloud\CleanPreviewsBackgroundJob;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\IJobList;
+use OCP\Files\Folder;
+use OCP\Files\IRootFolder;
+use OCP\Files\NotFoundException;
+use OCP\Files\NotPermittedException;
+use OCP\ILogger;
+use OCP\IUserManager;
+use Test\TestCase;
+
+class CleanPreviewsBackgroundJobTest extends TestCase {
+       /** @var IRootFolder|\PHPUnit_Framework_MockObject_MockObject */
+       private $rootFolder;
+
+       /** @var ILogger|\PHPUnit_Framework_MockObject_MockObject */
+       private $logger;
+
+       /** @var IJobList|\PHPUnit_Framework_MockObject_MockObject */
+       private $jobList;
+
+       /** @var ITimeFactory|\PHPUnit_Framework_MockObject_MockObject */
+       private $timeFactory;
+
+       /** @var CleanPreviewsBackgroundJob */
+       private $job;
+
+       /** @var  IUserManager|\PHPUnit_Framework_MockObject_MockObject */
+       private $userManager;
+
+       public function setUp() {
+               parent::setUp();
+
+               $this->rootFolder = $this->createMock(IRootFolder::class);
+               $this->logger = $this->createMock(ILogger::class);
+               $this->jobList = $this->createMock(IJobList::class);
+               $this->timeFactory = $this->createMock(ITimeFactory::class);
+               $this->userManager = $this->createMock(IUserManager::class);
+
+               $this->userManager->expects($this->any())->method('userExists')->willReturn(true);
+
+               $this->job = new CleanPreviewsBackgroundJob(
+                       $this->rootFolder,
+                       $this->logger,
+                       $this->jobList,
+                       $this->timeFactory,
+                       $this->userManager
+               );
+       }
+
+       public function testCleanupPreviewsUnfinished() {
+               $userFolder = $this->createMock(Folder::class);
+               $userRoot = $this->createMock(Folder::class);
+               $thumbnailFolder = $this->createMock(Folder::class);
+
+               $this->rootFolder->method('getUserFolder')
+                       ->with($this->equalTo('myuid'))
+                       ->willReturn($userFolder);
+
+               $userFolder->method('getParent')->willReturn($userRoot);
+
+               $userRoot->method('get')
+                       ->with($this->equalTo('thumbnails'))
+                       ->willReturn($thumbnailFolder);
+
+               $previewFolder1 = $this->createMock(Folder::class);
+
+               $previewFolder1->expects($this->once())
+                       ->method('delete');
+
+               $thumbnailFolder->method('getDirectoryListing')
+                       ->willReturn([$previewFolder1]);
+               $thumbnailFolder->expects($this->never())
+                       ->method('delete');
+
+               $this->timeFactory->method('getTime')
+                       ->will($this->onConsecutiveCalls(100, 200));
+
+               $this->jobList->expects($this->once())
+                       ->method('add')
+                       ->with(
+                               $this->equalTo(CleanPreviewsBackgroundJob::class),
+                               $this->equalTo(['uid' => 'myuid'])
+                       );
+
+               $this->logger->expects($this->at(0))
+                       ->method('info')
+                       ->with($this->equalTo('Started preview cleanup for myuid'));
+               $this->logger->expects($this->at(1))
+                       ->method('info')
+                       ->with($this->equalTo('New preview cleanup scheduled for myuid'));
+
+               $this->job->run(['uid' => 'myuid']);
+       }
+
+       public function testCleanupPreviewsFinished() {
+               $userFolder = $this->createMock(Folder::class);
+               $userRoot = $this->createMock(Folder::class);
+               $thumbnailFolder = $this->createMock(Folder::class);
+
+               $this->rootFolder->method('getUserFolder')
+                       ->with($this->equalTo('myuid'))
+                       ->willReturn($userFolder);
+
+               $userFolder->method('getParent')->willReturn($userRoot);
+
+               $userRoot->method('get')
+                       ->with($this->equalTo('thumbnails'))
+                       ->willReturn($thumbnailFolder);
+
+               $previewFolder1 = $this->createMock(Folder::class);
+
+               $previewFolder1->expects($this->once())
+                       ->method('delete');
+
+               $thumbnailFolder->method('getDirectoryListing')
+                       ->willReturn([$previewFolder1]);
+
+               $this->timeFactory->method('getTime')
+                       ->will($this->onConsecutiveCalls(100, 101));
+
+               $this->jobList->expects($this->never())
+                       ->method('add');
+
+               $this->logger->expects($this->at(0))
+                       ->method('info')
+                       ->with($this->equalTo('Started preview cleanup for myuid'));
+               $this->logger->expects($this->at(1))
+                       ->method('info')
+                       ->with($this->equalTo('Preview cleanup done for myuid'));
+
+               $thumbnailFolder->expects($this->once())
+                       ->method('delete');
+
+               $this->job->run(['uid' => 'myuid']);
+       }
+
+
+       public function testNoUserFolder() {
+               $this->rootFolder->method('getUserFolder')
+                       ->with($this->equalTo('myuid'))
+                       ->willThrowException(new NotFoundException());
+
+               $this->logger->expects($this->at(0))
+                       ->method('info')
+                       ->with($this->equalTo('Started preview cleanup for myuid'));
+               $this->logger->expects($this->at(1))
+                       ->method('info')
+                       ->with($this->equalTo('Preview cleanup done for myuid'));
+
+               $this->job->run(['uid' => 'myuid']);
+       }
+
+       public function testNoThumbnailFolder() {
+               $userFolder = $this->createMock(Folder::class);
+               $userRoot = $this->createMock(Folder::class);
+
+               $this->rootFolder->method('getUserFolder')
+                       ->with($this->equalTo('myuid'))
+                       ->willReturn($userFolder);
+
+               $userFolder->method('getParent')->willReturn($userRoot);
+
+               $userRoot->method('get')
+                       ->with($this->equalTo('thumbnails'))
+                       ->willThrowException(new NotFoundException());
+
+               $this->logger->expects($this->at(0))
+                       ->method('info')
+                       ->with($this->equalTo('Started preview cleanup for myuid'));
+               $this->logger->expects($this->at(1))
+                       ->method('info')
+                       ->with($this->equalTo('Preview cleanup done for myuid'));
+
+               $this->job->run(['uid' => 'myuid']);
+       }
+
+       public function testNotPermittedToDelete() {
+               $userFolder = $this->createMock(Folder::class);
+               $userRoot = $this->createMock(Folder::class);
+               $thumbnailFolder = $this->createMock(Folder::class);
+
+               $this->rootFolder->method('getUserFolder')
+                       ->with($this->equalTo('myuid'))
+                       ->willReturn($userFolder);
+
+               $userFolder->method('getParent')->willReturn($userRoot);
+
+               $userRoot->method('get')
+                       ->with($this->equalTo('thumbnails'))
+                       ->willReturn($thumbnailFolder);
+
+               $previewFolder1 = $this->createMock(Folder::class);
+
+               $previewFolder1->expects($this->once())
+                       ->method('delete')
+                       ->willThrowException(new NotPermittedException());
+
+               $thumbnailFolder->method('getDirectoryListing')
+                       ->willReturn([$previewFolder1]);
+
+               $this->timeFactory->method('getTime')
+                       ->will($this->onConsecutiveCalls(100, 101));
+
+               $this->jobList->expects($this->never())
+                       ->method('add');
+
+               $this->logger->expects($this->at(0))
+                       ->method('info')
+                       ->with($this->equalTo('Started preview cleanup for myuid'));
+               $this->logger->expects($this->at(1))
+                       ->method('info')
+                       ->with($this->equalTo('Preview cleanup done for myuid'));
+
+               $thumbnailFolder->expects($this->once())
+                       ->method('delete')
+                       ->willThrowException(new NotPermittedException());
+
+               $this->job->run(['uid' => 'myuid']);
+       }
+}
diff --git a/tests/Test/Repair/Owncloud/CleanPreviewsTest.php b/tests/Test/Repair/Owncloud/CleanPreviewsTest.php
new file mode 100644 (file)
index 0000000..131b805
--- /dev/null
@@ -0,0 +1,134 @@
+<?php
+/**
+ * @copyright 2016, Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @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 Test\Repair\Owncloud;
+
+use OC\Repair\Owncloud\CleanPreviews;
+use OC\Repair\Owncloud\CleanPreviewsBackgroundJob;
+use OCP\BackgroundJob\IJobList;
+use OCP\IConfig;
+use OCP\IUser;
+use OCP\IUserManager;
+use OCP\Migration\IOutput;
+use Test\TestCase;
+
+class CleanPreviewsTest extends TestCase {
+
+
+       /** @var IJobList|\PHPUnit_Framework_MockObject_MockObject */
+       private $jobList;
+
+       /** @var IUserManager|\PHPUnit_Framework_MockObject_MockObject */
+       private $userManager;
+
+       /** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */
+       private $config;
+
+       /** @var CleanPreviews */
+       private $repair;
+
+       public function setUp() {
+               parent::setUp();
+
+               $this->jobList = $this->createMock(IJobList::class);
+               $this->userManager = $this->createMock(IUserManager::class);
+               $this->config = $this->createMock(IConfig::class);
+
+               $this->repair = new CleanPreviews(
+                       $this->jobList,
+                       $this->userManager,
+                       $this->config
+               );
+       }
+
+       public function testGetName() {
+               $this->assertSame('Add preview cleanup background jobs', $this->repair->getName());
+       }
+
+       public function testRun() {
+               $user1 = $this->createMock(IUser::class);
+               $user1->method('getUID')
+                       ->willReturn('user1');
+               $user2 = $this->createMock(IUser::class);
+               $user2->method('getUID')
+                       ->willReturn('user2');
+
+               $this->userManager->expects($this->once())
+                       ->method('callForSeenUsers')
+                       ->will($this->returnCallback(function (\Closure $function) use ($user1, $user2) {
+                               $function($user1);
+                               $function($user2);
+                       }));
+
+               $this->jobList->expects($this->at(0))
+                       ->method('add')
+                       ->with(
+                               $this->equalTo(CleanPreviewsBackgroundJob::class),
+                               $this->equalTo(['uid' => 'user1'])
+                       );
+
+               $this->jobList->expects($this->at(1))
+                       ->method('add')
+                       ->with(
+                               $this->equalTo(CleanPreviewsBackgroundJob::class),
+                               $this->equalTo(['uid' => 'user2'])
+                       );
+
+               $this->config->expects($this->once())
+                       ->method('getAppValue')
+                       ->with(
+                               $this->equalTo('core'),
+                               $this->equalTo('previewsCleanedUp'),
+                               $this->equalTo(false)
+                       )->willReturn(false);
+               $this->config->expects($this->once())
+                       ->method('setAppValue')
+                       ->with(
+                               $this->equalTo('core'),
+                               $this->equalTo('previewsCleanedUp'),
+                               $this->equalTo(1)
+                       );
+
+               $this->repair->run($this->createMock(IOutput::class));
+       }
+
+
+       public function testRunAlreadyDoone() {
+               $this->userManager->expects($this->never())
+                       ->method($this->anything());
+
+               $this->jobList->expects($this->never())
+                       ->method($this->anything());
+
+               $this->config->expects($this->once())
+                       ->method('getAppValue')
+                       ->with(
+                               $this->equalTo('core'),
+                               $this->equalTo('previewsCleanedUp'),
+                               $this->equalTo(false)
+                       )->willReturn('1');
+               $this->config->expects($this->never())
+                       ->method('setAppValue');
+
+               $this->repair->run($this->createMock(IOutput::class));
+       }
+}
diff --git a/tests/Test/Repair/Owncloud/InstallCoreBundleTest.php b/tests/Test/Repair/Owncloud/InstallCoreBundleTest.php
new file mode 100644 (file)
index 0000000..37163ae
--- /dev/null
@@ -0,0 +1,142 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @author Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @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 Test\Repair\Owncloud;
+
+use OC\App\AppStore\Bundles\Bundle;
+use OC\App\AppStore\Bundles\BundleFetcher;
+use OC\Installer;
+use OC\Repair\Owncloud\InstallCoreBundle;
+use OCP\IConfig;
+use OCP\Migration\IOutput;
+use Test\TestCase;
+
+class InstallCoreBundleTest extends TestCase {
+       /** @var BundleFetcher|\PHPUnit_Framework_MockObject_MockObject */
+       private $bundleFetcher;
+       /** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */
+       private $config;
+       /** @var Installer|\PHPUnit_Framework_MockObject_MockObject */
+       private $installer;
+       /** @var InstallCoreBundle */
+       private $installCoreBundle;
+
+       public function setUp() {
+               parent::setUp();
+               $this->bundleFetcher = $this->createMock(BundleFetcher::class);
+               $this->config = $this->createMock(IConfig::class);
+               $this->installer = $this->createMock(Installer::class);
+
+               $this->installCoreBundle = new InstallCoreBundle(
+                       $this->bundleFetcher,
+                       $this->config,
+                       $this->installer
+               );
+       }
+
+       public function testGetName() {
+               $this->assertSame('Install new core bundle components', $this->installCoreBundle->getName());
+       }
+
+       public function testRunOlder() {
+               $this->config
+                       ->expects($this->once())
+                       ->method('getSystemValue')
+                       ->with('version', '0.0.0')
+                       ->willReturn('12.0.0.15');
+               $this->bundleFetcher
+                       ->expects($this->never())
+                       ->method('getDefaultInstallationBundle');
+               /** @var IOutput|\PHPUnit_Framework_MockObject_MockObject $output */
+               $output = $this->createMock(IOutput::class);
+               $output
+                       ->expects($this->never())
+                       ->method('info');
+               $output
+                       ->expects($this->never())
+                       ->method('warning');
+
+               $this->installCoreBundle->run($output);
+       }
+
+       public function testRunWithException() {
+               $this->config
+                       ->expects($this->once())
+                       ->method('getSystemValue')
+                       ->with('version', '0.0.0')
+                       ->willReturn('12.0.0.14');
+               $bundle = $this->createMock(Bundle::class);
+               $this->bundleFetcher
+                       ->expects($this->once())
+                       ->method('getDefaultInstallationBundle')
+                       ->willReturn([
+                               $bundle,
+                       ]);
+               $this->installer
+                       ->expects($this->once())
+                       ->method('installAppBundle')
+                       ->with($bundle)
+                       ->willThrowException(new \Exception('ExceptionText'));
+               /** @var IOutput|\PHPUnit_Framework_MockObject_MockObject $output */
+               $output = $this->createMock(IOutput::class);
+               $output
+                       ->expects($this->never())
+                       ->method('info');
+               $output
+                       ->expects($this->once())
+                       ->method('warning')
+                       ->with('Could not install core app bundle: ExceptionText');
+
+               $this->installCoreBundle->run($output);
+       }
+
+       public function testRun() {
+               $this->config
+                       ->expects($this->once())
+                       ->method('getSystemValue')
+                       ->with('version', '0.0.0')
+                       ->willReturn('12.0.0.14');
+               $bundle = $this->createMock(Bundle::class);
+               $this->bundleFetcher
+                       ->expects($this->once())
+                       ->method('getDefaultInstallationBundle')
+                       ->willReturn([
+                               $bundle,
+                       ]);
+               $this->installer
+                       ->expects($this->once())
+                       ->method('installAppBundle')
+                       ->with($bundle);
+               /** @var IOutput|\PHPUnit_Framework_MockObject_MockObject $output */
+               $output = $this->createMock(IOutput::class);
+               $output
+                       ->expects($this->once())
+                       ->method('info')
+                       ->with('Successfully installed core app bundle.');
+               $output
+                       ->expects($this->never())
+                       ->method('warning');
+
+               $this->installCoreBundle->run($output);
+       }
+}
diff --git a/tests/Test/Repair/Owncloud/UpdateLanguageCodesTest.php b/tests/Test/Repair/Owncloud/UpdateLanguageCodesTest.php
new file mode 100644 (file)
index 0000000..171b770
--- /dev/null
@@ -0,0 +1,175 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Morris Jobke <hey@morrisjobke.de>
+ *
+ * @author Morris Jobke <hey@morrisjobke.de>
+ *
+ * @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 Test\Repair\Owncloud;
+
+use OC\Repair\Owncloud\UpdateLanguageCodes;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IConfig;
+use OCP\Migration\IOutput;
+use Test\TestCase;
+
+/**
+ * Class UpdateLanguageCodesTest
+ *
+ * @group DB
+ *
+ * @package Test\Repair
+ */
+class UpdateLanguageCodesTest extends TestCase {
+       /** @var \OCP\IDBConnection */
+       protected $connection;
+
+       /** @var IConfig | \PHPUnit_Framework_MockObject_MockObject */
+       private $config;
+
+       protected function setUp() {
+               parent::setUp();
+
+               $this->connection = \OC::$server->getDatabaseConnection();
+               $this->config = $this->createMock(IConfig::class);
+       }
+
+       public function testRun() {
+               $users = [
+                       ['userid' => 'user1', 'configvalue' => 'fi_FI'],
+                       ['userid' => 'user2', 'configvalue' => 'de'],
+                       ['userid' => 'user3', 'configvalue' => 'fi'],
+                       ['userid' => 'user4', 'configvalue' => 'ja'],
+                       ['userid' => 'user5', 'configvalue' => 'bg_BG'],
+                       ['userid' => 'user6', 'configvalue' => 'ja'],
+                       ['userid' => 'user7', 'configvalue' => 'th_TH'],
+                       ['userid' => 'user8', 'configvalue' => 'th_TH'],
+               ];
+
+               // insert test data
+               $qb = $this->connection->getQueryBuilder();
+               $qb->insert('preferences')
+                               ->values([
+                                       'userid' => $qb->createParameter('userid'),
+                                       'appid' => $qb->createParameter('appid'),
+                                       'configkey' => $qb->createParameter('configkey'),
+                                       'configvalue' => $qb->createParameter('configvalue'),
+                               ]);
+               foreach ($users as $user) {
+                       $qb->setParameters([
+                               'userid' => $user['userid'],
+                               'appid' => 'core',
+                               'configkey' => 'lang',
+                               'configvalue' => $user['configvalue'],
+                       ])->execute();
+               }
+
+               // check if test data is written to DB
+               $qb = $this->connection->getQueryBuilder();
+               $result = $qb->select(['userid', 'configvalue'])
+                       ->from('preferences')
+                       ->where($qb->expr()->eq('appid', $qb->createNamedParameter('core')))
+                       ->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter('lang')))
+                       ->execute();
+
+               $rows = $result->fetchAll();
+               $result->closeCursor();
+
+               $this->assertSame($users, $rows, 'Asserts that the entries are the ones from the test data set');
+
+               /** @var IOutput|\PHPUnit_Framework_MockObject_MockObject $outputMock */
+               $outputMock = $this->createMock(IOutput::class);
+               $outputMock->expects($this->at(0))
+                       ->method('info')
+                       ->with('Changed 1 setting(s) from "bg_BG" to "bg" in preferences table.');
+               $outputMock->expects($this->at(1))
+                       ->method('info')
+                       ->with('Changed 0 setting(s) from "cs_CZ" to "cs" in preferences table.');
+               $outputMock->expects($this->at(2))
+                       ->method('info')
+                       ->with('Changed 1 setting(s) from "fi_FI" to "fi" in preferences table.');
+               $outputMock->expects($this->at(3))
+                       ->method('info')
+                       ->with('Changed 0 setting(s) from "hu_HU" to "hu" in preferences table.');
+               $outputMock->expects($this->at(4))
+                       ->method('info')
+                       ->with('Changed 0 setting(s) from "nb_NO" to "nb" in preferences table.');
+               $outputMock->expects($this->at(5))
+                       ->method('info')
+                       ->with('Changed 0 setting(s) from "sk_SK" to "sk" in preferences table.');
+               $outputMock->expects($this->at(6))
+                       ->method('info')
+                       ->with('Changed 2 setting(s) from "th_TH" to "th" in preferences table.');
+
+               $this->config->expects($this->once())
+                       ->method('getSystemValue')
+                       ->with('version', '0.0.0')
+                       ->willReturn('12.0.0.13');
+
+               // run repair step
+               $repair = new UpdateLanguageCodes($this->connection, $this->config);
+               $repair->run($outputMock);
+
+               // check if test data is correctly modified in DB
+               $qb = $this->connection->getQueryBuilder();
+               $result = $qb->select(['userid', 'configvalue'])
+                       ->from('preferences')
+                       ->where($qb->expr()->eq('appid', $qb->createNamedParameter('core')))
+                       ->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter('lang')))
+                       ->orderBy('userid')
+                       ->execute();
+
+               $rows = $result->fetchAll();
+               $result->closeCursor();
+
+               // value has changed for one user
+               $users[0]['configvalue'] = 'fi';
+               $users[4]['configvalue'] = 'bg';
+               $users[6]['configvalue'] = 'th';
+               $users[7]['configvalue'] = 'th';
+               $this->assertSame($users, $rows, 'Asserts that the entries are updated correctly.');
+
+               // remove test data
+               foreach ($users as $user) {
+                       $qb = $this->connection->getQueryBuilder();
+                       $qb->delete('preferences')
+                               ->where($qb->expr()->eq('userid', $qb->createNamedParameter($user['userid'])))
+                               ->andWhere($qb->expr()->eq('appid', $qb->createNamedParameter('core')))
+                               ->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter('lang')))
+                               ->andWhere($qb->expr()->eq('configvalue', $qb->createNamedParameter($user['configvalue']), IQueryBuilder::PARAM_STR))
+                               ->execute();
+               }
+       }
+
+       public function testSecondRun() {
+               /** @var IOutput|\PHPUnit_Framework_MockObject_MockObject $outputMock */
+               $outputMock = $this->createMock(IOutput::class);
+               $outputMock->expects($this->never())
+                       ->method('info');
+
+               $this->config->expects($this->once())
+                       ->method('getSystemValue')
+                       ->with('version', '0.0.0')
+                       ->willReturn('12.0.0.14');
+
+               // run repair step
+               $repair = new UpdateLanguageCodes($this->connection, $this->config);
+               $repair->run($outputMock);
+       }
+}