Passed
Push — master ( 85bdf6...f31d24 )
by Roeland
67:07 queued 52:02
created
lib/private/Files/Node/Folder.php 1 patch
Indentation   +587 added lines, -587 removed lines patch added patch discarded remove patch
@@ -52,598 +52,598 @@
 block discarded – undo
52 52
 use OCP\IUserManager;
53 53
 
54 54
 class Folder extends Node implements \OCP\Files\Folder {
55
-	/**
56
-	 * Creates a Folder that represents a non-existing path
57
-	 *
58
-	 * @param string $path path
59
-	 * @return string non-existing node class
60
-	 */
61
-	protected function createNonExistingNode($path) {
62
-		return new NonExistingFolder($this->root, $this->view, $path);
63
-	}
64
-
65
-	/**
66
-	 * @param string $path path relative to the folder
67
-	 * @return string
68
-	 * @throws \OCP\Files\NotPermittedException
69
-	 */
70
-	public function getFullPath($path) {
71
-		if (!$this->isValidPath($path)) {
72
-			throw new NotPermittedException('Invalid path');
73
-		}
74
-		return $this->path . $this->normalizePath($path);
75
-	}
76
-
77
-	/**
78
-	 * @param string $path
79
-	 * @return string|null
80
-	 */
81
-	public function getRelativePath($path) {
82
-		if ($this->path === '' or $this->path === '/') {
83
-			return $this->normalizePath($path);
84
-		}
85
-		if ($path === $this->path) {
86
-			return '/';
87
-		} elseif (strpos($path, $this->path . '/') !== 0) {
88
-			return null;
89
-		} else {
90
-			$path = substr($path, strlen($this->path));
91
-			return $this->normalizePath($path);
92
-		}
93
-	}
94
-
95
-	/**
96
-	 * check if a node is a (grand-)child of the folder
97
-	 *
98
-	 * @param \OC\Files\Node\Node $node
99
-	 * @return bool
100
-	 */
101
-	public function isSubNode($node) {
102
-		return strpos($node->getPath(), $this->path . '/') === 0;
103
-	}
104
-
105
-	/**
106
-	 * get the content of this directory
107
-	 *
108
-	 * @return Node[]
109
-	 * @throws \OCP\Files\NotFoundException
110
-	 */
111
-	public function getDirectoryListing() {
112
-		$folderContent = $this->view->getDirectoryContent($this->path);
113
-
114
-		return array_map(function (FileInfo $info) {
115
-			if ($info->getMimetype() === 'httpd/unix-directory') {
116
-				return new Folder($this->root, $this->view, $info->getPath(), $info);
117
-			} else {
118
-				return new File($this->root, $this->view, $info->getPath(), $info);
119
-			}
120
-		}, $folderContent);
121
-	}
122
-
123
-	/**
124
-	 * @param string $path
125
-	 * @param FileInfo $info
126
-	 * @return File|Folder
127
-	 */
128
-	protected function createNode($path, FileInfo $info = null) {
129
-		if (is_null($info)) {
130
-			$isDir = $this->view->is_dir($path);
131
-		} else {
132
-			$isDir = $info->getType() === FileInfo::TYPE_FOLDER;
133
-		}
134
-		if ($isDir) {
135
-			return new Folder($this->root, $this->view, $path, $info);
136
-		} else {
137
-			return new File($this->root, $this->view, $path, $info);
138
-		}
139
-	}
140
-
141
-	/**
142
-	 * Get the node at $path
143
-	 *
144
-	 * @param string $path
145
-	 * @return \OC\Files\Node\Node
146
-	 * @throws \OCP\Files\NotFoundException
147
-	 */
148
-	public function get($path) {
149
-		return $this->root->get($this->getFullPath($path));
150
-	}
151
-
152
-	/**
153
-	 * @param string $path
154
-	 * @return bool
155
-	 */
156
-	public function nodeExists($path) {
157
-		try {
158
-			$this->get($path);
159
-			return true;
160
-		} catch (NotFoundException $e) {
161
-			return false;
162
-		}
163
-	}
164
-
165
-	/**
166
-	 * @param string $path
167
-	 * @return \OC\Files\Node\Folder
168
-	 * @throws \OCP\Files\NotPermittedException
169
-	 */
170
-	public function newFolder($path) {
171
-		if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
172
-			$fullPath = $this->getFullPath($path);
173
-			$nonExisting = new NonExistingFolder($this->root, $this->view, $fullPath);
174
-			$this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
175
-			if (!$this->view->mkdir($fullPath)) {
176
-				throw new NotPermittedException('Could not create folder');
177
-			}
178
-			$node = new Folder($this->root, $this->view, $fullPath);
179
-			$this->sendHooks(['postWrite', 'postCreate'], [$node]);
180
-			return $node;
181
-		} else {
182
-			throw new NotPermittedException('No create permission for folder');
183
-		}
184
-	}
185
-
186
-	/**
187
-	 * @param string $path
188
-	 * @param string | resource | null $content
189
-	 * @return \OC\Files\Node\File
190
-	 * @throws \OCP\Files\NotPermittedException
191
-	 */
192
-	public function newFile($path, $content = null) {
193
-		if (empty($path)) {
194
-			throw new NotPermittedException('Could not create as provided path is empty');
195
-		}
196
-		if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
197
-			$fullPath = $this->getFullPath($path);
198
-			$nonExisting = new NonExistingFile($this->root, $this->view, $fullPath);
199
-			$this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
200
-			if ($content !== null) {
201
-				$result = $this->view->file_put_contents($fullPath, $content);
202
-			} else {
203
-				$result = $this->view->touch($fullPath);
204
-			}
205
-			if ($result === false) {
206
-				throw new NotPermittedException('Could not create path');
207
-			}
208
-			$node = new File($this->root, $this->view, $fullPath);
209
-			$this->sendHooks(['postWrite', 'postCreate'], [$node]);
210
-			return $node;
211
-		}
212
-		throw new NotPermittedException('No create permission for path');
213
-	}
214
-
215
-	private function queryFromOperator(ISearchOperator $operator, string $uid = null): ISearchQuery {
216
-		if ($uid === null) {
217
-			$user = null;
218
-		} else {
219
-			/** @var IUserManager $userManager */
220
-			$userManager = \OC::$server->query(IUserManager::class);
221
-			$user = $userManager->get($uid);
222
-		}
223
-		return new SearchQuery($operator, 0, 0, [], $user);
224
-	}
225
-
226
-	/**
227
-	 * search for files with the name matching $query
228
-	 *
229
-	 * @param string|ISearchQuery $query
230
-	 * @return \OC\Files\Node\Node[]
231
-	 */
232
-	public function search($query) {
233
-		if (is_string($query)) {
234
-			$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'name', '%' . $query . '%'));
235
-		}
236
-
237
-		// Limit+offset for queries with ordering
238
-		//
239
-		// Because we currently can't do ordering between the results from different storages in sql
240
-		// The only way to do ordering is requesting the $limit number of entries from all storages
241
-		// sorting them and returning the first $limit entries.
242
-		//
243
-		// For offset we have the same problem, we don't know how many entries from each storage should be skipped
244
-		// by a given $offset, so instead we query $offset + $limit from each storage and return entries $offset..($offset+$limit)
245
-		// after merging and sorting them.
246
-		//
247
-		// This is suboptimal but because limit and offset tend to be fairly small in real world use cases it should
248
-		// still be significantly better than disabling paging altogether
249
-
250
-		$limitToHome = $query->limitToHome();
251
-		if ($limitToHome && count(explode('/', $this->path)) !== 3) {
252
-			throw new \InvalidArgumentException('searching by owner is only allows on the users home folder');
253
-		}
254
-
255
-		$rootLength = strlen($this->path);
256
-		$mount = $this->root->getMount($this->path);
257
-		$storage = $mount->getStorage();
258
-		$internalPath = $mount->getInternalPath($this->path);
259
-		$internalPath = rtrim($internalPath, '/');
260
-		if ($internalPath !== '') {
261
-			$internalPath = $internalPath . '/';
262
-		}
263
-
264
-		$subQueryLimit = $query->getLimit() > 0 ? $query->getLimit() + $query->getOffset() : 0;
265
-		$rootQuery = new SearchQuery(
266
-			new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_AND, [
267
-				new SearchComparison(ISearchComparison::COMPARE_LIKE, 'path', $internalPath . '%'),
268
-				$query->getSearchOperation(),
269
-			]
270
-			),
271
-			$subQueryLimit,
272
-			0,
273
-			$query->getOrder(),
274
-			$query->getUser()
275
-		);
276
-
277
-		$files = [];
278
-
279
-		$cache = $storage->getCache('');
280
-
281
-		$results = $cache->searchQuery($rootQuery);
282
-		foreach ($results as $result) {
283
-			$files[] = $this->cacheEntryToFileInfo($mount, '', $internalPath, $result);
284
-		}
285
-
286
-		if (!$limitToHome) {
287
-			$mounts = $this->root->getMountsIn($this->path);
288
-			foreach ($mounts as $mount) {
289
-				$subQuery = new SearchQuery(
290
-					$query->getSearchOperation(),
291
-					$subQueryLimit,
292
-					0,
293
-					$query->getOrder(),
294
-					$query->getUser()
295
-				);
296
-
297
-				$storage = $mount->getStorage();
298
-				if ($storage) {
299
-					$cache = $storage->getCache('');
300
-
301
-					$relativeMountPoint = ltrim(substr($mount->getMountPoint(), $rootLength), '/');
302
-					$results = $cache->searchQuery($subQuery);
303
-					foreach ($results as $result) {
304
-						$files[] = $this->cacheEntryToFileInfo($mount, $relativeMountPoint, '', $result);
305
-					}
306
-				}
307
-			}
308
-		}
309
-
310
-		$order = $query->getOrder();
311
-		if ($order) {
312
-			usort($files, function (FileInfo $a,FileInfo  $b) use ($order) {
313
-				foreach ($order as $orderField) {
314
-					$cmp = $orderField->sortFileInfo($a, $b);
315
-					if ($cmp !== 0) {
316
-						return $cmp;
317
-					}
318
-				}
319
-				return 0;
320
-			});
321
-		}
322
-		$files = array_values(array_slice($files, $query->getOffset(), $query->getLimit() > 0 ? $query->getLimit() : null));
323
-
324
-		return array_map(function (FileInfo $file) {
325
-			return $this->createNode($file->getPath(), $file);
326
-		}, $files);
327
-	}
328
-
329
-	private function cacheEntryToFileInfo(IMountPoint $mount, string $appendRoot, string $trimRoot, ICacheEntry $cacheEntry): FileInfo {
330
-		$trimLength = strlen($trimRoot);
331
-		$cacheEntry['internalPath'] = $cacheEntry['path'];
332
-		$cacheEntry['path'] = $appendRoot . substr($cacheEntry['path'], $trimLength);
333
-		return new \OC\Files\FileInfo($this->path . '/' . $cacheEntry['path'], $mount->getStorage(), $cacheEntry['internalPath'], $cacheEntry, $mount);
334
-	}
335
-
336
-	/**
337
-	 * search for files by mimetype
338
-	 *
339
-	 * @param string $mimetype
340
-	 * @return Node[]
341
-	 */
342
-	public function searchByMime($mimetype) {
343
-		if (strpos($mimetype, '/') === false) {
344
-			$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', $mimetype . '/%'));
345
-		} else {
346
-			$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'mimetype', $mimetype));
347
-		}
348
-		return $this->search($query);
349
-	}
350
-
351
-	/**
352
-	 * search for files by tag
353
-	 *
354
-	 * @param string|int $tag name or tag id
355
-	 * @param string $userId owner of the tags
356
-	 * @return Node[]
357
-	 */
358
-	public function searchByTag($tag, $userId) {
359
-		$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'tagname', $tag), $userId);
360
-		return $this->search($query);
361
-	}
362
-
363
-	/**
364
-	 * @param int $id
365
-	 * @return \OC\Files\Node\Node[]
366
-	 */
367
-	public function getById($id) {
368
-		$mountCache = $this->root->getUserMountCache();
369
-		if (strpos($this->getPath(), '/', 1) > 0) {
370
-			[, $user] = explode('/', $this->getPath());
371
-		} else {
372
-			$user = null;
373
-		}
374
-		$mountsContainingFile = $mountCache->getMountsForFileId((int)$id, $user);
375
-		$mounts = $this->root->getMountsIn($this->path);
376
-		$mounts[] = $this->root->getMount($this->path);
377
-		/** @var IMountPoint[] $folderMounts */
378
-		$folderMounts = array_combine(array_map(function (IMountPoint $mountPoint) {
379
-			return $mountPoint->getMountPoint();
380
-		}, $mounts), $mounts);
381
-
382
-		/** @var ICachedMountInfo[] $mountsContainingFile */
383
-		$mountsContainingFile = array_values(array_filter($mountsContainingFile, function (ICachedMountInfo $cachedMountInfo) use ($folderMounts) {
384
-			return isset($folderMounts[$cachedMountInfo->getMountPoint()]);
385
-		}));
386
-
387
-		if (count($mountsContainingFile) === 0) {
388
-			if ($user === $this->getAppDataDirectoryName()) {
389
-				return $this->getByIdInRootMount((int)$id);
390
-			}
391
-			return [];
392
-		}
393
-
394
-		$nodes = array_map(function (ICachedMountInfo $cachedMountInfo) use ($folderMounts, $id) {
395
-			$mount = $folderMounts[$cachedMountInfo->getMountPoint()];
396
-			$cacheEntry = $mount->getStorage()->getCache()->get((int)$id);
397
-			if (!$cacheEntry) {
398
-				return null;
399
-			}
400
-
401
-			// cache jails will hide the "true" internal path
402
-			$internalPath = ltrim($cachedMountInfo->getRootInternalPath() . '/' . $cacheEntry->getPath(), '/');
403
-			$pathRelativeToMount = substr($internalPath, strlen($cachedMountInfo->getRootInternalPath()));
404
-			$pathRelativeToMount = ltrim($pathRelativeToMount, '/');
405
-			$absolutePath = rtrim($cachedMountInfo->getMountPoint() . $pathRelativeToMount, '/');
406
-			return $this->root->createNode($absolutePath, new \OC\Files\FileInfo(
407
-				$absolutePath, $mount->getStorage(), $cacheEntry->getPath(), $cacheEntry, $mount,
408
-				\OC::$server->getUserManager()->get($mount->getStorage()->getOwner($pathRelativeToMount))
409
-			));
410
-		}, $mountsContainingFile);
411
-
412
-		$nodes = array_filter($nodes);
413
-
414
-		return array_filter($nodes, function (Node $node) {
415
-			return $this->getRelativePath($node->getPath());
416
-		});
417
-	}
418
-
419
-	protected function getAppDataDirectoryName(): string {
420
-		$instanceId = \OC::$server->getConfig()->getSystemValueString('instanceid');
421
-		return 'appdata_' . $instanceId;
422
-	}
423
-
424
-	/**
425
-	 * In case the path we are currently in is inside the appdata_* folder,
426
-	 * the original getById method does not work, because it can only look inside
427
-	 * the user's mount points. But the user has no mount point for the root storage.
428
-	 *
429
-	 * So in that case we directly check the mount of the root if it contains
430
-	 * the id. If it does we check if the path is inside the path we are working
431
-	 * in.
432
-	 *
433
-	 * @param int $id
434
-	 * @return array
435
-	 */
436
-	protected function getByIdInRootMount(int $id): array {
437
-		$mount = $this->root->getMount('');
438
-		$cacheEntry = $mount->getStorage()->getCache($this->path)->get($id);
439
-		if (!$cacheEntry) {
440
-			return [];
441
-		}
442
-
443
-		$absolutePath = '/' . ltrim($cacheEntry->getPath(), '/');
444
-		$currentPath = rtrim($this->path, '/') . '/';
445
-
446
-		if (strpos($absolutePath, $currentPath) !== 0) {
447
-			return [];
448
-		}
449
-
450
-		return [$this->root->createNode(
451
-			$absolutePath, new \OC\Files\FileInfo(
452
-			$absolutePath,
453
-			$mount->getStorage(),
454
-			$cacheEntry->getPath(),
455
-			$cacheEntry,
456
-			$mount
457
-		))];
458
-	}
459
-
460
-	public function getFreeSpace() {
461
-		return $this->view->free_space($this->path);
462
-	}
463
-
464
-	public function delete() {
465
-		if ($this->checkPermissions(\OCP\Constants::PERMISSION_DELETE)) {
466
-			$this->sendHooks(['preDelete']);
467
-			$fileInfo = $this->getFileInfo();
468
-			$this->view->rmdir($this->path);
469
-			$nonExisting = new NonExistingFolder($this->root, $this->view, $this->path, $fileInfo);
470
-			$this->sendHooks(['postDelete'], [$nonExisting]);
471
-			$this->exists = false;
472
-		} else {
473
-			throw new NotPermittedException('No delete permission for path');
474
-		}
475
-	}
476
-
477
-	/**
478
-	 * Add a suffix to the name in case the file exists
479
-	 *
480
-	 * @param string $name
481
-	 * @return string
482
-	 * @throws NotPermittedException
483
-	 */
484
-	public function getNonExistingName($name) {
485
-		$uniqueName = \OC_Helper::buildNotExistingFileNameForView($this->getPath(), $name, $this->view);
486
-		return trim($this->getRelativePath($uniqueName), '/');
487
-	}
488
-
489
-	/**
490
-	 * @param int $limit
491
-	 * @param int $offset
492
-	 * @return \OCP\Files\Node[]
493
-	 */
494
-	public function getRecent($limit, $offset = 0) {
495
-		$mimetypeLoader = \OC::$server->getMimeTypeLoader();
496
-		$mounts = $this->root->getMountsIn($this->path);
497
-		$mounts[] = $this->getMountPoint();
498
-
499
-		$mounts = array_filter($mounts, function (IMountPoint $mount) {
500
-			return $mount->getStorage() !== null;
501
-		});
502
-		$storageIds = array_map(function (IMountPoint $mount) {
503
-			return $mount->getStorage()->getCache()->getNumericStorageId();
504
-		}, $mounts);
505
-		/** @var IMountPoint[] $mountMap */
506
-		$mountMap = array_combine($storageIds, $mounts);
507
-		$folderMimetype = $mimetypeLoader->getId(FileInfo::MIMETYPE_FOLDER);
508
-
509
-		/*
55
+    /**
56
+     * Creates a Folder that represents a non-existing path
57
+     *
58
+     * @param string $path path
59
+     * @return string non-existing node class
60
+     */
61
+    protected function createNonExistingNode($path) {
62
+        return new NonExistingFolder($this->root, $this->view, $path);
63
+    }
64
+
65
+    /**
66
+     * @param string $path path relative to the folder
67
+     * @return string
68
+     * @throws \OCP\Files\NotPermittedException
69
+     */
70
+    public function getFullPath($path) {
71
+        if (!$this->isValidPath($path)) {
72
+            throw new NotPermittedException('Invalid path');
73
+        }
74
+        return $this->path . $this->normalizePath($path);
75
+    }
76
+
77
+    /**
78
+     * @param string $path
79
+     * @return string|null
80
+     */
81
+    public function getRelativePath($path) {
82
+        if ($this->path === '' or $this->path === '/') {
83
+            return $this->normalizePath($path);
84
+        }
85
+        if ($path === $this->path) {
86
+            return '/';
87
+        } elseif (strpos($path, $this->path . '/') !== 0) {
88
+            return null;
89
+        } else {
90
+            $path = substr($path, strlen($this->path));
91
+            return $this->normalizePath($path);
92
+        }
93
+    }
94
+
95
+    /**
96
+     * check if a node is a (grand-)child of the folder
97
+     *
98
+     * @param \OC\Files\Node\Node $node
99
+     * @return bool
100
+     */
101
+    public function isSubNode($node) {
102
+        return strpos($node->getPath(), $this->path . '/') === 0;
103
+    }
104
+
105
+    /**
106
+     * get the content of this directory
107
+     *
108
+     * @return Node[]
109
+     * @throws \OCP\Files\NotFoundException
110
+     */
111
+    public function getDirectoryListing() {
112
+        $folderContent = $this->view->getDirectoryContent($this->path);
113
+
114
+        return array_map(function (FileInfo $info) {
115
+            if ($info->getMimetype() === 'httpd/unix-directory') {
116
+                return new Folder($this->root, $this->view, $info->getPath(), $info);
117
+            } else {
118
+                return new File($this->root, $this->view, $info->getPath(), $info);
119
+            }
120
+        }, $folderContent);
121
+    }
122
+
123
+    /**
124
+     * @param string $path
125
+     * @param FileInfo $info
126
+     * @return File|Folder
127
+     */
128
+    protected function createNode($path, FileInfo $info = null) {
129
+        if (is_null($info)) {
130
+            $isDir = $this->view->is_dir($path);
131
+        } else {
132
+            $isDir = $info->getType() === FileInfo::TYPE_FOLDER;
133
+        }
134
+        if ($isDir) {
135
+            return new Folder($this->root, $this->view, $path, $info);
136
+        } else {
137
+            return new File($this->root, $this->view, $path, $info);
138
+        }
139
+    }
140
+
141
+    /**
142
+     * Get the node at $path
143
+     *
144
+     * @param string $path
145
+     * @return \OC\Files\Node\Node
146
+     * @throws \OCP\Files\NotFoundException
147
+     */
148
+    public function get($path) {
149
+        return $this->root->get($this->getFullPath($path));
150
+    }
151
+
152
+    /**
153
+     * @param string $path
154
+     * @return bool
155
+     */
156
+    public function nodeExists($path) {
157
+        try {
158
+            $this->get($path);
159
+            return true;
160
+        } catch (NotFoundException $e) {
161
+            return false;
162
+        }
163
+    }
164
+
165
+    /**
166
+     * @param string $path
167
+     * @return \OC\Files\Node\Folder
168
+     * @throws \OCP\Files\NotPermittedException
169
+     */
170
+    public function newFolder($path) {
171
+        if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
172
+            $fullPath = $this->getFullPath($path);
173
+            $nonExisting = new NonExistingFolder($this->root, $this->view, $fullPath);
174
+            $this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
175
+            if (!$this->view->mkdir($fullPath)) {
176
+                throw new NotPermittedException('Could not create folder');
177
+            }
178
+            $node = new Folder($this->root, $this->view, $fullPath);
179
+            $this->sendHooks(['postWrite', 'postCreate'], [$node]);
180
+            return $node;
181
+        } else {
182
+            throw new NotPermittedException('No create permission for folder');
183
+        }
184
+    }
185
+
186
+    /**
187
+     * @param string $path
188
+     * @param string | resource | null $content
189
+     * @return \OC\Files\Node\File
190
+     * @throws \OCP\Files\NotPermittedException
191
+     */
192
+    public function newFile($path, $content = null) {
193
+        if (empty($path)) {
194
+            throw new NotPermittedException('Could not create as provided path is empty');
195
+        }
196
+        if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
197
+            $fullPath = $this->getFullPath($path);
198
+            $nonExisting = new NonExistingFile($this->root, $this->view, $fullPath);
199
+            $this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
200
+            if ($content !== null) {
201
+                $result = $this->view->file_put_contents($fullPath, $content);
202
+            } else {
203
+                $result = $this->view->touch($fullPath);
204
+            }
205
+            if ($result === false) {
206
+                throw new NotPermittedException('Could not create path');
207
+            }
208
+            $node = new File($this->root, $this->view, $fullPath);
209
+            $this->sendHooks(['postWrite', 'postCreate'], [$node]);
210
+            return $node;
211
+        }
212
+        throw new NotPermittedException('No create permission for path');
213
+    }
214
+
215
+    private function queryFromOperator(ISearchOperator $operator, string $uid = null): ISearchQuery {
216
+        if ($uid === null) {
217
+            $user = null;
218
+        } else {
219
+            /** @var IUserManager $userManager */
220
+            $userManager = \OC::$server->query(IUserManager::class);
221
+            $user = $userManager->get($uid);
222
+        }
223
+        return new SearchQuery($operator, 0, 0, [], $user);
224
+    }
225
+
226
+    /**
227
+     * search for files with the name matching $query
228
+     *
229
+     * @param string|ISearchQuery $query
230
+     * @return \OC\Files\Node\Node[]
231
+     */
232
+    public function search($query) {
233
+        if (is_string($query)) {
234
+            $query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'name', '%' . $query . '%'));
235
+        }
236
+
237
+        // Limit+offset for queries with ordering
238
+        //
239
+        // Because we currently can't do ordering between the results from different storages in sql
240
+        // The only way to do ordering is requesting the $limit number of entries from all storages
241
+        // sorting them and returning the first $limit entries.
242
+        //
243
+        // For offset we have the same problem, we don't know how many entries from each storage should be skipped
244
+        // by a given $offset, so instead we query $offset + $limit from each storage and return entries $offset..($offset+$limit)
245
+        // after merging and sorting them.
246
+        //
247
+        // This is suboptimal but because limit and offset tend to be fairly small in real world use cases it should
248
+        // still be significantly better than disabling paging altogether
249
+
250
+        $limitToHome = $query->limitToHome();
251
+        if ($limitToHome && count(explode('/', $this->path)) !== 3) {
252
+            throw new \InvalidArgumentException('searching by owner is only allows on the users home folder');
253
+        }
254
+
255
+        $rootLength = strlen($this->path);
256
+        $mount = $this->root->getMount($this->path);
257
+        $storage = $mount->getStorage();
258
+        $internalPath = $mount->getInternalPath($this->path);
259
+        $internalPath = rtrim($internalPath, '/');
260
+        if ($internalPath !== '') {
261
+            $internalPath = $internalPath . '/';
262
+        }
263
+
264
+        $subQueryLimit = $query->getLimit() > 0 ? $query->getLimit() + $query->getOffset() : 0;
265
+        $rootQuery = new SearchQuery(
266
+            new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_AND, [
267
+                new SearchComparison(ISearchComparison::COMPARE_LIKE, 'path', $internalPath . '%'),
268
+                $query->getSearchOperation(),
269
+            ]
270
+            ),
271
+            $subQueryLimit,
272
+            0,
273
+            $query->getOrder(),
274
+            $query->getUser()
275
+        );
276
+
277
+        $files = [];
278
+
279
+        $cache = $storage->getCache('');
280
+
281
+        $results = $cache->searchQuery($rootQuery);
282
+        foreach ($results as $result) {
283
+            $files[] = $this->cacheEntryToFileInfo($mount, '', $internalPath, $result);
284
+        }
285
+
286
+        if (!$limitToHome) {
287
+            $mounts = $this->root->getMountsIn($this->path);
288
+            foreach ($mounts as $mount) {
289
+                $subQuery = new SearchQuery(
290
+                    $query->getSearchOperation(),
291
+                    $subQueryLimit,
292
+                    0,
293
+                    $query->getOrder(),
294
+                    $query->getUser()
295
+                );
296
+
297
+                $storage = $mount->getStorage();
298
+                if ($storage) {
299
+                    $cache = $storage->getCache('');
300
+
301
+                    $relativeMountPoint = ltrim(substr($mount->getMountPoint(), $rootLength), '/');
302
+                    $results = $cache->searchQuery($subQuery);
303
+                    foreach ($results as $result) {
304
+                        $files[] = $this->cacheEntryToFileInfo($mount, $relativeMountPoint, '', $result);
305
+                    }
306
+                }
307
+            }
308
+        }
309
+
310
+        $order = $query->getOrder();
311
+        if ($order) {
312
+            usort($files, function (FileInfo $a,FileInfo  $b) use ($order) {
313
+                foreach ($order as $orderField) {
314
+                    $cmp = $orderField->sortFileInfo($a, $b);
315
+                    if ($cmp !== 0) {
316
+                        return $cmp;
317
+                    }
318
+                }
319
+                return 0;
320
+            });
321
+        }
322
+        $files = array_values(array_slice($files, $query->getOffset(), $query->getLimit() > 0 ? $query->getLimit() : null));
323
+
324
+        return array_map(function (FileInfo $file) {
325
+            return $this->createNode($file->getPath(), $file);
326
+        }, $files);
327
+    }
328
+
329
+    private function cacheEntryToFileInfo(IMountPoint $mount, string $appendRoot, string $trimRoot, ICacheEntry $cacheEntry): FileInfo {
330
+        $trimLength = strlen($trimRoot);
331
+        $cacheEntry['internalPath'] = $cacheEntry['path'];
332
+        $cacheEntry['path'] = $appendRoot . substr($cacheEntry['path'], $trimLength);
333
+        return new \OC\Files\FileInfo($this->path . '/' . $cacheEntry['path'], $mount->getStorage(), $cacheEntry['internalPath'], $cacheEntry, $mount);
334
+    }
335
+
336
+    /**
337
+     * search for files by mimetype
338
+     *
339
+     * @param string $mimetype
340
+     * @return Node[]
341
+     */
342
+    public function searchByMime($mimetype) {
343
+        if (strpos($mimetype, '/') === false) {
344
+            $query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', $mimetype . '/%'));
345
+        } else {
346
+            $query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'mimetype', $mimetype));
347
+        }
348
+        return $this->search($query);
349
+    }
350
+
351
+    /**
352
+     * search for files by tag
353
+     *
354
+     * @param string|int $tag name or tag id
355
+     * @param string $userId owner of the tags
356
+     * @return Node[]
357
+     */
358
+    public function searchByTag($tag, $userId) {
359
+        $query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'tagname', $tag), $userId);
360
+        return $this->search($query);
361
+    }
362
+
363
+    /**
364
+     * @param int $id
365
+     * @return \OC\Files\Node\Node[]
366
+     */
367
+    public function getById($id) {
368
+        $mountCache = $this->root->getUserMountCache();
369
+        if (strpos($this->getPath(), '/', 1) > 0) {
370
+            [, $user] = explode('/', $this->getPath());
371
+        } else {
372
+            $user = null;
373
+        }
374
+        $mountsContainingFile = $mountCache->getMountsForFileId((int)$id, $user);
375
+        $mounts = $this->root->getMountsIn($this->path);
376
+        $mounts[] = $this->root->getMount($this->path);
377
+        /** @var IMountPoint[] $folderMounts */
378
+        $folderMounts = array_combine(array_map(function (IMountPoint $mountPoint) {
379
+            return $mountPoint->getMountPoint();
380
+        }, $mounts), $mounts);
381
+
382
+        /** @var ICachedMountInfo[] $mountsContainingFile */
383
+        $mountsContainingFile = array_values(array_filter($mountsContainingFile, function (ICachedMountInfo $cachedMountInfo) use ($folderMounts) {
384
+            return isset($folderMounts[$cachedMountInfo->getMountPoint()]);
385
+        }));
386
+
387
+        if (count($mountsContainingFile) === 0) {
388
+            if ($user === $this->getAppDataDirectoryName()) {
389
+                return $this->getByIdInRootMount((int)$id);
390
+            }
391
+            return [];
392
+        }
393
+
394
+        $nodes = array_map(function (ICachedMountInfo $cachedMountInfo) use ($folderMounts, $id) {
395
+            $mount = $folderMounts[$cachedMountInfo->getMountPoint()];
396
+            $cacheEntry = $mount->getStorage()->getCache()->get((int)$id);
397
+            if (!$cacheEntry) {
398
+                return null;
399
+            }
400
+
401
+            // cache jails will hide the "true" internal path
402
+            $internalPath = ltrim($cachedMountInfo->getRootInternalPath() . '/' . $cacheEntry->getPath(), '/');
403
+            $pathRelativeToMount = substr($internalPath, strlen($cachedMountInfo->getRootInternalPath()));
404
+            $pathRelativeToMount = ltrim($pathRelativeToMount, '/');
405
+            $absolutePath = rtrim($cachedMountInfo->getMountPoint() . $pathRelativeToMount, '/');
406
+            return $this->root->createNode($absolutePath, new \OC\Files\FileInfo(
407
+                $absolutePath, $mount->getStorage(), $cacheEntry->getPath(), $cacheEntry, $mount,
408
+                \OC::$server->getUserManager()->get($mount->getStorage()->getOwner($pathRelativeToMount))
409
+            ));
410
+        }, $mountsContainingFile);
411
+
412
+        $nodes = array_filter($nodes);
413
+
414
+        return array_filter($nodes, function (Node $node) {
415
+            return $this->getRelativePath($node->getPath());
416
+        });
417
+    }
418
+
419
+    protected function getAppDataDirectoryName(): string {
420
+        $instanceId = \OC::$server->getConfig()->getSystemValueString('instanceid');
421
+        return 'appdata_' . $instanceId;
422
+    }
423
+
424
+    /**
425
+     * In case the path we are currently in is inside the appdata_* folder,
426
+     * the original getById method does not work, because it can only look inside
427
+     * the user's mount points. But the user has no mount point for the root storage.
428
+     *
429
+     * So in that case we directly check the mount of the root if it contains
430
+     * the id. If it does we check if the path is inside the path we are working
431
+     * in.
432
+     *
433
+     * @param int $id
434
+     * @return array
435
+     */
436
+    protected function getByIdInRootMount(int $id): array {
437
+        $mount = $this->root->getMount('');
438
+        $cacheEntry = $mount->getStorage()->getCache($this->path)->get($id);
439
+        if (!$cacheEntry) {
440
+            return [];
441
+        }
442
+
443
+        $absolutePath = '/' . ltrim($cacheEntry->getPath(), '/');
444
+        $currentPath = rtrim($this->path, '/') . '/';
445
+
446
+        if (strpos($absolutePath, $currentPath) !== 0) {
447
+            return [];
448
+        }
449
+
450
+        return [$this->root->createNode(
451
+            $absolutePath, new \OC\Files\FileInfo(
452
+            $absolutePath,
453
+            $mount->getStorage(),
454
+            $cacheEntry->getPath(),
455
+            $cacheEntry,
456
+            $mount
457
+        ))];
458
+    }
459
+
460
+    public function getFreeSpace() {
461
+        return $this->view->free_space($this->path);
462
+    }
463
+
464
+    public function delete() {
465
+        if ($this->checkPermissions(\OCP\Constants::PERMISSION_DELETE)) {
466
+            $this->sendHooks(['preDelete']);
467
+            $fileInfo = $this->getFileInfo();
468
+            $this->view->rmdir($this->path);
469
+            $nonExisting = new NonExistingFolder($this->root, $this->view, $this->path, $fileInfo);
470
+            $this->sendHooks(['postDelete'], [$nonExisting]);
471
+            $this->exists = false;
472
+        } else {
473
+            throw new NotPermittedException('No delete permission for path');
474
+        }
475
+    }
476
+
477
+    /**
478
+     * Add a suffix to the name in case the file exists
479
+     *
480
+     * @param string $name
481
+     * @return string
482
+     * @throws NotPermittedException
483
+     */
484
+    public function getNonExistingName($name) {
485
+        $uniqueName = \OC_Helper::buildNotExistingFileNameForView($this->getPath(), $name, $this->view);
486
+        return trim($this->getRelativePath($uniqueName), '/');
487
+    }
488
+
489
+    /**
490
+     * @param int $limit
491
+     * @param int $offset
492
+     * @return \OCP\Files\Node[]
493
+     */
494
+    public function getRecent($limit, $offset = 0) {
495
+        $mimetypeLoader = \OC::$server->getMimeTypeLoader();
496
+        $mounts = $this->root->getMountsIn($this->path);
497
+        $mounts[] = $this->getMountPoint();
498
+
499
+        $mounts = array_filter($mounts, function (IMountPoint $mount) {
500
+            return $mount->getStorage() !== null;
501
+        });
502
+        $storageIds = array_map(function (IMountPoint $mount) {
503
+            return $mount->getStorage()->getCache()->getNumericStorageId();
504
+        }, $mounts);
505
+        /** @var IMountPoint[] $mountMap */
506
+        $mountMap = array_combine($storageIds, $mounts);
507
+        $folderMimetype = $mimetypeLoader->getId(FileInfo::MIMETYPE_FOLDER);
508
+
509
+        /*
510 510
 		 * Construct an array of the storage id with their prefix path
511 511
 		 * This helps us to filter in the final query
512 512
 		 */
513
-		$filters = array_map(function (IMountPoint $mount) {
514
-			$storage = $mount->getStorage();
515
-
516
-			$storageId = $storage->getCache()->getNumericStorageId();
517
-			$prefix = '';
518
-
519
-			if ($storage->instanceOfStorage(Jail::class)) {
520
-				$prefix = $storage->getUnJailedPath('');
521
-			}
522
-
523
-			return [
524
-				'storageId' => $storageId,
525
-				'pathPrefix' => $prefix,
526
-			];
527
-		}, $mounts);
528
-
529
-		// Search in batches of 500 entries
530
-		$searchLimit = 500;
531
-		$results = [];
532
-		$searchResultCount = 0;
533
-		$count = 0;
534
-		do {
535
-			$searchResult = $this->recentSearch($searchLimit, $offset, $folderMimetype, $filters);
536
-
537
-			// Exit condition if there are no more results
538
-			if (count($searchResult) === 0) {
539
-				break;
540
-			}
541
-
542
-			$searchResultCount += count($searchResult);
543
-
544
-			$parseResult = $this->recentParse($searchResult, $mountMap, $mimetypeLoader);
545
-
546
-			foreach ($parseResult as $result) {
547
-				$results[] = $result;
548
-			}
549
-
550
-			$offset += $searchLimit;
551
-			$count++;
552
-		} while (count($results) < $limit && ($searchResultCount < (3 * $limit) || $count < 5));
553
-
554
-		return array_slice($results, 0, $limit);
555
-	}
556
-
557
-	private function recentSearch($limit, $offset, $folderMimetype, $filters) {
558
-		$dbconn = \OC::$server->getDatabaseConnection();
559
-		$builder = $dbconn->getQueryBuilder();
560
-		$query = $builder
561
-			->select('f.*')
562
-			->from('filecache', 'f');
563
-
564
-		/*
513
+        $filters = array_map(function (IMountPoint $mount) {
514
+            $storage = $mount->getStorage();
515
+
516
+            $storageId = $storage->getCache()->getNumericStorageId();
517
+            $prefix = '';
518
+
519
+            if ($storage->instanceOfStorage(Jail::class)) {
520
+                $prefix = $storage->getUnJailedPath('');
521
+            }
522
+
523
+            return [
524
+                'storageId' => $storageId,
525
+                'pathPrefix' => $prefix,
526
+            ];
527
+        }, $mounts);
528
+
529
+        // Search in batches of 500 entries
530
+        $searchLimit = 500;
531
+        $results = [];
532
+        $searchResultCount = 0;
533
+        $count = 0;
534
+        do {
535
+            $searchResult = $this->recentSearch($searchLimit, $offset, $folderMimetype, $filters);
536
+
537
+            // Exit condition if there are no more results
538
+            if (count($searchResult) === 0) {
539
+                break;
540
+            }
541
+
542
+            $searchResultCount += count($searchResult);
543
+
544
+            $parseResult = $this->recentParse($searchResult, $mountMap, $mimetypeLoader);
545
+
546
+            foreach ($parseResult as $result) {
547
+                $results[] = $result;
548
+            }
549
+
550
+            $offset += $searchLimit;
551
+            $count++;
552
+        } while (count($results) < $limit && ($searchResultCount < (3 * $limit) || $count < 5));
553
+
554
+        return array_slice($results, 0, $limit);
555
+    }
556
+
557
+    private function recentSearch($limit, $offset, $folderMimetype, $filters) {
558
+        $dbconn = \OC::$server->getDatabaseConnection();
559
+        $builder = $dbconn->getQueryBuilder();
560
+        $query = $builder
561
+            ->select('f.*')
562
+            ->from('filecache', 'f');
563
+
564
+        /*
565 565
 		 * Here is where we construct the filtering.
566 566
 		 * Note that this is expensive filtering as it is a lot of like queries.
567 567
 		 * However the alternative is we do this filtering and parsing later in php with the risk of looping endlessly
568 568
 		 */
569
-		$storageFilters = $builder->expr()->orX();
570
-		foreach ($filters as $filter) {
571
-			$storageFilter = $builder->expr()->andX(
572
-				$builder->expr()->eq('f.storage', $builder->createNamedParameter($filter['storageId']))
573
-			);
574
-
575
-			if ($filter['pathPrefix'] !== '') {
576
-				$storageFilter->add(
577
-					$builder->expr()->like('f.path', $builder->createNamedParameter($dbconn->escapeLikeParameter($filter['pathPrefix']) . '/%'))
578
-				);
579
-			}
580
-
581
-			$storageFilters->add($storageFilter);
582
-		}
583
-
584
-		$query->andWhere($storageFilters);
585
-
586
-		$query->andWhere($builder->expr()->orX(
587
-		// handle non empty folders separate
588
-			$builder->expr()->neq('f.mimetype', $builder->createNamedParameter($folderMimetype, IQueryBuilder::PARAM_INT)),
589
-			$builder->expr()->eq('f.size', new Literal(0))
590
-		))
591
-			->andWhere($builder->expr()->notLike('f.path', $builder->createNamedParameter('files_versions/%')))
592
-			->andWhere($builder->expr()->notLike('f.path', $builder->createNamedParameter('files_trashbin/%')))
593
-			->orderBy('f.mtime', 'DESC')
594
-			->setMaxResults($limit)
595
-			->setFirstResult($offset);
596
-
597
-		$result = $query->execute();
598
-		$rows = $result->fetchAll();
599
-		$result->closeCursor();
600
-
601
-		return $rows;
602
-	}
603
-
604
-	private function recentParse($result, $mountMap, $mimetypeLoader) {
605
-		$files = array_filter(array_map(function (array $entry) use ($mountMap, $mimetypeLoader) {
606
-			$mount = $mountMap[$entry['storage']];
607
-			$entry['internalPath'] = $entry['path'];
608
-			$entry['mimetype'] = $mimetypeLoader->getMimetypeById($entry['mimetype']);
609
-			$entry['mimepart'] = $mimetypeLoader->getMimetypeById($entry['mimepart']);
610
-			$path = $this->getAbsolutePath($mount, $entry['path']);
611
-			if (is_null($path)) {
612
-				return null;
613
-			}
614
-			$fileInfo = new \OC\Files\FileInfo($path, $mount->getStorage(), $entry['internalPath'], $entry, $mount);
615
-			return $this->root->createNode($fileInfo->getPath(), $fileInfo);
616
-		}, $result));
617
-
618
-		return array_values(array_filter($files, function (Node $node) {
619
-			$cacheEntry = $node->getMountPoint()->getStorage()->getCache()->get($node->getId());
620
-			if (!$cacheEntry) {
621
-				return false;
622
-			}
623
-			$relative = $this->getRelativePath($node->getPath());
624
-			return $relative !== null && $relative !== '/'
625
-				&& ($cacheEntry->getPermissions() & \OCP\Constants::PERMISSION_READ) === \OCP\Constants::PERMISSION_READ;
626
-		}));
627
-	}
628
-
629
-	private function getAbsolutePath(IMountPoint $mount, $path) {
630
-		$storage = $mount->getStorage();
631
-		if ($storage->instanceOfStorage('\OC\Files\Storage\Wrapper\Jail')) {
632
-			if ($storage->instanceOfStorage(SharedStorage::class)) {
633
-				$storage->getSourceStorage();
634
-			}
635
-			/** @var \OC\Files\Storage\Wrapper\Jail $storage */
636
-			$jailRoot = $storage->getUnjailedPath('');
637
-			$rootLength = strlen($jailRoot) + 1;
638
-			if ($path === $jailRoot) {
639
-				return $mount->getMountPoint();
640
-			} elseif (substr($path, 0, $rootLength) === $jailRoot . '/') {
641
-				return $mount->getMountPoint() . substr($path, $rootLength);
642
-			} else {
643
-				return null;
644
-			}
645
-		} else {
646
-			return $mount->getMountPoint() . $path;
647
-		}
648
-	}
569
+        $storageFilters = $builder->expr()->orX();
570
+        foreach ($filters as $filter) {
571
+            $storageFilter = $builder->expr()->andX(
572
+                $builder->expr()->eq('f.storage', $builder->createNamedParameter($filter['storageId']))
573
+            );
574
+
575
+            if ($filter['pathPrefix'] !== '') {
576
+                $storageFilter->add(
577
+                    $builder->expr()->like('f.path', $builder->createNamedParameter($dbconn->escapeLikeParameter($filter['pathPrefix']) . '/%'))
578
+                );
579
+            }
580
+
581
+            $storageFilters->add($storageFilter);
582
+        }
583
+
584
+        $query->andWhere($storageFilters);
585
+
586
+        $query->andWhere($builder->expr()->orX(
587
+        // handle non empty folders separate
588
+            $builder->expr()->neq('f.mimetype', $builder->createNamedParameter($folderMimetype, IQueryBuilder::PARAM_INT)),
589
+            $builder->expr()->eq('f.size', new Literal(0))
590
+        ))
591
+            ->andWhere($builder->expr()->notLike('f.path', $builder->createNamedParameter('files_versions/%')))
592
+            ->andWhere($builder->expr()->notLike('f.path', $builder->createNamedParameter('files_trashbin/%')))
593
+            ->orderBy('f.mtime', 'DESC')
594
+            ->setMaxResults($limit)
595
+            ->setFirstResult($offset);
596
+
597
+        $result = $query->execute();
598
+        $rows = $result->fetchAll();
599
+        $result->closeCursor();
600
+
601
+        return $rows;
602
+    }
603
+
604
+    private function recentParse($result, $mountMap, $mimetypeLoader) {
605
+        $files = array_filter(array_map(function (array $entry) use ($mountMap, $mimetypeLoader) {
606
+            $mount = $mountMap[$entry['storage']];
607
+            $entry['internalPath'] = $entry['path'];
608
+            $entry['mimetype'] = $mimetypeLoader->getMimetypeById($entry['mimetype']);
609
+            $entry['mimepart'] = $mimetypeLoader->getMimetypeById($entry['mimepart']);
610
+            $path = $this->getAbsolutePath($mount, $entry['path']);
611
+            if (is_null($path)) {
612
+                return null;
613
+            }
614
+            $fileInfo = new \OC\Files\FileInfo($path, $mount->getStorage(), $entry['internalPath'], $entry, $mount);
615
+            return $this->root->createNode($fileInfo->getPath(), $fileInfo);
616
+        }, $result));
617
+
618
+        return array_values(array_filter($files, function (Node $node) {
619
+            $cacheEntry = $node->getMountPoint()->getStorage()->getCache()->get($node->getId());
620
+            if (!$cacheEntry) {
621
+                return false;
622
+            }
623
+            $relative = $this->getRelativePath($node->getPath());
624
+            return $relative !== null && $relative !== '/'
625
+                && ($cacheEntry->getPermissions() & \OCP\Constants::PERMISSION_READ) === \OCP\Constants::PERMISSION_READ;
626
+        }));
627
+    }
628
+
629
+    private function getAbsolutePath(IMountPoint $mount, $path) {
630
+        $storage = $mount->getStorage();
631
+        if ($storage->instanceOfStorage('\OC\Files\Storage\Wrapper\Jail')) {
632
+            if ($storage->instanceOfStorage(SharedStorage::class)) {
633
+                $storage->getSourceStorage();
634
+            }
635
+            /** @var \OC\Files\Storage\Wrapper\Jail $storage */
636
+            $jailRoot = $storage->getUnjailedPath('');
637
+            $rootLength = strlen($jailRoot) + 1;
638
+            if ($path === $jailRoot) {
639
+                return $mount->getMountPoint();
640
+            } elseif (substr($path, 0, $rootLength) === $jailRoot . '/') {
641
+                return $mount->getMountPoint() . substr($path, $rootLength);
642
+            } else {
643
+                return null;
644
+            }
645
+        } else {
646
+            return $mount->getMountPoint() . $path;
647
+        }
648
+    }
649 649
 }
Please login to merge, or discard this patch.