Completed
Push — master ( 524450...ed6352 )
by Morris
108:40 queued 83:15
created
lib/private/Updater.php 1 patch
Indentation   +561 added lines, -561 removed lines patch added patch discarded remove patch
@@ -55,567 +55,567 @@
 block discarded – undo
55 55
  */
56 56
 class Updater extends BasicEmitter {
57 57
 
58
-	/** @var ILogger $log */
59
-	private $log;
60
-
61
-	/** @var IConfig */
62
-	private $config;
63
-
64
-	/** @var Checker */
65
-	private $checker;
66
-
67
-	/** @var Installer */
68
-	private $installer;
69
-
70
-	/** @var IJobList */
71
-	private $jobList;
72
-
73
-	private $logLevelNames = [
74
-		0 => 'Debug',
75
-		1 => 'Info',
76
-		2 => 'Warning',
77
-		3 => 'Error',
78
-		4 => 'Fatal',
79
-	];
80
-
81
-	public function __construct(IConfig $config,
82
-								Checker $checker,
83
-								ILogger $log,
84
-								Installer $installer,
85
-								IJobList $jobList) {
86
-		$this->log = $log;
87
-		$this->config = $config;
88
-		$this->checker = $checker;
89
-		$this->installer = $installer;
90
-		$this->jobList = $jobList;
91
-	}
92
-
93
-	/**
94
-	 * runs the update actions in maintenance mode, does not upgrade the source files
95
-	 * except the main .htaccess file
96
-	 *
97
-	 * @return bool true if the operation succeeded, false otherwise
98
-	 */
99
-	public function upgrade() {
100
-		$this->emitRepairEvents();
101
-		$this->logAllEvents();
102
-
103
-		$logLevel = $this->config->getSystemValue('loglevel', ILogger::WARN);
104
-		$this->emit('\OC\Updater', 'setDebugLogLevel', [ $logLevel, $this->logLevelNames[$logLevel] ]);
105
-		$this->config->setSystemValue('loglevel', ILogger::DEBUG);
106
-
107
-		$wasMaintenanceModeEnabled = $this->config->getSystemValue('maintenance', false);
108
-
109
-		if(!$wasMaintenanceModeEnabled) {
110
-			$this->config->setSystemValue('maintenance', true);
111
-			$this->emit('\OC\Updater', 'maintenanceEnabled');
112
-		}
113
-
114
-		$installedVersion = $this->config->getSystemValue('version', '0.0.0');
115
-		$currentVersion = implode('.', \OCP\Util::getVersion());
116
-
117
-		// see https://github.com/nextcloud/server/issues/9992 for potential problem
118
-		if (version_compare($installedVersion, '14.0.0.9', '>=')) {
119
-			$this->waitForCronToFinish();
120
-		}
121
-
122
-		$this->log->debug('starting upgrade from ' . $installedVersion . ' to ' . $currentVersion, array('app' => 'core'));
123
-
124
-		$success = true;
125
-		try {
126
-			$this->doUpgrade($currentVersion, $installedVersion);
127
-		} catch (HintException $exception) {
128
-			$this->log->logException($exception, ['app' => 'core']);
129
-			$this->emit('\OC\Updater', 'failure', array($exception->getMessage() . ': ' .$exception->getHint()));
130
-			$success = false;
131
-		} catch (\Exception $exception) {
132
-			$this->log->logException($exception, ['app' => 'core']);
133
-			$this->emit('\OC\Updater', 'failure', array(get_class($exception) . ': ' .$exception->getMessage()));
134
-			$success = false;
135
-		}
136
-
137
-		$this->emit('\OC\Updater', 'updateEnd', array($success));
138
-
139
-		if(!$wasMaintenanceModeEnabled && $success) {
140
-			$this->config->setSystemValue('maintenance', false);
141
-			$this->emit('\OC\Updater', 'maintenanceDisabled');
142
-		} else {
143
-			$this->emit('\OC\Updater', 'maintenanceActive');
144
-		}
145
-
146
-		$this->emit('\OC\Updater', 'resetLogLevel', [ $logLevel, $this->logLevelNames[$logLevel] ]);
147
-		$this->config->setSystemValue('loglevel', $logLevel);
148
-		$this->config->setSystemValue('installed', true);
149
-
150
-		return $success;
151
-	}
152
-
153
-	/**
154
-	 * Return version from which this version is allowed to upgrade from
155
-	 *
156
-	 * @return array allowed previous versions per vendor
157
-	 */
158
-	private function getAllowedPreviousVersions() {
159
-		// this should really be a JSON file
160
-		require \OC::$SERVERROOT . '/version.php';
161
-		/** @var array $OC_VersionCanBeUpgradedFrom */
162
-		return $OC_VersionCanBeUpgradedFrom;
163
-	}
164
-
165
-	/**
166
-	 * Return vendor from which this version was published
167
-	 *
168
-	 * @return string Get the vendor
169
-	 */
170
-	private function getVendor() {
171
-		// this should really be a JSON file
172
-		require \OC::$SERVERROOT . '/version.php';
173
-		/** @var string $vendor */
174
-		return (string) $vendor;
175
-	}
176
-
177
-	/**
178
-	 * Whether an upgrade to a specified version is possible
179
-	 * @param string $oldVersion
180
-	 * @param string $newVersion
181
-	 * @param array $allowedPreviousVersions
182
-	 * @return bool
183
-	 */
184
-	public function isUpgradePossible($oldVersion, $newVersion, array $allowedPreviousVersions) {
185
-		$version = explode('.', $oldVersion);
186
-		$majorMinor = $version[0] . '.' . $version[1];
187
-
188
-		$currentVendor = $this->config->getAppValue('core', 'vendor', '');
189
-
190
-		// Vendor was not set correctly on install, so we have to white-list known versions
191
-		if ($currentVendor === '' && isset($allowedPreviousVersions['owncloud'][$oldVersion])) {
192
-			$currentVendor = 'owncloud';
193
-		}
194
-
195
-		if ($currentVendor === 'nextcloud') {
196
-			return isset($allowedPreviousVersions[$currentVendor][$majorMinor])
197
-				&& (version_compare($oldVersion, $newVersion, '<=') ||
198
-					$this->config->getSystemValue('debug', false));
199
-		}
200
-
201
-		// Check if the instance can be migrated
202
-		return isset($allowedPreviousVersions[$currentVendor][$majorMinor]) ||
203
-			isset($allowedPreviousVersions[$currentVendor][$oldVersion]);
204
-	}
205
-
206
-	/**
207
-	 * runs the update actions in maintenance mode, does not upgrade the source files
208
-	 * except the main .htaccess file
209
-	 *
210
-	 * @param string $currentVersion current version to upgrade to
211
-	 * @param string $installedVersion previous version from which to upgrade from
212
-	 *
213
-	 * @throws \Exception
214
-	 */
215
-	private function doUpgrade($currentVersion, $installedVersion) {
216
-		// Stop update if the update is over several major versions
217
-		$allowedPreviousVersions = $this->getAllowedPreviousVersions();
218
-		if (!$this->isUpgradePossible($installedVersion, $currentVersion, $allowedPreviousVersions)) {
219
-			throw new \Exception('Updates between multiple major versions and downgrades are unsupported.');
220
-		}
221
-
222
-		// Update .htaccess files
223
-		try {
224
-			Setup::updateHtaccess();
225
-			Setup::protectDataDirectory();
226
-		} catch (\Exception $e) {
227
-			throw new \Exception($e->getMessage());
228
-		}
229
-
230
-		// create empty file in data dir, so we can later find
231
-		// out that this is indeed an ownCloud data directory
232
-		// (in case it didn't exist before)
233
-		file_put_contents($this->config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . '/.ocdata', '');
234
-
235
-		// pre-upgrade repairs
236
-		$repair = new Repair(Repair::getBeforeUpgradeRepairSteps(), \OC::$server->getEventDispatcher());
237
-		$repair->run();
238
-
239
-		$this->doCoreUpgrade();
240
-
241
-		try {
242
-			// TODO: replace with the new repair step mechanism https://github.com/owncloud/core/pull/24378
243
-			Setup::installBackgroundJobs();
244
-		} catch (\Exception $e) {
245
-			throw new \Exception($e->getMessage());
246
-		}
247
-
248
-		// update all shipped apps
249
-		$this->checkAppsRequirements();
250
-		$this->doAppUpgrade();
251
-
252
-		// Update the appfetchers version so it downloads the correct list from the appstore
253
-		\OC::$server->getAppFetcher()->setVersion($currentVersion);
254
-
255
-		// upgrade appstore apps
256
-		$this->upgradeAppStoreApps(\OC::$server->getAppManager()->getInstalledApps());
257
-
258
-		// install new shipped apps on upgrade
259
-		OC_App::loadApps(['authentication']);
260
-		$errors = Installer::installShippedApps(true);
261
-		foreach ($errors as $appId => $exception) {
262
-			/** @var \Exception $exception */
263
-			$this->log->logException($exception, ['app' => $appId]);
264
-			$this->emit('\OC\Updater', 'failure', [$appId . ': ' . $exception->getMessage()]);
265
-		}
266
-
267
-		// post-upgrade repairs
268
-		$repair = new Repair(Repair::getRepairSteps(), \OC::$server->getEventDispatcher());
269
-		$repair->run();
270
-
271
-		//Invalidate update feed
272
-		$this->config->setAppValue('core', 'lastupdatedat', 0);
273
-
274
-		// Check for code integrity if not disabled
275
-		if(\OC::$server->getIntegrityCodeChecker()->isCodeCheckEnforced()) {
276
-			$this->emit('\OC\Updater', 'startCheckCodeIntegrity');
277
-			$this->checker->runInstanceVerification();
278
-			$this->emit('\OC\Updater', 'finishedCheckCodeIntegrity');
279
-		}
280
-
281
-		// only set the final version if everything went well
282
-		$this->config->setSystemValue('version', implode('.', Util::getVersion()));
283
-		$this->config->setAppValue('core', 'vendor', $this->getVendor());
284
-	}
285
-
286
-	protected function doCoreUpgrade() {
287
-		$this->emit('\OC\Updater', 'dbUpgradeBefore');
288
-
289
-		// execute core migrations
290
-		$ms = new MigrationService('core', \OC::$server->getDatabaseConnection());
291
-		$ms->migrate();
292
-
293
-		$this->emit('\OC\Updater', 'dbUpgrade');
294
-	}
295
-
296
-	/**
297
-	 * @param string $version the oc version to check app compatibility with
298
-	 */
299
-	protected function checkAppUpgrade($version) {
300
-		$apps = \OC_App::getEnabledApps();
301
-		$this->emit('\OC\Updater', 'appUpgradeCheckBefore');
302
-
303
-		$appManager = \OC::$server->getAppManager();
304
-		foreach ($apps as $appId) {
305
-			$info = \OC_App::getAppInfo($appId);
306
-			$compatible = \OC_App::isAppCompatible($version, $info);
307
-			$isShipped = $appManager->isShipped($appId);
308
-
309
-			if ($compatible && $isShipped && \OC_App::shouldUpgrade($appId)) {
310
-				/**
311
-				 * FIXME: The preupdate check is performed before the database migration, otherwise database changes
312
-				 * are not possible anymore within it. - Consider this when touching the code.
313
-				 * @link https://github.com/owncloud/core/issues/10980
314
-				 * @see \OC_App::updateApp
315
-				 */
316
-				if (file_exists(\OC_App::getAppPath($appId) . '/appinfo/preupdate.php')) {
317
-					$this->includePreUpdate($appId);
318
-				}
319
-				if (file_exists(\OC_App::getAppPath($appId) . '/appinfo/database.xml')) {
320
-					$this->emit('\OC\Updater', 'appSimulateUpdate', array($appId));
321
-					\OC_DB::simulateUpdateDbFromStructure(\OC_App::getAppPath($appId) . '/appinfo/database.xml');
322
-				}
323
-			}
324
-		}
325
-
326
-		$this->emit('\OC\Updater', 'appUpgradeCheck');
327
-	}
328
-
329
-	/**
330
-	 * Includes the pre-update file. Done here to prevent namespace mixups.
331
-	 * @param string $appId
332
-	 */
333
-	private function includePreUpdate($appId) {
334
-		include \OC_App::getAppPath($appId) . '/appinfo/preupdate.php';
335
-	}
336
-
337
-	/**
338
-	 * upgrades all apps within a major ownCloud upgrade. Also loads "priority"
339
-	 * (types authentication, filesystem, logging, in that order) afterwards.
340
-	 *
341
-	 * @throws NeedsUpdateException
342
-	 */
343
-	protected function doAppUpgrade() {
344
-		$apps = \OC_App::getEnabledApps();
345
-		$priorityTypes = array('authentication', 'filesystem', 'logging');
346
-		$pseudoOtherType = 'other';
347
-		$stacks = array($pseudoOtherType => array());
348
-
349
-		foreach ($apps as $appId) {
350
-			$priorityType = false;
351
-			foreach ($priorityTypes as $type) {
352
-				if(!isset($stacks[$type])) {
353
-					$stacks[$type] = array();
354
-				}
355
-				if (\OC_App::isType($appId, [$type])) {
356
-					$stacks[$type][] = $appId;
357
-					$priorityType = true;
358
-					break;
359
-				}
360
-			}
361
-			if (!$priorityType) {
362
-				$stacks[$pseudoOtherType][] = $appId;
363
-			}
364
-		}
365
-		foreach ($stacks as $type => $stack) {
366
-			foreach ($stack as $appId) {
367
-				if (\OC_App::shouldUpgrade($appId)) {
368
-					$this->emit('\OC\Updater', 'appUpgradeStarted', [$appId, \OC_App::getAppVersion($appId)]);
369
-					\OC_App::updateApp($appId);
370
-					$this->emit('\OC\Updater', 'appUpgrade', [$appId, \OC_App::getAppVersion($appId)]);
371
-				}
372
-				if($type !== $pseudoOtherType) {
373
-					// load authentication, filesystem and logging apps after
374
-					// upgrading them. Other apps my need to rely on modifying
375
-					// user and/or filesystem aspects.
376
-					\OC_App::loadApp($appId);
377
-				}
378
-			}
379
-		}
380
-	}
381
-
382
-	/**
383
-	 * check if the current enabled apps are compatible with the current
384
-	 * ownCloud version. disable them if not.
385
-	 * This is important if you upgrade ownCloud and have non ported 3rd
386
-	 * party apps installed.
387
-	 *
388
-	 * @return array
389
-	 * @throws \Exception
390
-	 */
391
-	private function checkAppsRequirements() {
392
-		$isCoreUpgrade = $this->isCodeUpgrade();
393
-		$apps = OC_App::getEnabledApps();
394
-		$version = implode('.', Util::getVersion());
395
-		$disabledApps = [];
396
-		$appManager = \OC::$server->getAppManager();
397
-		foreach ($apps as $app) {
398
-			// check if the app is compatible with this version of ownCloud
399
-			$info = OC_App::getAppInfo($app);
400
-			if($info === null || !OC_App::isAppCompatible($version, $info)) {
401
-				if ($appManager->isShipped($app)) {
402
-					throw new \UnexpectedValueException('The files of the app "' . $app . '" were not correctly replaced before running the update');
403
-				}
404
-				\OC::$server->getAppManager()->disableApp($app);
405
-				$this->emit('\OC\Updater', 'incompatibleAppDisabled', array($app));
406
-			}
407
-			// no need to disable any app in case this is a non-core upgrade
408
-			if (!$isCoreUpgrade) {
409
-				continue;
410
-			}
411
-			// shipped apps will remain enabled
412
-			if ($appManager->isShipped($app)) {
413
-				continue;
414
-			}
415
-			// authentication and session apps will remain enabled as well
416
-			if (OC_App::isType($app, ['session', 'authentication'])) {
417
-				continue;
418
-			}
419
-		}
420
-		return $disabledApps;
421
-	}
422
-
423
-	/**
424
-	 * @return bool
425
-	 */
426
-	private function isCodeUpgrade() {
427
-		$installedVersion = $this->config->getSystemValue('version', '0.0.0');
428
-		$currentVersion = implode('.', Util::getVersion());
429
-		if (version_compare($currentVersion, $installedVersion, '>')) {
430
-			return true;
431
-		}
432
-		return false;
433
-	}
434
-
435
-	/**
436
-	 * @param array $disabledApps
437
-	 * @throws \Exception
438
-	 */
439
-	private function upgradeAppStoreApps(array $disabledApps) {
440
-		foreach($disabledApps as $app) {
441
-			try {
442
-				$this->emit('\OC\Updater', 'checkAppStoreAppBefore', [$app]);
443
-				if ($this->installer->isUpdateAvailable($app)) {
444
-					$this->emit('\OC\Updater', 'upgradeAppStoreApp', [$app]);
445
-					$this->installer->updateAppstoreApp($app);
446
-				}
447
-				$this->emit('\OC\Updater', 'checkAppStoreApp', [$app]);
448
-			} catch (\Exception $ex) {
449
-				$this->log->logException($ex, ['app' => 'core']);
450
-			}
451
-		}
452
-	}
453
-
454
-	/**
455
-	 * Forward messages emitted by the repair routine
456
-	 */
457
-	private function emitRepairEvents() {
458
-		$dispatcher = \OC::$server->getEventDispatcher();
459
-		$dispatcher->addListener('\OC\Repair::warning', function ($event) {
460
-			if ($event instanceof GenericEvent) {
461
-				$this->emit('\OC\Updater', 'repairWarning', $event->getArguments());
462
-			}
463
-		});
464
-		$dispatcher->addListener('\OC\Repair::error', function ($event) {
465
-			if ($event instanceof GenericEvent) {
466
-				$this->emit('\OC\Updater', 'repairError', $event->getArguments());
467
-			}
468
-		});
469
-		$dispatcher->addListener('\OC\Repair::info', function ($event) {
470
-			if ($event instanceof GenericEvent) {
471
-				$this->emit('\OC\Updater', 'repairInfo', $event->getArguments());
472
-			}
473
-		});
474
-		$dispatcher->addListener('\OC\Repair::step', function ($event) {
475
-			if ($event instanceof GenericEvent) {
476
-				$this->emit('\OC\Updater', 'repairStep', $event->getArguments());
477
-			}
478
-		});
479
-	}
480
-
481
-	private function logAllEvents() {
482
-		$log = $this->log;
483
-
484
-		$dispatcher = \OC::$server->getEventDispatcher();
485
-		$dispatcher->addListener('\OC\DB\Migrator::executeSql', function($event) use ($log) {
486
-			if (!$event instanceof GenericEvent) {
487
-				return;
488
-			}
489
-			$log->info('\OC\DB\Migrator::executeSql: ' . $event->getSubject() . ' (' . $event->getArgument(0) . ' of ' . $event->getArgument(1) . ')', ['app' => 'updater']);
490
-		});
491
-		$dispatcher->addListener('\OC\DB\Migrator::checkTable', function($event) use ($log) {
492
-			if (!$event instanceof GenericEvent) {
493
-				return;
494
-			}
495
-			$log->info('\OC\DB\Migrator::checkTable: ' . $event->getSubject() . ' (' . $event->getArgument(0) . ' of ' . $event->getArgument(1) . ')', ['app' => 'updater']);
496
-		});
497
-
498
-		$repairListener = function($event) use ($log) {
499
-			if (!$event instanceof GenericEvent) {
500
-				return;
501
-			}
502
-			switch ($event->getSubject()) {
503
-				case '\OC\Repair::startProgress':
504
-					$log->info('\OC\Repair::startProgress: Starting ... ' . $event->getArgument(1) .  ' (' . $event->getArgument(0) . ')', ['app' => 'updater']);
505
-					break;
506
-				case '\OC\Repair::advance':
507
-					$desc = $event->getArgument(1);
508
-					if (empty($desc)) {
509
-						$desc = '';
510
-					}
511
-					$log->info('\OC\Repair::advance: ' . $desc . ' (' . $event->getArgument(0) . ')', ['app' => 'updater']);
512
-
513
-					break;
514
-				case '\OC\Repair::finishProgress':
515
-					$log->info('\OC\Repair::finishProgress', ['app' => 'updater']);
516
-					break;
517
-				case '\OC\Repair::step':
518
-					$log->info('\OC\Repair::step: Repair step: ' . $event->getArgument(0), ['app' => 'updater']);
519
-					break;
520
-				case '\OC\Repair::info':
521
-					$log->info('\OC\Repair::info: Repair info: ' . $event->getArgument(0), ['app' => 'updater']);
522
-					break;
523
-				case '\OC\Repair::warning':
524
-					$log->warning('\OC\Repair::warning: Repair warning: ' . $event->getArgument(0), ['app' => 'updater']);
525
-					break;
526
-				case '\OC\Repair::error':
527
-					$log->error('\OC\Repair::error: Repair error: ' . $event->getArgument(0), ['app' => 'updater']);
528
-					break;
529
-			}
530
-		};
531
-
532
-		$dispatcher->addListener('\OC\Repair::startProgress', $repairListener);
533
-		$dispatcher->addListener('\OC\Repair::advance', $repairListener);
534
-		$dispatcher->addListener('\OC\Repair::finishProgress', $repairListener);
535
-		$dispatcher->addListener('\OC\Repair::step', $repairListener);
536
-		$dispatcher->addListener('\OC\Repair::info', $repairListener);
537
-		$dispatcher->addListener('\OC\Repair::warning', $repairListener);
538
-		$dispatcher->addListener('\OC\Repair::error', $repairListener);
539
-
540
-
541
-		$this->listen('\OC\Updater', 'maintenanceEnabled', function () use($log) {
542
-			$log->info('\OC\Updater::maintenanceEnabled: Turned on maintenance mode', ['app' => 'updater']);
543
-		});
544
-		$this->listen('\OC\Updater', 'maintenanceDisabled', function () use($log) {
545
-			$log->info('\OC\Updater::maintenanceDisabled: Turned off maintenance mode', ['app' => 'updater']);
546
-		});
547
-		$this->listen('\OC\Updater', 'maintenanceActive', function () use($log) {
548
-			$log->info('\OC\Updater::maintenanceActive: Maintenance mode is kept active', ['app' => 'updater']);
549
-		});
550
-		$this->listen('\OC\Updater', 'updateEnd', function ($success) use($log) {
551
-			if ($success) {
552
-				$log->info('\OC\Updater::updateEnd: Update successful', ['app' => 'updater']);
553
-			} else {
554
-				$log->error('\OC\Updater::updateEnd: Update failed', ['app' => 'updater']);
555
-			}
556
-		});
557
-		$this->listen('\OC\Updater', 'dbUpgradeBefore', function () use($log) {
558
-			$log->info('\OC\Updater::dbUpgradeBefore: Updating database schema', ['app' => 'updater']);
559
-		});
560
-		$this->listen('\OC\Updater', 'dbUpgrade', function () use($log) {
561
-			$log->info('\OC\Updater::dbUpgrade: Updated database', ['app' => 'updater']);
562
-		});
563
-		$this->listen('\OC\Updater', 'dbSimulateUpgradeBefore', function () use($log) {
564
-			$log->info('\OC\Updater::dbSimulateUpgradeBefore: Checking whether the database schema can be updated (this can take a long time depending on the database size)', ['app' => 'updater']);
565
-		});
566
-		$this->listen('\OC\Updater', 'dbSimulateUpgrade', function () use($log) {
567
-			$log->info('\OC\Updater::dbSimulateUpgrade: Checked database schema update', ['app' => 'updater']);
568
-		});
569
-		$this->listen('\OC\Updater', 'incompatibleAppDisabled', function ($app) use($log) {
570
-			$log->info('\OC\Updater::incompatibleAppDisabled: Disabled incompatible app: ' . $app, ['app' => 'updater']);
571
-		});
572
-		$this->listen('\OC\Updater', 'checkAppStoreAppBefore', function ($app) use($log) {
573
-			$log->info('\OC\Updater::checkAppStoreAppBefore: Checking for update of app "' . $app . '" in appstore', ['app' => 'updater']);
574
-		});
575
-		$this->listen('\OC\Updater', 'upgradeAppStoreApp', function ($app) use($log) {
576
-			$log->info('\OC\Updater::upgradeAppStoreApp: Update app "' . $app . '" from appstore', ['app' => 'updater']);
577
-		});
578
-		$this->listen('\OC\Updater', 'checkAppStoreApp', function ($app) use($log) {
579
-			$log->info('\OC\Updater::checkAppStoreApp: Checked for update of app "' . $app . '" in appstore', ['app' => 'updater']);
580
-		});
581
-		$this->listen('\OC\Updater', 'appUpgradeCheckBefore', function () use ($log) {
582
-			$log->info('\OC\Updater::appUpgradeCheckBefore: Checking updates of apps', ['app' => 'updater']);
583
-		});
584
-		$this->listen('\OC\Updater', 'appSimulateUpdate', function ($app) use ($log) {
585
-			$log->info('\OC\Updater::appSimulateUpdate: Checking whether the database schema for <' . $app . '> can be updated (this can take a long time depending on the database size)', ['app' => 'updater']);
586
-		});
587
-		$this->listen('\OC\Updater', 'appUpgradeCheck', function () use ($log) {
588
-			$log->info('\OC\Updater::appUpgradeCheck: Checked database schema update for apps', ['app' => 'updater']);
589
-		});
590
-		$this->listen('\OC\Updater', 'appUpgradeStarted', function ($app) use ($log) {
591
-			$log->info('\OC\Updater::appUpgradeStarted: Updating <' . $app . '> ...', ['app' => 'updater']);
592
-		});
593
-		$this->listen('\OC\Updater', 'appUpgrade', function ($app, $version) use ($log) {
594
-			$log->info('\OC\Updater::appUpgrade: Updated <' . $app . '> to ' . $version, ['app' => 'updater']);
595
-		});
596
-		$this->listen('\OC\Updater', 'failure', function ($message) use($log) {
597
-			$log->error('\OC\Updater::failure: ' . $message, ['app' => 'updater']);
598
-		});
599
-		$this->listen('\OC\Updater', 'setDebugLogLevel', function () use($log) {
600
-			$log->info('\OC\Updater::setDebugLogLevel: Set log level to debug', ['app' => 'updater']);
601
-		});
602
-		$this->listen('\OC\Updater', 'resetLogLevel', function ($logLevel, $logLevelName) use($log) {
603
-			$log->info('\OC\Updater::resetLogLevel: Reset log level to ' . $logLevelName . '(' . $logLevel . ')', ['app' => 'updater']);
604
-		});
605
-		$this->listen('\OC\Updater', 'startCheckCodeIntegrity', function () use($log) {
606
-			$log->info('\OC\Updater::startCheckCodeIntegrity: Starting code integrity check...', ['app' => 'updater']);
607
-		});
608
-		$this->listen('\OC\Updater', 'finishedCheckCodeIntegrity', function () use($log) {
609
-			$log->info('\OC\Updater::finishedCheckCodeIntegrity: Finished code integrity check', ['app' => 'updater']);
610
-		});
611
-
612
-	}
613
-	private function waitForCronToFinish() {
614
-		while ($this->jobList->isAnyJobRunning()) {
615
-			$this->emit('\OC\Updater', 'waitForCronToFinish');
616
-			sleep(5);
617
-		}
618
-	}
58
+    /** @var ILogger $log */
59
+    private $log;
60
+
61
+    /** @var IConfig */
62
+    private $config;
63
+
64
+    /** @var Checker */
65
+    private $checker;
66
+
67
+    /** @var Installer */
68
+    private $installer;
69
+
70
+    /** @var IJobList */
71
+    private $jobList;
72
+
73
+    private $logLevelNames = [
74
+        0 => 'Debug',
75
+        1 => 'Info',
76
+        2 => 'Warning',
77
+        3 => 'Error',
78
+        4 => 'Fatal',
79
+    ];
80
+
81
+    public function __construct(IConfig $config,
82
+                                Checker $checker,
83
+                                ILogger $log,
84
+                                Installer $installer,
85
+                                IJobList $jobList) {
86
+        $this->log = $log;
87
+        $this->config = $config;
88
+        $this->checker = $checker;
89
+        $this->installer = $installer;
90
+        $this->jobList = $jobList;
91
+    }
92
+
93
+    /**
94
+     * runs the update actions in maintenance mode, does not upgrade the source files
95
+     * except the main .htaccess file
96
+     *
97
+     * @return bool true if the operation succeeded, false otherwise
98
+     */
99
+    public function upgrade() {
100
+        $this->emitRepairEvents();
101
+        $this->logAllEvents();
102
+
103
+        $logLevel = $this->config->getSystemValue('loglevel', ILogger::WARN);
104
+        $this->emit('\OC\Updater', 'setDebugLogLevel', [ $logLevel, $this->logLevelNames[$logLevel] ]);
105
+        $this->config->setSystemValue('loglevel', ILogger::DEBUG);
106
+
107
+        $wasMaintenanceModeEnabled = $this->config->getSystemValue('maintenance', false);
108
+
109
+        if(!$wasMaintenanceModeEnabled) {
110
+            $this->config->setSystemValue('maintenance', true);
111
+            $this->emit('\OC\Updater', 'maintenanceEnabled');
112
+        }
113
+
114
+        $installedVersion = $this->config->getSystemValue('version', '0.0.0');
115
+        $currentVersion = implode('.', \OCP\Util::getVersion());
116
+
117
+        // see https://github.com/nextcloud/server/issues/9992 for potential problem
118
+        if (version_compare($installedVersion, '14.0.0.9', '>=')) {
119
+            $this->waitForCronToFinish();
120
+        }
121
+
122
+        $this->log->debug('starting upgrade from ' . $installedVersion . ' to ' . $currentVersion, array('app' => 'core'));
123
+
124
+        $success = true;
125
+        try {
126
+            $this->doUpgrade($currentVersion, $installedVersion);
127
+        } catch (HintException $exception) {
128
+            $this->log->logException($exception, ['app' => 'core']);
129
+            $this->emit('\OC\Updater', 'failure', array($exception->getMessage() . ': ' .$exception->getHint()));
130
+            $success = false;
131
+        } catch (\Exception $exception) {
132
+            $this->log->logException($exception, ['app' => 'core']);
133
+            $this->emit('\OC\Updater', 'failure', array(get_class($exception) . ': ' .$exception->getMessage()));
134
+            $success = false;
135
+        }
136
+
137
+        $this->emit('\OC\Updater', 'updateEnd', array($success));
138
+
139
+        if(!$wasMaintenanceModeEnabled && $success) {
140
+            $this->config->setSystemValue('maintenance', false);
141
+            $this->emit('\OC\Updater', 'maintenanceDisabled');
142
+        } else {
143
+            $this->emit('\OC\Updater', 'maintenanceActive');
144
+        }
145
+
146
+        $this->emit('\OC\Updater', 'resetLogLevel', [ $logLevel, $this->logLevelNames[$logLevel] ]);
147
+        $this->config->setSystemValue('loglevel', $logLevel);
148
+        $this->config->setSystemValue('installed', true);
149
+
150
+        return $success;
151
+    }
152
+
153
+    /**
154
+     * Return version from which this version is allowed to upgrade from
155
+     *
156
+     * @return array allowed previous versions per vendor
157
+     */
158
+    private function getAllowedPreviousVersions() {
159
+        // this should really be a JSON file
160
+        require \OC::$SERVERROOT . '/version.php';
161
+        /** @var array $OC_VersionCanBeUpgradedFrom */
162
+        return $OC_VersionCanBeUpgradedFrom;
163
+    }
164
+
165
+    /**
166
+     * Return vendor from which this version was published
167
+     *
168
+     * @return string Get the vendor
169
+     */
170
+    private function getVendor() {
171
+        // this should really be a JSON file
172
+        require \OC::$SERVERROOT . '/version.php';
173
+        /** @var string $vendor */
174
+        return (string) $vendor;
175
+    }
176
+
177
+    /**
178
+     * Whether an upgrade to a specified version is possible
179
+     * @param string $oldVersion
180
+     * @param string $newVersion
181
+     * @param array $allowedPreviousVersions
182
+     * @return bool
183
+     */
184
+    public function isUpgradePossible($oldVersion, $newVersion, array $allowedPreviousVersions) {
185
+        $version = explode('.', $oldVersion);
186
+        $majorMinor = $version[0] . '.' . $version[1];
187
+
188
+        $currentVendor = $this->config->getAppValue('core', 'vendor', '');
189
+
190
+        // Vendor was not set correctly on install, so we have to white-list known versions
191
+        if ($currentVendor === '' && isset($allowedPreviousVersions['owncloud'][$oldVersion])) {
192
+            $currentVendor = 'owncloud';
193
+        }
194
+
195
+        if ($currentVendor === 'nextcloud') {
196
+            return isset($allowedPreviousVersions[$currentVendor][$majorMinor])
197
+                && (version_compare($oldVersion, $newVersion, '<=') ||
198
+                    $this->config->getSystemValue('debug', false));
199
+        }
200
+
201
+        // Check if the instance can be migrated
202
+        return isset($allowedPreviousVersions[$currentVendor][$majorMinor]) ||
203
+            isset($allowedPreviousVersions[$currentVendor][$oldVersion]);
204
+    }
205
+
206
+    /**
207
+     * runs the update actions in maintenance mode, does not upgrade the source files
208
+     * except the main .htaccess file
209
+     *
210
+     * @param string $currentVersion current version to upgrade to
211
+     * @param string $installedVersion previous version from which to upgrade from
212
+     *
213
+     * @throws \Exception
214
+     */
215
+    private function doUpgrade($currentVersion, $installedVersion) {
216
+        // Stop update if the update is over several major versions
217
+        $allowedPreviousVersions = $this->getAllowedPreviousVersions();
218
+        if (!$this->isUpgradePossible($installedVersion, $currentVersion, $allowedPreviousVersions)) {
219
+            throw new \Exception('Updates between multiple major versions and downgrades are unsupported.');
220
+        }
221
+
222
+        // Update .htaccess files
223
+        try {
224
+            Setup::updateHtaccess();
225
+            Setup::protectDataDirectory();
226
+        } catch (\Exception $e) {
227
+            throw new \Exception($e->getMessage());
228
+        }
229
+
230
+        // create empty file in data dir, so we can later find
231
+        // out that this is indeed an ownCloud data directory
232
+        // (in case it didn't exist before)
233
+        file_put_contents($this->config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . '/.ocdata', '');
234
+
235
+        // pre-upgrade repairs
236
+        $repair = new Repair(Repair::getBeforeUpgradeRepairSteps(), \OC::$server->getEventDispatcher());
237
+        $repair->run();
238
+
239
+        $this->doCoreUpgrade();
240
+
241
+        try {
242
+            // TODO: replace with the new repair step mechanism https://github.com/owncloud/core/pull/24378
243
+            Setup::installBackgroundJobs();
244
+        } catch (\Exception $e) {
245
+            throw new \Exception($e->getMessage());
246
+        }
247
+
248
+        // update all shipped apps
249
+        $this->checkAppsRequirements();
250
+        $this->doAppUpgrade();
251
+
252
+        // Update the appfetchers version so it downloads the correct list from the appstore
253
+        \OC::$server->getAppFetcher()->setVersion($currentVersion);
254
+
255
+        // upgrade appstore apps
256
+        $this->upgradeAppStoreApps(\OC::$server->getAppManager()->getInstalledApps());
257
+
258
+        // install new shipped apps on upgrade
259
+        OC_App::loadApps(['authentication']);
260
+        $errors = Installer::installShippedApps(true);
261
+        foreach ($errors as $appId => $exception) {
262
+            /** @var \Exception $exception */
263
+            $this->log->logException($exception, ['app' => $appId]);
264
+            $this->emit('\OC\Updater', 'failure', [$appId . ': ' . $exception->getMessage()]);
265
+        }
266
+
267
+        // post-upgrade repairs
268
+        $repair = new Repair(Repair::getRepairSteps(), \OC::$server->getEventDispatcher());
269
+        $repair->run();
270
+
271
+        //Invalidate update feed
272
+        $this->config->setAppValue('core', 'lastupdatedat', 0);
273
+
274
+        // Check for code integrity if not disabled
275
+        if(\OC::$server->getIntegrityCodeChecker()->isCodeCheckEnforced()) {
276
+            $this->emit('\OC\Updater', 'startCheckCodeIntegrity');
277
+            $this->checker->runInstanceVerification();
278
+            $this->emit('\OC\Updater', 'finishedCheckCodeIntegrity');
279
+        }
280
+
281
+        // only set the final version if everything went well
282
+        $this->config->setSystemValue('version', implode('.', Util::getVersion()));
283
+        $this->config->setAppValue('core', 'vendor', $this->getVendor());
284
+    }
285
+
286
+    protected function doCoreUpgrade() {
287
+        $this->emit('\OC\Updater', 'dbUpgradeBefore');
288
+
289
+        // execute core migrations
290
+        $ms = new MigrationService('core', \OC::$server->getDatabaseConnection());
291
+        $ms->migrate();
292
+
293
+        $this->emit('\OC\Updater', 'dbUpgrade');
294
+    }
295
+
296
+    /**
297
+     * @param string $version the oc version to check app compatibility with
298
+     */
299
+    protected function checkAppUpgrade($version) {
300
+        $apps = \OC_App::getEnabledApps();
301
+        $this->emit('\OC\Updater', 'appUpgradeCheckBefore');
302
+
303
+        $appManager = \OC::$server->getAppManager();
304
+        foreach ($apps as $appId) {
305
+            $info = \OC_App::getAppInfo($appId);
306
+            $compatible = \OC_App::isAppCompatible($version, $info);
307
+            $isShipped = $appManager->isShipped($appId);
308
+
309
+            if ($compatible && $isShipped && \OC_App::shouldUpgrade($appId)) {
310
+                /**
311
+                 * FIXME: The preupdate check is performed before the database migration, otherwise database changes
312
+                 * are not possible anymore within it. - Consider this when touching the code.
313
+                 * @link https://github.com/owncloud/core/issues/10980
314
+                 * @see \OC_App::updateApp
315
+                 */
316
+                if (file_exists(\OC_App::getAppPath($appId) . '/appinfo/preupdate.php')) {
317
+                    $this->includePreUpdate($appId);
318
+                }
319
+                if (file_exists(\OC_App::getAppPath($appId) . '/appinfo/database.xml')) {
320
+                    $this->emit('\OC\Updater', 'appSimulateUpdate', array($appId));
321
+                    \OC_DB::simulateUpdateDbFromStructure(\OC_App::getAppPath($appId) . '/appinfo/database.xml');
322
+                }
323
+            }
324
+        }
325
+
326
+        $this->emit('\OC\Updater', 'appUpgradeCheck');
327
+    }
328
+
329
+    /**
330
+     * Includes the pre-update file. Done here to prevent namespace mixups.
331
+     * @param string $appId
332
+     */
333
+    private function includePreUpdate($appId) {
334
+        include \OC_App::getAppPath($appId) . '/appinfo/preupdate.php';
335
+    }
336
+
337
+    /**
338
+     * upgrades all apps within a major ownCloud upgrade. Also loads "priority"
339
+     * (types authentication, filesystem, logging, in that order) afterwards.
340
+     *
341
+     * @throws NeedsUpdateException
342
+     */
343
+    protected function doAppUpgrade() {
344
+        $apps = \OC_App::getEnabledApps();
345
+        $priorityTypes = array('authentication', 'filesystem', 'logging');
346
+        $pseudoOtherType = 'other';
347
+        $stacks = array($pseudoOtherType => array());
348
+
349
+        foreach ($apps as $appId) {
350
+            $priorityType = false;
351
+            foreach ($priorityTypes as $type) {
352
+                if(!isset($stacks[$type])) {
353
+                    $stacks[$type] = array();
354
+                }
355
+                if (\OC_App::isType($appId, [$type])) {
356
+                    $stacks[$type][] = $appId;
357
+                    $priorityType = true;
358
+                    break;
359
+                }
360
+            }
361
+            if (!$priorityType) {
362
+                $stacks[$pseudoOtherType][] = $appId;
363
+            }
364
+        }
365
+        foreach ($stacks as $type => $stack) {
366
+            foreach ($stack as $appId) {
367
+                if (\OC_App::shouldUpgrade($appId)) {
368
+                    $this->emit('\OC\Updater', 'appUpgradeStarted', [$appId, \OC_App::getAppVersion($appId)]);
369
+                    \OC_App::updateApp($appId);
370
+                    $this->emit('\OC\Updater', 'appUpgrade', [$appId, \OC_App::getAppVersion($appId)]);
371
+                }
372
+                if($type !== $pseudoOtherType) {
373
+                    // load authentication, filesystem and logging apps after
374
+                    // upgrading them. Other apps my need to rely on modifying
375
+                    // user and/or filesystem aspects.
376
+                    \OC_App::loadApp($appId);
377
+                }
378
+            }
379
+        }
380
+    }
381
+
382
+    /**
383
+     * check if the current enabled apps are compatible with the current
384
+     * ownCloud version. disable them if not.
385
+     * This is important if you upgrade ownCloud and have non ported 3rd
386
+     * party apps installed.
387
+     *
388
+     * @return array
389
+     * @throws \Exception
390
+     */
391
+    private function checkAppsRequirements() {
392
+        $isCoreUpgrade = $this->isCodeUpgrade();
393
+        $apps = OC_App::getEnabledApps();
394
+        $version = implode('.', Util::getVersion());
395
+        $disabledApps = [];
396
+        $appManager = \OC::$server->getAppManager();
397
+        foreach ($apps as $app) {
398
+            // check if the app is compatible with this version of ownCloud
399
+            $info = OC_App::getAppInfo($app);
400
+            if($info === null || !OC_App::isAppCompatible($version, $info)) {
401
+                if ($appManager->isShipped($app)) {
402
+                    throw new \UnexpectedValueException('The files of the app "' . $app . '" were not correctly replaced before running the update');
403
+                }
404
+                \OC::$server->getAppManager()->disableApp($app);
405
+                $this->emit('\OC\Updater', 'incompatibleAppDisabled', array($app));
406
+            }
407
+            // no need to disable any app in case this is a non-core upgrade
408
+            if (!$isCoreUpgrade) {
409
+                continue;
410
+            }
411
+            // shipped apps will remain enabled
412
+            if ($appManager->isShipped($app)) {
413
+                continue;
414
+            }
415
+            // authentication and session apps will remain enabled as well
416
+            if (OC_App::isType($app, ['session', 'authentication'])) {
417
+                continue;
418
+            }
419
+        }
420
+        return $disabledApps;
421
+    }
422
+
423
+    /**
424
+     * @return bool
425
+     */
426
+    private function isCodeUpgrade() {
427
+        $installedVersion = $this->config->getSystemValue('version', '0.0.0');
428
+        $currentVersion = implode('.', Util::getVersion());
429
+        if (version_compare($currentVersion, $installedVersion, '>')) {
430
+            return true;
431
+        }
432
+        return false;
433
+    }
434
+
435
+    /**
436
+     * @param array $disabledApps
437
+     * @throws \Exception
438
+     */
439
+    private function upgradeAppStoreApps(array $disabledApps) {
440
+        foreach($disabledApps as $app) {
441
+            try {
442
+                $this->emit('\OC\Updater', 'checkAppStoreAppBefore', [$app]);
443
+                if ($this->installer->isUpdateAvailable($app)) {
444
+                    $this->emit('\OC\Updater', 'upgradeAppStoreApp', [$app]);
445
+                    $this->installer->updateAppstoreApp($app);
446
+                }
447
+                $this->emit('\OC\Updater', 'checkAppStoreApp', [$app]);
448
+            } catch (\Exception $ex) {
449
+                $this->log->logException($ex, ['app' => 'core']);
450
+            }
451
+        }
452
+    }
453
+
454
+    /**
455
+     * Forward messages emitted by the repair routine
456
+     */
457
+    private function emitRepairEvents() {
458
+        $dispatcher = \OC::$server->getEventDispatcher();
459
+        $dispatcher->addListener('\OC\Repair::warning', function ($event) {
460
+            if ($event instanceof GenericEvent) {
461
+                $this->emit('\OC\Updater', 'repairWarning', $event->getArguments());
462
+            }
463
+        });
464
+        $dispatcher->addListener('\OC\Repair::error', function ($event) {
465
+            if ($event instanceof GenericEvent) {
466
+                $this->emit('\OC\Updater', 'repairError', $event->getArguments());
467
+            }
468
+        });
469
+        $dispatcher->addListener('\OC\Repair::info', function ($event) {
470
+            if ($event instanceof GenericEvent) {
471
+                $this->emit('\OC\Updater', 'repairInfo', $event->getArguments());
472
+            }
473
+        });
474
+        $dispatcher->addListener('\OC\Repair::step', function ($event) {
475
+            if ($event instanceof GenericEvent) {
476
+                $this->emit('\OC\Updater', 'repairStep', $event->getArguments());
477
+            }
478
+        });
479
+    }
480
+
481
+    private function logAllEvents() {
482
+        $log = $this->log;
483
+
484
+        $dispatcher = \OC::$server->getEventDispatcher();
485
+        $dispatcher->addListener('\OC\DB\Migrator::executeSql', function($event) use ($log) {
486
+            if (!$event instanceof GenericEvent) {
487
+                return;
488
+            }
489
+            $log->info('\OC\DB\Migrator::executeSql: ' . $event->getSubject() . ' (' . $event->getArgument(0) . ' of ' . $event->getArgument(1) . ')', ['app' => 'updater']);
490
+        });
491
+        $dispatcher->addListener('\OC\DB\Migrator::checkTable', function($event) use ($log) {
492
+            if (!$event instanceof GenericEvent) {
493
+                return;
494
+            }
495
+            $log->info('\OC\DB\Migrator::checkTable: ' . $event->getSubject() . ' (' . $event->getArgument(0) . ' of ' . $event->getArgument(1) . ')', ['app' => 'updater']);
496
+        });
497
+
498
+        $repairListener = function($event) use ($log) {
499
+            if (!$event instanceof GenericEvent) {
500
+                return;
501
+            }
502
+            switch ($event->getSubject()) {
503
+                case '\OC\Repair::startProgress':
504
+                    $log->info('\OC\Repair::startProgress: Starting ... ' . $event->getArgument(1) .  ' (' . $event->getArgument(0) . ')', ['app' => 'updater']);
505
+                    break;
506
+                case '\OC\Repair::advance':
507
+                    $desc = $event->getArgument(1);
508
+                    if (empty($desc)) {
509
+                        $desc = '';
510
+                    }
511
+                    $log->info('\OC\Repair::advance: ' . $desc . ' (' . $event->getArgument(0) . ')', ['app' => 'updater']);
512
+
513
+                    break;
514
+                case '\OC\Repair::finishProgress':
515
+                    $log->info('\OC\Repair::finishProgress', ['app' => 'updater']);
516
+                    break;
517
+                case '\OC\Repair::step':
518
+                    $log->info('\OC\Repair::step: Repair step: ' . $event->getArgument(0), ['app' => 'updater']);
519
+                    break;
520
+                case '\OC\Repair::info':
521
+                    $log->info('\OC\Repair::info: Repair info: ' . $event->getArgument(0), ['app' => 'updater']);
522
+                    break;
523
+                case '\OC\Repair::warning':
524
+                    $log->warning('\OC\Repair::warning: Repair warning: ' . $event->getArgument(0), ['app' => 'updater']);
525
+                    break;
526
+                case '\OC\Repair::error':
527
+                    $log->error('\OC\Repair::error: Repair error: ' . $event->getArgument(0), ['app' => 'updater']);
528
+                    break;
529
+            }
530
+        };
531
+
532
+        $dispatcher->addListener('\OC\Repair::startProgress', $repairListener);
533
+        $dispatcher->addListener('\OC\Repair::advance', $repairListener);
534
+        $dispatcher->addListener('\OC\Repair::finishProgress', $repairListener);
535
+        $dispatcher->addListener('\OC\Repair::step', $repairListener);
536
+        $dispatcher->addListener('\OC\Repair::info', $repairListener);
537
+        $dispatcher->addListener('\OC\Repair::warning', $repairListener);
538
+        $dispatcher->addListener('\OC\Repair::error', $repairListener);
539
+
540
+
541
+        $this->listen('\OC\Updater', 'maintenanceEnabled', function () use($log) {
542
+            $log->info('\OC\Updater::maintenanceEnabled: Turned on maintenance mode', ['app' => 'updater']);
543
+        });
544
+        $this->listen('\OC\Updater', 'maintenanceDisabled', function () use($log) {
545
+            $log->info('\OC\Updater::maintenanceDisabled: Turned off maintenance mode', ['app' => 'updater']);
546
+        });
547
+        $this->listen('\OC\Updater', 'maintenanceActive', function () use($log) {
548
+            $log->info('\OC\Updater::maintenanceActive: Maintenance mode is kept active', ['app' => 'updater']);
549
+        });
550
+        $this->listen('\OC\Updater', 'updateEnd', function ($success) use($log) {
551
+            if ($success) {
552
+                $log->info('\OC\Updater::updateEnd: Update successful', ['app' => 'updater']);
553
+            } else {
554
+                $log->error('\OC\Updater::updateEnd: Update failed', ['app' => 'updater']);
555
+            }
556
+        });
557
+        $this->listen('\OC\Updater', 'dbUpgradeBefore', function () use($log) {
558
+            $log->info('\OC\Updater::dbUpgradeBefore: Updating database schema', ['app' => 'updater']);
559
+        });
560
+        $this->listen('\OC\Updater', 'dbUpgrade', function () use($log) {
561
+            $log->info('\OC\Updater::dbUpgrade: Updated database', ['app' => 'updater']);
562
+        });
563
+        $this->listen('\OC\Updater', 'dbSimulateUpgradeBefore', function () use($log) {
564
+            $log->info('\OC\Updater::dbSimulateUpgradeBefore: Checking whether the database schema can be updated (this can take a long time depending on the database size)', ['app' => 'updater']);
565
+        });
566
+        $this->listen('\OC\Updater', 'dbSimulateUpgrade', function () use($log) {
567
+            $log->info('\OC\Updater::dbSimulateUpgrade: Checked database schema update', ['app' => 'updater']);
568
+        });
569
+        $this->listen('\OC\Updater', 'incompatibleAppDisabled', function ($app) use($log) {
570
+            $log->info('\OC\Updater::incompatibleAppDisabled: Disabled incompatible app: ' . $app, ['app' => 'updater']);
571
+        });
572
+        $this->listen('\OC\Updater', 'checkAppStoreAppBefore', function ($app) use($log) {
573
+            $log->info('\OC\Updater::checkAppStoreAppBefore: Checking for update of app "' . $app . '" in appstore', ['app' => 'updater']);
574
+        });
575
+        $this->listen('\OC\Updater', 'upgradeAppStoreApp', function ($app) use($log) {
576
+            $log->info('\OC\Updater::upgradeAppStoreApp: Update app "' . $app . '" from appstore', ['app' => 'updater']);
577
+        });
578
+        $this->listen('\OC\Updater', 'checkAppStoreApp', function ($app) use($log) {
579
+            $log->info('\OC\Updater::checkAppStoreApp: Checked for update of app "' . $app . '" in appstore', ['app' => 'updater']);
580
+        });
581
+        $this->listen('\OC\Updater', 'appUpgradeCheckBefore', function () use ($log) {
582
+            $log->info('\OC\Updater::appUpgradeCheckBefore: Checking updates of apps', ['app' => 'updater']);
583
+        });
584
+        $this->listen('\OC\Updater', 'appSimulateUpdate', function ($app) use ($log) {
585
+            $log->info('\OC\Updater::appSimulateUpdate: Checking whether the database schema for <' . $app . '> can be updated (this can take a long time depending on the database size)', ['app' => 'updater']);
586
+        });
587
+        $this->listen('\OC\Updater', 'appUpgradeCheck', function () use ($log) {
588
+            $log->info('\OC\Updater::appUpgradeCheck: Checked database schema update for apps', ['app' => 'updater']);
589
+        });
590
+        $this->listen('\OC\Updater', 'appUpgradeStarted', function ($app) use ($log) {
591
+            $log->info('\OC\Updater::appUpgradeStarted: Updating <' . $app . '> ...', ['app' => 'updater']);
592
+        });
593
+        $this->listen('\OC\Updater', 'appUpgrade', function ($app, $version) use ($log) {
594
+            $log->info('\OC\Updater::appUpgrade: Updated <' . $app . '> to ' . $version, ['app' => 'updater']);
595
+        });
596
+        $this->listen('\OC\Updater', 'failure', function ($message) use($log) {
597
+            $log->error('\OC\Updater::failure: ' . $message, ['app' => 'updater']);
598
+        });
599
+        $this->listen('\OC\Updater', 'setDebugLogLevel', function () use($log) {
600
+            $log->info('\OC\Updater::setDebugLogLevel: Set log level to debug', ['app' => 'updater']);
601
+        });
602
+        $this->listen('\OC\Updater', 'resetLogLevel', function ($logLevel, $logLevelName) use($log) {
603
+            $log->info('\OC\Updater::resetLogLevel: Reset log level to ' . $logLevelName . '(' . $logLevel . ')', ['app' => 'updater']);
604
+        });
605
+        $this->listen('\OC\Updater', 'startCheckCodeIntegrity', function () use($log) {
606
+            $log->info('\OC\Updater::startCheckCodeIntegrity: Starting code integrity check...', ['app' => 'updater']);
607
+        });
608
+        $this->listen('\OC\Updater', 'finishedCheckCodeIntegrity', function () use($log) {
609
+            $log->info('\OC\Updater::finishedCheckCodeIntegrity: Finished code integrity check', ['app' => 'updater']);
610
+        });
611
+
612
+    }
613
+    private function waitForCronToFinish() {
614
+        while ($this->jobList->isAnyJobRunning()) {
615
+            $this->emit('\OC\Updater', 'waitForCronToFinish');
616
+            sleep(5);
617
+        }
618
+    }
619 619
 
620 620
 }
621 621
 
Please login to merge, or discard this patch.
lib/private/Repair.php 1 patch
Indentation   +166 added lines, -166 removed lines patch added patch discarded remove patch
@@ -56,170 +56,170 @@
 block discarded – undo
56 56
 use Symfony\Component\EventDispatcher\GenericEvent;
57 57
 
58 58
 class Repair implements IOutput{
59
-	/* @var IRepairStep[] */
60
-	private $repairSteps;
61
-	/** @var EventDispatcher */
62
-	private $dispatcher;
63
-	/** @var string */
64
-	private $currentStep;
65
-
66
-	/**
67
-	 * Creates a new repair step runner
68
-	 *
69
-	 * @param IRepairStep[] $repairSteps array of RepairStep instances
70
-	 * @param EventDispatcher $dispatcher
71
-	 */
72
-	public function __construct($repairSteps = [], EventDispatcher $dispatcher = null) {
73
-		$this->repairSteps = $repairSteps;
74
-		$this->dispatcher = $dispatcher;
75
-	}
76
-
77
-	/**
78
-	 * Run a series of repair steps for common problems
79
-	 */
80
-	public function run() {
81
-		if (count($this->repairSteps) === 0) {
82
-			$this->emit('\OC\Repair', 'info', array('No repair steps available'));
83
-			return;
84
-		}
85
-		// run each repair step
86
-		foreach ($this->repairSteps as $step) {
87
-			$this->currentStep = $step->getName();
88
-			$this->emit('\OC\Repair', 'step', [$this->currentStep]);
89
-			$step->run($this);
90
-		}
91
-	}
92
-
93
-	/**
94
-	 * Add repair step
95
-	 *
96
-	 * @param IRepairStep|string $repairStep repair step
97
-	 * @throws \Exception
98
-	 */
99
-	public function addStep($repairStep) {
100
-		if (is_string($repairStep)) {
101
-			try {
102
-				$s = \OC::$server->query($repairStep);
103
-			} catch (QueryException $e) {
104
-				if (class_exists($repairStep)) {
105
-					$s = new $repairStep();
106
-				} else {
107
-					throw new \Exception("Repair step '$repairStep' is unknown");
108
-				}
109
-			}
110
-
111
-			if ($s instanceof IRepairStep) {
112
-				$this->repairSteps[] = $s;
113
-			} else {
114
-				throw new \Exception("Repair step '$repairStep' is not of type \\OCP\\Migration\\IRepairStep");
115
-			}
116
-		} else {
117
-			$this->repairSteps[] = $repairStep;
118
-		}
119
-	}
120
-
121
-	/**
122
-	 * Returns the default repair steps to be run on the
123
-	 * command line or after an upgrade.
124
-	 *
125
-	 * @return IRepairStep[]
126
-	 */
127
-	public static function getRepairSteps() {
128
-		return [
129
-			new Collation(\OC::$server->getConfig(), \OC::$server->getLogger(), \OC::$server->getDatabaseConnection(), false),
130
-			new RepairMimeTypes(\OC::$server->getConfig()),
131
-			new CleanTags(\OC::$server->getDatabaseConnection(), \OC::$server->getUserManager()),
132
-			new RepairInvalidShares(\OC::$server->getConfig(), \OC::$server->getDatabaseConnection()),
133
-			new RemoveRootShares(\OC::$server->getDatabaseConnection(), \OC::$server->getUserManager(), \OC::$server->getLazyRootFolder()),
134
-			new MoveUpdaterStepFile(\OC::$server->getConfig()),
135
-			new FixMountStorages(\OC::$server->getDatabaseConnection()),
136
-			new RepairInvalidPaths(\OC::$server->getDatabaseConnection(), \OC::$server->getConfig()),
137
-			new AddLogRotateJob(\OC::$server->getJobList()),
138
-			new ClearFrontendCaches(\OC::$server->getMemCacheFactory(), \OC::$server->query(SCSSCacher::class), \OC::$server->query(JSCombiner::class)),
139
-			new AddPreviewBackgroundCleanupJob(\OC::$server->getJobList()),
140
-			new AddCleanupUpdaterBackupsJob(\OC::$server->getJobList()),
141
-			new RepairPendingCronJobs(\OC::$server->getDatabaseConnection(), \OC::$server->getConfig()),
142
-		];
143
-	}
144
-
145
-	/**
146
-	 * Returns expensive repair steps to be run on the
147
-	 * command line with a special option.
148
-	 *
149
-	 * @return IRepairStep[]
150
-	 */
151
-	public static function getExpensiveRepairSteps() {
152
-		return [
153
-			new OldGroupMembershipShares(\OC::$server->getDatabaseConnection(), \OC::$server->getGroupManager()),
154
-		];
155
-	}
156
-
157
-	/**
158
-	 * Returns the repair steps to be run before an
159
-	 * upgrade.
160
-	 *
161
-	 * @return IRepairStep[]
162
-	 */
163
-	public static function getBeforeUpgradeRepairSteps() {
164
-		$connection = \OC::$server->getDatabaseConnection();
165
-		$config = \OC::$server->getConfig();
166
-		$steps = [
167
-			new Collation(\OC::$server->getConfig(), \OC::$server->getLogger(), $connection, true),
168
-			new SqliteAutoincrement($connection),
169
-			new SaveAccountsTableData($connection, $config),
170
-			new DropAccountTermsTable($connection),
171
-		];
172
-
173
-		return $steps;
174
-	}
175
-
176
-	/**
177
-	 * @param string $scope
178
-	 * @param string $method
179
-	 * @param array $arguments
180
-	 */
181
-	public function emit($scope, $method, array $arguments = []) {
182
-		if (!is_null($this->dispatcher)) {
183
-			$this->dispatcher->dispatch("$scope::$method",
184
-				new GenericEvent("$scope::$method", $arguments));
185
-		}
186
-	}
187
-
188
-	public function info($string) {
189
-		// for now just emit as we did in the past
190
-		$this->emit('\OC\Repair', 'info', array($string));
191
-	}
192
-
193
-	/**
194
-	 * @param string $message
195
-	 */
196
-	public function warning($message) {
197
-		// for now just emit as we did in the past
198
-		$this->emit('\OC\Repair', 'warning', [$message]);
199
-	}
200
-
201
-	/**
202
-	 * @param int $max
203
-	 */
204
-	public function startProgress($max = 0) {
205
-		// for now just emit as we did in the past
206
-		$this->emit('\OC\Repair', 'startProgress', [$max, $this->currentStep]);
207
-	}
208
-
209
-	/**
210
-	 * @param int $step
211
-	 * @param string $description
212
-	 */
213
-	public function advance($step = 1, $description = '') {
214
-		// for now just emit as we did in the past
215
-		$this->emit('\OC\Repair', 'advance', [$step, $description]);
216
-	}
217
-
218
-	/**
219
-	 * @param int $max
220
-	 */
221
-	public function finishProgress() {
222
-		// for now just emit as we did in the past
223
-		$this->emit('\OC\Repair', 'finishProgress', []);
224
-	}
59
+    /* @var IRepairStep[] */
60
+    private $repairSteps;
61
+    /** @var EventDispatcher */
62
+    private $dispatcher;
63
+    /** @var string */
64
+    private $currentStep;
65
+
66
+    /**
67
+     * Creates a new repair step runner
68
+     *
69
+     * @param IRepairStep[] $repairSteps array of RepairStep instances
70
+     * @param EventDispatcher $dispatcher
71
+     */
72
+    public function __construct($repairSteps = [], EventDispatcher $dispatcher = null) {
73
+        $this->repairSteps = $repairSteps;
74
+        $this->dispatcher = $dispatcher;
75
+    }
76
+
77
+    /**
78
+     * Run a series of repair steps for common problems
79
+     */
80
+    public function run() {
81
+        if (count($this->repairSteps) === 0) {
82
+            $this->emit('\OC\Repair', 'info', array('No repair steps available'));
83
+            return;
84
+        }
85
+        // run each repair step
86
+        foreach ($this->repairSteps as $step) {
87
+            $this->currentStep = $step->getName();
88
+            $this->emit('\OC\Repair', 'step', [$this->currentStep]);
89
+            $step->run($this);
90
+        }
91
+    }
92
+
93
+    /**
94
+     * Add repair step
95
+     *
96
+     * @param IRepairStep|string $repairStep repair step
97
+     * @throws \Exception
98
+     */
99
+    public function addStep($repairStep) {
100
+        if (is_string($repairStep)) {
101
+            try {
102
+                $s = \OC::$server->query($repairStep);
103
+            } catch (QueryException $e) {
104
+                if (class_exists($repairStep)) {
105
+                    $s = new $repairStep();
106
+                } else {
107
+                    throw new \Exception("Repair step '$repairStep' is unknown");
108
+                }
109
+            }
110
+
111
+            if ($s instanceof IRepairStep) {
112
+                $this->repairSteps[] = $s;
113
+            } else {
114
+                throw new \Exception("Repair step '$repairStep' is not of type \\OCP\\Migration\\IRepairStep");
115
+            }
116
+        } else {
117
+            $this->repairSteps[] = $repairStep;
118
+        }
119
+    }
120
+
121
+    /**
122
+     * Returns the default repair steps to be run on the
123
+     * command line or after an upgrade.
124
+     *
125
+     * @return IRepairStep[]
126
+     */
127
+    public static function getRepairSteps() {
128
+        return [
129
+            new Collation(\OC::$server->getConfig(), \OC::$server->getLogger(), \OC::$server->getDatabaseConnection(), false),
130
+            new RepairMimeTypes(\OC::$server->getConfig()),
131
+            new CleanTags(\OC::$server->getDatabaseConnection(), \OC::$server->getUserManager()),
132
+            new RepairInvalidShares(\OC::$server->getConfig(), \OC::$server->getDatabaseConnection()),
133
+            new RemoveRootShares(\OC::$server->getDatabaseConnection(), \OC::$server->getUserManager(), \OC::$server->getLazyRootFolder()),
134
+            new MoveUpdaterStepFile(\OC::$server->getConfig()),
135
+            new FixMountStorages(\OC::$server->getDatabaseConnection()),
136
+            new RepairInvalidPaths(\OC::$server->getDatabaseConnection(), \OC::$server->getConfig()),
137
+            new AddLogRotateJob(\OC::$server->getJobList()),
138
+            new ClearFrontendCaches(\OC::$server->getMemCacheFactory(), \OC::$server->query(SCSSCacher::class), \OC::$server->query(JSCombiner::class)),
139
+            new AddPreviewBackgroundCleanupJob(\OC::$server->getJobList()),
140
+            new AddCleanupUpdaterBackupsJob(\OC::$server->getJobList()),
141
+            new RepairPendingCronJobs(\OC::$server->getDatabaseConnection(), \OC::$server->getConfig()),
142
+        ];
143
+    }
144
+
145
+    /**
146
+     * Returns expensive repair steps to be run on the
147
+     * command line with a special option.
148
+     *
149
+     * @return IRepairStep[]
150
+     */
151
+    public static function getExpensiveRepairSteps() {
152
+        return [
153
+            new OldGroupMembershipShares(\OC::$server->getDatabaseConnection(), \OC::$server->getGroupManager()),
154
+        ];
155
+    }
156
+
157
+    /**
158
+     * Returns the repair steps to be run before an
159
+     * upgrade.
160
+     *
161
+     * @return IRepairStep[]
162
+     */
163
+    public static function getBeforeUpgradeRepairSteps() {
164
+        $connection = \OC::$server->getDatabaseConnection();
165
+        $config = \OC::$server->getConfig();
166
+        $steps = [
167
+            new Collation(\OC::$server->getConfig(), \OC::$server->getLogger(), $connection, true),
168
+            new SqliteAutoincrement($connection),
169
+            new SaveAccountsTableData($connection, $config),
170
+            new DropAccountTermsTable($connection),
171
+        ];
172
+
173
+        return $steps;
174
+    }
175
+
176
+    /**
177
+     * @param string $scope
178
+     * @param string $method
179
+     * @param array $arguments
180
+     */
181
+    public function emit($scope, $method, array $arguments = []) {
182
+        if (!is_null($this->dispatcher)) {
183
+            $this->dispatcher->dispatch("$scope::$method",
184
+                new GenericEvent("$scope::$method", $arguments));
185
+        }
186
+    }
187
+
188
+    public function info($string) {
189
+        // for now just emit as we did in the past
190
+        $this->emit('\OC\Repair', 'info', array($string));
191
+    }
192
+
193
+    /**
194
+     * @param string $message
195
+     */
196
+    public function warning($message) {
197
+        // for now just emit as we did in the past
198
+        $this->emit('\OC\Repair', 'warning', [$message]);
199
+    }
200
+
201
+    /**
202
+     * @param int $max
203
+     */
204
+    public function startProgress($max = 0) {
205
+        // for now just emit as we did in the past
206
+        $this->emit('\OC\Repair', 'startProgress', [$max, $this->currentStep]);
207
+    }
208
+
209
+    /**
210
+     * @param int $step
211
+     * @param string $description
212
+     */
213
+    public function advance($step = 1, $description = '') {
214
+        // for now just emit as we did in the past
215
+        $this->emit('\OC\Repair', 'advance', [$step, $description]);
216
+    }
217
+
218
+    /**
219
+     * @param int $max
220
+     */
221
+    public function finishProgress() {
222
+        // for now just emit as we did in the past
223
+        $this->emit('\OC\Repair', 'finishProgress', []);
224
+    }
225 225
 }
Please login to merge, or discard this patch.
lib/private/Repair/NC14/RepairPendingCronJobs.php 1 patch
Indentation   +34 added lines, -34 removed lines patch added patch discarded remove patch
@@ -30,48 +30,48 @@
 block discarded – undo
30 30
 use OCP\Migration\IRepairStep;
31 31
 
32 32
 class RepairPendingCronJobs implements IRepairStep {
33
-	const MAX_ROWS = 1000;
33
+    const MAX_ROWS = 1000;
34 34
 
35
-	/** @var IDBConnection */
36
-	private $connection;
37
-	/** @var IConfig */
38
-	private $config;
35
+    /** @var IDBConnection */
36
+    private $connection;
37
+    /** @var IConfig */
38
+    private $config;
39 39
 
40
-	public function __construct(IDBConnection $connection, IConfig $config) {
41
-		$this->connection = $connection;
42
-		$this->config = $config;
43
-	}
40
+    public function __construct(IDBConnection $connection, IConfig $config) {
41
+        $this->connection = $connection;
42
+        $this->config = $config;
43
+    }
44 44
 
45 45
 
46
-	public function getName() {
47
-		return 'Repair pending cron jobs';
48
-	}
46
+    public function getName() {
47
+        return 'Repair pending cron jobs';
48
+    }
49 49
 
50
-	private function shouldRun() {
51
-		$versionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0');
50
+    private function shouldRun() {
51
+        $versionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0');
52 52
 
53
-		return version_compare($versionFromBeforeUpdate, '14.0.0.9', '<');
54
-	}
53
+        return version_compare($versionFromBeforeUpdate, '14.0.0.9', '<');
54
+    }
55 55
 
56
-	/**
57
-	 * @suppress SqlInjectionChecker
58
-	 */
59
-	private function repair() {
60
-		$reset = $this->connection->getQueryBuilder();
61
-		$reset->update('jobs')
62
-			->set('reserved_at', $reset->expr()->literal(0, IQueryBuilder::PARAM_INT))
63
-			->where($reset->expr()->neq('reserved_at', $reset->expr()->literal(0, IQueryBuilder::PARAM_INT)));
56
+    /**
57
+     * @suppress SqlInjectionChecker
58
+     */
59
+    private function repair() {
60
+        $reset = $this->connection->getQueryBuilder();
61
+        $reset->update('jobs')
62
+            ->set('reserved_at', $reset->expr()->literal(0, IQueryBuilder::PARAM_INT))
63
+            ->where($reset->expr()->neq('reserved_at', $reset->expr()->literal(0, IQueryBuilder::PARAM_INT)));
64 64
 
65
-		return $reset->execute();
66
-	}
65
+        return $reset->execute();
66
+    }
67 67
 
68
-	public function run(IOutput $output) {
69
-		if ($this->shouldRun()) {
70
-			$count = $this->repair();
68
+    public function run(IOutput $output) {
69
+        if ($this->shouldRun()) {
70
+            $count = $this->repair();
71 71
 
72
-			$output->info('Repaired ' . $count . ' pending cron job(s).');
73
-		} else {
74
-			$output->info('No need to repair pending cron jobs.');
75
-		}
76
-	}
72
+            $output->info('Repaired ' . $count . ' pending cron job(s).');
73
+        } else {
74
+            $output->info('No need to repair pending cron jobs.');
75
+        }
76
+    }
77 77
 }
Please login to merge, or discard this patch.
lib/private/BackgroundJob/JobList.php 1 patch
Indentation   +342 added lines, -342 removed lines patch added patch discarded remove patch
@@ -39,346 +39,346 @@
 block discarded – undo
39 39
 
40 40
 class JobList implements IJobList {
41 41
 
42
-	/** @var IDBConnection */
43
-	protected $connection;
44
-
45
-	/**@var IConfig */
46
-	protected $config;
47
-
48
-	/**@var ITimeFactory */
49
-	protected $timeFactory;
50
-
51
-	/** @var int - 12 hours * 3600 seconds*/
52
-	private $jobTimeOut = 43200;
53
-
54
-	/**
55
-	 * @param IDBConnection $connection
56
-	 * @param IConfig $config
57
-	 * @param ITimeFactory $timeFactory
58
-	 */
59
-	public function __construct(IDBConnection $connection, IConfig $config, ITimeFactory $timeFactory) {
60
-		$this->connection = $connection;
61
-		$this->config = $config;
62
-		$this->timeFactory = $timeFactory;
63
-	}
64
-
65
-	/**
66
-	 * @param IJob|string $job
67
-	 * @param mixed $argument
68
-	 */
69
-	public function add($job, $argument = null) {
70
-		if (!$this->has($job, $argument)) {
71
-			if ($job instanceof IJob) {
72
-				$class = get_class($job);
73
-			} else {
74
-				$class = $job;
75
-			}
76
-
77
-			$argument = json_encode($argument);
78
-			if (strlen($argument) > 4000) {
79
-				throw new \InvalidArgumentException('Background job arguments can\'t exceed 4000 characters (json encoded)');
80
-			}
81
-
82
-			$query = $this->connection->getQueryBuilder();
83
-			$query->insert('jobs')
84
-				->values([
85
-					'class' => $query->createNamedParameter($class),
86
-					'argument' => $query->createNamedParameter($argument),
87
-					'last_run' => $query->createNamedParameter(0, IQueryBuilder::PARAM_INT),
88
-					'last_checked' => $query->createNamedParameter($this->timeFactory->getTime(), IQueryBuilder::PARAM_INT),
89
-				]);
90
-			$query->execute();
91
-		}
92
-	}
93
-
94
-	/**
95
-	 * @param IJob|string $job
96
-	 * @param mixed $argument
97
-	 */
98
-	public function remove($job, $argument = null) {
99
-		if ($job instanceof IJob) {
100
-			$class = get_class($job);
101
-		} else {
102
-			$class = $job;
103
-		}
104
-
105
-		$query = $this->connection->getQueryBuilder();
106
-		$query->delete('jobs')
107
-			->where($query->expr()->eq('class', $query->createNamedParameter($class)));
108
-		if (!is_null($argument)) {
109
-			$argument = json_encode($argument);
110
-			$query->andWhere($query->expr()->eq('argument', $query->createNamedParameter($argument)));
111
-		}
112
-		$query->execute();
113
-	}
114
-
115
-	/**
116
-	 * @param int $id
117
-	 */
118
-	protected function removeById($id) {
119
-		$query = $this->connection->getQueryBuilder();
120
-		$query->delete('jobs')
121
-			->where($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
122
-		$query->execute();
123
-	}
124
-
125
-	/**
126
-	 * check if a job is in the list
127
-	 *
128
-	 * @param IJob|string $job
129
-	 * @param mixed $argument
130
-	 * @return bool
131
-	 */
132
-	public function has($job, $argument) {
133
-		if ($job instanceof IJob) {
134
-			$class = get_class($job);
135
-		} else {
136
-			$class = $job;
137
-		}
138
-		$argument = json_encode($argument);
139
-
140
-		$query = $this->connection->getQueryBuilder();
141
-		$query->select('id')
142
-			->from('jobs')
143
-			->where($query->expr()->eq('class', $query->createNamedParameter($class)))
144
-			->andWhere($query->expr()->eq('argument', $query->createNamedParameter($argument)))
145
-			->setMaxResults(1);
146
-
147
-		$result = $query->execute();
148
-		$row = $result->fetch();
149
-		$result->closeCursor();
150
-
151
-		return (bool) $row;
152
-	}
153
-
154
-	/**
155
-	 * get all jobs in the list
156
-	 *
157
-	 * @return IJob[]
158
-	 * @deprecated 9.0.0 - This method is dangerous since it can cause load and
159
-	 * memory problems when creating too many instances.
160
-	 */
161
-	public function getAll() {
162
-		$query = $this->connection->getQueryBuilder();
163
-		$query->select('*')
164
-			->from('jobs');
165
-		$result = $query->execute();
166
-
167
-		$jobs = [];
168
-		while ($row = $result->fetch()) {
169
-			$job = $this->buildJob($row);
170
-			if ($job) {
171
-				$jobs[] = $job;
172
-			}
173
-		}
174
-		$result->closeCursor();
175
-
176
-		return $jobs;
177
-	}
178
-
179
-	/**
180
-	 * get the next job in the list
181
-	 *
182
-	 * @return IJob|null
183
-	 * @suppress SqlInjectionChecker
184
-	 */
185
-	public function getNext() {
186
-		$query = $this->connection->getQueryBuilder();
187
-		$query->select('*')
188
-			->from('jobs')
189
-			->where($query->expr()->lte('reserved_at', $query->createNamedParameter($this->timeFactory->getTime() - $this->jobTimeOut, IQueryBuilder::PARAM_INT)))
190
-			->andWhere($query->expr()->lte('last_checked', $query->createNamedParameter($this->timeFactory->getTime(), IQueryBuilder::PARAM_INT)))
191
-			->orderBy('last_checked', 'ASC')
192
-			->setMaxResults(1);
193
-
194
-		$update = $this->connection->getQueryBuilder();
195
-		$update->update('jobs')
196
-			->set('reserved_at', $update->createNamedParameter($this->timeFactory->getTime()))
197
-			->set('last_checked', $update->createNamedParameter($this->timeFactory->getTime()))
198
-			->where($update->expr()->eq('id', $update->createParameter('jobid')))
199
-			->andWhere($update->expr()->eq('reserved_at', $update->createParameter('reserved_at')))
200
-			->andWhere($update->expr()->eq('last_checked', $update->createParameter('last_checked')));
201
-
202
-		$result = $query->execute();
203
-		$row = $result->fetch();
204
-		$result->closeCursor();
205
-
206
-		if ($row) {
207
-			$update->setParameter('jobid', $row['id']);
208
-			$update->setParameter('reserved_at', $row['reserved_at']);
209
-			$update->setParameter('last_checked', $row['last_checked']);
210
-			$count = $update->execute();
211
-
212
-			if ($count === 0) {
213
-				// Background job already executed elsewhere, try again.
214
-				return $this->getNext();
215
-			}
216
-			$job = $this->buildJob($row);
217
-
218
-			if ($job === null) {
219
-				// set the last_checked to 12h in the future to not check failing jobs all over again
220
-				$reset = $this->connection->getQueryBuilder();
221
-				$reset->update('jobs')
222
-					->set('reserved_at', $reset->expr()->literal(0, IQueryBuilder::PARAM_INT))
223
-					->set('last_checked', $reset->createNamedParameter($this->timeFactory->getTime() + 12 * 3600, IQueryBuilder::PARAM_INT))
224
-					->where($reset->expr()->eq('id', $reset->createNamedParameter($row['id'], IQueryBuilder::PARAM_INT)));
225
-				$reset->execute();
226
-
227
-				// Background job from disabled app, try again.
228
-				return $this->getNext();
229
-			}
230
-
231
-			return $job;
232
-		} else {
233
-			return null;
234
-		}
235
-	}
236
-
237
-	/**
238
-	 * @param int $id
239
-	 * @return IJob|null
240
-	 */
241
-	public function getById($id) {
242
-		$query = $this->connection->getQueryBuilder();
243
-		$query->select('*')
244
-			->from('jobs')
245
-			->where($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
246
-		$result = $query->execute();
247
-		$row = $result->fetch();
248
-		$result->closeCursor();
249
-
250
-		if ($row) {
251
-			return $this->buildJob($row);
252
-		} else {
253
-			return null;
254
-		}
255
-	}
256
-
257
-	/**
258
-	 * get the job object from a row in the db
259
-	 *
260
-	 * @param array $row
261
-	 * @return IJob|null
262
-	 */
263
-	private function buildJob($row) {
264
-		try {
265
-			try {
266
-				// Try to load the job as a service
267
-				/** @var IJob $job */
268
-				$job = \OC::$server->query($row['class']);
269
-			} catch (QueryException $e) {
270
-				if (class_exists($row['class'])) {
271
-					$class = $row['class'];
272
-					$job = new $class();
273
-				} else {
274
-					// job from disabled app or old version of an app, no need to do anything
275
-					return null;
276
-				}
277
-			}
278
-
279
-			$job->setId($row['id']);
280
-			$job->setLastRun($row['last_run']);
281
-			$job->setArgument(json_decode($row['argument'], true));
282
-			return $job;
283
-		} catch (AutoloadNotAllowedException $e) {
284
-			// job is from a disabled app, ignore
285
-			return null;
286
-		}
287
-	}
288
-
289
-	/**
290
-	 * set the job that was last ran
291
-	 *
292
-	 * @param IJob $job
293
-	 */
294
-	public function setLastJob(IJob $job) {
295
-		$this->unlockJob($job);
296
-		$this->config->setAppValue('backgroundjob', 'lastjob', $job->getId());
297
-	}
298
-
299
-	/**
300
-	 * Remove the reservation for a job
301
-	 *
302
-	 * @param IJob $job
303
-	 * @suppress SqlInjectionChecker
304
-	 */
305
-	public function unlockJob(IJob $job) {
306
-		$query = $this->connection->getQueryBuilder();
307
-		$query->update('jobs')
308
-			->set('reserved_at', $query->expr()->literal(0, IQueryBuilder::PARAM_INT))
309
-			->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
310
-		$query->execute();
311
-	}
312
-
313
-	/**
314
-	 * get the id of the last ran job
315
-	 *
316
-	 * @return int
317
-	 * @deprecated 9.1.0 - The functionality behind the value is deprecated, it
318
-	 *    only tells you which job finished last, but since we now allow multiple
319
-	 *    executors to run in parallel, it's not used to calculate the next job.
320
-	 */
321
-	public function getLastJob() {
322
-		return (int) $this->config->getAppValue('backgroundjob', 'lastjob', 0);
323
-	}
324
-
325
-	/**
326
-	 * set the lastRun of $job to now
327
-	 *
328
-	 * @param IJob $job
329
-	 */
330
-	public function setLastRun(IJob $job) {
331
-		$query = $this->connection->getQueryBuilder();
332
-		$query->update('jobs')
333
-			->set('last_run', $query->createNamedParameter(time(), IQueryBuilder::PARAM_INT))
334
-			->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
335
-		$query->execute();
336
-	}
337
-
338
-	/**
339
-	 * @param IJob $job
340
-	 * @param $timeTaken
341
-	 */
342
-	public function setExecutionTime(IJob $job, $timeTaken) {
343
-		$query = $this->connection->getQueryBuilder();
344
-		$query->update('jobs')
345
-			->set('execution_duration', $query->createNamedParameter($timeTaken, IQueryBuilder::PARAM_INT))
346
-			->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
347
-		$query->execute();
348
-	}
349
-
350
-	/**
351
-	 * checks if a job is still running (reserved_at time is smaller than 12 hours ago)
352
-	 *
353
-	 * Background information:
354
-	 *
355
-	 * The 12 hours is the same timeout that is also used to re-schedule an non-terminated
356
-	 * job (see getNext()). The idea here is to give a job enough time to run very
357
-	 * long but still be able to recognize that it maybe crashed and re-schedule it
358
-	 * after the timeout. It's more likely to be crashed at that time than it ran
359
-	 * that long.
360
-	 *
361
-	 * In theory it could lead to an nearly endless loop (as in - at most 12 hours).
362
-	 * The cron command will not start new jobs when maintenance mode is active and
363
-	 * this method is only executed in maintenance mode (see where it is called in
364
-	 * the upgrader class. So this means in the worst case we wait 12 hours when a
365
-	 * job has crashed. On the other hand: then the instance should be fixed anyways.
366
-	 *
367
-	 * @return bool
368
-	 */
369
-	public function isAnyJobRunning(): bool {
370
-		$query = $this->connection->getQueryBuilder();
371
-		$query->select('*')
372
-			->from('jobs')
373
-			->where($query->expr()->gt('reserved_at', $query->createNamedParameter($this->timeFactory->getTime() - $this->jobTimeOut, IQueryBuilder::PARAM_INT)))
374
-			->setMaxResults(1);
375
-		$result = $query->execute();
376
-		$row = $result->fetch();
377
-		$result->closeCursor();
378
-
379
-		if ($row) {
380
-			return true;
381
-		}
382
-		return false;
383
-	}
42
+    /** @var IDBConnection */
43
+    protected $connection;
44
+
45
+    /**@var IConfig */
46
+    protected $config;
47
+
48
+    /**@var ITimeFactory */
49
+    protected $timeFactory;
50
+
51
+    /** @var int - 12 hours * 3600 seconds*/
52
+    private $jobTimeOut = 43200;
53
+
54
+    /**
55
+     * @param IDBConnection $connection
56
+     * @param IConfig $config
57
+     * @param ITimeFactory $timeFactory
58
+     */
59
+    public function __construct(IDBConnection $connection, IConfig $config, ITimeFactory $timeFactory) {
60
+        $this->connection = $connection;
61
+        $this->config = $config;
62
+        $this->timeFactory = $timeFactory;
63
+    }
64
+
65
+    /**
66
+     * @param IJob|string $job
67
+     * @param mixed $argument
68
+     */
69
+    public function add($job, $argument = null) {
70
+        if (!$this->has($job, $argument)) {
71
+            if ($job instanceof IJob) {
72
+                $class = get_class($job);
73
+            } else {
74
+                $class = $job;
75
+            }
76
+
77
+            $argument = json_encode($argument);
78
+            if (strlen($argument) > 4000) {
79
+                throw new \InvalidArgumentException('Background job arguments can\'t exceed 4000 characters (json encoded)');
80
+            }
81
+
82
+            $query = $this->connection->getQueryBuilder();
83
+            $query->insert('jobs')
84
+                ->values([
85
+                    'class' => $query->createNamedParameter($class),
86
+                    'argument' => $query->createNamedParameter($argument),
87
+                    'last_run' => $query->createNamedParameter(0, IQueryBuilder::PARAM_INT),
88
+                    'last_checked' => $query->createNamedParameter($this->timeFactory->getTime(), IQueryBuilder::PARAM_INT),
89
+                ]);
90
+            $query->execute();
91
+        }
92
+    }
93
+
94
+    /**
95
+     * @param IJob|string $job
96
+     * @param mixed $argument
97
+     */
98
+    public function remove($job, $argument = null) {
99
+        if ($job instanceof IJob) {
100
+            $class = get_class($job);
101
+        } else {
102
+            $class = $job;
103
+        }
104
+
105
+        $query = $this->connection->getQueryBuilder();
106
+        $query->delete('jobs')
107
+            ->where($query->expr()->eq('class', $query->createNamedParameter($class)));
108
+        if (!is_null($argument)) {
109
+            $argument = json_encode($argument);
110
+            $query->andWhere($query->expr()->eq('argument', $query->createNamedParameter($argument)));
111
+        }
112
+        $query->execute();
113
+    }
114
+
115
+    /**
116
+     * @param int $id
117
+     */
118
+    protected function removeById($id) {
119
+        $query = $this->connection->getQueryBuilder();
120
+        $query->delete('jobs')
121
+            ->where($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
122
+        $query->execute();
123
+    }
124
+
125
+    /**
126
+     * check if a job is in the list
127
+     *
128
+     * @param IJob|string $job
129
+     * @param mixed $argument
130
+     * @return bool
131
+     */
132
+    public function has($job, $argument) {
133
+        if ($job instanceof IJob) {
134
+            $class = get_class($job);
135
+        } else {
136
+            $class = $job;
137
+        }
138
+        $argument = json_encode($argument);
139
+
140
+        $query = $this->connection->getQueryBuilder();
141
+        $query->select('id')
142
+            ->from('jobs')
143
+            ->where($query->expr()->eq('class', $query->createNamedParameter($class)))
144
+            ->andWhere($query->expr()->eq('argument', $query->createNamedParameter($argument)))
145
+            ->setMaxResults(1);
146
+
147
+        $result = $query->execute();
148
+        $row = $result->fetch();
149
+        $result->closeCursor();
150
+
151
+        return (bool) $row;
152
+    }
153
+
154
+    /**
155
+     * get all jobs in the list
156
+     *
157
+     * @return IJob[]
158
+     * @deprecated 9.0.0 - This method is dangerous since it can cause load and
159
+     * memory problems when creating too many instances.
160
+     */
161
+    public function getAll() {
162
+        $query = $this->connection->getQueryBuilder();
163
+        $query->select('*')
164
+            ->from('jobs');
165
+        $result = $query->execute();
166
+
167
+        $jobs = [];
168
+        while ($row = $result->fetch()) {
169
+            $job = $this->buildJob($row);
170
+            if ($job) {
171
+                $jobs[] = $job;
172
+            }
173
+        }
174
+        $result->closeCursor();
175
+
176
+        return $jobs;
177
+    }
178
+
179
+    /**
180
+     * get the next job in the list
181
+     *
182
+     * @return IJob|null
183
+     * @suppress SqlInjectionChecker
184
+     */
185
+    public function getNext() {
186
+        $query = $this->connection->getQueryBuilder();
187
+        $query->select('*')
188
+            ->from('jobs')
189
+            ->where($query->expr()->lte('reserved_at', $query->createNamedParameter($this->timeFactory->getTime() - $this->jobTimeOut, IQueryBuilder::PARAM_INT)))
190
+            ->andWhere($query->expr()->lte('last_checked', $query->createNamedParameter($this->timeFactory->getTime(), IQueryBuilder::PARAM_INT)))
191
+            ->orderBy('last_checked', 'ASC')
192
+            ->setMaxResults(1);
193
+
194
+        $update = $this->connection->getQueryBuilder();
195
+        $update->update('jobs')
196
+            ->set('reserved_at', $update->createNamedParameter($this->timeFactory->getTime()))
197
+            ->set('last_checked', $update->createNamedParameter($this->timeFactory->getTime()))
198
+            ->where($update->expr()->eq('id', $update->createParameter('jobid')))
199
+            ->andWhere($update->expr()->eq('reserved_at', $update->createParameter('reserved_at')))
200
+            ->andWhere($update->expr()->eq('last_checked', $update->createParameter('last_checked')));
201
+
202
+        $result = $query->execute();
203
+        $row = $result->fetch();
204
+        $result->closeCursor();
205
+
206
+        if ($row) {
207
+            $update->setParameter('jobid', $row['id']);
208
+            $update->setParameter('reserved_at', $row['reserved_at']);
209
+            $update->setParameter('last_checked', $row['last_checked']);
210
+            $count = $update->execute();
211
+
212
+            if ($count === 0) {
213
+                // Background job already executed elsewhere, try again.
214
+                return $this->getNext();
215
+            }
216
+            $job = $this->buildJob($row);
217
+
218
+            if ($job === null) {
219
+                // set the last_checked to 12h in the future to not check failing jobs all over again
220
+                $reset = $this->connection->getQueryBuilder();
221
+                $reset->update('jobs')
222
+                    ->set('reserved_at', $reset->expr()->literal(0, IQueryBuilder::PARAM_INT))
223
+                    ->set('last_checked', $reset->createNamedParameter($this->timeFactory->getTime() + 12 * 3600, IQueryBuilder::PARAM_INT))
224
+                    ->where($reset->expr()->eq('id', $reset->createNamedParameter($row['id'], IQueryBuilder::PARAM_INT)));
225
+                $reset->execute();
226
+
227
+                // Background job from disabled app, try again.
228
+                return $this->getNext();
229
+            }
230
+
231
+            return $job;
232
+        } else {
233
+            return null;
234
+        }
235
+    }
236
+
237
+    /**
238
+     * @param int $id
239
+     * @return IJob|null
240
+     */
241
+    public function getById($id) {
242
+        $query = $this->connection->getQueryBuilder();
243
+        $query->select('*')
244
+            ->from('jobs')
245
+            ->where($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
246
+        $result = $query->execute();
247
+        $row = $result->fetch();
248
+        $result->closeCursor();
249
+
250
+        if ($row) {
251
+            return $this->buildJob($row);
252
+        } else {
253
+            return null;
254
+        }
255
+    }
256
+
257
+    /**
258
+     * get the job object from a row in the db
259
+     *
260
+     * @param array $row
261
+     * @return IJob|null
262
+     */
263
+    private function buildJob($row) {
264
+        try {
265
+            try {
266
+                // Try to load the job as a service
267
+                /** @var IJob $job */
268
+                $job = \OC::$server->query($row['class']);
269
+            } catch (QueryException $e) {
270
+                if (class_exists($row['class'])) {
271
+                    $class = $row['class'];
272
+                    $job = new $class();
273
+                } else {
274
+                    // job from disabled app or old version of an app, no need to do anything
275
+                    return null;
276
+                }
277
+            }
278
+
279
+            $job->setId($row['id']);
280
+            $job->setLastRun($row['last_run']);
281
+            $job->setArgument(json_decode($row['argument'], true));
282
+            return $job;
283
+        } catch (AutoloadNotAllowedException $e) {
284
+            // job is from a disabled app, ignore
285
+            return null;
286
+        }
287
+    }
288
+
289
+    /**
290
+     * set the job that was last ran
291
+     *
292
+     * @param IJob $job
293
+     */
294
+    public function setLastJob(IJob $job) {
295
+        $this->unlockJob($job);
296
+        $this->config->setAppValue('backgroundjob', 'lastjob', $job->getId());
297
+    }
298
+
299
+    /**
300
+     * Remove the reservation for a job
301
+     *
302
+     * @param IJob $job
303
+     * @suppress SqlInjectionChecker
304
+     */
305
+    public function unlockJob(IJob $job) {
306
+        $query = $this->connection->getQueryBuilder();
307
+        $query->update('jobs')
308
+            ->set('reserved_at', $query->expr()->literal(0, IQueryBuilder::PARAM_INT))
309
+            ->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
310
+        $query->execute();
311
+    }
312
+
313
+    /**
314
+     * get the id of the last ran job
315
+     *
316
+     * @return int
317
+     * @deprecated 9.1.0 - The functionality behind the value is deprecated, it
318
+     *    only tells you which job finished last, but since we now allow multiple
319
+     *    executors to run in parallel, it's not used to calculate the next job.
320
+     */
321
+    public function getLastJob() {
322
+        return (int) $this->config->getAppValue('backgroundjob', 'lastjob', 0);
323
+    }
324
+
325
+    /**
326
+     * set the lastRun of $job to now
327
+     *
328
+     * @param IJob $job
329
+     */
330
+    public function setLastRun(IJob $job) {
331
+        $query = $this->connection->getQueryBuilder();
332
+        $query->update('jobs')
333
+            ->set('last_run', $query->createNamedParameter(time(), IQueryBuilder::PARAM_INT))
334
+            ->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
335
+        $query->execute();
336
+    }
337
+
338
+    /**
339
+     * @param IJob $job
340
+     * @param $timeTaken
341
+     */
342
+    public function setExecutionTime(IJob $job, $timeTaken) {
343
+        $query = $this->connection->getQueryBuilder();
344
+        $query->update('jobs')
345
+            ->set('execution_duration', $query->createNamedParameter($timeTaken, IQueryBuilder::PARAM_INT))
346
+            ->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
347
+        $query->execute();
348
+    }
349
+
350
+    /**
351
+     * checks if a job is still running (reserved_at time is smaller than 12 hours ago)
352
+     *
353
+     * Background information:
354
+     *
355
+     * The 12 hours is the same timeout that is also used to re-schedule an non-terminated
356
+     * job (see getNext()). The idea here is to give a job enough time to run very
357
+     * long but still be able to recognize that it maybe crashed and re-schedule it
358
+     * after the timeout. It's more likely to be crashed at that time than it ran
359
+     * that long.
360
+     *
361
+     * In theory it could lead to an nearly endless loop (as in - at most 12 hours).
362
+     * The cron command will not start new jobs when maintenance mode is active and
363
+     * this method is only executed in maintenance mode (see where it is called in
364
+     * the upgrader class. So this means in the worst case we wait 12 hours when a
365
+     * job has crashed. On the other hand: then the instance should be fixed anyways.
366
+     *
367
+     * @return bool
368
+     */
369
+    public function isAnyJobRunning(): bool {
370
+        $query = $this->connection->getQueryBuilder();
371
+        $query->select('*')
372
+            ->from('jobs')
373
+            ->where($query->expr()->gt('reserved_at', $query->createNamedParameter($this->timeFactory->getTime() - $this->jobTimeOut, IQueryBuilder::PARAM_INT)))
374
+            ->setMaxResults(1);
375
+        $result = $query->execute();
376
+        $row = $result->fetch();
377
+        $result->closeCursor();
378
+
379
+        if ($row) {
380
+            return true;
381
+        }
382
+        return false;
383
+    }
384 384
 }
Please login to merge, or discard this patch.