optimize getById on LazyUserFolder to not require a full fs setup

Signed-off-by: Robin Appelman <robin@icewind.nl>
pull/31773/head
Robin Appelman 2022-03-28 18:47:17 +07:00
parent 700444e218
commit 44a8ebdc1f
No known key found for this signature in database
GPG Key ID: 42B69D8A64526EFB
6 changed files with 121 additions and 82 deletions

@ -333,70 +333,7 @@ class Folder extends Node implements \OCP\Files\Folder {
* @return \OC\Files\Node\Node[] * @return \OC\Files\Node\Node[]
*/ */
public function getById($id) { public function getById($id) {
$mountCache = $this->root->getUserMountCache(); return $this->root->getByIdInPath((int)$id, $this->getPath());
if (strpos($this->getPath(), '/', 1) > 0) {
[, $user] = explode('/', $this->getPath());
} else {
$user = null;
}
$mountsContainingFile = $mountCache->getMountsForFileId((int)$id, $user);
// when a user has access trough the same storage trough multiple paths
// (such as an external storage that is both mounted for a user and shared to the user)
// the mount cache will only hold a single entry for the storage
// this can lead to issues as the different ways the user has access to a storage can have different permissions
//
// so instead of using the cached entries directly, we instead filter the current mounts by the rootid of the cache entry
$mountRootIds = array_map(function ($mount) {
return $mount->getRootId();
}, $mountsContainingFile);
$mountRootPaths = array_map(function ($mount) {
return $mount->getRootInternalPath();
}, $mountsContainingFile);
$mountRoots = array_combine($mountRootIds, $mountRootPaths);
$mounts = $this->root->getMountsIn($this->path);
$mounts[] = $this->root->getMount($this->path);
$mountsContainingFile = array_filter($mounts, function ($mount) use ($mountRoots) {
return isset($mountRoots[$mount->getStorageRootId()]);
});
if (count($mountsContainingFile) === 0) {
if ($user === $this->getAppDataDirectoryName()) {
return $this->getByIdInRootMount((int)$id);
}
return [];
}
$nodes = array_map(function (IMountPoint $mount) use ($id, $mountRoots) {
$rootInternalPath = $mountRoots[$mount->getStorageRootId()];
$cacheEntry = $mount->getStorage()->getCache()->get((int)$id);
if (!$cacheEntry) {
return null;
}
// cache jails will hide the "true" internal path
$internalPath = ltrim($rootInternalPath . '/' . $cacheEntry->getPath(), '/');
$pathRelativeToMount = substr($internalPath, strlen($rootInternalPath));
$pathRelativeToMount = ltrim($pathRelativeToMount, '/');
$absolutePath = rtrim($mount->getMountPoint() . $pathRelativeToMount, '/');
return $this->root->createNode($absolutePath, new \OC\Files\FileInfo(
$absolutePath, $mount->getStorage(), $cacheEntry->getPath(), $cacheEntry, $mount,
\OC::$server->getUserManager()->get($mount->getStorage()->getOwner($pathRelativeToMount))
));
}, $mountsContainingFile);
$nodes = array_filter($nodes);
$folders = array_filter($nodes, function (Node $node) {
return $this->getRelativePath($node->getPath());
});
usort($folders, function ($a, $b) {
return $b->getPath() <=> $a->getPath();
});
return $folders;
} }
protected function getAppDataDirectoryName(): string { protected function getAppDataDirectoryName(): string {

@ -25,6 +25,7 @@ declare(strict_types=1);
*/ */
namespace OC\Files\Node; namespace OC\Files\Node;
use OC\Files\Utils\PathHelper;
use OCP\Constants; use OCP\Constants;
/** /**
@ -379,13 +380,6 @@ class LazyFolder implements \OCP\Files\Folder {
return $this->__call(__FUNCTION__, func_get_args()); return $this->__call(__FUNCTION__, func_get_args());
} }
/**
* @inheritDoc
*/
public function getRelativePath($path) {
return $this->__call(__FUNCTION__, func_get_args());
}
/** /**
* @inheritDoc * @inheritDoc
*/ */
@ -518,4 +512,8 @@ class LazyFolder implements \OCP\Files\Folder {
public function getUploadTime(): int { public function getUploadTime(): int {
return $this->__call(__FUNCTION__, func_get_args()); return $this->__call(__FUNCTION__, func_get_args());
} }
public function getRelativePath($path) {
return PathHelper::getRelativePath($this->getPath(), $path);
}
} }

@ -39,4 +39,8 @@ class LazyRoot extends LazyFolder implements IRootFolder {
public function getUserFolder($userId) { public function getUserFolder($userId) {
return $this->__call(__FUNCTION__, func_get_args()); return $this->__call(__FUNCTION__, func_get_args());
} }
public function getByIdInPath(int $id, string $path) {
return $this->__call(__FUNCTION__, func_get_args());
}
} }

@ -29,28 +29,38 @@ use OCP\Files\NotFoundException;
use OCP\IUser; use OCP\IUser;
class LazyUserFolder extends LazyFolder { class LazyUserFolder extends LazyFolder {
private IRootFolder $rootFolder; private IRootFolder $root;
private IUser $user; private IUser $user;
private string $path;
public function __construct(IRootFolder $rootFolder, IUser $user) { public function __construct(IRootFolder $rootFolder, IUser $user) {
$this->rootFolder = $rootFolder; $this->root = $rootFolder;
$this->user = $user; $this->user = $user;
$this->path = '/' . $user->getUID() . '/files';
parent::__construct(function () use ($user) { parent::__construct(function () use ($user) {
try { try {
return $this->rootFolder->get('/' . $user->getUID() . '/files'); return $this->root->get('/' . $user->getUID() . '/files');
} catch (NotFoundException $e) { } catch (NotFoundException $e) {
if (!$this->rootFolder->nodeExists('/' . $user->getUID())) { if (!$this->root->nodeExists('/' . $user->getUID())) {
$this->rootFolder->newFolder('/' . $user->getUID()); $this->root->newFolder('/' . $user->getUID());
} }
return $this->rootFolder->newFolder('/' . $user->getUID() . '/files'); return $this->root->newFolder('/' . $user->getUID() . '/files');
} }
}, [ }, [
'path' => '/' . $user->getUID() . '/files', 'path' => $this->path,
'permissions' => Constants::PERMISSION_ALL, 'permissions' => Constants::PERMISSION_ALL,
]); ]);
} }
public function get($path) { public function get($path) {
return $this->rootFolder->get('/' . $this->user->getUID() . '/files/' . rtrim($path, '/')); return $this->root->get('/' . $this->user->getUID() . '/files/' . ltrim($path, '/'));
}
/**
* @param int $id
* @return \OC\Files\Node\Node[]
*/
public function getById($id) {
return $this->root->getByIdInPath((int)$id, $this->getPath());
} }
} }

@ -44,6 +44,7 @@ use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Config\IUserMountCache; use OCP\Files\Config\IUserMountCache;
use OCP\Files\Events\Node\FilesystemTornDownEvent; use OCP\Files\Events\Node\FilesystemTornDownEvent;
use OCP\Files\IRootFolder; use OCP\Files\IRootFolder;
use OCP\Files\Mount\IMountPoint;
use OCP\Files\NotFoundException; use OCP\Files\NotFoundException;
use OCP\Files\NotPermittedException; use OCP\Files\NotPermittedException;
use OCP\IUser; use OCP\IUser;
@ -216,7 +217,7 @@ class Root extends Folder implements IRootFolder {
/** /**
* @param string $targetPath * @param string $targetPath
* @return \OC\Files\Node\Node * @return Node
* @throws \OCP\Files\NotPermittedException * @throws \OCP\Files\NotPermittedException
*/ */
public function rename($targetPath) { public function rename($targetPath) {
@ -229,7 +230,7 @@ class Root extends Folder implements IRootFolder {
/** /**
* @param string $targetPath * @param string $targetPath
* @return \OC\Files\Node\Node * @return Node
* @throws \OCP\Files\NotPermittedException * @throws \OCP\Files\NotPermittedException
*/ */
public function copy($targetPath) { public function copy($targetPath) {
@ -404,4 +405,82 @@ class Root extends Folder implements IRootFolder {
public function getUserMountCache() { public function getUserMountCache() {
return $this->userMountCache; return $this->userMountCache;
} }
/**
* @param int $id
* @return Node[]
*/
public function getByIdInPath(int $id, string $path): array {
$mountCache = $this->getUserMountCache();
if (strpos($path, '/', 1) > 0) {
[, $user] = explode('/', $path);
} else {
$user = null;
}
$mountsContainingFile = $mountCache->getMountsForFileId($id, $user);
// when a user has access trough the same storage trough multiple paths
// (such as an external storage that is both mounted for a user and shared to the user)
// the mount cache will only hold a single entry for the storage
// this can lead to issues as the different ways the user has access to a storage can have different permissions
//
// so instead of using the cached entries directly, we instead filter the current mounts by the rootid of the cache entry
$mountRootIds = array_map(function ($mount) {
return $mount->getRootId();
}, $mountsContainingFile);
$mountRootPaths = array_map(function ($mount) {
return $mount->getRootInternalPath();
}, $mountsContainingFile);
$mountProviders = array_unique(array_map(function ($mount) {
return $mount->getMountProvider();
}, $mountsContainingFile));
$mountRoots = array_combine($mountRootIds, $mountRootPaths);
$mounts = $this->mountManager->getMountsByMountProvider($path, $mountProviders);
$mountsContainingFile = array_filter($mounts, function ($mount) use ($mountRoots) {
return isset($mountRoots[$mount->getStorageRootId()]);
});
if (count($mountsContainingFile) === 0) {
if ($user === $this->getAppDataDirectoryName()) {
$folder = $this->get($path);
if ($folder instanceof Folder) {
return $folder->getByIdInRootMount($id);
} else {
throw new \Exception("getByIdInPath with non folder");
}
}
return [];
}
$nodes = array_map(function (IMountPoint $mount) use ($id, $mountRoots) {
$rootInternalPath = $mountRoots[$mount->getStorageRootId()];
$cacheEntry = $mount->getStorage()->getCache()->get($id);
if (!$cacheEntry) {
return null;
}
// cache jails will hide the "true" internal path
$internalPath = ltrim($rootInternalPath . '/' . $cacheEntry->getPath(), '/');
$pathRelativeToMount = substr($internalPath, strlen($rootInternalPath));
$pathRelativeToMount = ltrim($pathRelativeToMount, '/');
$absolutePath = rtrim($mount->getMountPoint() . $pathRelativeToMount, '/');
return $this->createNode($absolutePath, new FileInfo(
$absolutePath, $mount->getStorage(), $cacheEntry->getPath(), $cacheEntry, $mount,
\OC::$server->getUserManager()->get($mount->getStorage()->getOwner($pathRelativeToMount))
));
}, $mountsContainingFile);
$nodes = array_filter($nodes);
$folders = array_filter($nodes, function (Node $node) use ($path) {
return PathHelper::getRelativePath($path, $node->getPath()) !== null;
});
usort($folders, function ($a, $b) {
return $b->getPath() <=> $a->getPath();
});
return $folders;
}
} }

@ -38,11 +38,22 @@ interface IRootFolder extends Folder, Emitter {
* Returns a view to user's files folder * Returns a view to user's files folder
* *
* @param string $userId user ID * @param string $userId user ID
* @return \OCP\Files\Folder * @return Folder
* @throws NoUserException * @throws NoUserException
* @throws NotPermittedException * @throws NotPermittedException
* *
* @since 8.2.0 * @since 8.2.0
*/ */
public function getUserFolder($userId); public function getUserFolder($userId);
/**
* Get a file or folder by fileid, inside a parent path
*
* @param int $id
* @param string $path
* @return Node[]
*
* @since 24.0.0
*/
public function getByIdInPath(int $id, string $path);
} }