Passed
Push — master ( 9d1d4d...58de24 )
by Morris
12:22
created

Folder::recentSearch()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 14
nc 1
nop 4
dl 0
loc 17
rs 9.7998
c 0
b 0
f 0
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
			$this->view->mkdir($fullPath);
162
			$node = new Folder($this->root, $this->view, $fullPath);
163
			$this->root->emit('\OC\Files', 'postWrite', array($node));
164
			$this->root->emit('\OC\Files', 'postCreate', array($node));
165
			return $node;
166
		} else {
167
			throw new NotPermittedException('No create permission for folder');
168
		}
169
	}
170
171
	/**
172
	 * @param string $path
173
	 * @return \OC\Files\Node\File
174
	 * @throws \OCP\Files\NotPermittedException
175
	 */
176
	public function newFile($path) {
177
		if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
178
			$fullPath = $this->getFullPath($path);
179
			$nonExisting = new NonExistingFile($this->root, $this->view, $fullPath);
180
			$this->root->emit('\OC\Files', 'preWrite', array($nonExisting));
181
			$this->root->emit('\OC\Files', 'preCreate', array($nonExisting));
182
			$this->view->touch($fullPath);
183
			$node = new File($this->root, $this->view, $fullPath);
184
			$this->root->emit('\OC\Files', 'postWrite', array($node));
185
			$this->root->emit('\OC\Files', 'postCreate', array($node));
186
			return $node;
187
		} else {
188
			throw new NotPermittedException('No create permission for path');
189
		}
190
	}
191
192
	/**
193
	 * search for files with the name matching $query
194
	 *
195
	 * @param string|ISearchOperator $query
196
	 * @return \OC\Files\Node\Node[]
197
	 */
198
	public function search($query) {
199
		if (is_string($query)) {
200
			return $this->searchCommon('search', array('%' . $query . '%'));
201
		} else {
202
			return $this->searchCommon('searchQuery', array($query));
203
		}
204
	}
205
206
	/**
207
	 * search for files by mimetype
208
	 *
209
	 * @param string $mimetype
210
	 * @return Node[]
211
	 */
212
	public function searchByMime($mimetype) {
213
		return $this->searchCommon('searchByMime', array($mimetype));
214
	}
215
216
	/**
217
	 * search for files by tag
218
	 *
219
	 * @param string|int $tag name or tag id
220
	 * @param string $userId owner of the tags
221
	 * @return Node[]
222
	 */
223
	public function searchByTag($tag, $userId) {
224
		return $this->searchCommon('searchByTag', array($tag, $userId));
225
	}
226
227
	/**
228
	 * @param string $method cache method
229
	 * @param array $args call args
230
	 * @return \OC\Files\Node\Node[]
231
	 */
232
	private function searchCommon($method, $args) {
233
		$files = array();
234
		$rootLength = strlen($this->path);
235
		$mount = $this->root->getMount($this->path);
236
		$storage = $mount->getStorage();
237
		$internalPath = $mount->getInternalPath($this->path);
238
		$internalPath = rtrim($internalPath, '/');
239
		if ($internalPath !== '') {
240
			$internalPath = $internalPath . '/';
241
		}
242
		$internalRootLength = strlen($internalPath);
243
244
		$cache = $storage->getCache('');
245
246
		$results = call_user_func_array(array($cache, $method), $args);
247
		foreach ($results as $result) {
248
			if ($internalRootLength === 0 or substr($result['path'], 0, $internalRootLength) === $internalPath) {
249
				$result['internalPath'] = $result['path'];
250
				$result['path'] = substr($result['path'], $internalRootLength);
251
				$result['storage'] = $storage;
252
				$files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage, $result['internalPath'], $result, $mount);
253
			}
254
		}
255
256
		$mounts = $this->root->getMountsIn($this->path);
257
		foreach ($mounts as $mount) {
258
			$storage = $mount->getStorage();
259
			if ($storage) {
260
				$cache = $storage->getCache('');
261
262
				$relativeMountPoint = ltrim(substr($mount->getMountPoint(), $rootLength), '/');
263
				$results = call_user_func_array(array($cache, $method), $args);
264
				foreach ($results as $result) {
265
					$result['internalPath'] = $result['path'];
266
					$result['path'] = $relativeMountPoint . $result['path'];
267
					$result['storage'] = $storage;
268
					$files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage, $result['internalPath'], $result, $mount);
269
				}
270
			}
271
		}
272
273
		return array_map(function (FileInfo $file) {
274
			return $this->createNode($file->getPath(), $file);
275
		}, $files);
276
	}
277
278
	/**
279
	 * @param int $id
280
	 * @return \OC\Files\Node\Node[]
281
	 */
282
	public function getById($id) {
283
		$mountCache = $this->root->getUserMountCache();
284
		if (strpos($this->getPath(), '/', 1) > 0) {
285
			list(, $user) = explode('/', $this->getPath());
286
		} else {
287
			$user = null;
288
		}
289
		$mountsContainingFile = $mountCache->getMountsForFileId((int)$id, $user);
290
		$mounts = $this->root->getMountsIn($this->path);
291
		$mounts[] = $this->root->getMount($this->path);
292
		/** @var IMountPoint[] $folderMounts */
293
		$folderMounts = array_combine(array_map(function (IMountPoint $mountPoint) {
294
			return $mountPoint->getMountPoint();
295
		}, $mounts), $mounts);
296
297
		/** @var ICachedMountInfo[] $mountsContainingFile */
298
		$mountsContainingFile = array_values(array_filter($mountsContainingFile, function (ICachedMountInfo $cachedMountInfo) use ($folderMounts) {
299
			return isset($folderMounts[$cachedMountInfo->getMountPoint()]);
300
		}));
301
302
		if (count($mountsContainingFile) === 0) {
303
			return [];
304
		}
305
306
		$nodes = array_map(function (ICachedMountInfo $cachedMountInfo) use ($folderMounts, $id) {
307
			$mount = $folderMounts[$cachedMountInfo->getMountPoint()];
308
			$cacheEntry = $mount->getStorage()->getCache()->get((int)$id);
309
			if (!$cacheEntry) {
310
				return null;
311
			}
312
313
			// cache jails will hide the "true" internal path
314
			$internalPath = ltrim($cachedMountInfo->getRootInternalPath() . '/' . $cacheEntry->getPath(), '/');
315
			$pathRelativeToMount = substr($internalPath, strlen($cachedMountInfo->getRootInternalPath()));
316
			$pathRelativeToMount = ltrim($pathRelativeToMount, '/');
317
			$absolutePath = rtrim($cachedMountInfo->getMountPoint() . $pathRelativeToMount, '/');
318
			return $this->root->createNode($absolutePath, new \OC\Files\FileInfo(
319
				$absolutePath, $mount->getStorage(), $cacheEntry->getPath(), $cacheEntry, $mount,
320
				\OC::$server->getUserManager()->get($mount->getStorage()->getOwner($pathRelativeToMount))
321
			));
322
		}, $mountsContainingFile);
323
324
		$nodes = array_filter($nodes);
325
326
		return array_filter($nodes, function (Node $node) {
327
			return $this->getRelativePath($node->getPath());
328
		});
329
	}
330
331
	public function getFreeSpace() {
332
		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...
333
	}
334
335
	public function delete() {
336
		if ($this->checkPermissions(\OCP\Constants::PERMISSION_DELETE)) {
337
			$this->sendHooks(array('preDelete'));
338
			$fileInfo = $this->getFileInfo();
339
			$this->view->rmdir($this->path);
340
			$nonExisting = new NonExistingFolder($this->root, $this->view, $this->path, $fileInfo);
341
			$this->root->emit('\OC\Files', 'postDelete', array($nonExisting));
342
			$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...
343
		} else {
344
			throw new NotPermittedException('No delete permission for path');
345
		}
346
	}
347
348
	/**
349
	 * Add a suffix to the name in case the file exists
350
	 *
351
	 * @param string $name
352
	 * @return string
353
	 * @throws NotPermittedException
354
	 */
355
	public function getNonExistingName($name) {
356
		$uniqueName = \OC_Helper::buildNotExistingFileNameForView($this->getPath(), $name, $this->view);
357
		return trim($this->getRelativePath($uniqueName), '/');
358
	}
359
360
	/**
361
	 * @param int $limit
362
	 * @param int $offset
363
	 * @return \OCP\Files\Node[]
364
	 */
365
	public function getRecent($limit, $offset = 0) {
366
		$mimetypeLoader = \OC::$server->getMimeTypeLoader();
367
		$mounts = $this->root->getMountsIn($this->path);
368
		$mounts[] = $this->getMountPoint();
369
370
		$mounts = array_filter($mounts, function (IMountPoint $mount) {
371
			return $mount->getStorage();
372
		});
373
		$storageIds = array_map(function (IMountPoint $mount) {
374
			return $mount->getStorage()->getCache()->getNumericStorageId();
375
		}, $mounts);
376
		/** @var IMountPoint[] $mountMap */
377
		$mountMap = array_combine($storageIds, $mounts);
378
		$folderMimetype = $mimetypeLoader->getId(FileInfo::MIMETYPE_FOLDER);
379
380
		// Search in batches of 500 entries
381
		$searchLimit = 500;
382
		$results = [];
383
		do {
384
			$searchResult = $this->recentSearch($searchLimit, $offset, $storageIds, $folderMimetype);
385
386
			// Exit condition if there are no more results
387
			if (count($searchResult) === 0) {
388
				break;
389
			}
390
391
			$parseResult = $this->recentParse($searchResult, $mountMap, $mimetypeLoader);
392
393
			foreach ($parseResult as $result) {
394
				$results[] = $result;
395
			}
396
397
			$offset += $searchLimit;
398
		} while (count($results) < $limit);
399
400
		return array_slice($results, 0, $limit);
401
	}
402
403
	private function recentSearch($limit, $offset, $storageIds, $folderMimetype) {
404
		$builder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
405
		$query = $builder
406
			->select('f.*')
407
			->from('filecache', 'f')
408
			->andWhere($builder->expr()->in('f.storage', $builder->createNamedParameter($storageIds, IQueryBuilder::PARAM_INT_ARRAY)))
409
			->andWhere($builder->expr()->orX(
410
			// handle non empty folders separate
411
				$builder->expr()->neq('f.mimetype', $builder->createNamedParameter($folderMimetype, IQueryBuilder::PARAM_INT)),
412
				$builder->expr()->eq('f.size', new Literal(0))
413
			))
414
			->andWhere($builder->expr()->notLike('f.path', $builder->createNamedParameter('files_versions/%')))
415
			->andWhere($builder->expr()->notLike('f.path', $builder->createNamedParameter('files_trashbin/%')))
416
			->orderBy('f.mtime', 'DESC')
417
			->setMaxResults($limit)
418
			->setFirstResult($offset);
419
		return $query->execute()->fetchAll();
420
	}
421
422
	private function recentParse($result, $mountMap, $mimetypeLoader) {
423
		$files = array_filter(array_map(function (array $entry) use ($mountMap, $mimetypeLoader) {
424
			$mount = $mountMap[$entry['storage']];
425
			$entry['internalPath'] = $entry['path'];
426
			$entry['mimetype'] = $mimetypeLoader->getMimetypeById($entry['mimetype']);
427
			$entry['mimepart'] = $mimetypeLoader->getMimetypeById($entry['mimepart']);
428
			$path = $this->getAbsolutePath($mount, $entry['path']);
429
			if (is_null($path)) {
430
				return null;
431
			}
432
			$fileInfo = new \OC\Files\FileInfo($path, $mount->getStorage(), $entry['internalPath'], $entry, $mount);
433
			return $this->root->createNode($fileInfo->getPath(), $fileInfo);
434
		}, $result));
435
436
		return array_values(array_filter($files, function (Node $node) {
437
			$relative = $this->getRelativePath($node->getPath());
438
			return $relative !== null && $relative !== '/';
439
		}));
440
	}
441
442
	private function getAbsolutePath(IMountPoint $mount, $path) {
443
		$storage = $mount->getStorage();
444
		if ($storage->instanceOfStorage('\OC\Files\Storage\Wrapper\Jail')) {
445
			if ($storage->instanceOfStorage(SharedStorage::class)) {
446
				$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

446
				$storage->/** @scrutinizer ignore-call */ 
447
              getSourceStorage();
Loading history...
447
			}
448
			/** @var \OC\Files\Storage\Wrapper\Jail $storage */
449
			$jailRoot = $storage->getUnjailedPath('');
450
			$rootLength = strlen($jailRoot) + 1;
451
			if ($path === $jailRoot) {
452
				return $mount->getMountPoint();
453
			} else if (substr($path, 0, $rootLength) === $jailRoot . '/') {
454
				return $mount->getMountPoint() . substr($path, $rootLength);
455
			} else {
456
				return null;
457
			}
458
		} else {
459
			return $mount->getMountPoint() . $path;
460
		}
461
	}
462
}
463