Completed
Pull Request — master (#32178)
by Phil
09:29
created

ListCommand   B

Complexity

Total Complexity 52

Size/Duplication

Total Lines 305
Duplicated Lines 7.54 %

Coupling/Cohesion

Components 2
Dependencies 11

Importance

Changes 0
Metric Value
dl 23
loc 305
rs 7.44
c 0
b 0
f 0
wmc 52
lcom 2
cbo 11

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A configure() 0 31 1
A execute() 0 15 2
F listMounts() 11 180 38
B getColumns() 0 23 7
A getStorageService() 12 12 3

How to fix   Duplicated Code    Complexity   

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:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like ListCommand often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ListCommand, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @author Robin Appelman <[email protected]>
4
 * @author Vincent Petry <[email protected]>
5
 *
6
 * @copyright Copyright (c) 2018, ownCloud GmbH
7
 * @license AGPL-3.0
8
 *
9
 * This code is free software: you can redistribute it and/or modify
10
 * it under the terms of the GNU Affero General Public License, version 3,
11
 * as published by the Free Software Foundation.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
 * GNU Affero General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Affero General Public License, version 3,
19
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
20
 *
21
 */
22
23
namespace OCA\Files_External\Command;
24
25
use OC\Core\Command\Base;
26
use OC\User\NoUserException;
27
use OCP\Files\External\Auth\InvalidAuth;
28
use OCP\Files\External\Backend\InvalidBackend;
29
use OCP\Files\External\IStorageConfig;
30
use OCP\Files\External\Service\IGlobalStoragesService;
31
use OCP\Files\External\Service\IUserStoragesService;
32
use OCP\IUserManager;
33
use OCP\IUserSession;
34
use Symfony\Component\Console\Helper\Table;
35
use Symfony\Component\Console\Input\InputArgument;
36
use Symfony\Component\Console\Input\InputInterface;
37
use Symfony\Component\Console\Input\InputOption;
38
use Symfony\Component\Console\Output\OutputInterface;
39
40
class ListCommand extends Base {
41
	/**
42
	 * @var IGlobalStoragesService
43
	 */
44
	protected $globalService;
45
46
	/**
47
	 * @var IUserStoragesService
48
	 */
49
	protected $userService;
50
51
	/**
52
	 * @var IUserSession
53
	 */
54
	protected $userSession;
55
56
	/**
57
	 * @var IUserManager
58
	 */
59
	protected $userManager;
60
61
	const ALL = -1;
62
63
	public function __construct(IGlobalStoragesService $globalService, IUserStoragesService $userService, IUserSession $userSession, IUserManager $userManager) {
64
		parent::__construct();
65
		$this->globalService = $globalService;
66
		$this->userService = $userService;
67
		$this->userSession = $userSession;
68
		$this->userManager = $userManager;
69
	}
70
71
	protected function configure() {
72
		$this
73
			->setName('files_external:list')
74
			->setDescription('List configured admin or personal mounts')
75
			->addArgument(
76
				'user_id',
77
				InputArgument::OPTIONAL,
78
				'user id to list the personal mounts for, if no user is provided admin mounts will be listed'
79
			)->addOption(
80
				'show-password',
81
				null,
82
				InputOption::VALUE_NONE,
83
				'show passwords and secrets'
84
			)->addOption(
85
				'full',
86
				null,
87
				InputOption::VALUE_NONE,
88
				'don\'t truncate long values in table output'
89
			)->addOption(
90
				'all',
91
				'a',
92
				InputOption::VALUE_NONE,
93
				'show both system wide mounts and all personal mounts'
94
			)->addOption(
95
				'short',
96
				's',
97
				InputOption::VALUE_NONE,
98
				'show only a reduced mount info'
99
			);
100
		parent::configure();
101
	}
102
103
	protected function execute(InputInterface $input, OutputInterface $output) {
104
		if ($input->getOption('all')) {
105
			/** @var  $mounts IStorageConfig[] */
106
			$mounts = $this->globalService->getStorageForAllUsers();
107
			$userId = self::ALL;
108
		} else {
109
			$userId = $input->getArgument('user_id');
110
			$storageService = $this->getStorageService($userId);
111
112
			/** @var  $mounts IStorageConfig[] */
113
			$mounts = $storageService->getAllStorages();
114
		}
115
116
		$this->listMounts($userId, $mounts, $input, $output);
117
	}
118
119
	/**
120
	 * @param $userId $userId
0 ignored issues
show
Documentation introduced by
The doc-type $userId could not be parsed: Unknown type name "$userId" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
121
	 * @param IStorageConfig[] $mounts
122
	 * @param InputInterface $input
123
	 * @param OutputInterface $output
124
	 */
125
	public function listMounts($userId, array $mounts, InputInterface $input, OutputInterface $output) {
126
		$outputType = $input->getOption('output');
127
		$shortView = $input->getOption('short');
128
129
		if (\count($mounts) === 0) {
130
			if ($outputType === self::OUTPUT_FORMAT_JSON || $outputType === self::OUTPUT_FORMAT_JSON_PRETTY) {
131
				$output->writeln('[]');
132
			} else {
133
				if ($userId === self::ALL) {
134
					$output->writeln("<info>No mounts configured</info>");
135
				} elseif ($userId) {
136
					$output->writeln("<info>No mounts configured by $userId</info>");
137
				} else {
138
					$output->writeln("<info>No admin mounts configured</info>");
139
				}
140
			}
141
			return;
142
		}
143
144
		if ($shortView) {
145
			$headers = ['Mount ID', 'Mount Point', 'Type'];
146
		} else {
147
			$headers = ['Mount ID', 'Mount Point', 'Storage', 'Authentication Type', 'Configuration', 'Options'];
148
149
			if (!$userId || $userId === self::ALL) {
150
				$headers[] = 'Applicable Users';
151
				$headers[] = 'Applicable Groups';
152
			}
153
154
			if ($userId === self::ALL) {
155
				$headers[] = 'Type';
156
			}
157
158
			if (!$input->getOption('show-password')) {
159
				$hideKeys = ['password', 'refresh_token', 'token', 'client_secret', 'public_key', 'private_key'];
160
				foreach ($mounts as $mount) {
161
					$config = $mount->getBackendOptions();
162
					foreach ($config as $key => $value) {
163
						if (\in_array($key, $hideKeys)) {
164
							$mount->setBackendOption($key, '***');
165
						}
166
					}
167
				}
168
			}
169
		}
170
		
171
		if ($outputType === self::OUTPUT_FORMAT_JSON || $outputType === self::OUTPUT_FORMAT_JSON_PRETTY) {
172
			$keys = \array_map(function ($header) {
173
				return \strtolower(\str_replace(' ', '_', $header));
174
			}, $headers);
175
176
			if ($shortView) {
177
				$pairs = \array_map(function (IStorageConfig $config) use ($keys, $userId) {
178
					$values = [
179
							$config->getId(),
180
							$config->getMountPoint(),
181
							$config->getType() === IStorageConfig::MOUNT_TYPE_ADMIN ? 'admin' : 'personal'
182
						];
183
184
					return \array_combine($keys, $values);
185
				}, $mounts);
186
			} else {
187
				$pairs = \array_map(function (IStorageConfig $config) use ($keys, $userId) {
188
					$values = [
189
							$config->getId(),
190
							$config->getMountPoint(),
191
							$config->getBackend()->getStorageClass(),
192
							$config->getAuthMechanism()->getIdentifier(),
193
							$config->getBackendOptions(),
194
							$config->getMountOptions()
195
						];
196
					if (!$userId || $userId === self::ALL) {
197
						$values[] = $config->getApplicableUsers();
198
						$values[] = $config->getApplicableGroups();
199
					}
200 View Code Duplication
					if ($userId === self::ALL) {
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...
201
						$values[] = $config->getType() === IStorageConfig::MOUNT_TYPE_ADMIN ? 'admin' : 'personal';
202
					}
203
204
					return \array_combine($keys, $values);
205
				}, $mounts);
206
			}
207
208 View Code Duplication
			if ($outputType === self::OUTPUT_FORMAT_JSON) {
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...
209
				$output->writeln(\json_encode(\array_values($pairs)));
210
			} else {
211
				$output->writeln(\json_encode(\array_values($pairs), JSON_PRETTY_PRINT));
212
			}
213
		} else {
214
215
			// default output style
216
			$full = $input->getOption('full');
217
			$defaultMountOptions = [
218
				'encrypt' => true,
219
				'previews' => true,
220
				'filesystem_check_changes' => 1,
221
				'enable_sharing' => false,
222
				'encoding_compatibility' => false
223
			];
224
			$countInvalid = 0;
225
			// In case adding array elements, add them only after the first two (Mount ID / Mount Point)
226
			// and before the last one entry (Type). Necessary for option -s
227
			$rows = \array_map(function (IStorageConfig $config) use ($shortView, $userId, $defaultMountOptions, $full, &$countInvalid) {
228
				if ($config->getBackend() instanceof InvalidBackend || $config->getAuthMechanism() instanceof InvalidAuth) {
229
					$countInvalid++;
230
				}
231
				$storageConfig = $config->getBackendOptions();
232
				$keys = \array_keys($storageConfig);
233
				$values = \array_values($storageConfig);
234
235
				if (!$full) {
236
					$values = \array_map(function ($value) {
237
						if (\is_string($value) && \strlen($value) > 32) {
238
							return \substr($value, 0, 6) . '...' . \substr($value, -6, 6);
239
						} else {
240
							return $value;
241
						}
242
					}, $values);
243
				}
244
245
				$configStrings = \array_map(function ($key, $value) {
246
					return $key . ': ' . \json_encode($value);
247
				}, $keys, $values);
248
				$configString = \implode(', ', $configStrings);
249
250
				$mountOptions = $config->getMountOptions();
251
				// hide defaults
252
				foreach ($mountOptions as $key => $value) {
253
					if ($value === $defaultMountOptions[$key]) {
254
						unset($mountOptions[$key]);
255
					}
256
				}
257
				$keys = \array_keys($mountOptions);
258
				$values = \array_values($mountOptions);
259
260
				$optionsStrings = \array_map(function ($key, $value) {
261
					return $key . ': ' . \json_encode($value);
262
				}, $keys, $values);
263
				$optionsString = \implode(', ', $optionsStrings);
264
265
				$values = [
266
					$config->getId(),
267
					$config->getMountPoint(),
268
					$config->getBackend()->getText(),
269
					$config->getAuthMechanism()->getText(),
270
					$configString,
271
					$optionsString
272
				];
273
274
				if (!$userId || $userId === self::ALL) {
275
					$applicableUsers = \implode(', ', $config->getApplicableUsers());
276
					$applicableGroups = \implode(', ', $config->getApplicableGroups());
277
					if ($applicableUsers === '' && $applicableGroups === '') {
278
						$applicableUsers = 'All';
279
					}
280
					$values[] = $applicableUsers;
281
					$values[] = $applicableGroups;
282
				}
283
				// This MUST stay the last entry
284 View Code Duplication
				if ($shortView || $userId === self::ALL) {
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...
285
					$values[] = $config->getType() === IStorageConfig::MOUNT_TYPE_ADMIN ? 'Admin' : 'Personal';
286
				}
287
288
				return $values;
289
			}, $mounts);
290
291
			$table = new Table($output);
292
			$table->setHeaders($headers);
293
			$table->setRows($this->getColumns($shortView, $rows));
294
			$table->render();
295
296
			if ($countInvalid > 0) {
297
				$output->writeln(
298
					"<error>Number of invalid storages found: $countInvalid.\n" .
299
					"The listed configuration details are likely incomplete.\n" .
300
					"Please make sure that all related apps that provide these storages are enabled or delete these.</error>"
301
				);
302
			}
303
		}
304
	}
305
306
	// Removes all unused columns for option -s.
307
	// Only the first two (Mount ID / Mount Point) and the last column (Mount Type) is kept.
308
	protected function getColumns($shortView, $rows) {
309
		if ($shortView) {
310
			$newRows = [];
311
			// $subArr is a copy of $rows anyways...
312
			foreach ($rows as $subArr) {
313
				$c = \count($subArr) - 1;
314
				$u = false;
315
				foreach ($subArr as $key => $val) {
316
					if ((int)$key > 1 && (int)$key < $c) {
317
						unset($subArr[$key]);
318
						$u = true;
319
					}
320
				}
321
				if ($u) {
322
					$subArr = \array_values($subArr);
323
				}
324
				$newRows[] = $subArr;
325
			}
326
			return $newRows;
327
		} else {
328
			return $rows;
329
		}
330
	}
331
	
332 View Code Duplication
	protected function getStorageService($userId) {
333
		if (!empty($userId)) {
334
			$user = $this->userManager->get($userId);
335
			if ($user === null) {
336
				throw new NoUserException("user $userId not found");
337
			}
338
			$this->userSession->setUser($user);
339
			return $this->userService;
340
		} else {
341
			return $this->globalService;
342
		}
343
	}
344
}
345