Passed
Push — master ( 63276c...4a52d9 )
by Roeland
08:29
created

Folder   D

Complexity

Total Complexity 58

Size/Duplication

Total Lines 429
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 210
c 1
b 0
f 0
dl 0
loc 429
rs 4.5599
wmc 58

22 Methods

Rating   Name   Duplication   Size   Complexity  
A createNode() 0 10 3
A nodeExists() 0 6 2
A getFullPath() 0 5 2
A createNonExistingNode() 0 2 1
A get() 0 2 1
A getRelativePath() 0 11 5
A getDirectoryListing() 0 10 2
A isSubNode() 0 2 1
A newFolder() 0 15 3
A getFreeSpace() 0 2 1
A newFile() 0 15 3
A search() 0 5 2
A getNonExistingName() 0 3 1
A delete() 0 10 2
A searchByTag() 0 2 1
A getById() 0 46 4
A searchByMime() 0 2 1
B searchCommon() 0 44 8
A getAbsolutePath() 0 18 5
A recentSearch() 0 17 1
A recentParse() 0 17 3
B getRecent() 0 41 6

How to fix   Complexity   

Complex Class

Complex classes like Folder 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 Folder, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Joas Schilling <[email protected]>
6
 * @author Morris Jobke <[email protected]>
7
 * @author Robin Appelman <[email protected]>
8
 * @author Robin McCorkell <[email protected]>
9
 * @author Vincent Petry <[email protected]>
10
 *
11
 * @license AGPL-3.0
12
 *
13
 * This code is free software: you can redistribute it and/or modify
14
 * it under the terms of the GNU Affero General Public License, version 3,
15
 * as published by the Free Software Foundation.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
 * GNU Affero General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU Affero General Public License, version 3,
23
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
24
 *
25
 */
26
27
namespace OC\Files\Node;
28
29
use OC\DB\QueryBuilder\Literal;
30
use OCA\Files_Sharing\SharedStorage;
31
use OCP\DB\QueryBuilder\IQueryBuilder;
32
use OCP\Files\Config\ICachedMountInfo;
33
use OCP\Files\FileInfo;
34
use OCP\Files\Mount\IMountPoint;
35
use OCP\Files\NotFoundException;
36
use OCP\Files\NotPermittedException;
37
use OCP\Files\Search\ISearchOperator;
38
39
class Folder extends Node implements \OCP\Files\Folder {
40
	/**
41
	 * Creates a Folder that represents a non-existing path
42
	 *
43
	 * @param string $path path
44
	 * @return string non-existing node class
45
	 */
46
	protected function createNonExistingNode($path) {
47
		return new NonExistingFolder($this->root, $this->view, $path);
0 ignored issues
show
Bug Best Practice introduced by
The expression return new OC\Files\Node...ot, $this->view, $path) returns the type OC\Files\Node\NonExistingFolder which is incompatible with the documented return type string.
Loading history...
48
	}
49
50
	/**
51
	 * @param string $path path relative to the folder
52
	 * @return string
53
	 * @throws \OCP\Files\NotPermittedException
54
	 */
55
	public function getFullPath($path) {
56
		if (!$this->isValidPath($path)) {
57
			throw new NotPermittedException('Invalid path');
58
		}
59
		return $this->path . $this->normalizePath($path);
60
	}
61
62
	/**
63
	 * @param string $path
64
	 * @return string
65
	 */
66
	public function getRelativePath($path) {
67
		if ($this->path === '' or $this->path === '/') {
68
			return $this->normalizePath($path);
69
		}
70
		if ($path === $this->path) {
71
			return '/';
72
		} else if (strpos($path, $this->path . '/') !== 0) {
73
			return null;
74
		} else {
75
			$path = substr($path, strlen($this->path));
76
			return $this->normalizePath($path);
77
		}
78
	}
79
80
	/**
81
	 * check if a node is a (grand-)child of the folder
82
	 *
83
	 * @param \OC\Files\Node\Node $node
84
	 * @return bool
85
	 */
86
	public function isSubNode($node) {
87
		return strpos($node->getPath(), $this->path . '/') === 0;
88
	}
89
90
	/**
91
	 * get the content of this directory
92
	 *
93
	 * @throws \OCP\Files\NotFoundException
94
	 * @return Node[]
95
	 */
96
	public function getDirectoryListing() {
97
		$folderContent = $this->view->getDirectoryContent($this->path);
98
99
		return array_map(function (FileInfo $info) {
100
			if ($info->getMimetype() === 'httpd/unix-directory') {
101
				return new Folder($this->root, $this->view, $info->getPath(), $info);
102
			} else {
103
				return new File($this->root, $this->view, $info->getPath(), $info);
104
			}
105
		}, $folderContent);
106
	}
107
108
	/**
109
	 * @param string $path
110
	 * @param FileInfo $info
111
	 * @return File|Folder
112
	 */
113
	protected function createNode($path, FileInfo $info = null) {
114
		if (is_null($info)) {
115
			$isDir = $this->view->is_dir($path);
116
		} else {
117
			$isDir = $info->getType() === FileInfo::TYPE_FOLDER;
118
		}
119
		if ($isDir) {
120
			return new Folder($this->root, $this->view, $path, $info);
121
		} else {
122
			return new File($this->root, $this->view, $path, $info);
123
		}
124
	}
125
126
	/**
127
	 * Get the node at $path
128
	 *
129
	 * @param string $path
130
	 * @return \OC\Files\Node\Node
131
	 * @throws \OCP\Files\NotFoundException
132
	 */
133
	public function get($path) {
134
		return $this->root->get($this->getFullPath($path));
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->root->get(...is->getFullPath($path)) returns the type string which is incompatible with the documented return type OC\Files\Node\Node.
Loading history...
135
	}
136
137
	/**
138
	 * @param string $path
139
	 * @return bool
140
	 */
141
	public function nodeExists($path) {
142
		try {
143
			$this->get($path);
144
			return true;
145
		} catch (NotFoundException $e) {
146
			return false;
147
		}
148
	}
149
150
	/**
151
	 * @param string $path
152
	 * @return \OC\Files\Node\Folder
153
	 * @throws \OCP\Files\NotPermittedException
154
	 */
155
	public function newFolder($path) {
156
		if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
157
			$fullPath = $this->getFullPath($path);
158
			$nonExisting = new NonExistingFolder($this->root, $this->view, $fullPath);
159
			$this->root->emit('\OC\Files', 'preWrite', array($nonExisting));
160
			$this->root->emit('\OC\Files', 'preCreate', array($nonExisting));
161
			if(!$this->view->mkdir($fullPath)) {
162
				throw new NotPermittedException('Could not create folder');
163
			}
164
			$node = new Folder($this->root, $this->view, $fullPath);
165
			$this->root->emit('\OC\Files', 'postWrite', array($node));
166
			$this->root->emit('\OC\Files', 'postCreate', array($node));
167
			return $node;
168
		} else {
169
			throw new NotPermittedException('No create permission for folder');
170
		}
171
	}
172
173
	/**
174
	 * @param string $path
175
	 * @return \OC\Files\Node\File
176
	 * @throws \OCP\Files\NotPermittedException
177
	 */
178
	public function newFile($path) {
179
		if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
180
			$fullPath = $this->getFullPath($path);
181
			$nonExisting = new NonExistingFile($this->root, $this->view, $fullPath);
182
			$this->root->emit('\OC\Files', 'preWrite', array($nonExisting));
183
			$this->root->emit('\OC\Files', 'preCreate', array($nonExisting));
184
			if (!$this->view->touch($fullPath)) {
185
				throw new NotPermittedException('Could not create path');
186
			}
187
			$node = new File($this->root, $this->view, $fullPath);
188
			$this->root->emit('\OC\Files', 'postWrite', array($node));
189
			$this->root->emit('\OC\Files', 'postCreate', array($node));
190
			return $node;
191
		}
192
		throw new NotPermittedException('No create permission for path');
193
	}
194
195
	/**
196
	 * search for files with the name matching $query
197
	 *
198
	 * @param string|ISearchOperator $query
199
	 * @return \OC\Files\Node\Node[]
200
	 */
201
	public function search($query) {
202
		if (is_string($query)) {
203
			return $this->searchCommon('search', array('%' . $query . '%'));
204
		} else {
205
			return $this->searchCommon('searchQuery', array($query));
206
		}
207
	}
208
209
	/**
210
	 * search for files by mimetype
211
	 *
212
	 * @param string $mimetype
213
	 * @return Node[]
214
	 */
215
	public function searchByMime($mimetype) {
216
		return $this->searchCommon('searchByMime', array($mimetype));
217
	}
218
219
	/**
220
	 * search for files by tag
221
	 *
222
	 * @param string|int $tag name or tag id
223
	 * @param string $userId owner of the tags
224
	 * @return Node[]
225
	 */
226
	public function searchByTag($tag, $userId) {
227
		return $this->searchCommon('searchByTag', array($tag, $userId));
228
	}
229
230
	/**
231
	 * @param string $method cache method
232
	 * @param array $args call args
233
	 * @return \OC\Files\Node\Node[]
234
	 */
235
	private function searchCommon($method, $args) {
236
		$files = array();
237
		$rootLength = strlen($this->path);
238
		$mount = $this->root->getMount($this->path);
239
		$storage = $mount->getStorage();
240
		$internalPath = $mount->getInternalPath($this->path);
241
		$internalPath = rtrim($internalPath, '/');
242
		if ($internalPath !== '') {
243
			$internalPath = $internalPath . '/';
244
		}
245
		$internalRootLength = strlen($internalPath);
246
247
		$cache = $storage->getCache('');
248
249
		$results = call_user_func_array(array($cache, $method), $args);
250
		foreach ($results as $result) {
251
			if ($internalRootLength === 0 or substr($result['path'], 0, $internalRootLength) === $internalPath) {
252
				$result['internalPath'] = $result['path'];
253
				$result['path'] = substr($result['path'], $internalRootLength);
254
				$result['storage'] = $storage;
255
				$files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage, $result['internalPath'], $result, $mount);
256
			}
257
		}
258
259
		$mounts = $this->root->getMountsIn($this->path);
260
		foreach ($mounts as $mount) {
261
			$storage = $mount->getStorage();
262
			if ($storage) {
263
				$cache = $storage->getCache('');
264
265
				$relativeMountPoint = ltrim(substr($mount->getMountPoint(), $rootLength), '/');
266
				$results = call_user_func_array(array($cache, $method), $args);
267
				foreach ($results as $result) {
268
					$result['internalPath'] = $result['path'];
269
					$result['path'] = $relativeMountPoint . $result['path'];
270
					$result['storage'] = $storage;
271
					$files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage, $result['internalPath'], $result, $mount);
272
				}
273
			}
274
		}
275
276
		return array_map(function (FileInfo $file) {
277
			return $this->createNode($file->getPath(), $file);
278
		}, $files);
279
	}
280
281
	/**
282
	 * @param int $id
283
	 * @return \OC\Files\Node\Node[]
284
	 */
285
	public function getById($id) {
286
		$mountCache = $this->root->getUserMountCache();
287
		if (strpos($this->getPath(), '/', 1) > 0) {
288
			list(, $user) = explode('/', $this->getPath());
289
		} else {
290
			$user = null;
291
		}
292
		$mountsContainingFile = $mountCache->getMountsForFileId((int)$id, $user);
293
		$mounts = $this->root->getMountsIn($this->path);
294
		$mounts[] = $this->root->getMount($this->path);
295
		/** @var IMountPoint[] $folderMounts */
296
		$folderMounts = array_combine(array_map(function (IMountPoint $mountPoint) {
297
			return $mountPoint->getMountPoint();
298
		}, $mounts), $mounts);
299
300
		/** @var ICachedMountInfo[] $mountsContainingFile */
301
		$mountsContainingFile = array_values(array_filter($mountsContainingFile, function (ICachedMountInfo $cachedMountInfo) use ($folderMounts) {
302
			return isset($folderMounts[$cachedMountInfo->getMountPoint()]);
303
		}));
304
305
		if (count($mountsContainingFile) === 0) {
306
			return [];
307
		}
308
309
		$nodes = array_map(function (ICachedMountInfo $cachedMountInfo) use ($folderMounts, $id) {
310
			$mount = $folderMounts[$cachedMountInfo->getMountPoint()];
311
			$cacheEntry = $mount->getStorage()->getCache()->get((int)$id);
312
			if (!$cacheEntry) {
313
				return null;
314
			}
315
316
			// cache jails will hide the "true" internal path
317
			$internalPath = ltrim($cachedMountInfo->getRootInternalPath() . '/' . $cacheEntry->getPath(), '/');
318
			$pathRelativeToMount = substr($internalPath, strlen($cachedMountInfo->getRootInternalPath()));
319
			$pathRelativeToMount = ltrim($pathRelativeToMount, '/');
320
			$absolutePath = rtrim($cachedMountInfo->getMountPoint() . $pathRelativeToMount, '/');
321
			return $this->root->createNode($absolutePath, new \OC\Files\FileInfo(
322
				$absolutePath, $mount->getStorage(), $cacheEntry->getPath(), $cacheEntry, $mount,
323
				\OC::$server->getUserManager()->get($mount->getStorage()->getOwner($pathRelativeToMount))
324
			));
325
		}, $mountsContainingFile);
326
327
		$nodes = array_filter($nodes);
328
329
		return array_filter($nodes, function (Node $node) {
330
			return $this->getRelativePath($node->getPath());
331
		});
332
	}
333
334
	public function getFreeSpace() {
335
		return $this->view->free_space($this->path);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->view->free_space($this->path) also could return the type false which is incompatible with the return type mandated by OCP\Files\Folder::getFreeSpace() of integer.
Loading history...
336
	}
337
338
	public function delete() {
339
		if ($this->checkPermissions(\OCP\Constants::PERMISSION_DELETE)) {
340
			$this->sendHooks(array('preDelete'));
341
			$fileInfo = $this->getFileInfo();
342
			$this->view->rmdir($this->path);
343
			$nonExisting = new NonExistingFolder($this->root, $this->view, $this->path, $fileInfo);
344
			$this->root->emit('\OC\Files', 'postDelete', array($nonExisting));
345
			$this->exists = false;
0 ignored issues
show
Bug Best Practice introduced by
The property exists does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
346
		} else {
347
			throw new NotPermittedException('No delete permission for path');
348
		}
349
	}
350
351
	/**
352
	 * Add a suffix to the name in case the file exists
353
	 *
354
	 * @param string $name
355
	 * @return string
356
	 * @throws NotPermittedException
357
	 */
358
	public function getNonExistingName($name) {
359
		$uniqueName = \OC_Helper::buildNotExistingFileNameForView($this->getPath(), $name, $this->view);
360
		return trim($this->getRelativePath($uniqueName), '/');
361
	}
362
363
	/**
364
	 * @param int $limit
365
	 * @param int $offset
366
	 * @return \OCP\Files\Node[]
367
	 */
368
	public function getRecent($limit, $offset = 0) {
369
		$mimetypeLoader = \OC::$server->getMimeTypeLoader();
370
		$mounts = $this->root->getMountsIn($this->path);
371
		$mounts[] = $this->getMountPoint();
372
373
		$mounts = array_filter($mounts, function (IMountPoint $mount) {
374
			return $mount->getStorage();
375
		});
376
		$storageIds = array_map(function (IMountPoint $mount) {
377
			return $mount->getStorage()->getCache()->getNumericStorageId();
378
		}, $mounts);
379
		/** @var IMountPoint[] $mountMap */
380
		$mountMap = array_combine($storageIds, $mounts);
381
		$folderMimetype = $mimetypeLoader->getId(FileInfo::MIMETYPE_FOLDER);
382
383
		// Search in batches of 500 entries
384
		$searchLimit = 500;
385
		$results = [];
386
		$searchResultCount = 0;
387
		$count = 0;
388
		do {
389
			$searchResult = $this->recentSearch($searchLimit, $offset, $storageIds, $folderMimetype);
390
391
			// Exit condition if there are no more results
392
			if (count($searchResult) === 0) {
393
				break;
394
			}
395
396
			$searchResultCount += count($searchResult);
397
398
			$parseResult = $this->recentParse($searchResult, $mountMap, $mimetypeLoader);
399
400
			foreach ($parseResult as $result) {
401
				$results[] = $result;
402
			}
403
404
			$offset += $searchLimit;
405
			$count++;
406
		} while (count($results) < $limit && ($searchResultCount < (3 * $limit) || $count < 5));
407
408
		return array_slice($results, 0, $limit);
409
	}
410
411
	private function recentSearch($limit, $offset, $storageIds, $folderMimetype) {
412
		$builder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
413
		$query = $builder
414
			->select('f.*')
415
			->from('filecache', 'f')
416
			->andWhere($builder->expr()->in('f.storage', $builder->createNamedParameter($storageIds, IQueryBuilder::PARAM_INT_ARRAY)))
417
			->andWhere($builder->expr()->orX(
418
			// handle non empty folders separate
419
				$builder->expr()->neq('f.mimetype', $builder->createNamedParameter($folderMimetype, IQueryBuilder::PARAM_INT)),
420
				$builder->expr()->eq('f.size', new Literal(0))
421
			))
422
			->andWhere($builder->expr()->notLike('f.path', $builder->createNamedParameter('files_versions/%')))
423
			->andWhere($builder->expr()->notLike('f.path', $builder->createNamedParameter('files_trashbin/%')))
424
			->orderBy('f.mtime', 'DESC')
425
			->setMaxResults($limit)
426
			->setFirstResult($offset);
427
		return $query->execute()->fetchAll();
428
	}
429
430
	private function recentParse($result, $mountMap, $mimetypeLoader) {
431
		$files = array_filter(array_map(function (array $entry) use ($mountMap, $mimetypeLoader) {
432
			$mount = $mountMap[$entry['storage']];
433
			$entry['internalPath'] = $entry['path'];
434
			$entry['mimetype'] = $mimetypeLoader->getMimetypeById($entry['mimetype']);
435
			$entry['mimepart'] = $mimetypeLoader->getMimetypeById($entry['mimepart']);
436
			$path = $this->getAbsolutePath($mount, $entry['path']);
437
			if (is_null($path)) {
438
				return null;
439
			}
440
			$fileInfo = new \OC\Files\FileInfo($path, $mount->getStorage(), $entry['internalPath'], $entry, $mount);
441
			return $this->root->createNode($fileInfo->getPath(), $fileInfo);
442
		}, $result));
443
444
		return array_values(array_filter($files, function (Node $node) {
445
			$relative = $this->getRelativePath($node->getPath());
446
			return $relative !== null && $relative !== '/';
447
		}));
448
	}
449
450
	private function getAbsolutePath(IMountPoint $mount, $path) {
451
		$storage = $mount->getStorage();
452
		if ($storage->instanceOfStorage('\OC\Files\Storage\Wrapper\Jail')) {
453
			if ($storage->instanceOfStorage(SharedStorage::class)) {
454
				$storage->getSourceStorage();
0 ignored issues
show
Bug introduced by
The method getSourceStorage() does not exist on OC\Files\Storage\Storage. It seems like you code against a sub-type of OC\Files\Storage\Storage such as OC\Files\Storage\Wrapper\Wrapper. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

454
				$storage->/** @scrutinizer ignore-call */ 
455
              getSourceStorage();
Loading history...
455
			}
456
			/** @var \OC\Files\Storage\Wrapper\Jail $storage */
457
			$jailRoot = $storage->getUnjailedPath('');
458
			$rootLength = strlen($jailRoot) + 1;
459
			if ($path === $jailRoot) {
460
				return $mount->getMountPoint();
461
			} else if (substr($path, 0, $rootLength) === $jailRoot . '/') {
462
				return $mount->getMountPoint() . substr($path, $rootLength);
463
			} else {
464
				return null;
465
			}
466
		} else {
467
			return $mount->getMountPoint() . $path;
468
		}
469
	}
470
}
471