-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
fix(settings): Rework download/install/update/remove app handling and respect read-only app roots #51667
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
fix(settings): Rework download/install/update/remove app handling and respect read-only app roots #51667
Changes from all commits
4e8786c
75b59b9
f206c60
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -347,7 +347,6 @@ public function listApps(): JSONResponse { | |
| } | ||
| } | ||
| $appData['groups'] = $groups; | ||
| $appData['canUnInstall'] = !$appData['active'] && $appData['removable']; | ||
|
|
||
| // fix licence vs license | ||
| if (isset($appData['license']) && !isset($appData['licence'])) { | ||
|
|
@@ -381,6 +380,14 @@ public function listApps(): JSONResponse { | |
| * @throws \Exception | ||
| */ | ||
| private function getAppsForCategory($requestedCategory = ''): array { | ||
| $anyAppsRootWritable = false; | ||
| foreach (\OC::$APPSROOTS as $appsRoot) { | ||
| if ($appsRoot['writable'] ?? false) { | ||
| $anyAppsRootWritable = true; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| $versionParser = new VersionParser(); | ||
| $formattedApps = []; | ||
| $apps = $this->appFetcher->get(); | ||
|
|
@@ -411,11 +418,23 @@ private function getAppsForCategory($requestedCategory = ''): array { | |
| } | ||
| $phpVersion = $versionParser->getVersion($app['releases'][0]['rawPhpVersionSpec']); | ||
|
|
||
| $needsDownload = true; | ||
| $canUpdate = false; | ||
| $canUnInstall = false; | ||
|
|
||
| try { | ||
| $this->appManager->getAppPath($app['id']); | ||
| $existsLocally = true; | ||
| $appPath = $this->appManager->getAppPath($app['id']); | ||
| $needsDownload = false; | ||
|
|
||
| $appRootPath = dirname($appPath); | ||
| foreach (\OC::$APPSROOTS as $appsRoot) { | ||
| if ($appsRoot['path'] === $appRootPath) { | ||
| $appsRootWritable = $appsRoot['writable'] ?? false; | ||
| $canUpdate = $appsRootWritable; | ||
| $canUnInstall = $appsRootWritable; | ||
| } | ||
| } | ||
| } catch (AppPathNotFoundException) { | ||
| $existsLocally = false; | ||
| } | ||
|
|
||
| $phpDependencies = []; | ||
|
|
@@ -482,9 +501,11 @@ private function getAppsForCategory($requestedCategory = ''): array { | |
| 'score' => $app['ratingOverall'], | ||
| 'ratingNumOverall' => $app['ratingNumOverall'], | ||
| 'ratingNumThresholdReached' => $app['ratingNumOverall'] > 5, | ||
| 'removable' => $existsLocally, | ||
| 'active' => $this->appManager->isEnabledForUser($app['id']), | ||
| 'needsDownload' => !$existsLocally, | ||
| 'canDownload' => $anyAppsRootWritable, | ||
| 'canUpdate' => $canUpdate, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would remove the crucial part that allows updating apps into another folder when the current one is read-only.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But unfortunately it's also the case, that some paths are hardcoded to
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
What? Can you provide links? |
||
| 'canUnInstall' => $canUnInstall && !$this->appManager->isShipped($app['id']), | ||
| 'active' => $this->appManager->isEnabledForAnyone($app['id']), | ||
| 'needsDownload' => $needsDownload, | ||
| 'groups' => $groups, | ||
| 'fromAppStore' => true, | ||
| 'appstoreData' => $app, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,7 @@ | |
| use OC\DB\MigrationService; | ||
| use OC_App; | ||
| use OC_Helper; | ||
| use OCP\App\AppPathNotFoundException; | ||
| use OCP\App\IAppManager; | ||
| use OCP\HintException; | ||
| use OCP\Http\Client\IClientService; | ||
|
|
@@ -176,10 +177,28 @@ private function splitCerts(string $cert): array { | |
| */ | ||
| public function downloadApp(string $appId, bool $allowUnstable = false): void { | ||
| $appId = strtolower($appId); | ||
| $appManager = \OCP\Server::get(IAppManager::class); | ||
|
|
||
| $apps = $this->appFetcher->get($allowUnstable); | ||
| foreach ($apps as $app) { | ||
| if ($app['id'] === $appId) { | ||
| try { | ||
| $appPath = $appManager->getAppPath($appId); | ||
| } catch (AppPathNotFoundException) { | ||
| $appPath = OC_App::getInstallPath() . '/' . $appId; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
|
|
||
| $appsRootWritable = false; | ||
| $appRootPath = dirname($appPath); | ||
| foreach (\OC::$APPSROOTS as $appsRoot) { | ||
| if ($appsRoot['path'] === $appRootPath) { | ||
| $appsRootWritable = $appsRoot['writable'] ?? false; | ||
| } | ||
| } | ||
| if (!$appsRootWritable) { | ||
| throw new \Exception(sprintf('App %s can not be updated because the app root is not writable.', $appId)); | ||
| } | ||
|
|
||
| // Load the certificate | ||
| $certificate = new X509(); | ||
| $rootCrt = file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt'); | ||
|
|
@@ -322,15 +341,14 @@ public function downloadApp(string $appId, bool $allowUnstable = false): void { | |
| ); | ||
| } | ||
|
|
||
| $baseDir = OC_App::getInstallPath() . '/' . $appId; | ||
| // Remove old app with the ID if existent | ||
| OC_Helper::rmdirr($baseDir); | ||
| OC_Helper::rmdirr($appPath); | ||
| // Move to app folder | ||
| if (@mkdir($baseDir)) { | ||
| if (@mkdir($appPath)) { | ||
| $extractDir .= '/' . $folders[0]; | ||
| OC_Helper::copyr($extractDir, $baseDir); | ||
| OC_Helper::copyr($extractDir, $appPath); | ||
| } | ||
| OC_Helper::copyr($extractDir, $baseDir); | ||
| OC_Helper::copyr($extractDir, $appPath); | ||
| OC_Helper::rmdirr($extractDir); | ||
| return; | ||
| } | ||
|
|
@@ -446,11 +464,26 @@ public function isDownloaded(string $name): bool { | |
| */ | ||
| public function removeApp(string $appId): bool { | ||
| if ($this->isDownloaded($appId)) { | ||
| if (\OCP\Server::get(IAppManager::class)->isShipped($appId)) { | ||
| $appManager = \OCP\Server::get(IAppManager::class); | ||
|
|
||
| if ($appManager->isShipped($appId)) { | ||
| return false; | ||
| } | ||
| $appDir = OC_App::getInstallPath() . '/' . $appId; | ||
| OC_Helper::rmdirr($appDir); | ||
|
|
||
| $appPath = $appManager->getAppPath($appId); | ||
|
|
||
| $appsRootWritable = false; | ||
| $appRootPath = dirname($appPath); | ||
| foreach (\OC::$APPSROOTS as $appsRoot) { | ||
| if ($appsRoot['path'] === $appRootPath) { | ||
| $appsRootWritable = $appsRoot['writable'] ?? false; | ||
| } | ||
| } | ||
|
Comment on lines
+475
to
+481
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code is duplicated in your PR, please create a method for it. |
||
| if (!$appsRootWritable) { | ||
| return false; | ||
| } | ||
|
|
||
| OC_Helper::rmdirr($appPath); | ||
| return true; | ||
| } else { | ||
| $this->logger->error('can\'t remove app ' . $appId . '. It is not installed.'); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should have a public getter somewhere for app roots instead of accessing an OC static var from an application.
Also, this logic of testing whether at least one app folder is writable could be in a method of the AppManager directly.