From 2552b97c4a9e060ff6d67973aba45df205e01868 Mon Sep 17 00:00:00 2001 From: Julien Veyssier Date: Tue, 14 Apr 2026 11:30:34 +0200 Subject: [PATCH] fix(upgrade): restore missing apps on upgrade Signed-off-by: Julien Veyssier --- lib/private/Updater.php | 14 ++++++++++++++ tests/lib/UpdaterTest.php | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/lib/private/Updater.php b/lib/private/Updater.php index da7b52e549306..64263bb895aab 100644 --- a/lib/private/Updater.php +++ b/lib/private/Updater.php @@ -23,6 +23,7 @@ use OC\Repair\Events\RepairStartEvent; use OC\Repair\Events\RepairStepEvent; use OC\Repair\Events\RepairWarningEvent; +use OCP\App\AppPathNotFoundException; use OCP\App\IAppManager; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventDispatcher; @@ -390,6 +391,8 @@ private function upgradeAppStoreApps(array $apps, array $previousEnableStates = $this->emit('\OC\Updater', 'checkAppStoreApp', [$app]); if (isset($previousEnableStates[$app])) { + $this->restoreMissingAppStoreApp($app); + if (!empty($previousEnableStates[$app]) && is_array($previousEnableStates[$app])) { $this->appManager->enableAppForGroups($app, $previousEnableStates[$app]); } elseif ($previousEnableStates[$app] === 'yes') { @@ -404,6 +407,17 @@ private function upgradeAppStoreApps(array $apps, array $previousEnableStates = } } + private function restoreMissingAppStoreApp(string $appId): void { + try { + $this->appManager->getAppPath($appId, true); + } catch (AppPathNotFoundException) { + // the app was not found locally but we know it was previously enabled + // so we automatically download it from the appstore and run its missing migrations + $this->installer->downloadApp($appId); + $this->installer->installApp($appId); + } + } + private function logAllEvents(): void { $log = $this->log; diff --git a/tests/lib/UpdaterTest.php b/tests/lib/UpdaterTest.php index 23a4d5329c87c..c7717327ca97d 100644 --- a/tests/lib/UpdaterTest.php +++ b/tests/lib/UpdaterTest.php @@ -11,6 +11,7 @@ use OC\Installer; use OC\IntegrityCheck\Checker; use OC\Updater; +use OCP\App\AppPathNotFoundException; use OCP\App\IAppManager; use OCP\IAppConfig; use OCP\IConfig; @@ -107,4 +108,36 @@ public function testIsUpgradePossible($oldVersion, $newVersion, $allowedVersions $this->assertSame($result, $this->updater->isUpgradePossible($oldVersion, $newVersion, $allowedVersions)); } + + public function testUpgradeAppStoreAppsRestoresMissingAutoDisabledAppBeforeEnabling(): void { + $this->installer->expects($this->once()) + ->method('isUpdateAvailable') + ->with('mailroundcube') + ->willReturn(false); + + $this->installer->expects($this->once()) + ->method('downloadApp') + ->with('mailroundcube'); + + $this->installer->expects($this->once()) + ->method('installApp') + ->with('mailroundcube'); + + $this->appManager->expects($this->once()) + ->method('getAppPath') + ->with('mailroundcube', true) + ->willThrowException(new AppPathNotFoundException('missing')); + + $this->appManager->expects($this->once()) + ->method('enableApp') + ->with('mailroundcube'); + + $this->appManager->expects($this->never()) + ->method('enableAppForGroups'); + + self::invokePrivate($this->updater, 'upgradeAppStoreApps', [ + ['mailroundcube'], + ['mailroundcube' => 'yes'], + ]); + } }