Skip to content

Commit 2555545

Browse files
committed
refactor(updater): Move updater to a Controller
Signed-off-by: Carl Schwan <carlschwan@kde.org>
1 parent 346c4bd commit 2555545

7 files changed

Lines changed: 187 additions & 169 deletions

File tree

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH
7+
* SPDX-FileContributor: Carl Schwan
8+
* SPDX-License-Identifier: AGPL-3.0-or-later
9+
*/
10+
11+
namespace OC\Core\Controller;
12+
13+
use OC\Core\Listener\FeedBackHandler;
14+
use OC\DB\MigratorExecuteSqlEvent;
15+
use OC\Repair\Events\RepairAdvanceEvent;
16+
use OC\Repair\Events\RepairErrorEvent;
17+
use OC\Repair\Events\RepairFinishEvent;
18+
use OC\Repair\Events\RepairInfoEvent;
19+
use OC\Repair\Events\RepairStartEvent;
20+
use OC\Repair\Events\RepairStepEvent;
21+
use OC\Repair\Events\RepairWarningEvent;
22+
use OC\Updater;
23+
use OCP\AppFramework\Http\Attribute\ApiRoute;
24+
use OCP\AppFramework\Http\Attribute\PublicPage;
25+
use OCP\AppFramework\Http\DataResponse;
26+
use OCP\EventDispatcher\IEventDispatcher;
27+
use OCP\IConfig;
28+
use OCP\IEventSourceFactory;
29+
use OCP\IL10N;
30+
use OCP\IRequest;
31+
use OCP\Util;
32+
use Psr\Log\LoggerInterface;
33+
34+
class UpdateController extends \OCP\AppFramework\OCSController {
35+
public function __construct(
36+
string $appName,
37+
IRequest $request,
38+
private readonly IEventSourceFactory $eventSourceFactory,
39+
private readonly IL10N $l,
40+
private readonly IConfig $config,
41+
private readonly Updater $updater,
42+
private readonly IEventDispatcher $dispatcher,
43+
private readonly LoggerInterface $logger,
44+
) {
45+
parent::__construct($appName, $request);
46+
}
47+
48+
#[ApiRoute(verb: 'GET', url: '/update', root: '/core')]
49+
#[PublicPage]
50+
public function update(): DataResponse {
51+
if (!str_contains(@ini_get('disable_functions'), 'set_time_limit')) {
52+
@set_time_limit(0);
53+
}
54+
55+
\OC_User::setIncognitoMode(true);
56+
57+
$eventSource = $this->eventSourceFactory->create();
58+
// need to send an initial message to force-init the event source,
59+
// which will then trigger its own CSRF check and produces its own CSRF error
60+
// message
61+
$eventSource->send('success', $this->l->t('Preparing update'));
62+
if (!Util::needUpgrade()) {
63+
$eventSource->send('notice', $this->l->t('Already up to date'));
64+
$eventSource->send('done', '');
65+
$eventSource->close();
66+
return new DataResponse([]);
67+
}
68+
69+
if ($this->config->getSystemValueBool('upgrade.disable-web', false)) {
70+
$eventSource->send('failure', $this->l->t('Please use the command line updater because updating via browser is disabled in your config.php.'));
71+
$eventSource->close();
72+
return new DataResponse([]);
73+
}
74+
75+
// if a user is currently logged in, their session must be ignored to
76+
// avoid side effects
77+
\OC_User::setIncognitoMode(true);
78+
79+
$incompatibleApps = [];
80+
$incompatibleOverwrites = $this->config->getSystemValue('app_install_overwrite', []);
81+
82+
$this->dispatcher->addListener(
83+
MigratorExecuteSqlEvent::class,
84+
function (MigratorExecuteSqlEvent $event) use ($eventSource): void {
85+
$eventSource->send('success', $this->l->t('[%d / %d]: %s', [$event->getCurrentStep(), $event->getMaxStep(), $event->getSql()]));
86+
}
87+
);
88+
$feedBack = new FeedBackHandler($eventSource, $this->l);
89+
$this->dispatcher->addListener(RepairStartEvent::class, $feedBack->handleRepairFeedback(...));
90+
$this->dispatcher->addListener(RepairAdvanceEvent::class, $feedBack->handleRepairFeedback(...));
91+
$this->dispatcher->addListener(RepairFinishEvent::class, $feedBack->handleRepairFeedback(...));
92+
$this->dispatcher->addListener(RepairStepEvent::class, $feedBack->handleRepairFeedback(...));
93+
$this->dispatcher->addListener(RepairInfoEvent::class, $feedBack->handleRepairFeedback(...));
94+
$this->dispatcher->addListener(RepairWarningEvent::class, $feedBack->handleRepairFeedback(...));
95+
$this->dispatcher->addListener(RepairErrorEvent::class, $feedBack->handleRepairFeedback(...));
96+
97+
$this->updater->listen('\OC\Updater', 'maintenanceEnabled', function () use ($eventSource): void {
98+
$eventSource->send('success', $this->l->t('Turned on maintenance mode'));
99+
});
100+
$this->updater->listen('\OC\Updater', 'maintenanceDisabled', function () use ($eventSource): void {
101+
$eventSource->send('success', $this->l->t('Turned off maintenance mode'));
102+
});
103+
$this->updater->listen('\OC\Updater', 'maintenanceActive', function () use ($eventSource): void {
104+
$eventSource->send('success', $this->l->t('Maintenance mode is kept active'));
105+
});
106+
$this->updater->listen('\OC\Updater', 'dbUpgradeBefore', function () use ($eventSource): void {
107+
$eventSource->send('success', $this->l->t('Updating database schema'));
108+
});
109+
$this->updater->listen('\OC\Updater', 'dbUpgrade', function () use ($eventSource): void {
110+
$eventSource->send('success', $this->l->t('Updated database'));
111+
});
112+
$this->updater->listen('\OC\Updater', 'upgradeAppStoreApp', function ($app) use ($eventSource): void {
113+
$eventSource->send('success', $this->l->t('Update app "%s" from App Store', [$app]));
114+
});
115+
$this->updater->listen('\OC\Updater', 'appSimulateUpdate', function ($app) use ($eventSource): void {
116+
$eventSource->send('success', $this->l->t('Checking whether the database schema for %s can be updated (this can take a long time depending on the database size)', [$app]));
117+
});
118+
$this->updater->listen('\OC\Updater', 'appUpgrade', function ($app, $version) use ($eventSource): void {
119+
$eventSource->send('success', $this->l->t('Updated "%1$s" to %2$s', [$app, $version]));
120+
});
121+
$this->updater->listen('\OC\Updater', 'incompatibleAppDisabled', function ($app) use (&$incompatibleApps, &$incompatibleOverwrites): void {
122+
if (!in_array($app, $incompatibleOverwrites)) {
123+
$incompatibleApps[] = $app;
124+
}
125+
});
126+
$this->updater->listen('\OC\Updater', 'failure', function ($message) use ($eventSource): void {
127+
$eventSource->send('failure', $message);
128+
$this->config->setSystemValue('maintenance', false);
129+
});
130+
$this->updater->listen('\OC\Updater', 'setDebugLogLevel', function ($logLevel, $logLevelName) use ($eventSource): void {
131+
$eventSource->send('success', $this->l->t('Set log level to debug'));
132+
});
133+
$this->updater->listen('\OC\Updater', 'resetLogLevel', function ($logLevel, $logLevelName) use ($eventSource): void {
134+
$eventSource->send('success', $this->l->t('Reset log level'));
135+
});
136+
$this->updater->listen('\OC\Updater', 'startCheckCodeIntegrity', function () use ($eventSource): void {
137+
$eventSource->send('success', $this->l->t('Starting code integrity check'));
138+
});
139+
$this->updater->listen('\OC\Updater', 'finishedCheckCodeIntegrity', function () use ($eventSource): void {
140+
$eventSource->send('success', $this->l->t('Finished code integrity check'));
141+
});
142+
143+
try {
144+
$this->updater->upgrade();
145+
} catch (\Exception $e) {
146+
$this->logger->error(
147+
$e->getMessage(),
148+
[
149+
'exception' => $e,
150+
'app' => 'update',
151+
]);
152+
$eventSource->send('failure', get_class($e) . ': ' . $e->getMessage());
153+
$eventSource->close();
154+
return new DataResponse([]);
155+
}
156+
157+
$disabledApps = [];
158+
foreach ($incompatibleApps as $app) {
159+
$disabledApps[$app] = $this->l->t('%s (incompatible)', [$app]);
160+
}
161+
162+
if (!empty($disabledApps)) {
163+
$eventSource->send('notice', $this->l->t('The following apps have been disabled: %s', [implode(', ', $disabledApps)]));
164+
}
165+
166+
$eventSource->send('done', '');
167+
$eventSource->close();
168+
return new DataResponse([]);
169+
}
170+
}

core/ajax/update.php

Lines changed: 0 additions & 151 deletions
This file was deleted.

core/routes.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,4 @@
1010
* SPDX-License-Identifier: AGPL-3.0-only
1111
*/
1212
/** @var Router $this */
13-
// Core ajax actions
14-
// Routing
15-
$this->create('core_ajax_update', '/core/ajax/update.php')
16-
->actionInclude('core/ajax/update.php');
17-
1813
$this->create('heartbeat', '/heartbeat')->get();

core/src/views/UpdaterAdmin.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
} from '@mdi/js'
1515
import { loadState } from '@nextcloud/initial-state'
1616
import { t } from '@nextcloud/l10n'
17-
import { generateFilePath } from '@nextcloud/router'
17+
import { generateOcsUrl } from '@nextcloud/router'
1818
import { NcButton, NcIconSvgWrapper, NcLoadingIcon } from '@nextcloud/vue'
1919
import { computed, onMounted, onUnmounted, ref } from 'vue'
2020
import NcGuestContent from '@nextcloud/vue/components/NcGuestContent'
@@ -92,7 +92,7 @@ async function onStartUpdate() {
9292
}
9393
9494
isUpdateRunning.value = true
95-
const eventSource = new OCEventSource(generateFilePath('core', '', 'ajax/update.php'))
95+
const eventSource = new OCEventSource(generateOcsUrl('/core/update'))
9696
eventSource.listen('success', (message) => {
9797
messages.value.push({ message, type: 'success' })
9898
})

lib/composer/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1485,6 +1485,7 @@
14851485
'OC\\Core\\Controller\\TwoFactorChallengeController' => $baseDir . '/core/Controller/TwoFactorChallengeController.php',
14861486
'OC\\Core\\Controller\\UnifiedSearchController' => $baseDir . '/core/Controller/UnifiedSearchController.php',
14871487
'OC\\Core\\Controller\\UnsupportedBrowserController' => $baseDir . '/core/Controller/UnsupportedBrowserController.php',
1488+
'OC\\Core\\Controller\\UpdateController' => $baseDir . '/core/Controller/UpdateController.php',
14881489
'OC\\Core\\Controller\\UserController' => $baseDir . '/core/Controller/UserController.php',
14891490
'OC\\Core\\Controller\\WalledGardenController' => $baseDir . '/core/Controller/WalledGardenController.php',
14901491
'OC\\Core\\Controller\\WebAuthnController' => $baseDir . '/core/Controller/WebAuthnController.php',

lib/composer/composer/autoload_static.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1526,6 +1526,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
15261526
'OC\\Core\\Controller\\TwoFactorChallengeController' => __DIR__ . '/../../..' . '/core/Controller/TwoFactorChallengeController.php',
15271527
'OC\\Core\\Controller\\UnifiedSearchController' => __DIR__ . '/../../..' . '/core/Controller/UnifiedSearchController.php',
15281528
'OC\\Core\\Controller\\UnsupportedBrowserController' => __DIR__ . '/../../..' . '/core/Controller/UnsupportedBrowserController.php',
1529+
'OC\\Core\\Controller\\UpdateController' => __DIR__ . '/../../..' . '/core/Controller/UpdateController.php',
15291530
'OC\\Core\\Controller\\UserController' => __DIR__ . '/../../..' . '/core/Controller/UserController.php',
15301531
'OC\\Core\\Controller\\WalledGardenController' => __DIR__ . '/../../..' . '/core/Controller/WalledGardenController.php',
15311532
'OC\\Core\\Controller\\WebAuthnController' => __DIR__ . '/../../..' . '/core/Controller/WebAuthnController.php',

0 commit comments

Comments
 (0)