Completed
Pull Request — master (#27930)
by Tom
52:10
created

Apps   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 235
Duplicated Lines 14.04 %

Coupling/Cohesion

Components 1
Dependencies 13

Importance

Changes 0
Metric Value
dl 33
loc 235
rs 9.2
c 0
b 0
f 0
wmc 34
lcom 1
cbo 13

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A getName() 0 3 1
A isCoreUpdate() 9 9 2
A requiresMarketEnable() 0 8 2
F run() 24 79 12
C getAppsFromMarket() 0 34 8
B getAppsToUpgrade() 0 20 5
A getOccDisableMessage() 0 12 2
A loadApp() 0 3 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
/**
3
 * @author Victor Dubiniuk <[email protected]>
4
 *
5
 * @copyright Copyright (c) 2017, ownCloud GmbH
6
 * @license AGPL-3.0
7
 *
8
 * This code is free software: you can redistribute it and/or modify
9
 * it under the terms of the GNU Affero General Public License, version 3,
10
 * as published by the Free Software Foundation.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
 * GNU Affero General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Affero General Public License, version 3,
18
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
19
 *
20
 */
21
22
namespace OC\Repair;
23
24
use OC\RepairException;
25
use OC_App;
26
use OCP\App\AppAlreadyInstalledException;
27
use OCP\App\AppManagerException;
28
use OCP\App\AppNotFoundException;
29
use OCP\App\AppNotInstalledException;
30
use OCP\App\AppUpdateNotFoundException;
31
use OCP\App\IAppManager;
32
use OCP\Migration\IOutput;
33
use OCP\Migration\IRepairStep;
34
use OCP\Util;
35
use Symfony\Component\EventDispatcher\EventDispatcher;
36
use Symfony\Component\EventDispatcher\GenericEvent;
37
use OCP\IConfig;
38
39
class Apps implements IRepairStep {
40
41
	const KEY_COMPATIBLE = 'compatible';
42
	const KEY_INCOMPATIBLE = 'incompatible';
43
	const KEY_MISSING = 'missing';
44
45
	/** @var  IAppManager */
46
	private $appManager;
47
48
	/** @var  EventDispatcher */
49
	private $eventDispatcher;
50
51
	/** @var IConfig */
52
	private $config;
53
54
	/**
55
	 * Apps constructor.
56
	 *
57
	 * @param IAppManager $appManager
58
	 */
59
	public function __construct(IAppManager $appManager, EventDispatcher $eventDispatcher, IConfig $config) {
60
		$this->appManager = $appManager;
61
		$this->eventDispatcher = $eventDispatcher;
62
		$this->config = $config;
63
	}
64
65
	/**
66
	 * @return string
67
	 */
68
	public function getName() {
69
		return 'Upgrade app code from the marketplace';
70
	}
71
72
	/**
73
	 * Are we updating from an older version?
74
	 * @return bool
75
	 */
76 View Code Duplication
	private function isCoreUpdate() {
77
		$installedVersion = $this->config->getSystemValue('version', '0.0.0');
78
		$currentVersion = implode('.', \OCP\Util::getVersion());
79
		$versionDiff = version_compare($currentVersion, $installedVersion);
80
		if ($versionDiff > 0) {
81
			return true;
82
		}
83
		return false;
84
	}
85
86
	/**
87
	 * If we are updating from <= 10.0.0 we need to enable the marketplace before running the update
88
	 * @return bool
89
	 */
90
	private function requiresMarketEnable() {
91
		$installedVersion = $this->config->getSystemValue('version', '0.0.0');
92
		$versionDiff = version_compare('10.0.0', $installedVersion);
93
		if ($versionDiff >= 0) {
94
			return true;
95
		}
96
		return false;
97
	}
98
99
	/**
100
	 * @param IOutput $output
101
	 * @throws RepairException
102
	 */
103
	public function run(IOutput $output) {
104
		$isCoreUpdate = $this->isCoreUpdate();
105
		$appsToUpgrade = $this->getAppsToUpgrade();
106
		$failedCompatibleApps = [];
107
		$failedMissingApps = $appsToUpgrade[self::KEY_MISSING];
108
		$failedIncompatibleApps = $appsToUpgrade[self::KEY_INCOMPATIBLE];
109
		$hasNotUpdatedCompatibleApps = 0;
110
		$requiresMarketEnable = $this->requiresMarketEnable();
111
112
		if($isCoreUpdate && $requiresMarketEnable) {
113
			// Then we need to enable the market app to support app updates / downloads during upgrade
114
			$output->info('Enabling market app to assist with update');
115
			$this->appManager->enableApp('market');
116
		}
117
118
		// Check if we can use the marketplace to update apps as needed?
119
		if($this->appManager->isEnabledForUser('market')) {
120
			// Use market to fix missing / old apps
121
			$this->loadApp('market');
122
			$output->info('Using market to update existing apps');
123
			try {
124
				// Try to update incompatible apps
125 View Code Duplication
				if(!empty($appsToUpgrade[self::KEY_INCOMPATIBLE])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
126
					$output->info('Attempting to update the following existing but incompatible app from market: '.implode(', ', $appsToUpgrade[self::KEY_INCOMPATIBLE]));
127
					$failedIncompatibleApps = $this->getAppsFromMarket(
128
						$output,
129
						$appsToUpgrade[self::KEY_INCOMPATIBLE],
130
						'upgradeAppStoreApp'
131
					);
132
				}
133
134
				// Try to download missing apps
135 View Code Duplication
				if(!empty($appsToUpgrade[self::KEY_MISSING])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
136
					$output->info('Attempting to update the following missing apps from market: '.implode(', ', $appsToUpgrade[self::KEY_MISSING]));
137
					$failedMissingApps = $this->getAppsFromMarket(
138
						$output,
139
						$appsToUpgrade[self::KEY_MISSING],
140
						'reinstallAppStoreApp'
141
					);
142
				}
143
144
				// Try to update compatible apps
145 View Code Duplication
				if(!empty($appsToUpgrade[self::KEY_COMPATIBLE])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
146
					$output->info('Attempting to update the following existing compatible apps from market: '.implode(', ', $appsToUpgrade[self::KEY_MISSING]));
147
					$failedCompatibleApps = $this->getAppsFromMarket(
148
						$output,
149
						$appsToUpgrade[self::KEY_COMPATIBLE],
150
						'upgradeAppStoreApp'
151
					);
152
				}
153
154
				$hasNotUpdatedCompatibleApps = count($failedCompatibleApps);
155
			} catch (AppManagerException $e) {
156
				$output->warning('No connection to marketplace: ' . $e->getPrevious());
157
			}
158
		} else {
159
			// No market available, output error and continue attempt
160
			$output->warning('Market app is unavailable for updating of apps. Enable with: occ:app:enable market');
161
		}
162
163
		$hasBlockingMissingApps = count($failedMissingApps);
164
		$hasBlockingIncompatibleApps = count($failedIncompatibleApps);
165
166
		if ($hasBlockingIncompatibleApps || $hasBlockingMissingApps) {
167
			// fail
168
			$output->warning('You have incompatible or missing apps enabled that could not be found or updated via the marketplace.');
169
			$output->warning(
170
				'please install app manually with tarball or disable them with:'
171
				. $this->getOccDisableMessage(array_merge($failedIncompatibleApps, $failedMissingApps))
172
			);
173
174
			throw new RepairException('Upgrade is not possible');
175
		} elseif ($hasNotUpdatedCompatibleApps) {
176
			foreach ($failedCompatibleApps as $app) {
177
				// TODO: Show reason
178
				$output->info("App was not updated: $app");
179
			}
180
		}
181
	}
182
183
	/**
184
	 * Upgrade appList from market
185
	 * Return an array of apps that were not upgraded successfully
186
	 *
187
	 * @param IOutput $output
188
	 * @param string[] $appList
189
	 * @return array
190
	 * @throws AppManagerException
191
	 */
192
	protected function getAppsFromMarket(IOutput $output, $appList, $event) {
193
		$failedApps = [];
194
		foreach ($appList as $app) {
195
			$output->info("Fetching app from market: $app");
196
			try {
197
				$this->eventDispatcher->dispatch(
198
					sprintf('%s::%s', IRepairStep::class, $event),
199
					new GenericEvent($app)
200
				);
201
			} catch (AppAlreadyInstalledException $e) {
202
				$output->info($e->getMessage());
203
				$failedApps[] = $app;
204
			} catch (AppNotInstalledException $e) {
205
				$output->info($e->getMessage());
206
				$failedApps[] = $app;
207
			} catch (AppNotFoundException $e) {
208
				$output->info($e->getMessage());
209
				$failedApps[] = $app;
210
			} catch (AppUpdateNotFoundException $e) {
211
				$output->info($e->getMessage());
212
				$failedApps[] = $app;
213
			} catch (AppManagerException $e) {
214
				// No connection to market. Abort.
215
				throw $e;
216
			} catch (\Exception $e) {
217
				// TODO: check the reason
218
				$failedApps[] = $app;
219
				$output->warning(get_class($e));
220
221
				$output->warning($e->getMessage());
222
			}
223
		}
224
		return $failedApps;
225
	}
226
227
	/**
228
	 * Get app list separated as compatible/incompatible/missing
229
	 *
230
	 * @return array
231
	 */
232
	protected function getAppsToUpgrade() {
233
		$installedApps = $this->appManager->getInstalledApps();
234
		$appsToUpgrade = [
235
			self::KEY_COMPATIBLE => [],
236
			self::KEY_INCOMPATIBLE => [],
237
			self::KEY_MISSING => []
238
		];
239
240
		foreach ($installedApps as $appId) {
241
			$info = $this->appManager->getAppInfo($appId);
242
			if (!isset($info['id']) || is_null($info['id'])) {
243
				$appsToUpgrade[self::KEY_MISSING][] = $appId;
244
				continue;
245
			}
246
			$version = Util::getVersion();
247
			$key = (\OC_App::isAppCompatible($version, $info)) ? self::KEY_COMPATIBLE : self::KEY_INCOMPATIBLE;
0 ignored issues
show
Documentation introduced by
$version is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
248
			$appsToUpgrade[$key][] = $appId;
249
		}
250
		return $appsToUpgrade;
251
	}
252
253
	protected function getOccDisableMessage($appList) {
254
		if (!count($appList)) {
255
			return '';
256
		}
257
		$appList = array_map(
258
			function ($appId) {
259
				return "occ app:disable $appId";
260
			},
261
			$appList
262
		);
263
		return "\n" . implode("\n", $appList);
264
	}
265
266
	/**
267
	 * @codeCoverageIgnore
268
	 * @param string $app
269
	 */
270
	protected function loadApp($app) {
271
		OC_App::loadApp($app, false);
272
	}
273
}
274