Completed
Push — master ( d4b380...2f1c74 )
by
unknown
33:25 queued 17s
created
lib/private/Files/Node/Folder.php 2 patches
Indentation   +432 added lines, -432 removed lines patch added patch discarded remove patch
@@ -28,436 +28,436 @@
 block discarded – undo
28 28
 
29 29
 class Folder extends Node implements \OCP\Files\Folder {
30 30
 
31
-	private ?IUserManager $userManager = null;
32
-
33
-	/**
34
-	 * Creates a Folder that represents a non-existing path
35
-	 *
36
-	 * @param string $path path
37
-	 * @return NonExistingFolder non-existing node
38
-	 */
39
-	protected function createNonExistingNode($path) {
40
-		return new NonExistingFolder($this->root, $this->view, $path);
41
-	}
42
-
43
-	/**
44
-	 * @param string $path path relative to the folder
45
-	 * @return string
46
-	 * @throws \OCP\Files\NotPermittedException
47
-	 */
48
-	public function getFullPath($path) {
49
-		$path = $this->normalizePath($path);
50
-		if (!$this->isValidPath($path)) {
51
-			throw new NotPermittedException('Invalid path "' . $path . '"');
52
-		}
53
-		return $this->path . $path;
54
-	}
55
-
56
-	/**
57
-	 * @param string $path
58
-	 * @return string|null
59
-	 */
60
-	public function getRelativePath($path) {
61
-		return PathHelper::getRelativePath($this->getPath(), $path);
62
-	}
63
-
64
-	/**
65
-	 * check if a node is a (grand-)child of the folder
66
-	 *
67
-	 * @param \OC\Files\Node\Node $node
68
-	 * @return bool
69
-	 */
70
-	public function isSubNode($node) {
71
-		return str_starts_with($node->getPath(), $this->path . '/');
72
-	}
73
-
74
-	/**
75
-	 * get the content of this directory
76
-	 *
77
-	 * @return Node[]
78
-	 * @throws \OCP\Files\NotFoundException
79
-	 */
80
-	public function getDirectoryListing() {
81
-		$folderContent = $this->view->getDirectoryContent($this->path, '', $this->getFileInfo(false));
82
-
83
-		return array_map(function (FileInfo $info) {
84
-			if ($info->getMimetype() === FileInfo::MIMETYPE_FOLDER) {
85
-				return new Folder($this->root, $this->view, $info->getPath(), $info, $this);
86
-			} else {
87
-				return new File($this->root, $this->view, $info->getPath(), $info, $this);
88
-			}
89
-		}, $folderContent);
90
-	}
91
-
92
-	protected function createNode(string $path, ?FileInfo $info = null, bool $infoHasSubMountsIncluded = true): INode {
93
-		if (is_null($info)) {
94
-			$isDir = $this->view->is_dir($path);
95
-		} else {
96
-			$isDir = $info->getType() === FileInfo::TYPE_FOLDER;
97
-		}
98
-		$parent = dirname($path) === $this->getPath() ? $this : null;
99
-		if ($isDir) {
100
-			return new Folder($this->root, $this->view, $path, $info, $parent, $infoHasSubMountsIncluded);
101
-		} else {
102
-			return new File($this->root, $this->view, $path, $info, $parent);
103
-		}
104
-	}
105
-
106
-	public function get($path) {
107
-		return $this->root->get($this->getFullPath($path));
108
-	}
109
-
110
-	public function nodeExists($path) {
111
-		try {
112
-			$this->get($path);
113
-			return true;
114
-		} catch (NotFoundException|NotPermittedException) {
115
-			return false;
116
-		}
117
-	}
118
-
119
-	/**
120
-	 * @param string $path
121
-	 * @return \OC\Files\Node\Folder
122
-	 * @throws \OCP\Files\NotPermittedException
123
-	 */
124
-	public function newFolder($path) {
125
-		if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
126
-			$fullPath = $this->getFullPath($path);
127
-			$nonExisting = new NonExistingFolder($this->root, $this->view, $fullPath);
128
-			$this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
129
-			if (!$this->view->mkdir($fullPath)) {
130
-				// maybe another concurrent process created the folder already
131
-				if (!$this->view->is_dir($fullPath)) {
132
-					throw new NotPermittedException('Could not create folder "' . $fullPath . '"');
133
-				} else {
134
-					// we need to ensure we don't return before the concurrent request has finished updating the cache
135
-					$tries = 5;
136
-					while (!$this->view->getFileInfo($fullPath)) {
137
-						if ($tries < 1) {
138
-							throw new NotPermittedException('Could not create folder "' . $fullPath . '", folder exists but unable to get cache entry');
139
-						}
140
-						usleep(5 * 1000);
141
-						$tries--;
142
-					}
143
-				}
144
-			}
145
-			$parent = dirname($fullPath) === $this->getPath() ? $this : null;
146
-			$node = new Folder($this->root, $this->view, $fullPath, null, $parent);
147
-			$this->sendHooks(['postWrite', 'postCreate'], [$node]);
148
-			return $node;
149
-		} else {
150
-			throw new NotPermittedException('No create permission for folder "' . $path . '"');
151
-		}
152
-	}
153
-
154
-	/**
155
-	 * @param string $path
156
-	 * @param string | resource | null $content
157
-	 * @return \OC\Files\Node\File
158
-	 * @throws \OCP\Files\NotPermittedException
159
-	 */
160
-	public function newFile($path, $content = null) {
161
-		if ($path === '') {
162
-			throw new NotPermittedException('Could not create as provided path is empty');
163
-		}
164
-		if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
165
-			$fullPath = $this->getFullPath($path);
166
-			$nonExisting = new NonExistingFile($this->root, $this->view, $fullPath);
167
-			$this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
168
-			if ($content !== null) {
169
-				$result = $this->view->file_put_contents($fullPath, $content);
170
-			} else {
171
-				$result = $this->view->touch($fullPath);
172
-			}
173
-			if ($result === false) {
174
-				throw new NotPermittedException('Could not create path "' . $fullPath . '"');
175
-			}
176
-			$node = new File($this->root, $this->view, $fullPath, null, $this);
177
-			$this->sendHooks(['postWrite', 'postCreate'], [$node]);
178
-			return $node;
179
-		}
180
-		throw new NotPermittedException('No create permission for path "' . $path . '"');
181
-	}
182
-
183
-	private function queryFromOperator(ISearchOperator $operator, ?string $uid = null, int $limit = 0, int $offset = 0): ISearchQuery {
184
-		if ($uid === null) {
185
-			$user = null;
186
-		} else {
187
-			/** @var IUserManager $userManager */
188
-			$userManager = \OCP\Server::get(IUserManager::class);
189
-			$user = $userManager->get($uid);
190
-		}
191
-		return new SearchQuery($operator, $limit, $offset, [], $user);
192
-	}
193
-
194
-	/**
195
-	 * search for files with the name matching $query
196
-	 *
197
-	 * @param string|ISearchQuery $query
198
-	 * @return \OC\Files\Node\Node[]
199
-	 */
200
-	public function search($query) {
201
-		if (is_string($query)) {
202
-			$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'name', '%' . $query . '%'));
203
-		}
204
-
205
-		// search is handled by a single query covering all caches that this folder contains
206
-		// this is done by collect
207
-
208
-		$limitToHome = $query->limitToHome();
209
-		if ($limitToHome && count(explode('/', $this->path)) !== 3) {
210
-			throw new \InvalidArgumentException('searching by owner is only allowed in the users home folder');
211
-		}
212
-
213
-		/** @var QuerySearchHelper $searchHelper */
214
-		$searchHelper = \OC::$server->get(QuerySearchHelper::class);
215
-		[$caches, $mountByMountPoint] = $searchHelper->getCachesAndMountPointsForSearch($this->root, $this->path, $limitToHome);
216
-		$resultsPerCache = $searchHelper->searchInCaches($query, $caches);
217
-
218
-		// loop through all results per-cache, constructing the FileInfo object from the CacheEntry and merge them all
219
-		$files = array_merge(...array_map(function (array $results, string $relativeMountPoint) use ($mountByMountPoint) {
220
-			$mount = $mountByMountPoint[$relativeMountPoint];
221
-			return array_map(function (ICacheEntry $result) use ($relativeMountPoint, $mount) {
222
-				return $this->cacheEntryToFileInfo($mount, $relativeMountPoint, $result);
223
-			}, $results);
224
-		}, array_values($resultsPerCache), array_keys($resultsPerCache)));
225
-
226
-		// don't include this folder in the results
227
-		$files = array_values(array_filter($files, function (FileInfo $file) {
228
-			return $file->getPath() !== $this->getPath();
229
-		}));
230
-
231
-		// since results were returned per-cache, they are no longer fully sorted
232
-		$order = $query->getOrder();
233
-		if ($order) {
234
-			usort($files, function (FileInfo $a, FileInfo $b) use ($order) {
235
-				foreach ($order as $orderField) {
236
-					$cmp = $orderField->sortFileInfo($a, $b);
237
-					if ($cmp !== 0) {
238
-						return $cmp;
239
-					}
240
-				}
241
-				return 0;
242
-			});
243
-		}
244
-
245
-		return array_map(function (FileInfo $file) {
246
-			return $this->createNode($file->getPath(), $file);
247
-		}, $files);
248
-	}
249
-
250
-	private function cacheEntryToFileInfo(IMountPoint $mount, string $appendRoot, ICacheEntry $cacheEntry): FileInfo {
251
-		$cacheEntry['internalPath'] = $cacheEntry['path'];
252
-		$cacheEntry['path'] = rtrim($appendRoot . $cacheEntry->getPath(), '/');
253
-		$subPath = $cacheEntry['path'] !== '' ? '/' . $cacheEntry['path'] : '';
254
-		$storage = $mount->getStorage();
255
-
256
-		$owner = null;
257
-		$ownerId = $storage->getOwner($cacheEntry['internalPath']);
258
-		if ($ownerId !== false) {
259
-			// Cache the user manager (for performance)
260
-			if ($this->userManager === null) {
261
-				$this->userManager = \OCP\Server::get(IUserManager::class);
262
-			}
263
-			$owner = new LazyUser($ownerId, $this->userManager);
264
-		}
265
-
266
-		return new \OC\Files\FileInfo(
267
-			$this->path . $subPath,
268
-			$storage,
269
-			$cacheEntry['internalPath'],
270
-			$cacheEntry,
271
-			$mount,
272
-			$owner,
273
-		);
274
-	}
275
-
276
-	/**
277
-	 * search for files by mimetype
278
-	 *
279
-	 * @param string $mimetype
280
-	 * @return Node[]
281
-	 */
282
-	public function searchByMime($mimetype) {
283
-		if (!str_contains($mimetype, '/')) {
284
-			$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', $mimetype . '/%'));
285
-		} else {
286
-			$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'mimetype', $mimetype));
287
-		}
288
-		return $this->search($query);
289
-	}
290
-
291
-	/**
292
-	 * search for files by tag
293
-	 *
294
-	 * @param string|int $tag name or tag id
295
-	 * @param string $userId owner of the tags
296
-	 * @return Node[]
297
-	 */
298
-	public function searchByTag($tag, $userId) {
299
-		$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'tagname', $tag), $userId);
300
-		return $this->search($query);
301
-	}
302
-
303
-	public function searchBySystemTag(string $tagName, string $userId, int $limit = 0, int $offset = 0): array {
304
-		$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'systemtag', $tagName), $userId, $limit, $offset);
305
-		return $this->search($query);
306
-	}
307
-
308
-	/**
309
-	 * @param int $id
310
-	 * @return \OCP\Files\Node[]
311
-	 */
312
-	public function getById($id) {
313
-		return $this->root->getByIdInPath((int)$id, $this->getPath());
314
-	}
315
-
316
-	public function getFirstNodeById(int $id): ?\OCP\Files\Node {
317
-		return $this->root->getFirstNodeByIdInPath($id, $this->getPath());
318
-	}
319
-
320
-	public function getAppDataDirectoryName(): string {
321
-		$instanceId = \OC::$server->getConfig()->getSystemValueString('instanceid');
322
-		return 'appdata_' . $instanceId;
323
-	}
324
-
325
-	/**
326
-	 * In case the path we are currently in is inside the appdata_* folder,
327
-	 * the original getById method does not work, because it can only look inside
328
-	 * the user's mount points. But the user has no mount point for the root storage.
329
-	 *
330
-	 * So in that case we directly check the mount of the root if it contains
331
-	 * the id. If it does we check if the path is inside the path we are working
332
-	 * in.
333
-	 *
334
-	 * @param int $id
335
-	 * @return array
336
-	 */
337
-	protected function getByIdInRootMount(int $id): array {
338
-		if (!method_exists($this->root, 'createNode')) {
339
-			// Always expected to be false. Being a method of Folder, this is
340
-			// always implemented. For it is an internal method and should not
341
-			// be exposed and made public, it is not part of an interface.
342
-			return [];
343
-		}
344
-		$mount = $this->root->getMount('');
345
-		$storage = $mount->getStorage();
346
-		$cacheEntry = $storage?->getCache($this->path)->get($id);
347
-		if (!$cacheEntry) {
348
-			return [];
349
-		}
350
-
351
-		$absolutePath = '/' . ltrim($cacheEntry->getPath(), '/');
352
-		$currentPath = rtrim($this->path, '/') . '/';
353
-
354
-		if (!str_starts_with($absolutePath, $currentPath)) {
355
-			return [];
356
-		}
357
-
358
-		return [$this->root->createNode(
359
-			$absolutePath, new \OC\Files\FileInfo(
360
-				$absolutePath,
361
-				$storage,
362
-				$cacheEntry->getPath(),
363
-				$cacheEntry,
364
-				$mount
365
-			))];
366
-	}
367
-
368
-	public function getFreeSpace() {
369
-		return $this->view->free_space($this->path);
370
-	}
371
-
372
-	public function delete() {
373
-		if ($this->checkPermissions(\OCP\Constants::PERMISSION_DELETE)) {
374
-			$this->sendHooks(['preDelete']);
375
-			$fileInfo = $this->getFileInfo();
376
-			$this->view->rmdir($this->path);
377
-			$nonExisting = new NonExistingFolder($this->root, $this->view, $this->path, $fileInfo);
378
-			$this->sendHooks(['postDelete'], [$nonExisting]);
379
-		} else {
380
-			throw new NotPermittedException('No delete permission for path "' . $this->path . '"');
381
-		}
382
-	}
383
-
384
-	/**
385
-	 * Add a suffix to the name in case the file exists
386
-	 *
387
-	 * @param string $name
388
-	 * @return string
389
-	 * @throws NotPermittedException
390
-	 */
391
-	public function getNonExistingName($name) {
392
-		$uniqueName = \OC_Helper::buildNotExistingFileNameForView($this->getPath(), $name, $this->view);
393
-		return trim($this->getRelativePath($uniqueName), '/');
394
-	}
395
-
396
-	/**
397
-	 * @param int $limit
398
-	 * @param int $offset
399
-	 * @return INode[]
400
-	 */
401
-	public function getRecent($limit, $offset = 0) {
402
-		$filterOutNonEmptyFolder = new SearchBinaryOperator(
403
-			// filter out non empty folders
404
-			ISearchBinaryOperator::OPERATOR_OR,
405
-			[
406
-				new SearchBinaryOperator(
407
-					ISearchBinaryOperator::OPERATOR_NOT,
408
-					[
409
-						new SearchComparison(
410
-							ISearchComparison::COMPARE_EQUAL,
411
-							'mimetype',
412
-							FileInfo::MIMETYPE_FOLDER
413
-						),
414
-					]
415
-				),
416
-				new SearchComparison(
417
-					ISearchComparison::COMPARE_EQUAL,
418
-					'size',
419
-					0
420
-				),
421
-			]
422
-		);
423
-
424
-		$filterNonRecentFiles = new SearchComparison(
425
-			ISearchComparison::COMPARE_GREATER_THAN,
426
-			'mtime',
427
-			strtotime('-2 week')
428
-		);
429
-		if ($offset === 0 && $limit <= 100) {
430
-			$query = new SearchQuery(
431
-				new SearchBinaryOperator(
432
-					ISearchBinaryOperator::OPERATOR_AND,
433
-					[
434
-						$filterOutNonEmptyFolder,
435
-						$filterNonRecentFiles,
436
-					],
437
-				),
438
-				$limit,
439
-				$offset,
440
-				[
441
-					new SearchOrder(
442
-						ISearchOrder::DIRECTION_DESCENDING,
443
-						'mtime'
444
-					),
445
-				]
446
-			);
447
-		} else {
448
-			$query = new SearchQuery(
449
-				$filterOutNonEmptyFolder,
450
-				$limit,
451
-				$offset,
452
-				[
453
-					new SearchOrder(
454
-						ISearchOrder::DIRECTION_DESCENDING,
455
-						'mtime'
456
-					),
457
-				]
458
-			);
459
-		}
460
-
461
-		return $this->search($query);
462
-	}
31
+    private ?IUserManager $userManager = null;
32
+
33
+    /**
34
+     * Creates a Folder that represents a non-existing path
35
+     *
36
+     * @param string $path path
37
+     * @return NonExistingFolder non-existing node
38
+     */
39
+    protected function createNonExistingNode($path) {
40
+        return new NonExistingFolder($this->root, $this->view, $path);
41
+    }
42
+
43
+    /**
44
+     * @param string $path path relative to the folder
45
+     * @return string
46
+     * @throws \OCP\Files\NotPermittedException
47
+     */
48
+    public function getFullPath($path) {
49
+        $path = $this->normalizePath($path);
50
+        if (!$this->isValidPath($path)) {
51
+            throw new NotPermittedException('Invalid path "' . $path . '"');
52
+        }
53
+        return $this->path . $path;
54
+    }
55
+
56
+    /**
57
+     * @param string $path
58
+     * @return string|null
59
+     */
60
+    public function getRelativePath($path) {
61
+        return PathHelper::getRelativePath($this->getPath(), $path);
62
+    }
63
+
64
+    /**
65
+     * check if a node is a (grand-)child of the folder
66
+     *
67
+     * @param \OC\Files\Node\Node $node
68
+     * @return bool
69
+     */
70
+    public function isSubNode($node) {
71
+        return str_starts_with($node->getPath(), $this->path . '/');
72
+    }
73
+
74
+    /**
75
+     * get the content of this directory
76
+     *
77
+     * @return Node[]
78
+     * @throws \OCP\Files\NotFoundException
79
+     */
80
+    public function getDirectoryListing() {
81
+        $folderContent = $this->view->getDirectoryContent($this->path, '', $this->getFileInfo(false));
82
+
83
+        return array_map(function (FileInfo $info) {
84
+            if ($info->getMimetype() === FileInfo::MIMETYPE_FOLDER) {
85
+                return new Folder($this->root, $this->view, $info->getPath(), $info, $this);
86
+            } else {
87
+                return new File($this->root, $this->view, $info->getPath(), $info, $this);
88
+            }
89
+        }, $folderContent);
90
+    }
91
+
92
+    protected function createNode(string $path, ?FileInfo $info = null, bool $infoHasSubMountsIncluded = true): INode {
93
+        if (is_null($info)) {
94
+            $isDir = $this->view->is_dir($path);
95
+        } else {
96
+            $isDir = $info->getType() === FileInfo::TYPE_FOLDER;
97
+        }
98
+        $parent = dirname($path) === $this->getPath() ? $this : null;
99
+        if ($isDir) {
100
+            return new Folder($this->root, $this->view, $path, $info, $parent, $infoHasSubMountsIncluded);
101
+        } else {
102
+            return new File($this->root, $this->view, $path, $info, $parent);
103
+        }
104
+    }
105
+
106
+    public function get($path) {
107
+        return $this->root->get($this->getFullPath($path));
108
+    }
109
+
110
+    public function nodeExists($path) {
111
+        try {
112
+            $this->get($path);
113
+            return true;
114
+        } catch (NotFoundException|NotPermittedException) {
115
+            return false;
116
+        }
117
+    }
118
+
119
+    /**
120
+     * @param string $path
121
+     * @return \OC\Files\Node\Folder
122
+     * @throws \OCP\Files\NotPermittedException
123
+     */
124
+    public function newFolder($path) {
125
+        if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
126
+            $fullPath = $this->getFullPath($path);
127
+            $nonExisting = new NonExistingFolder($this->root, $this->view, $fullPath);
128
+            $this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
129
+            if (!$this->view->mkdir($fullPath)) {
130
+                // maybe another concurrent process created the folder already
131
+                if (!$this->view->is_dir($fullPath)) {
132
+                    throw new NotPermittedException('Could not create folder "' . $fullPath . '"');
133
+                } else {
134
+                    // we need to ensure we don't return before the concurrent request has finished updating the cache
135
+                    $tries = 5;
136
+                    while (!$this->view->getFileInfo($fullPath)) {
137
+                        if ($tries < 1) {
138
+                            throw new NotPermittedException('Could not create folder "' . $fullPath . '", folder exists but unable to get cache entry');
139
+                        }
140
+                        usleep(5 * 1000);
141
+                        $tries--;
142
+                    }
143
+                }
144
+            }
145
+            $parent = dirname($fullPath) === $this->getPath() ? $this : null;
146
+            $node = new Folder($this->root, $this->view, $fullPath, null, $parent);
147
+            $this->sendHooks(['postWrite', 'postCreate'], [$node]);
148
+            return $node;
149
+        } else {
150
+            throw new NotPermittedException('No create permission for folder "' . $path . '"');
151
+        }
152
+    }
153
+
154
+    /**
155
+     * @param string $path
156
+     * @param string | resource | null $content
157
+     * @return \OC\Files\Node\File
158
+     * @throws \OCP\Files\NotPermittedException
159
+     */
160
+    public function newFile($path, $content = null) {
161
+        if ($path === '') {
162
+            throw new NotPermittedException('Could not create as provided path is empty');
163
+        }
164
+        if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
165
+            $fullPath = $this->getFullPath($path);
166
+            $nonExisting = new NonExistingFile($this->root, $this->view, $fullPath);
167
+            $this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
168
+            if ($content !== null) {
169
+                $result = $this->view->file_put_contents($fullPath, $content);
170
+            } else {
171
+                $result = $this->view->touch($fullPath);
172
+            }
173
+            if ($result === false) {
174
+                throw new NotPermittedException('Could not create path "' . $fullPath . '"');
175
+            }
176
+            $node = new File($this->root, $this->view, $fullPath, null, $this);
177
+            $this->sendHooks(['postWrite', 'postCreate'], [$node]);
178
+            return $node;
179
+        }
180
+        throw new NotPermittedException('No create permission for path "' . $path . '"');
181
+    }
182
+
183
+    private function queryFromOperator(ISearchOperator $operator, ?string $uid = null, int $limit = 0, int $offset = 0): ISearchQuery {
184
+        if ($uid === null) {
185
+            $user = null;
186
+        } else {
187
+            /** @var IUserManager $userManager */
188
+            $userManager = \OCP\Server::get(IUserManager::class);
189
+            $user = $userManager->get($uid);
190
+        }
191
+        return new SearchQuery($operator, $limit, $offset, [], $user);
192
+    }
193
+
194
+    /**
195
+     * search for files with the name matching $query
196
+     *
197
+     * @param string|ISearchQuery $query
198
+     * @return \OC\Files\Node\Node[]
199
+     */
200
+    public function search($query) {
201
+        if (is_string($query)) {
202
+            $query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'name', '%' . $query . '%'));
203
+        }
204
+
205
+        // search is handled by a single query covering all caches that this folder contains
206
+        // this is done by collect
207
+
208
+        $limitToHome = $query->limitToHome();
209
+        if ($limitToHome && count(explode('/', $this->path)) !== 3) {
210
+            throw new \InvalidArgumentException('searching by owner is only allowed in the users home folder');
211
+        }
212
+
213
+        /** @var QuerySearchHelper $searchHelper */
214
+        $searchHelper = \OC::$server->get(QuerySearchHelper::class);
215
+        [$caches, $mountByMountPoint] = $searchHelper->getCachesAndMountPointsForSearch($this->root, $this->path, $limitToHome);
216
+        $resultsPerCache = $searchHelper->searchInCaches($query, $caches);
217
+
218
+        // loop through all results per-cache, constructing the FileInfo object from the CacheEntry and merge them all
219
+        $files = array_merge(...array_map(function (array $results, string $relativeMountPoint) use ($mountByMountPoint) {
220
+            $mount = $mountByMountPoint[$relativeMountPoint];
221
+            return array_map(function (ICacheEntry $result) use ($relativeMountPoint, $mount) {
222
+                return $this->cacheEntryToFileInfo($mount, $relativeMountPoint, $result);
223
+            }, $results);
224
+        }, array_values($resultsPerCache), array_keys($resultsPerCache)));
225
+
226
+        // don't include this folder in the results
227
+        $files = array_values(array_filter($files, function (FileInfo $file) {
228
+            return $file->getPath() !== $this->getPath();
229
+        }));
230
+
231
+        // since results were returned per-cache, they are no longer fully sorted
232
+        $order = $query->getOrder();
233
+        if ($order) {
234
+            usort($files, function (FileInfo $a, FileInfo $b) use ($order) {
235
+                foreach ($order as $orderField) {
236
+                    $cmp = $orderField->sortFileInfo($a, $b);
237
+                    if ($cmp !== 0) {
238
+                        return $cmp;
239
+                    }
240
+                }
241
+                return 0;
242
+            });
243
+        }
244
+
245
+        return array_map(function (FileInfo $file) {
246
+            return $this->createNode($file->getPath(), $file);
247
+        }, $files);
248
+    }
249
+
250
+    private function cacheEntryToFileInfo(IMountPoint $mount, string $appendRoot, ICacheEntry $cacheEntry): FileInfo {
251
+        $cacheEntry['internalPath'] = $cacheEntry['path'];
252
+        $cacheEntry['path'] = rtrim($appendRoot . $cacheEntry->getPath(), '/');
253
+        $subPath = $cacheEntry['path'] !== '' ? '/' . $cacheEntry['path'] : '';
254
+        $storage = $mount->getStorage();
255
+
256
+        $owner = null;
257
+        $ownerId = $storage->getOwner($cacheEntry['internalPath']);
258
+        if ($ownerId !== false) {
259
+            // Cache the user manager (for performance)
260
+            if ($this->userManager === null) {
261
+                $this->userManager = \OCP\Server::get(IUserManager::class);
262
+            }
263
+            $owner = new LazyUser($ownerId, $this->userManager);
264
+        }
265
+
266
+        return new \OC\Files\FileInfo(
267
+            $this->path . $subPath,
268
+            $storage,
269
+            $cacheEntry['internalPath'],
270
+            $cacheEntry,
271
+            $mount,
272
+            $owner,
273
+        );
274
+    }
275
+
276
+    /**
277
+     * search for files by mimetype
278
+     *
279
+     * @param string $mimetype
280
+     * @return Node[]
281
+     */
282
+    public function searchByMime($mimetype) {
283
+        if (!str_contains($mimetype, '/')) {
284
+            $query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', $mimetype . '/%'));
285
+        } else {
286
+            $query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'mimetype', $mimetype));
287
+        }
288
+        return $this->search($query);
289
+    }
290
+
291
+    /**
292
+     * search for files by tag
293
+     *
294
+     * @param string|int $tag name or tag id
295
+     * @param string $userId owner of the tags
296
+     * @return Node[]
297
+     */
298
+    public function searchByTag($tag, $userId) {
299
+        $query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'tagname', $tag), $userId);
300
+        return $this->search($query);
301
+    }
302
+
303
+    public function searchBySystemTag(string $tagName, string $userId, int $limit = 0, int $offset = 0): array {
304
+        $query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'systemtag', $tagName), $userId, $limit, $offset);
305
+        return $this->search($query);
306
+    }
307
+
308
+    /**
309
+     * @param int $id
310
+     * @return \OCP\Files\Node[]
311
+     */
312
+    public function getById($id) {
313
+        return $this->root->getByIdInPath((int)$id, $this->getPath());
314
+    }
315
+
316
+    public function getFirstNodeById(int $id): ?\OCP\Files\Node {
317
+        return $this->root->getFirstNodeByIdInPath($id, $this->getPath());
318
+    }
319
+
320
+    public function getAppDataDirectoryName(): string {
321
+        $instanceId = \OC::$server->getConfig()->getSystemValueString('instanceid');
322
+        return 'appdata_' . $instanceId;
323
+    }
324
+
325
+    /**
326
+     * In case the path we are currently in is inside the appdata_* folder,
327
+     * the original getById method does not work, because it can only look inside
328
+     * the user's mount points. But the user has no mount point for the root storage.
329
+     *
330
+     * So in that case we directly check the mount of the root if it contains
331
+     * the id. If it does we check if the path is inside the path we are working
332
+     * in.
333
+     *
334
+     * @param int $id
335
+     * @return array
336
+     */
337
+    protected function getByIdInRootMount(int $id): array {
338
+        if (!method_exists($this->root, 'createNode')) {
339
+            // Always expected to be false. Being a method of Folder, this is
340
+            // always implemented. For it is an internal method and should not
341
+            // be exposed and made public, it is not part of an interface.
342
+            return [];
343
+        }
344
+        $mount = $this->root->getMount('');
345
+        $storage = $mount->getStorage();
346
+        $cacheEntry = $storage?->getCache($this->path)->get($id);
347
+        if (!$cacheEntry) {
348
+            return [];
349
+        }
350
+
351
+        $absolutePath = '/' . ltrim($cacheEntry->getPath(), '/');
352
+        $currentPath = rtrim($this->path, '/') . '/';
353
+
354
+        if (!str_starts_with($absolutePath, $currentPath)) {
355
+            return [];
356
+        }
357
+
358
+        return [$this->root->createNode(
359
+            $absolutePath, new \OC\Files\FileInfo(
360
+                $absolutePath,
361
+                $storage,
362
+                $cacheEntry->getPath(),
363
+                $cacheEntry,
364
+                $mount
365
+            ))];
366
+    }
367
+
368
+    public function getFreeSpace() {
369
+        return $this->view->free_space($this->path);
370
+    }
371
+
372
+    public function delete() {
373
+        if ($this->checkPermissions(\OCP\Constants::PERMISSION_DELETE)) {
374
+            $this->sendHooks(['preDelete']);
375
+            $fileInfo = $this->getFileInfo();
376
+            $this->view->rmdir($this->path);
377
+            $nonExisting = new NonExistingFolder($this->root, $this->view, $this->path, $fileInfo);
378
+            $this->sendHooks(['postDelete'], [$nonExisting]);
379
+        } else {
380
+            throw new NotPermittedException('No delete permission for path "' . $this->path . '"');
381
+        }
382
+    }
383
+
384
+    /**
385
+     * Add a suffix to the name in case the file exists
386
+     *
387
+     * @param string $name
388
+     * @return string
389
+     * @throws NotPermittedException
390
+     */
391
+    public function getNonExistingName($name) {
392
+        $uniqueName = \OC_Helper::buildNotExistingFileNameForView($this->getPath(), $name, $this->view);
393
+        return trim($this->getRelativePath($uniqueName), '/');
394
+    }
395
+
396
+    /**
397
+     * @param int $limit
398
+     * @param int $offset
399
+     * @return INode[]
400
+     */
401
+    public function getRecent($limit, $offset = 0) {
402
+        $filterOutNonEmptyFolder = new SearchBinaryOperator(
403
+            // filter out non empty folders
404
+            ISearchBinaryOperator::OPERATOR_OR,
405
+            [
406
+                new SearchBinaryOperator(
407
+                    ISearchBinaryOperator::OPERATOR_NOT,
408
+                    [
409
+                        new SearchComparison(
410
+                            ISearchComparison::COMPARE_EQUAL,
411
+                            'mimetype',
412
+                            FileInfo::MIMETYPE_FOLDER
413
+                        ),
414
+                    ]
415
+                ),
416
+                new SearchComparison(
417
+                    ISearchComparison::COMPARE_EQUAL,
418
+                    'size',
419
+                    0
420
+                ),
421
+            ]
422
+        );
423
+
424
+        $filterNonRecentFiles = new SearchComparison(
425
+            ISearchComparison::COMPARE_GREATER_THAN,
426
+            'mtime',
427
+            strtotime('-2 week')
428
+        );
429
+        if ($offset === 0 && $limit <= 100) {
430
+            $query = new SearchQuery(
431
+                new SearchBinaryOperator(
432
+                    ISearchBinaryOperator::OPERATOR_AND,
433
+                    [
434
+                        $filterOutNonEmptyFolder,
435
+                        $filterNonRecentFiles,
436
+                    ],
437
+                ),
438
+                $limit,
439
+                $offset,
440
+                [
441
+                    new SearchOrder(
442
+                        ISearchOrder::DIRECTION_DESCENDING,
443
+                        'mtime'
444
+                    ),
445
+                ]
446
+            );
447
+        } else {
448
+            $query = new SearchQuery(
449
+                $filterOutNonEmptyFolder,
450
+                $limit,
451
+                $offset,
452
+                [
453
+                    new SearchOrder(
454
+                        ISearchOrder::DIRECTION_DESCENDING,
455
+                        'mtime'
456
+                    ),
457
+                ]
458
+            );
459
+        }
460
+
461
+        return $this->search($query);
462
+    }
463 463
 }
Please login to merge, or discard this patch.
Spacing   +25 added lines, -25 removed lines patch added patch discarded remove patch
@@ -48,9 +48,9 @@  discard block
 block discarded – undo
48 48
 	public function getFullPath($path) {
49 49
 		$path = $this->normalizePath($path);
50 50
 		if (!$this->isValidPath($path)) {
51
-			throw new NotPermittedException('Invalid path "' . $path . '"');
51
+			throw new NotPermittedException('Invalid path "'.$path.'"');
52 52
 		}
53
-		return $this->path . $path;
53
+		return $this->path.$path;
54 54
 	}
55 55
 
56 56
 	/**
@@ -68,7 +68,7 @@  discard block
 block discarded – undo
68 68
 	 * @return bool
69 69
 	 */
70 70
 	public function isSubNode($node) {
71
-		return str_starts_with($node->getPath(), $this->path . '/');
71
+		return str_starts_with($node->getPath(), $this->path.'/');
72 72
 	}
73 73
 
74 74
 	/**
@@ -80,7 +80,7 @@  discard block
 block discarded – undo
80 80
 	public function getDirectoryListing() {
81 81
 		$folderContent = $this->view->getDirectoryContent($this->path, '', $this->getFileInfo(false));
82 82
 
83
-		return array_map(function (FileInfo $info) {
83
+		return array_map(function(FileInfo $info) {
84 84
 			if ($info->getMimetype() === FileInfo::MIMETYPE_FOLDER) {
85 85
 				return new Folder($this->root, $this->view, $info->getPath(), $info, $this);
86 86
 			} else {
@@ -111,7 +111,7 @@  discard block
 block discarded – undo
111 111
 		try {
112 112
 			$this->get($path);
113 113
 			return true;
114
-		} catch (NotFoundException|NotPermittedException) {
114
+		} catch (NotFoundException | NotPermittedException) {
115 115
 			return false;
116 116
 		}
117 117
 	}
@@ -129,13 +129,13 @@  discard block
 block discarded – undo
129 129
 			if (!$this->view->mkdir($fullPath)) {
130 130
 				// maybe another concurrent process created the folder already
131 131
 				if (!$this->view->is_dir($fullPath)) {
132
-					throw new NotPermittedException('Could not create folder "' . $fullPath . '"');
132
+					throw new NotPermittedException('Could not create folder "'.$fullPath.'"');
133 133
 				} else {
134 134
 					// we need to ensure we don't return before the concurrent request has finished updating the cache
135 135
 					$tries = 5;
136 136
 					while (!$this->view->getFileInfo($fullPath)) {
137 137
 						if ($tries < 1) {
138
-							throw new NotPermittedException('Could not create folder "' . $fullPath . '", folder exists but unable to get cache entry');
138
+							throw new NotPermittedException('Could not create folder "'.$fullPath.'", folder exists but unable to get cache entry');
139 139
 						}
140 140
 						usleep(5 * 1000);
141 141
 						$tries--;
@@ -147,7 +147,7 @@  discard block
 block discarded – undo
147 147
 			$this->sendHooks(['postWrite', 'postCreate'], [$node]);
148 148
 			return $node;
149 149
 		} else {
150
-			throw new NotPermittedException('No create permission for folder "' . $path . '"');
150
+			throw new NotPermittedException('No create permission for folder "'.$path.'"');
151 151
 		}
152 152
 	}
153 153
 
@@ -171,13 +171,13 @@  discard block
 block discarded – undo
171 171
 				$result = $this->view->touch($fullPath);
172 172
 			}
173 173
 			if ($result === false) {
174
-				throw new NotPermittedException('Could not create path "' . $fullPath . '"');
174
+				throw new NotPermittedException('Could not create path "'.$fullPath.'"');
175 175
 			}
176 176
 			$node = new File($this->root, $this->view, $fullPath, null, $this);
177 177
 			$this->sendHooks(['postWrite', 'postCreate'], [$node]);
178 178
 			return $node;
179 179
 		}
180
-		throw new NotPermittedException('No create permission for path "' . $path . '"');
180
+		throw new NotPermittedException('No create permission for path "'.$path.'"');
181 181
 	}
182 182
 
183 183
 	private function queryFromOperator(ISearchOperator $operator, ?string $uid = null, int $limit = 0, int $offset = 0): ISearchQuery {
@@ -199,7 +199,7 @@  discard block
 block discarded – undo
199 199
 	 */
200 200
 	public function search($query) {
201 201
 		if (is_string($query)) {
202
-			$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'name', '%' . $query . '%'));
202
+			$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'name', '%'.$query.'%'));
203 203
 		}
204 204
 
205 205
 		// search is handled by a single query covering all caches that this folder contains
@@ -216,22 +216,22 @@  discard block
 block discarded – undo
216 216
 		$resultsPerCache = $searchHelper->searchInCaches($query, $caches);
217 217
 
218 218
 		// loop through all results per-cache, constructing the FileInfo object from the CacheEntry and merge them all
219
-		$files = array_merge(...array_map(function (array $results, string $relativeMountPoint) use ($mountByMountPoint) {
219
+		$files = array_merge(...array_map(function(array $results, string $relativeMountPoint) use ($mountByMountPoint) {
220 220
 			$mount = $mountByMountPoint[$relativeMountPoint];
221
-			return array_map(function (ICacheEntry $result) use ($relativeMountPoint, $mount) {
221
+			return array_map(function(ICacheEntry $result) use ($relativeMountPoint, $mount) {
222 222
 				return $this->cacheEntryToFileInfo($mount, $relativeMountPoint, $result);
223 223
 			}, $results);
224 224
 		}, array_values($resultsPerCache), array_keys($resultsPerCache)));
225 225
 
226 226
 		// don't include this folder in the results
227
-		$files = array_values(array_filter($files, function (FileInfo $file) {
227
+		$files = array_values(array_filter($files, function(FileInfo $file) {
228 228
 			return $file->getPath() !== $this->getPath();
229 229
 		}));
230 230
 
231 231
 		// since results were returned per-cache, they are no longer fully sorted
232 232
 		$order = $query->getOrder();
233 233
 		if ($order) {
234
-			usort($files, function (FileInfo $a, FileInfo $b) use ($order) {
234
+			usort($files, function(FileInfo $a, FileInfo $b) use ($order) {
235 235
 				foreach ($order as $orderField) {
236 236
 					$cmp = $orderField->sortFileInfo($a, $b);
237 237
 					if ($cmp !== 0) {
@@ -242,15 +242,15 @@  discard block
 block discarded – undo
242 242
 			});
243 243
 		}
244 244
 
245
-		return array_map(function (FileInfo $file) {
245
+		return array_map(function(FileInfo $file) {
246 246
 			return $this->createNode($file->getPath(), $file);
247 247
 		}, $files);
248 248
 	}
249 249
 
250 250
 	private function cacheEntryToFileInfo(IMountPoint $mount, string $appendRoot, ICacheEntry $cacheEntry): FileInfo {
251 251
 		$cacheEntry['internalPath'] = $cacheEntry['path'];
252
-		$cacheEntry['path'] = rtrim($appendRoot . $cacheEntry->getPath(), '/');
253
-		$subPath = $cacheEntry['path'] !== '' ? '/' . $cacheEntry['path'] : '';
252
+		$cacheEntry['path'] = rtrim($appendRoot.$cacheEntry->getPath(), '/');
253
+		$subPath = $cacheEntry['path'] !== '' ? '/'.$cacheEntry['path'] : '';
254 254
 		$storage = $mount->getStorage();
255 255
 
256 256
 		$owner = null;
@@ -264,7 +264,7 @@  discard block
 block discarded – undo
264 264
 		}
265 265
 
266 266
 		return new \OC\Files\FileInfo(
267
-			$this->path . $subPath,
267
+			$this->path.$subPath,
268 268
 			$storage,
269 269
 			$cacheEntry['internalPath'],
270 270
 			$cacheEntry,
@@ -281,7 +281,7 @@  discard block
 block discarded – undo
281 281
 	 */
282 282
 	public function searchByMime($mimetype) {
283 283
 		if (!str_contains($mimetype, '/')) {
284
-			$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', $mimetype . '/%'));
284
+			$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', $mimetype.'/%'));
285 285
 		} else {
286 286
 			$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'mimetype', $mimetype));
287 287
 		}
@@ -310,7 +310,7 @@  discard block
 block discarded – undo
310 310
 	 * @return \OCP\Files\Node[]
311 311
 	 */
312 312
 	public function getById($id) {
313
-		return $this->root->getByIdInPath((int)$id, $this->getPath());
313
+		return $this->root->getByIdInPath((int) $id, $this->getPath());
314 314
 	}
315 315
 
316 316
 	public function getFirstNodeById(int $id): ?\OCP\Files\Node {
@@ -319,7 +319,7 @@  discard block
 block discarded – undo
319 319
 
320 320
 	public function getAppDataDirectoryName(): string {
321 321
 		$instanceId = \OC::$server->getConfig()->getSystemValueString('instanceid');
322
-		return 'appdata_' . $instanceId;
322
+		return 'appdata_'.$instanceId;
323 323
 	}
324 324
 
325 325
 	/**
@@ -348,8 +348,8 @@  discard block
 block discarded – undo
348 348
 			return [];
349 349
 		}
350 350
 
351
-		$absolutePath = '/' . ltrim($cacheEntry->getPath(), '/');
352
-		$currentPath = rtrim($this->path, '/') . '/';
351
+		$absolutePath = '/'.ltrim($cacheEntry->getPath(), '/');
352
+		$currentPath = rtrim($this->path, '/').'/';
353 353
 
354 354
 		if (!str_starts_with($absolutePath, $currentPath)) {
355 355
 			return [];
@@ -377,7 +377,7 @@  discard block
 block discarded – undo
377 377
 			$nonExisting = new NonExistingFolder($this->root, $this->view, $this->path, $fileInfo);
378 378
 			$this->sendHooks(['postDelete'], [$nonExisting]);
379 379
 		} else {
380
-			throw new NotPermittedException('No delete permission for path "' . $this->path . '"');
380
+			throw new NotPermittedException('No delete permission for path "'.$this->path.'"');
381 381
 		}
382 382
 	}
383 383
 
Please login to merge, or discard this patch.