runPreparation(); break; case self::STAGE_EXECUTE: $this->runMigration(); break; default: break; } } protected function runPreparation(): void { try { $selector = $this->dbc->getQueryBuilder(); $result = $selector->select('userid') ->from('preferences') ->where($selector->expr()->eq('appid', $selector->createNamedParameter('theming'))) ->andWhere($selector->expr()->eq('configkey', $selector->createNamedParameter('background'))) ->andWhere($selector->expr()->eq('configvalue', $selector->createNamedParameter('custom', IQueryBuilder::PARAM_STR), IQueryBuilder::PARAM_STR)) ->executeQuery(); $userIds = $result->fetchAll(\PDO::FETCH_COLUMN); $this->storeUserIdsToProcess($userIds); } catch (\Throwable $t) { $this->jobList->add(self::class, ['stage' => self::STAGE_PREPARE]); throw $t; } $this->jobList->add(self::class, ['stage' => self::STAGE_EXECUTE]); } /** * @throws NotPermittedException * @throws NotFoundException */ protected function runMigration(): void { $allUserIds = $this->readUserIdsToProcess(); $notSoFastMode = count($allUserIds) > 5000; $dashboardData = $this->appDataFactory->get('dashboard'); $userIds = $notSoFastMode ? array_slice($allUserIds, 0, 5000) : $allUserIds; foreach ($userIds as $userId) { try { // migration $file = $dashboardData->getFolder($userId)->getFile('background.jpg'); $targetDir = $this->getUserFolder($userId); if (!$targetDir->fileExists('background.jpg')) { $targetDir->newFile('background.jpg', $file->getContent()); } $file->delete(); } catch (NotFoundException|NotPermittedException $e) { } } if ($notSoFastMode) { $remainingUserIds = array_slice($allUserIds, 5000); $this->storeUserIdsToProcess($remainingUserIds); $this->jobList->add(self::class, ['stage' => self::STAGE_EXECUTE]); } else { $this->deleteStateFile(); } } /** * @throws NotPermittedException * @throws NotFoundException */ protected function readUserIdsToProcess(): array { $globalFolder = $this->appData->getFolder('global'); if ($globalFolder->fileExists(self::STATE_FILE_NAME)) { $file = $globalFolder->getFile(self::STATE_FILE_NAME); try { $userIds = \json_decode($file->getContent(), true); } catch (NotFoundException $e) { $userIds = []; } if ($userIds === null) { $userIds = []; } } else { $userIds = []; } return $userIds; } /** * @throws NotFoundException */ protected function storeUserIdsToProcess(array $userIds): void { $storableUserIds = \json_encode($userIds); $globalFolder = $this->appData->getFolder('global'); try { if ($globalFolder->fileExists(self::STATE_FILE_NAME)) { $file = $globalFolder->getFile(self::STATE_FILE_NAME); } else { $file = $globalFolder->newFile(self::STATE_FILE_NAME); } $file->putContent($storableUserIds); } catch (NotFoundException $e) { } catch (NotPermittedException $e) { $this->logger->warning('Lacking permissions to create {file}', [ 'app' => 'theming', 'file' => self::STATE_FILE_NAME, 'exception' => $e, ] ); } } /** * @throws NotFoundException */ protected function deleteStateFile(): void { $globalFolder = $this->appData->getFolder('global'); if ($globalFolder->fileExists(self::STATE_FILE_NAME)) { $file = $globalFolder->getFile(self::STATE_FILE_NAME); try { $file->delete(); } catch (NotPermittedException $e) { $this->logger->info('Could not delete {file} due to permissions. It is safe to delete manually inside data -> appdata -> theming -> global.', [ 'app' => 'theming', 'file' => $file->getName(), 'exception' => $e, ] ); } } } /** * Get the root location for users theming data */ protected function getUserFolder(string $userId): ISimpleFolder { $themingData = $this->appDataFactory->get(Application::APP_ID); try { $rootFolder = $themingData->getFolder('users'); } catch (NotFoundException $e) { $rootFolder = $themingData->newFolder('users'); } try { return $rootFolder->getFolder($userId); } catch (NotFoundException $e) { return $rootFolder->newFolder($userId); } } }