2013-09-01 13:47:48 -04:00
|
|
|
<?php
|
2025-06-30 09:04:05 -04:00
|
|
|
|
2013-09-01 13:47:48 -04:00
|
|
|
/**
|
2024-05-23 03:26:56 -04:00
|
|
|
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
|
|
|
|
|
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
|
|
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
2013-09-01 13:47:48 -04:00
|
|
|
*/
|
|
|
|
|
namespace OC\Files\Node;
|
|
|
|
|
|
2021-05-05 13:36:41 -04:00
|
|
|
use OC\Files\Cache\QuerySearchHelper;
|
|
|
|
|
use OC\Files\Search\SearchBinaryOperator;
|
2021-03-18 11:35:41 -04:00
|
|
|
use OC\Files\Search\SearchComparison;
|
2021-03-26 12:10:25 -04:00
|
|
|
use OC\Files\Search\SearchOrder;
|
2021-03-18 11:35:41 -04:00
|
|
|
use OC\Files\Search\SearchQuery;
|
2022-03-28 12:46:38 -04:00
|
|
|
use OC\Files\Utils\PathHelper;
|
2024-07-09 12:33:45 -04:00
|
|
|
use OC\User\LazyUser;
|
2021-03-19 08:35:37 -04:00
|
|
|
use OCP\Files\Cache\ICacheEntry;
|
2015-09-08 16:38:50 -04:00
|
|
|
use OCP\Files\FileInfo;
|
2025-11-21 04:04:41 -05:00
|
|
|
use OCP\Files\Folder as IFolder;
|
2016-07-22 08:37:37 -04:00
|
|
|
use OCP\Files\Mount\IMountPoint;
|
2023-06-16 18:15:49 -04:00
|
|
|
use OCP\Files\Node as INode;
|
2013-09-10 13:44:23 -04:00
|
|
|
use OCP\Files\NotFoundException;
|
|
|
|
|
use OCP\Files\NotPermittedException;
|
2021-03-19 08:41:00 -04:00
|
|
|
use OCP\Files\Search\ISearchBinaryOperator;
|
2021-03-18 11:35:41 -04:00
|
|
|
use OCP\Files\Search\ISearchComparison;
|
|
|
|
|
use OCP\Files\Search\ISearchOperator;
|
2021-03-26 12:10:25 -04:00
|
|
|
use OCP\Files\Search\ISearchOrder;
|
2019-11-08 09:05:21 -05:00
|
|
|
use OCP\Files\Search\ISearchQuery;
|
2021-03-18 11:35:41 -04:00
|
|
|
use OCP\IUserManager;
|
2025-11-21 04:04:41 -05:00
|
|
|
use Override;
|
2013-09-01 13:47:48 -04:00
|
|
|
|
2025-11-21 04:04:41 -05:00
|
|
|
class Folder extends Node implements IFolder {
|
2024-07-09 12:33:45 -04:00
|
|
|
|
|
|
|
|
private ?IUserManager $userManager = null;
|
|
|
|
|
|
2025-09-26 09:11:36 -04:00
|
|
|
private bool $wasDeleted = false;
|
|
|
|
|
|
2016-11-14 12:29:11 -05:00
|
|
|
/**
|
|
|
|
|
* Creates a Folder that represents a non-existing path
|
|
|
|
|
*
|
|
|
|
|
* @param string $path path
|
2023-02-08 08:34:40 -05:00
|
|
|
* @return NonExistingFolder non-existing node
|
2016-11-14 12:29:11 -05:00
|
|
|
*/
|
|
|
|
|
protected function createNonExistingNode($path) {
|
|
|
|
|
return new NonExistingFolder($this->root, $this->view, $path);
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-01 13:47:48 -04:00
|
|
|
/**
|
|
|
|
|
* @param string $path path relative to the folder
|
|
|
|
|
* @return string
|
2013-09-10 13:44:23 -04:00
|
|
|
* @throws \OCP\Files\NotPermittedException
|
2013-09-01 13:47:48 -04:00
|
|
|
*/
|
|
|
|
|
public function getFullPath($path) {
|
2022-11-10 09:03:15 -05:00
|
|
|
$path = $this->normalizePath($path);
|
2013-09-01 13:47:48 -04:00
|
|
|
if (!$this->isValidPath($path)) {
|
2022-07-01 05:16:21 -04:00
|
|
|
throw new NotPermittedException('Invalid path "' . $path . '"');
|
2013-09-01 13:47:48 -04:00
|
|
|
}
|
2022-11-10 09:03:15 -05:00
|
|
|
return $this->path . $path;
|
2013-09-01 13:47:48 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param string $path
|
2021-03-19 13:13:59 -04:00
|
|
|
* @return string|null
|
2013-09-01 13:47:48 -04:00
|
|
|
*/
|
|
|
|
|
public function getRelativePath($path) {
|
2022-03-28 12:46:38 -04:00
|
|
|
return PathHelper::getRelativePath($this->getPath(), $path);
|
2013-09-01 13:47:48 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* check if a node is a (grand-)child of the folder
|
|
|
|
|
*
|
|
|
|
|
* @param \OC\Files\Node\Node $node
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function isSubNode($node) {
|
2023-05-15 07:47:19 -04:00
|
|
|
return str_starts_with($node->getPath(), $this->path . '/');
|
2013-09-01 13:47:48 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* get the content of this directory
|
|
|
|
|
*
|
|
|
|
|
* @return Node[]
|
2021-03-18 11:35:41 -04:00
|
|
|
* @throws \OCP\Files\NotFoundException
|
2013-09-01 13:47:48 -04:00
|
|
|
*/
|
|
|
|
|
public function getDirectoryListing() {
|
2023-02-08 08:34:40 -05:00
|
|
|
$folderContent = $this->view->getDirectoryContent($this->path, '', $this->getFileInfo(false));
|
2013-09-01 13:47:48 -04:00
|
|
|
|
2016-09-18 12:36:53 -04:00
|
|
|
return array_map(function (FileInfo $info) {
|
2022-04-05 09:29:49 -04:00
|
|
|
if ($info->getMimetype() === FileInfo::MIMETYPE_FOLDER) {
|
2022-08-18 07:29:40 -04:00
|
|
|
return new Folder($this->root, $this->view, $info->getPath(), $info, $this);
|
2015-09-08 16:38:50 -04:00
|
|
|
} else {
|
2022-08-18 07:29:40 -04:00
|
|
|
return new File($this->root, $this->view, $info->getPath(), $info, $this);
|
2013-09-01 13:47:48 -04:00
|
|
|
}
|
2015-09-08 16:38:50 -04:00
|
|
|
}, $folderContent);
|
2013-09-01 13:47:48 -04:00
|
|
|
}
|
|
|
|
|
|
2023-06-16 18:15:49 -04:00
|
|
|
protected function createNode(string $path, ?FileInfo $info = null, bool $infoHasSubMountsIncluded = true): INode {
|
2016-03-21 09:20:33 -04:00
|
|
|
if (is_null($info)) {
|
2013-09-01 13:47:48 -04:00
|
|
|
$isDir = $this->view->is_dir($path);
|
|
|
|
|
} else {
|
2016-03-21 09:20:33 -04:00
|
|
|
$isDir = $info->getType() === FileInfo::TYPE_FOLDER;
|
2013-09-01 13:47:48 -04:00
|
|
|
}
|
2022-08-24 12:01:10 -04:00
|
|
|
$parent = dirname($path) === $this->getPath() ? $this : null;
|
2013-09-01 13:47:48 -04:00
|
|
|
if ($isDir) {
|
2023-02-08 08:34:40 -05:00
|
|
|
return new Folder($this->root, $this->view, $path, $info, $parent, $infoHasSubMountsIncluded);
|
2013-09-01 13:47:48 -04:00
|
|
|
} else {
|
2022-08-24 12:01:10 -04:00
|
|
|
return new File($this->root, $this->view, $path, $info, $parent);
|
2013-09-01 13:47:48 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function get($path) {
|
|
|
|
|
return $this->root->get($this->getFullPath($path));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function nodeExists($path) {
|
|
|
|
|
try {
|
|
|
|
|
$this->get($path);
|
|
|
|
|
return true;
|
2024-09-30 07:54:40 -04:00
|
|
|
} catch (NotFoundException|NotPermittedException) {
|
2013-09-01 13:47:48 -04:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param string $path
|
2013-09-10 13:44:23 -04:00
|
|
|
* @return \OC\Files\Node\Folder
|
|
|
|
|
* @throws \OCP\Files\NotPermittedException
|
2013-09-01 13:47:48 -04:00
|
|
|
*/
|
|
|
|
|
public function newFolder($path) {
|
2014-11-25 10:28:41 -05:00
|
|
|
if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
|
2013-09-01 13:47:48 -04:00
|
|
|
$fullPath = $this->getFullPath($path);
|
|
|
|
|
$nonExisting = new NonExistingFolder($this->root, $this->view, $fullPath);
|
2019-09-03 06:30:10 -04:00
|
|
|
$this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
|
2025-04-03 11:26:09 -04:00
|
|
|
if (!$this->view->mkdir($fullPath)) {
|
|
|
|
|
// maybe another concurrent process created the folder already
|
|
|
|
|
if (!$this->view->is_dir($fullPath)) {
|
|
|
|
|
throw new NotPermittedException('Could not create folder "' . $fullPath . '"');
|
|
|
|
|
} else {
|
|
|
|
|
// we need to ensure we don't return before the concurrent request has finished updating the cache
|
|
|
|
|
$tries = 5;
|
|
|
|
|
while (!$this->view->getFileInfo($fullPath)) {
|
|
|
|
|
if ($tries < 1) {
|
|
|
|
|
throw new NotPermittedException('Could not create folder "' . $fullPath . '", folder exists but unable to get cache entry');
|
|
|
|
|
}
|
|
|
|
|
usleep(5 * 1000);
|
|
|
|
|
$tries--;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-29 10:12:01 -04:00
|
|
|
}
|
2022-08-24 12:01:10 -04:00
|
|
|
$parent = dirname($fullPath) === $this->getPath() ? $this : null;
|
|
|
|
|
$node = new Folder($this->root, $this->view, $fullPath, null, $parent);
|
2019-09-03 06:30:10 -04:00
|
|
|
$this->sendHooks(['postWrite', 'postCreate'], [$node]);
|
2013-09-01 13:47:48 -04:00
|
|
|
return $node;
|
|
|
|
|
} else {
|
2022-07-01 05:16:21 -04:00
|
|
|
throw new NotPermittedException('No create permission for folder "' . $path . '"');
|
2013-09-01 13:47:48 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param string $path
|
2020-02-15 19:14:52 -05:00
|
|
|
* @param string | resource | null $content
|
2013-09-10 13:44:23 -04:00
|
|
|
* @return \OC\Files\Node\File
|
|
|
|
|
* @throws \OCP\Files\NotPermittedException
|
2013-09-01 13:47:48 -04:00
|
|
|
*/
|
2020-02-15 19:14:52 -05:00
|
|
|
public function newFile($path, $content = null) {
|
2023-10-22 06:22:30 -04:00
|
|
|
if ($path === '') {
|
2021-04-20 05:20:20 -04:00
|
|
|
throw new NotPermittedException('Could not create as provided path is empty');
|
|
|
|
|
}
|
2025-09-26 09:11:36 -04:00
|
|
|
$this->recreateIfNeeded();
|
2014-11-25 10:28:41 -05:00
|
|
|
if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
|
2013-09-01 13:47:48 -04:00
|
|
|
$fullPath = $this->getFullPath($path);
|
|
|
|
|
$nonExisting = new NonExistingFile($this->root, $this->view, $fullPath);
|
2019-09-03 06:30:10 -04:00
|
|
|
$this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
|
2020-02-15 19:14:52 -05:00
|
|
|
if ($content !== null) {
|
|
|
|
|
$result = $this->view->file_put_contents($fullPath, $content);
|
|
|
|
|
} else {
|
|
|
|
|
$result = $this->view->touch($fullPath);
|
|
|
|
|
}
|
2020-08-13 09:41:26 -04:00
|
|
|
if ($result === false) {
|
2022-07-01 05:16:21 -04:00
|
|
|
throw new NotPermittedException('Could not create path "' . $fullPath . '"');
|
2019-05-28 07:05:20 -04:00
|
|
|
}
|
2022-08-18 07:29:40 -04:00
|
|
|
$node = new File($this->root, $this->view, $fullPath, null, $this);
|
2019-09-03 06:30:10 -04:00
|
|
|
$this->sendHooks(['postWrite', 'postCreate'], [$node]);
|
2013-09-01 13:47:48 -04:00
|
|
|
return $node;
|
|
|
|
|
}
|
2022-07-01 05:16:21 -04:00
|
|
|
throw new NotPermittedException('No create permission for path "' . $path . '"');
|
2013-09-01 13:47:48 -04:00
|
|
|
}
|
|
|
|
|
|
2024-03-28 11:13:19 -04:00
|
|
|
private function queryFromOperator(ISearchOperator $operator, ?string $uid = null, int $limit = 0, int $offset = 0): ISearchQuery {
|
2021-03-18 12:38:31 -04:00
|
|
|
if ($uid === null) {
|
|
|
|
|
$user = null;
|
2021-03-18 11:35:41 -04:00
|
|
|
} else {
|
|
|
|
|
/** @var IUserManager $userManager */
|
2023-06-06 05:09:24 -04:00
|
|
|
$userManager = \OCP\Server::get(IUserManager::class);
|
2021-03-18 11:35:41 -04:00
|
|
|
$user = $userManager->get($uid);
|
|
|
|
|
}
|
2023-04-27 16:24:16 -04:00
|
|
|
return new SearchQuery($operator, $limit, $offset, [], $user);
|
2021-03-18 11:35:41 -04:00
|
|
|
}
|
|
|
|
|
|
2023-04-27 16:24:16 -04:00
|
|
|
/**
|
|
|
|
|
* search for files with the name matching $query
|
|
|
|
|
*
|
|
|
|
|
* @param string|ISearchQuery $query
|
|
|
|
|
* @return \OC\Files\Node\Node[]
|
|
|
|
|
*/
|
|
|
|
|
public function search($query) {
|
|
|
|
|
if (is_string($query)) {
|
|
|
|
|
$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'name', '%' . $query . '%'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// search is handled by a single query covering all caches that this folder contains
|
|
|
|
|
// this is done by collect
|
|
|
|
|
|
|
|
|
|
$limitToHome = $query->limitToHome();
|
|
|
|
|
if ($limitToHome && count(explode('/', $this->path)) !== 3) {
|
|
|
|
|
throw new \InvalidArgumentException('searching by owner is only allowed in the users home folder');
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-05 13:36:41 -04:00
|
|
|
/** @var QuerySearchHelper $searchHelper */
|
|
|
|
|
$searchHelper = \OC::$server->get(QuerySearchHelper::class);
|
2023-05-05 15:28:09 -04:00
|
|
|
[$caches, $mountByMountPoint] = $searchHelper->getCachesAndMountPointsForSearch($this->root, $this->path, $limitToHome);
|
2021-05-05 13:36:41 -04:00
|
|
|
$resultsPerCache = $searchHelper->searchInCaches($query, $caches);
|
2021-05-06 15:23:09 -04:00
|
|
|
|
2022-07-27 08:51:42 -04:00
|
|
|
// loop through all results per-cache, constructing the FileInfo object from the CacheEntry and merge them all
|
2023-04-27 16:24:16 -04:00
|
|
|
$files = array_merge(...array_map(function (array $results, string $relativeMountPoint) use ($mountByMountPoint) {
|
2021-05-06 15:23:09 -04:00
|
|
|
$mount = $mountByMountPoint[$relativeMountPoint];
|
2021-05-07 08:38:11 -04:00
|
|
|
return array_map(function (ICacheEntry $result) use ($relativeMountPoint, $mount) {
|
2021-05-06 15:23:09 -04:00
|
|
|
return $this->cacheEntryToFileInfo($mount, $relativeMountPoint, $result);
|
2021-05-05 13:36:41 -04:00
|
|
|
}, $results);
|
|
|
|
|
}, array_values($resultsPerCache), array_keys($resultsPerCache)));
|
2021-03-19 09:26:44 -04:00
|
|
|
|
2021-07-01 11:21:09 -04:00
|
|
|
// don't include this folder in the results
|
2024-07-30 21:19:55 -04:00
|
|
|
$files = array_values(array_filter($files, function (FileInfo $file) {
|
2021-07-01 11:21:09 -04:00
|
|
|
return $file->getPath() !== $this->getPath();
|
2024-07-30 21:19:55 -04:00
|
|
|
}));
|
2021-07-01 11:21:09 -04:00
|
|
|
|
2021-05-07 08:38:11 -04:00
|
|
|
// since results were returned per-cache, they are no longer fully sorted
|
|
|
|
|
$order = $query->getOrder();
|
|
|
|
|
if ($order) {
|
|
|
|
|
usort($files, function (FileInfo $a, FileInfo $b) use ($order) {
|
|
|
|
|
foreach ($order as $orderField) {
|
|
|
|
|
$cmp = $orderField->sortFileInfo($a, $b);
|
|
|
|
|
if ($cmp !== 0) {
|
|
|
|
|
return $cmp;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-18 12:36:53 -04:00
|
|
|
return array_map(function (FileInfo $file) {
|
2016-03-21 09:20:33 -04:00
|
|
|
return $this->createNode($file->getPath(), $file);
|
|
|
|
|
}, $files);
|
2013-09-01 13:47:48 -04:00
|
|
|
}
|
|
|
|
|
|
2021-05-06 15:23:09 -04:00
|
|
|
private function cacheEntryToFileInfo(IMountPoint $mount, string $appendRoot, ICacheEntry $cacheEntry): FileInfo {
|
2021-03-19 08:35:37 -04:00
|
|
|
$cacheEntry['internalPath'] = $cacheEntry['path'];
|
2022-08-29 11:33:43 -04:00
|
|
|
$cacheEntry['path'] = rtrim($appendRoot . $cacheEntry->getPath(), '/');
|
2021-07-01 11:21:09 -04:00
|
|
|
$subPath = $cacheEntry['path'] !== '' ? '/' . $cacheEntry['path'] : '';
|
2024-07-09 12:33:45 -04:00
|
|
|
$storage = $mount->getStorage();
|
|
|
|
|
|
|
|
|
|
$owner = null;
|
|
|
|
|
$ownerId = $storage->getOwner($cacheEntry['internalPath']);
|
2024-09-16 10:00:46 -04:00
|
|
|
if ($ownerId !== false) {
|
2024-07-09 12:33:45 -04:00
|
|
|
// Cache the user manager (for performance)
|
|
|
|
|
if ($this->userManager === null) {
|
|
|
|
|
$this->userManager = \OCP\Server::get(IUserManager::class);
|
|
|
|
|
}
|
|
|
|
|
$owner = new LazyUser($ownerId, $this->userManager);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new \OC\Files\FileInfo(
|
|
|
|
|
$this->path . $subPath,
|
|
|
|
|
$storage,
|
|
|
|
|
$cacheEntry['internalPath'],
|
|
|
|
|
$cacheEntry,
|
|
|
|
|
$mount,
|
|
|
|
|
$owner,
|
|
|
|
|
);
|
2021-03-19 08:35:37 -04:00
|
|
|
}
|
|
|
|
|
|
2021-03-18 11:35:41 -04:00
|
|
|
/**
|
|
|
|
|
* search for files by mimetype
|
|
|
|
|
*
|
|
|
|
|
* @param string $mimetype
|
|
|
|
|
* @return Node[]
|
|
|
|
|
*/
|
|
|
|
|
public function searchByMime($mimetype) {
|
2023-05-15 07:47:19 -04:00
|
|
|
if (!str_contains($mimetype, '/')) {
|
2021-03-18 11:35:41 -04:00
|
|
|
$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', $mimetype . '/%'));
|
|
|
|
|
} else {
|
|
|
|
|
$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'mimetype', $mimetype));
|
|
|
|
|
}
|
|
|
|
|
return $this->search($query);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* search for files by tag
|
|
|
|
|
*
|
|
|
|
|
* @param string|int $tag name or tag id
|
|
|
|
|
* @param string $userId owner of the tags
|
|
|
|
|
* @return Node[]
|
|
|
|
|
*/
|
|
|
|
|
public function searchByTag($tag, $userId) {
|
|
|
|
|
$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'tagname', $tag), $userId);
|
|
|
|
|
return $this->search($query);
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-21 12:01:49 -04:00
|
|
|
public function searchBySystemTag(string $tagName, string $userId, int $limit = 0, int $offset = 0): array {
|
|
|
|
|
$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'systemtag', $tagName), $userId, $limit, $offset);
|
2021-03-18 11:35:41 -04:00
|
|
|
return $this->search($query);
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-01 13:47:48 -04:00
|
|
|
/**
|
2014-05-11 16:51:30 -04:00
|
|
|
* @param int $id
|
2024-02-08 05:34:22 -05:00
|
|
|
* @return \OCP\Files\Node[]
|
2013-09-01 13:47:48 -04:00
|
|
|
*/
|
|
|
|
|
public function getById($id) {
|
2022-03-28 12:47:17 -04:00
|
|
|
return $this->root->getByIdInPath((int)$id, $this->getPath());
|
2013-09-01 13:47:48 -04:00
|
|
|
}
|
|
|
|
|
|
2024-02-08 05:34:22 -05:00
|
|
|
public function getFirstNodeById(int $id): ?\OCP\Files\Node {
|
2024-06-20 11:29:19 -04:00
|
|
|
return $this->root->getFirstNodeByIdInPath($id, $this->getPath());
|
2024-02-08 05:34:22 -05:00
|
|
|
}
|
|
|
|
|
|
2024-04-29 10:21:07 -04:00
|
|
|
public function getAppDataDirectoryName(): string {
|
2019-09-17 05:15:59 -04:00
|
|
|
$instanceId = \OC::$server->getConfig()->getSystemValueString('instanceid');
|
|
|
|
|
return 'appdata_' . $instanceId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* In case the path we are currently in is inside the appdata_* folder,
|
|
|
|
|
* the original getById method does not work, because it can only look inside
|
|
|
|
|
* the user's mount points. But the user has no mount point for the root storage.
|
|
|
|
|
*
|
|
|
|
|
* So in that case we directly check the mount of the root if it contains
|
|
|
|
|
* the id. If it does we check if the path is inside the path we are working
|
|
|
|
|
* in.
|
|
|
|
|
*
|
|
|
|
|
* @param int $id
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
|
|
|
|
protected function getByIdInRootMount(int $id): array {
|
2023-06-16 18:15:49 -04:00
|
|
|
if (!method_exists($this->root, 'createNode')) {
|
|
|
|
|
// Always expected to be false. Being a method of Folder, this is
|
|
|
|
|
// always implemented. For it is an internal method and should not
|
|
|
|
|
// be exposed and made public, it is not part of an interface.
|
|
|
|
|
return [];
|
|
|
|
|
}
|
2023-06-15 17:21:56 -04:00
|
|
|
$mount = $this->root->getMount('');
|
|
|
|
|
$storage = $mount->getStorage();
|
2023-06-08 17:32:16 -04:00
|
|
|
$cacheEntry = $storage?->getCache($this->path)->get($id);
|
2019-09-17 05:15:59 -04:00
|
|
|
if (!$cacheEntry) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$absolutePath = '/' . ltrim($cacheEntry->getPath(), '/');
|
|
|
|
|
$currentPath = rtrim($this->path, '/') . '/';
|
|
|
|
|
|
2023-05-15 07:47:19 -04:00
|
|
|
if (!str_starts_with($absolutePath, $currentPath)) {
|
2019-09-17 05:15:59 -04:00
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [$this->root->createNode(
|
|
|
|
|
$absolutePath, new \OC\Files\FileInfo(
|
2022-11-10 09:03:15 -05:00
|
|
|
$absolutePath,
|
2023-06-08 17:32:16 -04:00
|
|
|
$storage,
|
2022-11-10 09:03:15 -05:00
|
|
|
$cacheEntry->getPath(),
|
|
|
|
|
$cacheEntry,
|
|
|
|
|
$mount
|
|
|
|
|
))];
|
2019-09-17 05:15:59 -04:00
|
|
|
}
|
|
|
|
|
|
2013-09-01 13:47:48 -04:00
|
|
|
public function getFreeSpace() {
|
|
|
|
|
return $this->view->free_space($this->path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function delete() {
|
2014-11-25 10:28:41 -05:00
|
|
|
if ($this->checkPermissions(\OCP\Constants::PERMISSION_DELETE)) {
|
2020-03-26 04:30:18 -04:00
|
|
|
$this->sendHooks(['preDelete']);
|
2015-12-01 07:22:58 -05:00
|
|
|
$fileInfo = $this->getFileInfo();
|
2013-09-01 13:47:48 -04:00
|
|
|
$this->view->rmdir($this->path);
|
2015-12-01 07:22:58 -05:00
|
|
|
$nonExisting = new NonExistingFolder($this->root, $this->view, $this->path, $fileInfo);
|
2019-09-03 06:30:10 -04:00
|
|
|
$this->sendHooks(['postDelete'], [$nonExisting]);
|
2025-09-26 09:11:36 -04:00
|
|
|
$this->wasDeleted = true;
|
2013-09-01 13:47:48 -04:00
|
|
|
} else {
|
2022-07-01 05:16:21 -04:00
|
|
|
throw new NotPermittedException('No delete permission for path "' . $this->path . '"');
|
2013-09-01 13:47:48 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-24 10:00:36 -04:00
|
|
|
/**
|
|
|
|
|
* Add a suffix to the name in case the file exists
|
|
|
|
|
*
|
2025-09-28 05:37:43 -04:00
|
|
|
* @param string $filename
|
2015-03-24 10:00:36 -04:00
|
|
|
* @return string
|
|
|
|
|
* @throws NotPermittedException
|
|
|
|
|
*/
|
2025-09-28 05:37:43 -04:00
|
|
|
public function getNonExistingName($filename) {
|
|
|
|
|
$path = $this->getPath();
|
|
|
|
|
if ($path === '/') {
|
|
|
|
|
$path = '';
|
|
|
|
|
}
|
|
|
|
|
if ($pos = strrpos($filename, '.')) {
|
|
|
|
|
$name = substr($filename, 0, $pos);
|
|
|
|
|
$ext = substr($filename, $pos);
|
|
|
|
|
} else {
|
|
|
|
|
$name = $filename;
|
|
|
|
|
$ext = '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$newpath = $path . '/' . $filename;
|
|
|
|
|
if ($this->view->file_exists($newpath)) {
|
|
|
|
|
if (preg_match_all('/\((\d+)\)/', $name, $matches, PREG_OFFSET_CAPTURE)) {
|
|
|
|
|
/** @var array<int<0, max>, array> $matches */
|
|
|
|
|
//Replace the last "(number)" with "(number+1)"
|
|
|
|
|
$last_match = count($matches[0]) - 1;
|
|
|
|
|
$counter = $matches[1][$last_match][0] + 1;
|
|
|
|
|
$offset = $matches[0][$last_match][1];
|
|
|
|
|
$match_length = strlen($matches[0][$last_match][0]);
|
|
|
|
|
} else {
|
|
|
|
|
$counter = 2;
|
|
|
|
|
$match_length = 0;
|
|
|
|
|
$offset = false;
|
|
|
|
|
}
|
|
|
|
|
do {
|
|
|
|
|
if ($offset) {
|
|
|
|
|
//Replace the last "(number)" with "(number+1)"
|
|
|
|
|
$newname = substr_replace($name, '(' . $counter . ')', $offset, $match_length);
|
|
|
|
|
} else {
|
|
|
|
|
$newname = $name . ' (' . $counter . ')';
|
|
|
|
|
}
|
|
|
|
|
$newpath = $path . '/' . $newname . $ext;
|
|
|
|
|
$counter++;
|
|
|
|
|
} while ($this->view->file_exists($newpath));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return trim($this->getRelativePath($newpath), '/');
|
2015-03-24 10:00:36 -04:00
|
|
|
}
|
2016-07-22 08:37:37 -04:00
|
|
|
|
|
|
|
|
/**
|
2016-07-22 07:58:53 -04:00
|
|
|
* @param int $limit
|
|
|
|
|
* @param int $offset
|
2023-06-16 18:15:49 -04:00
|
|
|
* @return INode[]
|
2016-07-22 08:37:37 -04:00
|
|
|
*/
|
2016-07-22 07:58:53 -04:00
|
|
|
public function getRecent($limit, $offset = 0) {
|
2022-09-01 04:45:50 -04:00
|
|
|
$filterOutNonEmptyFolder = new SearchBinaryOperator(
|
|
|
|
|
// filter out non empty folders
|
|
|
|
|
ISearchBinaryOperator::OPERATOR_OR,
|
2021-03-26 12:10:25 -04:00
|
|
|
[
|
2022-09-01 04:45:50 -04:00
|
|
|
new SearchBinaryOperator(
|
|
|
|
|
ISearchBinaryOperator::OPERATOR_NOT,
|
|
|
|
|
[
|
|
|
|
|
new SearchComparison(
|
|
|
|
|
ISearchComparison::COMPARE_EQUAL,
|
|
|
|
|
'mimetype',
|
|
|
|
|
FileInfo::MIMETYPE_FOLDER
|
|
|
|
|
),
|
|
|
|
|
]
|
|
|
|
|
),
|
|
|
|
|
new SearchComparison(
|
|
|
|
|
ISearchComparison::COMPARE_EQUAL,
|
|
|
|
|
'size',
|
|
|
|
|
0
|
2021-03-26 12:10:25 -04:00
|
|
|
),
|
|
|
|
|
]
|
|
|
|
|
);
|
2022-09-01 04:45:50 -04:00
|
|
|
|
|
|
|
|
$filterNonRecentFiles = new SearchComparison(
|
|
|
|
|
ISearchComparison::COMPARE_GREATER_THAN,
|
|
|
|
|
'mtime',
|
2024-08-23 09:10:27 -04:00
|
|
|
strtotime('-2 week')
|
2022-09-01 04:45:50 -04:00
|
|
|
);
|
|
|
|
|
if ($offset === 0 && $limit <= 100) {
|
|
|
|
|
$query = new SearchQuery(
|
|
|
|
|
new SearchBinaryOperator(
|
|
|
|
|
ISearchBinaryOperator::OPERATOR_AND,
|
|
|
|
|
[
|
|
|
|
|
$filterOutNonEmptyFolder,
|
|
|
|
|
$filterNonRecentFiles,
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
$limit,
|
|
|
|
|
$offset,
|
|
|
|
|
[
|
|
|
|
|
new SearchOrder(
|
|
|
|
|
ISearchOrder::DIRECTION_DESCENDING,
|
|
|
|
|
'mtime'
|
|
|
|
|
),
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
$query = new SearchQuery(
|
|
|
|
|
$filterOutNonEmptyFolder,
|
|
|
|
|
$limit,
|
|
|
|
|
$offset,
|
|
|
|
|
[
|
|
|
|
|
new SearchOrder(
|
|
|
|
|
ISearchOrder::DIRECTION_DESCENDING,
|
|
|
|
|
'mtime'
|
|
|
|
|
),
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-26 12:10:25 -04:00
|
|
|
return $this->search($query);
|
2016-07-22 08:37:37 -04:00
|
|
|
}
|
2025-06-03 04:12:35 -04:00
|
|
|
|
|
|
|
|
public function verifyPath($fileName, $readonly = false): void {
|
|
|
|
|
$this->view->verifyPath(
|
|
|
|
|
$this->getPath(),
|
|
|
|
|
$fileName,
|
|
|
|
|
$readonly,
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-09-26 09:11:36 -04:00
|
|
|
|
|
|
|
|
private function recreateIfNeeded(): void {
|
|
|
|
|
if ($this->wasDeleted) {
|
|
|
|
|
$this->newFolder('');
|
|
|
|
|
$this->wasDeleted = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-11-21 04:04:41 -05:00
|
|
|
|
|
|
|
|
#[Override]
|
|
|
|
|
public function getOrCreateFolder(string $path, int $maxRetries = 5): IFolder {
|
|
|
|
|
$i = 0;
|
|
|
|
|
while (true) {
|
|
|
|
|
$path = $i === 0 ? $path : $path . ' (' . $i . ')';
|
|
|
|
|
try {
|
|
|
|
|
$folder = $this->get($path);
|
|
|
|
|
if ($folder instanceof IFolder) {
|
|
|
|
|
return $folder;
|
|
|
|
|
}
|
|
|
|
|
} catch (NotFoundException) {
|
|
|
|
|
$folder = dirname($path) === '.' ? $this : $this->get(dirname($path));
|
|
|
|
|
if (!($folder instanceof Folder)) {
|
|
|
|
|
throw new NotPermittedException("Unable to create folder $path. Parent is not a directory.");
|
|
|
|
|
}
|
|
|
|
|
return $folder->newFolder(basename($path));
|
|
|
|
|
}
|
|
|
|
|
$i++;
|
|
|
|
|
if ($i === $maxRetries) {
|
|
|
|
|
throw new NotPermittedException('Unable to load or create folder.');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-09-01 13:47:48 -04:00
|
|
|
}
|