Passed
Push — master ( 76c318...963d96 )
by Morris
88:25 queued 74:01
created

Node   F

Complexity

Total Complexity 66

Size/Duplication

Total Lines 394
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 108
dl 0
loc 394
rs 3.12
c 0
b 0
f 0
wmc 66

40 Methods

Rating   Name   Duplication   Size   Complexity  
A getFileInfo() 0 13 4
A stat() 0 2 1
A isMounted() 0 2 1
A getChecksum() 0 1 1
A createNonExistingNode() 0 2 1
A isCreatable() 0 2 1
A isEncrypted() 0 2 1
A getMimeType() 0 2 1
A isReadable() 0 2 1
A getEtag() 0 2 1
A checkPermissions() 0 2 1
A isUpdateable() 0 2 1
A isDeletable() 0 2 1
A touch() 0 13 4
A getOwner() 0 2 1
A getPermissions() 0 2 1
A getSize() 0 2 1
A delete() 0 1 1
A getId() 0 2 1
A getPath() 0 2 1
A getMTime() 0 2 1
A normalizePath() 0 18 5
A sendHooks() 0 3 2
A getMountPoint() 0 2 1
A __construct() 0 5 1
A getMimePart() 0 2 1
A getStorage() 0 3 1
A isShareable() 0 2 1
A getName() 0 2 1
A isValidPath() 0 8 5
A isShared() 0 2 1
A getInternalPath() 0 3 1
A getParent() 0 6 4
A getType() 0 2 1
A lock() 0 2 1
A unlock() 0 2 1
A getExtension() 0 2 1
A changeLock() 0 2 1
A move() 0 17 5
A copy() 0 16 5

How to fix   Complexity   

Complex Class

Complex classes like Node 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.

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 Node, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Bernhard Posselt <[email protected]>
6
 * @author Joas Schilling <[email protected]>
7
 * @author Morris Jobke <[email protected]>
8
 * @author Robin Appelman <[email protected]>
9
 * @author Roeland Jago Douma <[email protected]>
10
 * @author Vincent Petry <[email protected]>
11
 *
12
 * @license AGPL-3.0
13
 *
14
 * This code is free software: you can redistribute it and/or modify
15
 * it under the terms of the GNU Affero General Public License, version 3,
16
 * as published by the Free Software Foundation.
17
 *
18
 * This program is distributed in the hope that it will be useful,
19
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21
 * GNU Affero General Public License for more details.
22
 *
23
 * You should have received a copy of the GNU Affero General Public License, version 3,
24
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
25
 *
26
 */
27
28
namespace OC\Files\Node;
29
30
use OC\Files\Filesystem;
31
use OCP\Files\FileInfo;
32
use OCP\Files\InvalidPathException;
33
use OCP\Files\NotFoundException;
34
use OCP\Files\NotPermittedException;
35
36
// FIXME: this class really should be abstract
37
class Node implements \OCP\Files\Node {
38
	/**
39
	 * @var \OC\Files\View $view
40
	 */
41
	protected $view;
42
43
	/**
44
	 * @var \OC\Files\Node\Root $root
45
	 */
46
	protected $root;
47
48
	/**
49
	 * @var string $path
50
	 */
51
	protected $path;
52
53
	/**
54
	 * @var \OCP\Files\FileInfo
55
	 */
56
	protected $fileInfo;
57
58
	/**
59
	 * @param \OC\Files\View $view
60
	 * @param \OCP\Files\IRootFolder $root
61
	 * @param string $path
62
	 * @param FileInfo $fileInfo
63
	 */
64
	public function __construct($root, $view, $path, $fileInfo = null) {
65
		$this->view = $view;
66
		$this->root = $root;
0 ignored issues
show
Documentation Bug introduced by
$root is of type OCP\Files\IRootFolder, but the property $root was declared to be of type OC\Files\Node\Root. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
67
		$this->path = $path;
68
		$this->fileInfo = $fileInfo;
69
	}
70
71
	/**
72
	 * Creates a Node of the same type that represents a non-existing path
73
	 *
74
	 * @param string $path path
75
	 * @return string non-existing node class
76
	 */
77
	protected function createNonExistingNode($path) {
78
		throw new \Exception('Must be implemented by subclasses');
79
	}
80
81
	/**
82
	 * Returns the matching file info
83
	 *
84
	 * @return FileInfo
85
	 * @throws InvalidPathException
86
	 * @throws NotFoundException
87
	 */
88
	public function getFileInfo() {
89
		if (!Filesystem::isValidPath($this->path)) {
90
			throw new InvalidPathException();
91
		}
92
		if (!$this->fileInfo) {
93
			$fileInfo = $this->view->getFileInfo($this->path);
94
			if ($fileInfo instanceof FileInfo) {
95
				$this->fileInfo = $fileInfo;
96
			} else {
97
				throw new NotFoundException();
98
			}
99
		}
100
		return $this->fileInfo;
101
	}
102
103
	/**
104
	 * @param string[] $hooks
105
	 */
106
	protected function sendHooks($hooks) {
107
		foreach ($hooks as $hook) {
108
			$this->root->emit('\OC\Files', $hook, array($this));
109
		}
110
	}
111
112
	/**
113
	 * @param int $permissions
114
	 * @return bool
115
	 */
116
	protected function checkPermissions($permissions) {
117
		return ($this->getPermissions() & $permissions) === $permissions;
118
	}
119
120
	public function delete() {
121
	}
122
123
	/**
124
	 * @param int $mtime
125
	 * @throws \OCP\Files\NotPermittedException
126
	 */
127
	public function touch($mtime = null) {
128
		if ($this->checkPermissions(\OCP\Constants::PERMISSION_UPDATE)) {
129
			$this->sendHooks(array('preTouch'));
130
			$this->view->touch($this->path, $mtime);
131
			$this->sendHooks(array('postTouch'));
132
			if ($this->fileInfo) {
133
				if (is_null($mtime)) {
134
					$mtime = time();
135
				}
136
				$this->fileInfo['mtime'] = $mtime;
137
			}
138
		} else {
139
			throw new NotPermittedException();
140
		}
141
	}
142
143
	/**
144
	 * @return \OC\Files\Storage\Storage
145
	 * @throws \OCP\Files\NotFoundException
146
	 */
147
	public function getStorage() {
148
		list($storage,) = $this->view->resolvePath($this->path);
149
		return $storage;
150
	}
151
152
	/**
153
	 * @return string
154
	 */
155
	public function getPath() {
156
		return $this->path;
157
	}
158
159
	/**
160
	 * @return string
161
	 */
162
	public function getInternalPath() {
163
		list(, $internalPath) = $this->view->resolvePath($this->path);
164
		return $internalPath;
165
	}
166
167
	/**
168
	 * @return int
169
	 * @throws InvalidPathException
170
	 * @throws NotFoundException
171
	 */
172
	public function getId() {
173
		return $this->getFileInfo()->getId();
174
	}
175
176
	/**
177
	 * @return array
178
	 */
179
	public function stat() {
180
		return $this->view->stat($this->path);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->view->stat($this->path) could also return false which is incompatible with the documented return type array. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
181
	}
182
183
	/**
184
	 * @return int
185
	 * @throws InvalidPathException
186
	 * @throws NotFoundException
187
	 */
188
	public function getMTime() {
189
		return $this->getFileInfo()->getMTime();
190
	}
191
192
	/**
193
	 * @return int
194
	 * @throws InvalidPathException
195
	 * @throws NotFoundException
196
	 */
197
	public function getSize() {
198
		return $this->getFileInfo()->getSize();
199
	}
200
201
	/**
202
	 * @return string
203
	 * @throws InvalidPathException
204
	 * @throws NotFoundException
205
	 */
206
	public function getEtag() {
207
		return $this->getFileInfo()->getEtag();
208
	}
209
210
	/**
211
	 * @return int
212
	 * @throws InvalidPathException
213
	 * @throws NotFoundException
214
	 */
215
	public function getPermissions() {
216
		return $this->getFileInfo()->getPermissions();
217
	}
218
219
	/**
220
	 * @return bool
221
	 * @throws InvalidPathException
222
	 * @throws NotFoundException
223
	 */
224
	public function isReadable() {
225
		return $this->getFileInfo()->isReadable();
226
	}
227
228
	/**
229
	 * @return bool
230
	 * @throws InvalidPathException
231
	 * @throws NotFoundException
232
	 */
233
	public function isUpdateable() {
234
		return $this->getFileInfo()->isUpdateable();
235
	}
236
237
	/**
238
	 * @return bool
239
	 * @throws InvalidPathException
240
	 * @throws NotFoundException
241
	 */
242
	public function isDeletable() {
243
		return $this->getFileInfo()->isDeletable();
244
	}
245
246
	/**
247
	 * @return bool
248
	 * @throws InvalidPathException
249
	 * @throws NotFoundException
250
	 */
251
	public function isShareable() {
252
		return $this->getFileInfo()->isShareable();
253
	}
254
255
	/**
256
	 * @return bool
257
	 * @throws InvalidPathException
258
	 * @throws NotFoundException
259
	 */
260
	public function isCreatable() {
261
		return $this->getFileInfo()->isCreatable();
262
	}
263
264
	/**
265
	 * @return Node
266
	 */
267
	public function getParent() {
268
		$newPath = dirname($this->path);
269
		if ($newPath === '' || $newPath === '.' || $newPath === '/') {
270
			return $this->root;
271
		}
272
		return $this->root->get($newPath);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->root->get($newPath) returns the type string which is incompatible with the documented return type OC\Files\Node\Node.
Loading history...
273
	}
274
275
	/**
276
	 * @return string
277
	 */
278
	public function getName() {
279
		return basename($this->path);
280
	}
281
282
	/**
283
	 * @param string $path
284
	 * @return string
285
	 */
286
	protected function normalizePath($path) {
287
		if ($path === '' or $path === '/') {
288
			return '/';
289
		}
290
		//no windows style slashes
291
		$path = str_replace('\\', '/', $path);
292
		//add leading slash
293
		if ($path[0] !== '/') {
294
			$path = '/' . $path;
295
		}
296
		//remove duplicate slashes
297
		while (strpos($path, '//') !== false) {
298
			$path = str_replace('//', '/', $path);
299
		}
300
		//remove trailing slash
301
		$path = rtrim($path, '/');
302
303
		return $path;
304
	}
305
306
	/**
307
	 * check if the requested path is valid
308
	 *
309
	 * @param string $path
310
	 * @return bool
311
	 */
312
	public function isValidPath($path) {
313
		if (!$path || $path[0] !== '/') {
314
			$path = '/' . $path;
315
		}
316
		if (strstr($path, '/../') || strrchr($path, '/') === '/..') {
317
			return false;
318
		}
319
		return true;
320
	}
321
322
	public function isMounted() {
323
		return $this->getFileInfo()->isMounted();
324
	}
325
326
	public function isShared() {
327
		return $this->getFileInfo()->isShared();
328
	}
329
330
	public function getMimeType() {
331
		return $this->getFileInfo()->getMimetype();
332
	}
333
334
	public function getMimePart() {
335
		return $this->getFileInfo()->getMimePart();
336
	}
337
338
	public function getType() {
339
		return $this->getFileInfo()->getType();
340
	}
341
342
	public function isEncrypted() {
343
		return $this->getFileInfo()->isEncrypted();
344
	}
345
346
	public function getMountPoint() {
347
		return $this->getFileInfo()->getMountPoint();
348
	}
349
350
	public function getOwner() {
351
		return $this->getFileInfo()->getOwner();
352
	}
353
354
	public function getChecksum() {
355
	}
356
357
	public function getExtension(): string {
358
		return $this->getFileInfo()->getExtension();
359
	}
360
361
	/**
362
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
363
	 * @throws \OCP\Lock\LockedException
364
	 */
365
	public function lock($type) {
366
		$this->view->lockFile($this->path, $type);
367
	}
368
369
	/**
370
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
371
	 * @throws \OCP\Lock\LockedException
372
	 */
373
	public function changeLock($type) {
374
		$this->view->changeLock($this->path, $type);
375
	}
376
377
	/**
378
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
379
	 * @throws \OCP\Lock\LockedException
380
	 */
381
	public function unlock($type) {
382
		$this->view->unlockFile($this->path, $type);
383
	}
384
385
	/**
386
	 * @param string $targetPath
387
	 * @throws \OCP\Files\NotPermittedException if copy not allowed or failed
388
	 * @return \OC\Files\Node\Node
389
	 */
390
	public function copy($targetPath) {
391
		$targetPath = $this->normalizePath($targetPath);
392
		$parent = $this->root->get(dirname($targetPath));
393
		if ($parent instanceof Folder and $this->isValidPath($targetPath) and $parent->isCreatable()) {
0 ignored issues
show
introduced by
$parent is never a sub-type of OC\Files\Node\Folder.
Loading history...
394
			$nonExisting = $this->createNonExistingNode($targetPath);
395
			$this->root->emit('\OC\Files', 'preCopy', [$this, $nonExisting]);
396
			$this->root->emit('\OC\Files', 'preWrite', [$nonExisting]);
397
			if (!$this->view->copy($this->path, $targetPath)) {
398
				throw new NotPermittedException('Could not copy ' . $this->path . ' to ' . $targetPath);
399
			}
400
			$targetNode = $this->root->get($targetPath);
401
			$this->root->emit('\OC\Files', 'postCopy', [$this, $targetNode]);
402
			$this->root->emit('\OC\Files', 'postWrite', [$targetNode]);
403
			return $targetNode;
404
		} else {
405
			throw new NotPermittedException('No permission to copy to path ' . $targetPath);
406
		}
407
	}
408
409
	/**
410
	 * @param string $targetPath
411
	 * @throws \OCP\Files\NotPermittedException if move not allowed or failed
412
	 * @return \OC\Files\Node\Node
413
	 */
414
	public function move($targetPath) {
415
		$targetPath = $this->normalizePath($targetPath);
416
		$parent = $this->root->get(dirname($targetPath));
417
		if ($parent instanceof Folder and $this->isValidPath($targetPath) and $parent->isCreatable()) {
0 ignored issues
show
introduced by
$parent is never a sub-type of OC\Files\Node\Folder.
Loading history...
418
			$nonExisting = $this->createNonExistingNode($targetPath);
419
			$this->root->emit('\OC\Files', 'preRename', [$this, $nonExisting]);
420
			$this->root->emit('\OC\Files', 'preWrite', [$nonExisting]);
421
			if (!$this->view->rename($this->path, $targetPath)) {
422
				throw new NotPermittedException('Could not move ' . $this->path . ' to ' . $targetPath);
423
			}
424
			$targetNode = $this->root->get($targetPath);
425
			$this->root->emit('\OC\Files', 'postRename', [$this, $targetNode]);
426
			$this->root->emit('\OC\Files', 'postWrite', [$targetNode]);
427
			$this->path = $targetPath;
428
			return $targetNode;
429
		} else {
430
			throw new NotPermittedException('No permission to move to path ' . $targetPath);
431
		}
432
	}
433
434
}
435