mirror of
https://github.com/nextcloud/server.git
synced 2026-06-08 16:26:59 -04:00
perf: Allow filtering the directory content by mimetype
Signed-off-by: Carl Schwan <carlschwan@kde.org>
This commit is contained in:
parent
935cd2910f
commit
9741f5f17d
15 changed files with 132 additions and 121 deletions
|
|
@ -249,7 +249,7 @@ class ApiController extends Controller {
|
|||
* @param \OCP\Files\Node[] $nodes
|
||||
* @param int $depth The depth to traverse into the contents of each node
|
||||
*/
|
||||
private function getChildren(array $nodes, int $depth = 1, int $currentDepth = 0): array {
|
||||
private function getChildren(array $nodes, int $depth = 1, int $currentDepth = 0, string $mimeTypeFilter = ''): array {
|
||||
if ($currentDepth >= $depth) {
|
||||
return [];
|
||||
}
|
||||
|
|
@ -264,7 +264,7 @@ class ApiController extends Controller {
|
|||
$entry = [
|
||||
'id' => $node->getId(),
|
||||
'basename' => $basename,
|
||||
'children' => $this->getChildren($node->getDirectoryListing(), $depth, $currentDepth + 1),
|
||||
'children' => $this->getChildren($node->getDirectoryListing($mimeTypeFilter), $depth, $currentDepth + 1),
|
||||
];
|
||||
$displayName = $node->getName();
|
||||
if ($basename !== $displayName) {
|
||||
|
|
@ -308,8 +308,8 @@ class ApiController extends Controller {
|
|||
'message' => $this->l10n->t('Invalid folder path'),
|
||||
], Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
$nodes = $node->getDirectoryListing();
|
||||
$tree = $this->getChildren($nodes, $depth);
|
||||
$nodes = $node->getDirectoryListing('httpd/unix-directory');
|
||||
$tree = $this->getChildren($nodes, $depth, 0, 'httpd/unix-directory');
|
||||
} catch (NotFoundException $e) {
|
||||
return new JSONResponse([
|
||||
'message' => $this->l10n->t('Folder not found'),
|
||||
|
|
|
|||
4
apps/files_sharing/lib/External/Cache.php
vendored
4
apps/files_sharing/lib/External/Cache.php
vendored
|
|
@ -41,8 +41,8 @@ class Cache extends \OC\Files\Cache\Cache {
|
|||
return $result;
|
||||
}
|
||||
|
||||
public function getFolderContentsById($fileId) {
|
||||
$results = parent::getFolderContentsById($fileId);
|
||||
public function getFolderContentsById($fileId, ?string $mimeTypeFilter = null): array {
|
||||
$results = parent::getFolderContentsById($fileId, $mimeTypeFilter);
|
||||
foreach ($results as &$file) {
|
||||
$file['displayname_owner'] = $this->cloudId->getDisplayId();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -309,7 +309,7 @@ function execute_tests {
|
|||
if [ ! -z "$USEDOCKER" ] ; then
|
||||
echo "Fire up the postgres docker"
|
||||
DOCKER_CONTAINER_ID=$(docker run -e POSTGRES_DB="$DATABASENAME" -e POSTGRES_USER="$DATABASEUSER" -e POSTGRES_PASSWORD=owncloud -d postgres)
|
||||
DATABASEHOST=$(docker inspect --format="{{.NetworkSettings.IPAddress}}" "$DOCKER_CONTAINER_ID")
|
||||
DATABASEHOST=$(docker inspect --format="{{ range .NetworkSettings.Networks }}{{ .IPAddress }}{{ end }}" "$DOCKER_CONTAINER_ID")
|
||||
|
||||
echo "Waiting for Postgres initialisation ..."
|
||||
|
||||
|
|
|
|||
|
|
@ -218,7 +218,7 @@ class Cache implements ICache {
|
|||
* @param int $fileId the file id of the folder
|
||||
* @return ICacheEntry[]
|
||||
*/
|
||||
public function getFolderContentsById($fileId) {
|
||||
public function getFolderContentsById(int $fileId, ?string $mimeTypeFilter = null) {
|
||||
if ($fileId > -1) {
|
||||
$query = $this->getQueryBuilder();
|
||||
$query->selectFileCache()
|
||||
|
|
@ -226,13 +226,22 @@ class Cache implements ICache {
|
|||
->whereStorageId($this->getNumericStorageId())
|
||||
->orderBy('name', 'ASC');
|
||||
|
||||
if ($mimeTypeFilter !== null) {
|
||||
$mimetype = $this->mimetypeLoader->getId($mimeTypeFilter);
|
||||
if (str_contains($mimeTypeFilter, '/')) {
|
||||
$query->andWhere($query->expr()->eq('mimetype', $query->createNamedParameter($mimetype)));
|
||||
} else {
|
||||
$query->andWhere($query->expr()->eq('mimepart', $query->createNamedParameter($mimetype)));
|
||||
}
|
||||
}
|
||||
|
||||
$metadataQuery = $query->selectMetadata();
|
||||
|
||||
$result = $query->executeQuery();
|
||||
$files = $result->fetchAll();
|
||||
$result->closeCursor();
|
||||
|
||||
return array_map(function (array $data) use ($metadataQuery) {
|
||||
return array_map(function (array $data) use ($metadataQuery): ICacheEntry {
|
||||
$data['metadata'] = $metadataQuery->extractMetadata($data)->asArray();
|
||||
return self::cacheEntryFromData($data, $this->mimetypeLoader);
|
||||
}, $files);
|
||||
|
|
|
|||
|
|
@ -26,12 +26,11 @@ class FailedCache implements ICache {
|
|||
) {
|
||||
}
|
||||
|
||||
|
||||
public function getNumericStorageId() {
|
||||
public function getNumericStorageId(): int {
|
||||
return -1;
|
||||
}
|
||||
|
||||
public function get($file) {
|
||||
public function get($file): false|ICacheEntry {
|
||||
if ($file === '') {
|
||||
return new CacheEntry([
|
||||
'fileid' => -1,
|
||||
|
|
@ -46,11 +45,11 @@ class FailedCache implements ICache {
|
|||
}
|
||||
}
|
||||
|
||||
public function getFolderContents($folder) {
|
||||
public function getFolderContents($folder): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getFolderContentsById($fileId) {
|
||||
public function getFolderContentsById(int $fileId, ?string $mimeTypeFilter = null): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
|
@ -63,15 +62,15 @@ class FailedCache implements ICache {
|
|||
public function update($id, array $data) {
|
||||
}
|
||||
|
||||
public function getId($file) {
|
||||
public function getId($file): int {
|
||||
return -1;
|
||||
}
|
||||
|
||||
public function getParentId($file) {
|
||||
public function getParentId($file): int {
|
||||
return -1;
|
||||
}
|
||||
|
||||
public function inCache($file) {
|
||||
public function inCache($file): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -102,9 +102,9 @@ class CacheWrapper extends Cache {
|
|||
* @param int $fileId the file id of the folder
|
||||
* @return array
|
||||
*/
|
||||
public function getFolderContentsById($fileId) {
|
||||
$results = $this->getCache()->getFolderContentsById($fileId);
|
||||
return array_map([$this, 'formatCacheEntry'], $results);
|
||||
public function getFolderContentsById(int $fileId, ?string $mimeTypeFilter = null) {
|
||||
$results = $this->getCache()->getFolderContentsById($fileId, $mimeTypeFilter);
|
||||
return array_map($this->formatCacheEntry(...), $results);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -663,14 +663,14 @@ class Filesystem {
|
|||
}
|
||||
|
||||
/**
|
||||
* get the content of a directory
|
||||
* Get the content of a directory.
|
||||
*
|
||||
* @param string $directory path under datadirectory
|
||||
* @param string $mimetype_filter limit returned content to this mimetype or mimepart
|
||||
* @param string $mimeTypeFilter limit returned content to this mimetype or mimepart
|
||||
* @return FileInfo[]
|
||||
*/
|
||||
public static function getDirectoryContent($directory, $mimetype_filter = '') {
|
||||
return self::$defaultInstance->getDirectoryContent($directory, $mimetype_filter);
|
||||
public static function getDirectoryContent($directory, string $mimeTypeFilter = '') {
|
||||
return self::$defaultInstance->getDirectoryContent($directory, $mimeTypeFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -76,16 +76,11 @@ class Folder extends Node implements IFolder {
|
|||
return str_starts_with($node->getPath(), $this->path . '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* get the content of this directory
|
||||
*
|
||||
* @return Node[]
|
||||
* @throws \OCP\Files\NotFoundException
|
||||
*/
|
||||
public function getDirectoryListing() {
|
||||
$folderContent = $this->view->getDirectoryContent($this->path, '', $this->getFileInfo(false));
|
||||
#[Override]
|
||||
public function getDirectoryListing(?string $mimetypeFilter = null): array {
|
||||
$folderContent = $this->view->getDirectoryContent($this->path, $mimetypeFilter, $this->getFileInfo(false));
|
||||
|
||||
return array_map(function (FileInfo $info) {
|
||||
return array_map(function (FileInfo $info): Node {
|
||||
if ($info->getMimetype() === FileInfo::MIMETYPE_FOLDER) {
|
||||
return new Folder($this->root, $this->view, $info->getPath(), $info, $this);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -415,10 +415,8 @@ class LazyFolder implements Folder {
|
|||
return $this->__call(__FUNCTION__, func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getDirectoryListing() {
|
||||
#[Override]
|
||||
public function getDirectoryListing(?string $mimetypeFilter = null): array {
|
||||
return $this->__call(__FUNCTION__, func_get_args());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
namespace OC\Files\Node;
|
||||
|
||||
use OCP\Files\NotFoundException;
|
||||
use Override;
|
||||
|
||||
class NonExistingFolder extends Folder {
|
||||
/**
|
||||
|
|
@ -118,7 +119,8 @@ class NonExistingFolder extends Folder {
|
|||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
public function getDirectoryListing() {
|
||||
#[Override]
|
||||
public function getDirectoryListing(?string $mimetypeFilter = null): never {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1481,15 +1481,20 @@ class View {
|
|||
* get the content of a directory
|
||||
*
|
||||
* @param string $directory path under datadirectory
|
||||
* @param string $mimetype_filter limit returned content to this mimetype or mimepart
|
||||
* @param ?non-empty-string $mimeTypeFilter limit returned content to this mimetype or mimepart
|
||||
* @return FileInfo[]
|
||||
*/
|
||||
public function getDirectoryContent($directory, $mimetype_filter = '', ?\OCP\Files\FileInfo $directoryInfo = null) {
|
||||
public function getDirectoryContent(string $directory, ?string $mimeTypeFilter = null, ?\OCP\Files\FileInfo $directoryInfo = null) {
|
||||
$this->assertPathLength($directory);
|
||||
if (!Filesystem::isValidPath($directory)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/** @psalm-suppress TypeDoesNotContainType For legacy compatibility */
|
||||
if ($mimeTypeFilter === '') {
|
||||
$mimeTypeFilter = null;
|
||||
}
|
||||
|
||||
$path = $this->getAbsolutePath($directory);
|
||||
$path = Filesystem::normalizePath($path);
|
||||
$mount = $this->getMount($directory);
|
||||
|
|
@ -1516,7 +1521,7 @@ class View {
|
|||
}
|
||||
|
||||
$folderId = $data->getId();
|
||||
$contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter
|
||||
$contents = $cache->getFolderContentsById($folderId, $mimeTypeFilter);
|
||||
|
||||
$sharingDisabled = \OCP\Util::isSharingDisabledForUser();
|
||||
$permissionsMask = ~\OCP\Constants::PERMISSION_SHARE;
|
||||
|
|
@ -1577,79 +1582,79 @@ class View {
|
|||
$rootEntry = $subCache->get('');
|
||||
}
|
||||
|
||||
if ($rootEntry && ($rootEntry->getPermissions() & Constants::PERMISSION_READ)) {
|
||||
$relativePath = trim(substr($mountPoint, $dirLength), '/');
|
||||
if ($pos = strpos($relativePath, '/')) {
|
||||
//mountpoint inside subfolder add size to the correct folder
|
||||
$entryName = substr($relativePath, 0, $pos);
|
||||
if (!$rootEntry || !($rootEntry->getPermissions() & Constants::PERMISSION_READ)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create parent folders if the mountpoint is inside a subfolder that doesn't exist yet
|
||||
if (!isset($files[$entryName])) {
|
||||
try {
|
||||
[$storage, ] = $this->resolvePath($path . '/' . $entryName);
|
||||
// make sure we can create the mountpoint folder, even if the user has a quota of 0
|
||||
if ($storage->instanceOfStorage(Quota::class)) {
|
||||
$storage->enableQuota(false);
|
||||
}
|
||||
|
||||
if ($this->mkdir($path . '/' . $entryName) !== false) {
|
||||
$info = $this->getFileInfo($path . '/' . $entryName);
|
||||
if ($info !== false) {
|
||||
$files[$entryName] = $info;
|
||||
}
|
||||
}
|
||||
|
||||
if ($storage->instanceOfStorage(Quota::class)) {
|
||||
$storage->enableQuota(true);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// Creating the parent folder might not be possible, for example due to a lack of permissions.
|
||||
$this->logger->debug('Failed to create non-existent parent', ['exception' => $e, 'path' => $path . '/' . $entryName]);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($files[$entryName])) {
|
||||
$files[$entryName]->addSubEntry($rootEntry, $mountPoint);
|
||||
}
|
||||
} else { //mountpoint in this folder, add an entry for it
|
||||
$rootEntry['name'] = $relativePath;
|
||||
$rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file';
|
||||
$permissions = $rootEntry['permissions'];
|
||||
// do not allow renaming/deleting the mount point if they are not shared files/folders
|
||||
// for shared files/folders we use the permissions given by the owner
|
||||
if ($mount instanceof MoveableMount) {
|
||||
$rootEntry['permissions'] = $permissions | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE;
|
||||
} else {
|
||||
$rootEntry['permissions'] = $permissions & (Constants::PERMISSION_ALL - (Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE));
|
||||
}
|
||||
|
||||
$rootEntry['path'] = substr(Filesystem::normalizePath($path . '/' . $rootEntry['name']), strlen($user) + 2); // full path without /$user/
|
||||
|
||||
// if sharing was disabled for the user we remove the share permissions
|
||||
if ($sharingDisabled) {
|
||||
$rootEntry['permissions'] = $rootEntry['permissions'] & ~Constants::PERMISSION_SHARE;
|
||||
}
|
||||
|
||||
$ownerId = $subStorage->getOwner('');
|
||||
if ($ownerId !== false) {
|
||||
$owner = $this->getUserObjectForOwner($ownerId);
|
||||
} else {
|
||||
$owner = null;
|
||||
}
|
||||
$files[$rootEntry->getName()] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner);
|
||||
if ($mimeTypeFilter !== null) {
|
||||
if (strpos($mimeTypeFilter, '/') !== false && $rootEntry['mimetype'] !== $mimeTypeFilter) {
|
||||
continue;
|
||||
} elseif (strpos($mimeTypeFilter, '/') === false && $rootEntry['mimepart'] !== $mimeTypeFilter) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($mimetype_filter) {
|
||||
$files = array_filter($files, function (FileInfo $file) use ($mimetype_filter) {
|
||||
if (strpos($mimetype_filter, '/')) {
|
||||
return $file->getMimetype() === $mimetype_filter;
|
||||
} else {
|
||||
return $file->getMimePart() === $mimetype_filter;
|
||||
$relativePath = trim(substr($mountPoint, $dirLength), '/');
|
||||
if ($pos = strpos($relativePath, '/')) {
|
||||
//mountpoint inside subfolder add size to the correct folder
|
||||
$entryName = substr($relativePath, 0, $pos);
|
||||
|
||||
// Create parent folders if the mountpoint is inside a subfolder that doesn't exist yet
|
||||
if (!isset($files[$entryName])) {
|
||||
try {
|
||||
[$storage, ] = $this->resolvePath($path . '/' . $entryName);
|
||||
// make sure we can create the mountpoint folder, even if the user has a quota of 0
|
||||
if ($storage->instanceOfStorage(Quota::class)) {
|
||||
$storage->enableQuota(false);
|
||||
}
|
||||
|
||||
if ($this->mkdir($path . '/' . $entryName) !== false) {
|
||||
$info = $this->getFileInfo($path . '/' . $entryName);
|
||||
if ($info !== false) {
|
||||
$files[$entryName] = $info;
|
||||
}
|
||||
}
|
||||
|
||||
if ($storage->instanceOfStorage(Quota::class)) {
|
||||
$storage->enableQuota(true);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// Creating the parent folder might not be possible, for example due to a lack of permissions.
|
||||
$this->logger->debug('Failed to create non-existent parent', ['exception' => $e, 'path' => $path . '/' . $entryName]);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($files[$entryName])) {
|
||||
$files[$entryName]->addSubEntry($rootEntry, $mountPoint);
|
||||
}
|
||||
} else { //mountpoint in this folder, add an entry for it
|
||||
$rootEntry['name'] = $relativePath;
|
||||
$rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file';
|
||||
$permissions = $rootEntry['permissions'];
|
||||
// do not allow renaming/deleting the mount point if they are not shared files/folders
|
||||
// for shared files/folders we use the permissions given by the owner
|
||||
if ($mount instanceof MoveableMount) {
|
||||
$rootEntry['permissions'] = $permissions | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE;
|
||||
} else {
|
||||
$rootEntry['permissions'] = $permissions & (Constants::PERMISSION_ALL - (Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE));
|
||||
}
|
||||
|
||||
$rootEntry['path'] = substr(Filesystem::normalizePath($path . '/' . $rootEntry['name']), strlen($user) + 2); // full path without /$user/
|
||||
|
||||
// if sharing was disabled for the user we remove the share permissions
|
||||
if ($sharingDisabled) {
|
||||
$rootEntry['permissions'] = $rootEntry['permissions'] & ~Constants::PERMISSION_SHARE;
|
||||
}
|
||||
|
||||
$ownerId = $subStorage->getOwner('');
|
||||
if ($ownerId !== false) {
|
||||
$owner = $this->getUserObjectForOwner($ownerId);
|
||||
} else {
|
||||
$owner = null;
|
||||
}
|
||||
$files[$rootEntry->getName()] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($files);
|
||||
|
|
|
|||
|
|
@ -20,11 +20,11 @@ use OCP\Files\Search\ISearchOperator;
|
|||
use OCP\Files\Search\ISearchQuery;
|
||||
|
||||
class NullCache implements ICache {
|
||||
public function getNumericStorageId() {
|
||||
public function getNumericStorageId(): int {
|
||||
return -1;
|
||||
}
|
||||
|
||||
public function get($file) {
|
||||
public function get($file): false|ICacheEntry {
|
||||
if ($file !== '') {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -44,23 +44,23 @@ class NullCache implements ICache {
|
|||
]);
|
||||
}
|
||||
|
||||
public function getFolderContents($folder) {
|
||||
public function getFolderContents($folder): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getFolderContentsById($fileId) {
|
||||
public function getFolderContentsById(int $fileId, ?string $mimeTypeFilter = null): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
public function put($file, array $data) {
|
||||
public function put($file, array $data): never {
|
||||
throw new ForbiddenException('This request is not allowed to access the filesystem');
|
||||
}
|
||||
|
||||
public function insert($file, array $data) {
|
||||
public function insert($file, array $data): never {
|
||||
throw new ForbiddenException('This request is not allowed to access the filesystem');
|
||||
}
|
||||
|
||||
public function update($id, array $data) {
|
||||
public function update($id, array $data): never {
|
||||
throw new ForbiddenException('This request is not allowed to access the filesystem');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,10 +77,12 @@ interface ICache {
|
|||
* Only returns files one level deep, no recursion
|
||||
*
|
||||
* @param int $fileId the file id of the folder
|
||||
* @param ?non-empty-string $mimeTypeFilter The mimetype or mimepart for which the content should be filtered
|
||||
* @return ICacheEntry[]
|
||||
* @since 9.0.0
|
||||
* @since 34.0.0 The $mimetypeFilter was added.
|
||||
*/
|
||||
public function getFolderContentsById($fileId);
|
||||
public function getFolderContentsById(int $fileId, ?string $mimeTypeFilter = null);
|
||||
|
||||
/**
|
||||
* store meta data for a file or folder
|
||||
|
|
|
|||
|
|
@ -46,13 +46,14 @@ interface Folder extends Node {
|
|||
public function isSubNode($node);
|
||||
|
||||
/**
|
||||
* get the content of this directory
|
||||
* Get the content of this directory.
|
||||
*
|
||||
* @param ?non-empty-string $mimetypeFilter Limit the returned content to this mimetype or mimepart
|
||||
* @throws \OCP\Files\NotFoundException
|
||||
* @return \OCP\Files\Node[]
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public function getDirectoryListing();
|
||||
public function getDirectoryListing(?string $mimetypeFilter = null): array;
|
||||
|
||||
/**
|
||||
* Get the node at $path
|
||||
|
|
|
|||
|
|
@ -248,7 +248,7 @@ class DecryptAllTest extends TestCase {
|
|||
->method('getDirectoryContent')
|
||||
->willReturnMap([
|
||||
[
|
||||
'/user1/files', '', null,
|
||||
'/user1/files', null, null,
|
||||
[
|
||||
new FileInfo('path', $storage, 'intPath', ['name' => 'foo', 'type' => 'dir'], null),
|
||||
new FileInfo('path', $storage, 'intPath', ['name' => 'bar', 'type' => 'file', 'encrypted' => true], null),
|
||||
|
|
@ -256,7 +256,7 @@ class DecryptAllTest extends TestCase {
|
|||
],
|
||||
],
|
||||
[
|
||||
'/user1/files/foo', '', null,
|
||||
'/user1/files/foo', null, null,
|
||||
[
|
||||
new FileInfo('path', $storage, 'intPath', ['name' => 'subfile', 'type' => 'file', 'encrypted' => true], null)
|
||||
],
|
||||
|
|
|
|||
Loading…
Reference in a new issue