Skip to content

Commit 4a4ee4f

Browse files
authored
Merge pull request #35326 from nextcloud/fix/34602/background-image-migration/stable25
[stable25] optimize background image migration job
2 parents 166f076 + 6916a31 commit 4a4ee4f

File tree

2 files changed

+137
-33
lines changed

2 files changed

+137
-33
lines changed

apps/theming/lib/Jobs/MigrateBackgroundImages.php

Lines changed: 136 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -27,56 +27,96 @@
2727
namespace OCA\Theming\Jobs;
2828

2929
use OCA\Theming\AppInfo\Application;
30-
use OCP\App\IAppManager;
3130
use OCP\AppFramework\Utility\ITimeFactory;
3231
use OCP\BackgroundJob\IJobList;
3332
use OCP\BackgroundJob\QueuedJob;
3433
use OCP\Files\AppData\IAppDataFactory;
34+
use OCP\Files\IAppData;
3535
use OCP\Files\NotFoundException;
3636
use OCP\Files\NotPermittedException;
3737
use OCP\Files\SimpleFS\ISimpleFolder;
38-
use OCP\IConfig;
38+
use OCP\IDBConnection;
39+
use Psr\Log\LoggerInterface;
3940

4041
class MigrateBackgroundImages extends QueuedJob {
4142
public const TIME_SENSITIVE = 0;
4243

43-
private IConfig $config;
44-
private IAppManager $appManager;
44+
public const STAGE_PREPARE = 'prepare';
45+
public const STAGE_EXECUTE = 'execute';
46+
// will be saved in appdata/theming/global/
47+
protected const STATE_FILE_NAME = '25_dashboard_to_theming_migration_users.json';
48+
4549
private IAppDataFactory $appDataFactory;
4650
private IJobList $jobList;
47-
48-
public function __construct(ITimeFactory $time, IAppDataFactory $appDataFactory, IConfig $config, IAppManager $appManager, IJobList $jobList) {
51+
private IDBConnection $dbc;
52+
private IAppData $appData;
53+
private LoggerInterface $logger;
54+
55+
public function __construct(
56+
ITimeFactory $time,
57+
IAppDataFactory $appDataFactory,
58+
IJobList $jobList,
59+
IDBConnection $dbc,
60+
IAppData $appData,
61+
LoggerInterface $logger
62+
) {
4963
parent::__construct($time);
50-
$this->config = $config;
51-
$this->appManager = $appManager;
5264
$this->appDataFactory = $appDataFactory;
5365
$this->jobList = $jobList;
66+
$this->dbc = $dbc;
67+
$this->appData = $appData;
68+
$this->logger = $logger;
5469
}
5570

5671
protected function run($argument): void {
57-
if (!$this->appManager->isEnabledForUser('dashboard')) {
58-
return;
72+
if (!isset($argument['stage'])) {
73+
// not executed in 25.0.0?!
74+
$argument['stage'] = self::STAGE_PREPARE;
5975
}
6076

61-
$dashboardData = $this->appDataFactory->get('dashboard');
77+
switch ($argument['stage']) {
78+
case self::STAGE_PREPARE:
79+
$this->runPreparation();
80+
break;
81+
case self::STAGE_EXECUTE:
82+
$this->runMigration();
83+
break;
84+
default:
85+
break;
86+
}
87+
}
6288

63-
$userIds = $this->config->getUsersForUserValue('theming', 'background', 'custom');
89+
protected function runPreparation(): void {
90+
try {
91+
$selector = $this->dbc->getQueryBuilder();
92+
$result = $selector->select('userid')
93+
->from('preferences')
94+
->where($selector->expr()->eq('appid', $selector->createNamedParameter('theming')))
95+
->andWhere($selector->expr()->eq('configkey', $selector->createNamedParameter('background')))
96+
->andWhere($selector->expr()->eq('configvalue', $selector->createNamedParameter('custom')))
97+
->executeQuery();
98+
99+
$userIds = $result->fetchAll(\PDO::FETCH_COLUMN);
100+
$this->storeUserIdsToProcess($userIds);
101+
} catch (\Throwable $t) {
102+
$this->jobList->add(self::class, self::STAGE_PREPARE);
103+
throw $t;
104+
}
105+
$this->jobList->add(self::class, self::STAGE_EXECUTE);
106+
}
64107

65-
$notSoFastMode = \count($userIds) > 5000;
66-
$reTrigger = false;
67-
$processed = 0;
108+
/**
109+
* @throws NotPermittedException
110+
* @throws NotFoundException
111+
*/
112+
protected function runMigration(): void {
113+
$allUserIds = $this->readUserIdsToProcess();
114+
$notSoFastMode = count($allUserIds) > 5000;
115+
$dashboardData = $this->appDataFactory->get('dashboard');
68116

117+
$userIds = $notSoFastMode ? array_slice($allUserIds, 0, 5000) : $allUserIds;
69118
foreach ($userIds as $userId) {
70119
try {
71-
// precondition
72-
if ($notSoFastMode) {
73-
if ($this->config->getUserValue($userId, 'theming', 'background-migrated', '0') === '1') {
74-
// already migrated
75-
continue;
76-
}
77-
$reTrigger = true;
78-
}
79-
80120
// migration
81121
$file = $dashboardData->getFolder($userId)->getFile('background.jpg');
82122
$targetDir = $this->getUserFolder($userId);
@@ -87,18 +127,82 @@ protected function run($argument): void {
87127
$file->delete();
88128
} catch (NotFoundException|NotPermittedException $e) {
89129
}
90-
// capture state
91-
if ($notSoFastMode) {
92-
$this->config->setUserValue($userId, 'theming', 'background-migrated', '1');
93-
$processed++;
130+
}
131+
132+
if ($notSoFastMode) {
133+
$remainingUserIds = array_slice($allUserIds, 5000);
134+
$this->storeUserIdsToProcess($remainingUserIds);
135+
$this->jobList->add(self::class, ['stage' => self::STAGE_EXECUTE]);
136+
} else {
137+
$this->deleteStateFile();
138+
}
139+
}
140+
141+
/**
142+
* @throws NotPermittedException
143+
* @throws NotFoundException
144+
*/
145+
protected function readUserIdsToProcess(): array {
146+
$globalFolder = $this->appData->getFolder('global');
147+
if ($globalFolder->fileExists(self::STATE_FILE_NAME)) {
148+
$file = $globalFolder->getFile(self::STATE_FILE_NAME);
149+
try {
150+
$userIds = \json_decode($file->getContent(), true);
151+
} catch (NotFoundException $e) {
152+
$userIds = [];
94153
}
95-
if ($processed > 4999) {
96-
break;
154+
if ($userIds === null) {
155+
$userIds = [];
97156
}
157+
} else {
158+
$userIds = [];
98159
}
160+
return $userIds;
161+
}
99162

100-
if ($reTrigger) {
101-
$this->jobList->add(self::class);
163+
/**
164+
* @throws NotFoundException
165+
*/
166+
protected function storeUserIdsToProcess(array $userIds): void {
167+
$storableUserIds = \json_encode($userIds);
168+
$globalFolder = $this->appData->getFolder('global');
169+
try {
170+
if ($globalFolder->fileExists(self::STATE_FILE_NAME)) {
171+
$file = $globalFolder->getFile(self::STATE_FILE_NAME);
172+
} else {
173+
$file = $globalFolder->newFile(self::STATE_FILE_NAME);
174+
}
175+
$file->putContent($storableUserIds);
176+
} catch (NotFoundException $e) {
177+
} catch (NotPermittedException $e) {
178+
$this->logger->warning('Lacking permissions to create {file}',
179+
[
180+
'app' => 'theming',
181+
'file' => self::STATE_FILE_NAME,
182+
'exception' => $e,
183+
]
184+
);
185+
}
186+
}
187+
188+
/**
189+
* @throws NotFoundException
190+
*/
191+
protected function deleteStateFile(): void {
192+
$globalFolder = $this->appData->getFolder('global');
193+
if ($globalFolder->fileExists(self::STATE_FILE_NAME)) {
194+
$file = $globalFolder->getFile(self::STATE_FILE_NAME);
195+
try {
196+
$file->delete();
197+
} catch (NotPermittedException $e) {
198+
$this->logger->info('Could not delete {file} due to permissions. It is safe to delete manually inside data -> appdata -> theming -> global.',
199+
[
200+
'app' => 'theming',
201+
'file' => $file->getName(),
202+
'exception' => $e,
203+
]
204+
);
205+
}
102206
}
103207
}
104208

apps/theming/lib/Migration/InitBackgroundImagesMigration.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,6 @@ public function getName() {
4343
}
4444

4545
public function run(IOutput $output) {
46-
$this->jobList->add(MigrateBackgroundImages::class);
46+
$this->jobList->add(MigrateBackgroundImages::class, ['stage' => MigrateBackgroundImages::STAGE_PREPARE]);
4747
}
4848
}

0 commit comments

Comments
 (0)