Completed
Push — stable9 ( 83515c...0ffb5e )
by Morris
26s
created

ObjectTree   C

Complexity

Total Complexity 59

Size/Duplication

Total Lines 296
Duplicated Lines 1.69 %

Coupling/Cohesion

Components 1
Dependencies 12

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 5
loc 296
rs 6.1904
wmc 59
lcom 1
cbo 12

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 2 1
A init() 0 5 1
B resolveChunkFile() 0 19 5
A cacheNode() 0 3 1
C getNodeForPath() 5 70 14
F move() 0 83 27
D copy() 0 38 10

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

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Bjoern Schiessle <[email protected]>
6
 * @author Björn Schießle <[email protected]>
7
 * @author Joas Schilling <[email protected]>
8
 * @author Morris Jobke <[email protected]>
9
 * @author Robin Appelman <[email protected]>
10
 * @author Thomas Müller <[email protected]>
11
 * @author Vincent Petry <[email protected]>
12
 *
13
 * @license AGPL-3.0
14
 *
15
 * This code is free software: you can redistribute it and/or modify
16
 * it under the terms of the GNU Affero General Public License, version 3,
17
 * as published by the Free Software Foundation.
18
 *
19
 * This program is distributed in the hope that it will be useful,
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
 * GNU Affero General Public License for more details.
23
 *
24
 * You should have received a copy of the GNU Affero General Public License, version 3,
25
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
26
 *
27
 */
28
29
namespace OCA\DAV\Connector\Sabre;
30
31
use OCA\DAV\Connector\Sabre\Exception\Forbidden;
32
use OCA\DAV\Connector\Sabre\Exception\InvalidPath;
33
use OCA\DAV\Connector\Sabre\Exception\FileLocked;
34
use OC\Files\FileInfo;
35
use OC\Files\Mount\MoveableMount;
36
use OCP\Files\ForbiddenException;
37
use OCP\Files\StorageInvalidException;
38
use OCP\Files\StorageNotAvailableException;
39
use OCP\Lock\LockedException;
40
41
class ObjectTree extends \Sabre\DAV\Tree {
42
43
	/**
44
	 * @var \OC\Files\View
45
	 */
46
	protected $fileView;
47
48
	/**
49
	 * @var  \OCP\Files\Mount\IMountManager
50
	 */
51
	protected $mountManager;
52
53
	/**
54
	 * Creates the object
55
	 */
56
	public function __construct() {
57
	}
58
59
	/**
60
	 * @param \Sabre\DAV\INode $rootNode
61
	 * @param \OC\Files\View $view
62
	 * @param  \OCP\Files\Mount\IMountManager $mountManager
63
	 */
64
	public function init(\Sabre\DAV\INode $rootNode, \OC\Files\View $view, \OCP\Files\Mount\IMountManager $mountManager) {
65
		$this->rootNode = $rootNode;
66
		$this->fileView = $view;
67
		$this->mountManager = $mountManager;
68
	}
69
70
	/**
71
	 * If the given path is a chunked file name, converts it
72
	 * to the real file name. Only applies if the OC-CHUNKED header
73
	 * is present.
74
	 *
75
	 * @param string $path chunk file path to convert
76
	 *
77
	 * @return string path to real file
78
	 */
79
	private function resolveChunkFile($path) {
80
		if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
81
			// resolve to real file name to find the proper node
82
			list($dir, $name) = \Sabre\HTTP\URLUtil::splitPath($path);
83
			if ($dir == '/' || $dir == '.') {
84
				$dir = '';
85
			}
86
87
			$info = \OC_FileChunking::decodeName($name);
88
			// only replace path if it was really the chunked file
89
			if (isset($info['transferid'])) {
90
				// getNodePath is called for multiple nodes within a chunk
91
				// upload call
92
				$path = $dir . '/' . $info['name'];
93
				$path = ltrim($path, '/');
94
			}
95
		}
96
		return $path;
97
	}
98
99
	public function cacheNode(Node $node) {
100
		$this->cache[trim($node->getPath(), '/')] = $node;
101
	}
102
103
	/**
104
	 * Returns the INode object for the requested path
105
	 *
106
	 * @param string $path
107
	 * @return \Sabre\DAV\INode
108
	 * @throws InvalidPath
109
	 * @throws \Sabre\DAV\Exception\Locked
110
	 * @throws \Sabre\DAV\Exception\NotFound
111
	 * @throws \Sabre\DAV\Exception\ServiceUnavailable
112
	 */
113
	public function getNodeForPath($path) {
114
		if (!$this->fileView) {
115
			throw new \Sabre\DAV\Exception\ServiceUnavailable('filesystem not setup');
116
		}
117
118
		$path = trim($path, '/');
119
120
		if (isset($this->cache[$path])) {
121
			return $this->cache[$path];
122
		}
123
124
		if ($path) {
125
			try {
126
				$this->fileView->verifyPath($path, basename($path));
127
			} catch (\OCP\Files\InvalidPathException $ex) {
128
				throw new InvalidPath($ex->getMessage());
129
			}
130
		}
131
132
		// Is it the root node?
133
		if (!strlen($path)) {
134
			return $this->rootNode;
135
		}
136
137
		if (pathinfo($path, PATHINFO_EXTENSION) === 'part') {
138
			// read from storage
139
			$absPath = $this->fileView->getAbsolutePath($path);
140
			$mount = $this->fileView->getMount($path);
141
			$storage = $mount->getStorage();
142
			$internalPath = $mount->getInternalPath($absPath);
143
			if ($storage && $storage->file_exists($internalPath)) {
144
				/**
145
				 * @var \OC\Files\Storage\Storage $storage
146
				 */
147
				// get data directly
148
				$data = $storage->getMetaData($internalPath);
149
				$info = new FileInfo($absPath, $storage, $internalPath, $data, $mount);
0 ignored issues
show
Bug introduced by
It seems like $mount defined by $this->fileView->getMount($path) on line 140 can be null; however, OC\Files\FileInfo::__construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
150
			} else {
151
				$info = null;
152
			}
153
		} else {
154
			// resolve chunk file name to real name, if applicable
155
			$path = $this->resolveChunkFile($path);
156
157
			// read from cache
158
			try {
159
				$info = $this->fileView->getFileInfo($path);
160
			} catch (StorageNotAvailableException $e) {
161
				throw new \Sabre\DAV\Exception\ServiceUnavailable('Storage not available');
162
			} catch (StorageInvalidException $e) {
163
				throw new \Sabre\DAV\Exception\NotFound('Storage ' . $path . ' is invalid');
164
			} catch (LockedException $e) {
165
				throw new \Sabre\DAV\Exception\Locked();
166
			}
167
		}
168
169
		if (!$info) {
170
			throw new \Sabre\DAV\Exception\NotFound('File with name ' . $path . ' could not be located');
171
		}
172
173 View Code Duplication
		if ($info->getType() === 'dir') {
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...
174
			$node = new \OCA\DAV\Connector\Sabre\Directory($this->fileView, $info, $this);
175
		} else {
176
			$node = new \OCA\DAV\Connector\Sabre\File($this->fileView, $info);
177
		}
178
179
		$this->cache[$path] = $node;
180
		return $node;
181
182
	}
183
184
	/**
185
	 * Moves a file from one location to another
186
	 *
187
	 * @param string $sourcePath The path to the file which should be moved
188
	 * @param string $destinationPath The full destination path, so not just the destination parent node
189
	 * @return int
190
	 * @throws FileLocked
191
	 * @throws Forbidden
192
	 * @throws InvalidPath
193
	 * @throws \Sabre\DAV\Exception\Forbidden
194
	 * @throws \Sabre\DAV\Exception\Locked
195
	 * @throws \Sabre\DAV\Exception\NotFound
196
	 * @throws \Sabre\DAV\Exception\ServiceUnavailable
197
	 */
198
	public function move($sourcePath, $destinationPath) {
199
		if (!$this->fileView) {
200
			throw new \Sabre\DAV\Exception\ServiceUnavailable('filesystem not setup');
201
		}
202
203
		$infoDestination = $this->fileView->getFileInfo(dirname($destinationPath));
204
		if (dirname($destinationPath) === dirname($sourcePath)) {
205
			$sourcePermission = $infoDestination && $infoDestination->isUpdateable();
206
			$destinationPermission = $sourcePermission;
207
		} else {
208
			$infoSource = $this->fileView->getFileInfo($sourcePath);
209
			if ($this->fileView->file_exists($destinationPath)) {
210
				$destinationPermission = $infoDestination && $infoDestination->isUpdateable();
211
			} else {
212
				$destinationPermission = $infoDestination && $infoDestination->isCreatable();
213
			}
214
			$sourcePermission =  $infoSource && $infoSource->isDeletable();
215
		}
216
217
		if (!$destinationPermission || !$sourcePermission) {
218
			throw new Forbidden('No permissions to move object.');
219
		}
220
221
		$targetNodeExists = $this->nodeExists($destinationPath);
222
		$sourceNode = $this->getNodeForPath($sourcePath);
223
		if ($sourceNode instanceof \Sabre\DAV\ICollection && $targetNodeExists) {
0 ignored issues
show
Bug introduced by
The class Sabre\DAV\ICollection does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
224
			throw new \Sabre\DAV\Exception\Forbidden('Could not copy directory ' . $sourceNode->getName() . ', target exists');
225
		}
226
		list($sourceDir,) = \Sabre\HTTP\URLUtil::splitPath($sourcePath);
227
		list($destinationDir,) = \Sabre\HTTP\URLUtil::splitPath($destinationPath);
228
229
		$isMovableMount = false;
230
		$sourceMount = $this->mountManager->find($this->fileView->getAbsolutePath($sourcePath));
231
		$internalPath = $sourceMount->getInternalPath($this->fileView->getAbsolutePath($sourcePath));
232
		if ($sourceMount instanceof MoveableMount && $internalPath === '') {
233
			$isMovableMount = true;
234
		}
235
236
		try {
237
			$sameFolder = ($sourceDir === $destinationDir);
238
			// if we're overwriting or same folder
239
			if ($targetNodeExists || $sameFolder) {
240
				// note that renaming a share mount point is always allowed
241
				if (!$this->fileView->isUpdatable($destinationDir) && !$isMovableMount) {
242
					throw new \Sabre\DAV\Exception\Forbidden();
243
				}
244
			} else {
245
				if (!$this->fileView->isCreatable($destinationDir)) {
246
					throw new \Sabre\DAV\Exception\Forbidden();
247
				}
248
			}
249
250
			if (!$sameFolder) {
251
				// moving to a different folder, source will be gone, like a deletion
252
				// note that moving a share mount point is always allowed
253
				if (!$this->fileView->isDeletable($sourcePath) && !$isMovableMount) {
254
					throw new \Sabre\DAV\Exception\Forbidden();
255
				}
256
			}
257
258
			$fileName = basename($destinationPath);
259
			try {
260
				$this->fileView->verifyPath($destinationDir, $fileName);
261
			} catch (\OCP\Files\InvalidPathException $ex) {
262
				throw new InvalidPath($ex->getMessage());
263
			}
264
265
			$renameOkay = $this->fileView->rename($sourcePath, $destinationPath);
266
			if (!$renameOkay) {
267
				throw new \Sabre\DAV\Exception\Forbidden('');
268
			}
269
		} catch (StorageNotAvailableException $e) {
270
			throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage());
271
		} catch (ForbiddenException $ex) {
272
			throw new Forbidden($ex->getMessage(), $ex->getRetry());
273
		} catch (LockedException $e) {
274
			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
275
		}
276
277
		$this->markDirty($sourceDir);
278
		$this->markDirty($destinationDir);
279
280
	}
281
282
	/**
283
	 * Copies a file or directory.
284
	 *
285
	 * This method must work recursively and delete the destination
286
	 * if it exists
287
	 *
288
	 * @param string $source
289
	 * @param string $destination
290
	 * @throws FileLocked
291
	 * @throws Forbidden
292
	 * @throws InvalidPath
293
	 * @throws \Exception
294
	 * @throws \Sabre\DAV\Exception\Locked
295
	 * @throws \Sabre\DAV\Exception\NotFound
296
	 * @throws \Sabre\DAV\Exception\ServiceUnavailable
297
	 */
298
	public function copy($source, $destination) {
299
		if (!$this->fileView) {
300
			throw new \Sabre\DAV\Exception\ServiceUnavailable('filesystem not setup');
301
		}
302
303
		$info = $this->fileView->getFileInfo(dirname($destination));
304
		if ($this->fileView->file_exists($destination)) {
305
			$destinationPermission = $info && $info->isUpdateable();
306
		} else {
307
			$destinationPermission = $info && $info->isCreatable();
308
		}
309
		if (!$destinationPermission) {
310
			throw new Forbidden('No permissions to copy object.');
311
		}
312
		
313
		// this will trigger existence check
314
		$this->getNodeForPath($source);
315
316
		list($destinationDir, $destinationName) = \Sabre\HTTP\URLUtil::splitPath($destination);
317
		try {
318
			$this->fileView->verifyPath($destinationDir, $destinationName);
319
		} catch (\OCP\Files\InvalidPathException $ex) {
320
			throw new InvalidPath($ex->getMessage());
321
		}
322
323
		try {
324
			$this->fileView->copy($source, $destination);
325
		} catch (StorageNotAvailableException $e) {
326
			throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage());
327
		} catch (ForbiddenException $ex) {
328
			throw new Forbidden($ex->getMessage(), $ex->getRetry());
329
		} catch (LockedException $e) {
330
			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
331
		}
332
333
		list($destinationDir,) = \Sabre\HTTP\URLUtil::splitPath($destination);
334
		$this->markDirty($destinationDir);
335
	}
336
}
337