diff options
Diffstat (limited to 'lib/private/Files')
-rw-r--r-- | lib/private/Files/Cache/Storage.php | 17 | ||||
-rw-r--r-- | lib/private/Files/Cache/StorageGlobal.php | 32 | ||||
-rw-r--r-- | lib/private/Files/Config/MountProviderCollection.php | 4 | ||||
-rw-r--r-- | lib/private/Files/FileInfo.php | 2 | ||||
-rw-r--r-- | lib/private/Files/Mount/Manager.php | 19 | ||||
-rw-r--r-- | lib/private/Files/Node/Folder.php | 82 | ||||
-rw-r--r-- | lib/private/Files/Node/LazyFolder.php | 12 | ||||
-rw-r--r-- | lib/private/Files/Node/LazyRoot.php | 4 | ||||
-rw-r--r-- | lib/private/Files/Node/LazyUserFolder.php | 26 | ||||
-rw-r--r-- | lib/private/Files/Node/Node.php | 40 | ||||
-rw-r--r-- | lib/private/Files/Node/Root.php | 85 | ||||
-rw-r--r-- | lib/private/Files/SetupManager.php | 102 | ||||
-rw-r--r-- | lib/private/Files/Utils/PathHelper.php | 71 | ||||
-rw-r--r-- | lib/private/Files/View.php | 198 |
14 files changed, 441 insertions, 253 deletions
diff --git a/lib/private/Files/Cache/Storage.php b/lib/private/Files/Cache/Storage.php index 2de2c2f84d7..fb9e5500658 100644 --- a/lib/private/Files/Cache/Storage.php +++ b/lib/private/Files/Cache/Storage.php @@ -126,19 +126,9 @@ class Storage { * @param int $numericId * @return string|null either the storage id string or null if the numeric id is not known */ - public static function getStorageId($numericId) { - $query = \OC::$server->getDatabaseConnection()->getQueryBuilder(); - $query->select('id') - ->from('storages') - ->where($query->expr()->eq('numeric_id', $query->createNamedParameter($numericId))); - $result = $query->execute(); - $row = $result->fetch(); - $result->closeCursor(); - if ($row) { - return $row['id']; - } else { - return null; - } + public static function getStorageId(int $numericId): ?string { + $storage = self::getGlobalCache()->getStorageInfoByNumericId($numericId); + return $storage['id'] ?? null; } /** @@ -240,6 +230,7 @@ class Storage { ->from('mounts') ->where($query->expr()->eq('mount_id', $query->createNamedParameter($mountId, IQueryBuilder::PARAM_INT))); $storageIds = $query->executeQuery()->fetchAll(\PDO::FETCH_COLUMN); + $storageIds = array_unique($storageIds); $query = $db->getQueryBuilder(); $query->delete('filecache') diff --git a/lib/private/Files/Cache/StorageGlobal.php b/lib/private/Files/Cache/StorageGlobal.php index 7162f8e4908..a898c435415 100644 --- a/lib/private/Files/Cache/StorageGlobal.php +++ b/lib/private/Files/Cache/StorageGlobal.php @@ -41,8 +41,10 @@ class StorageGlobal { /** @var IDBConnection */ private $connection; - /** @var array[] */ + /** @var array<string, array> */ private $cache = []; + /** @var array<int, array> */ + private $numericIdCache = []; public function __construct(IDBConnection $connection) { $this->connection = $connection; @@ -68,7 +70,7 @@ class StorageGlobal { * @param string $storageId * @return array|null */ - public function getStorageInfo($storageId) { + public function getStorageInfo(string $storageId): ?array { if (!isset($this->cache[$storageId])) { $builder = $this->connection->getQueryBuilder(); $query = $builder->select(['id', 'numeric_id', 'available', 'last_checked']) @@ -81,9 +83,33 @@ class StorageGlobal { if ($row) { $this->cache[$storageId] = $row; + $this->numericIdCache[(int)$row['numeric_id']] = $row; } } - return isset($this->cache[$storageId]) ? $this->cache[$storageId] : null; + return $this->cache[$storageId] ?? null; + } + + /** + * @param int $numericId + * @return array|null + */ + public function getStorageInfoByNumericId(int $numericId): ?array { + if (!isset($this->numericIdCache[$numericId])) { + $builder = $this->connection->getQueryBuilder(); + $query = $builder->select(['id', 'numeric_id', 'available', 'last_checked']) + ->from('storages') + ->where($builder->expr()->eq('numeric_id', $builder->createNamedParameter($numericId))); + + $result = $query->execute(); + $row = $result->fetch(); + $result->closeCursor(); + + if ($row) { + $this->numericIdCache[$numericId] = $row; + $this->cache[$row['id']] = $row; + } + } + return $this->numericIdCache[$numericId] ?? null; } public function clearCache() { diff --git a/lib/private/Files/Config/MountProviderCollection.php b/lib/private/Files/Config/MountProviderCollection.php index 2b0acf7d69d..334fce15d9e 100644 --- a/lib/private/Files/Config/MountProviderCollection.php +++ b/lib/private/Files/Config/MountProviderCollection.php @@ -97,10 +97,10 @@ class MountProviderCollection implements IMountProviderCollection, Emitter { return $this->getUserMountsForProviders($user, $this->providers); } - public function getUserMountsForProviderClass(IUser $user, string $mountProviderClass): array { + public function getUserMountsForProviderClasses(IUser $user, array $mountProviderClasses): array { $providers = array_filter( $this->providers, - fn (IMountProvider $mountProvider) => (get_class($mountProvider) === $mountProviderClass) + fn (IMountProvider $mountProvider) => (in_array(get_class($mountProvider), $mountProviderClasses)) ); return $this->getUserMountsForProviders($user, $providers); } diff --git a/lib/private/Files/FileInfo.php b/lib/private/Files/FileInfo.php index 2f361fc051d..6389544184f 100644 --- a/lib/private/Files/FileInfo.php +++ b/lib/private/Files/FileInfo.php @@ -254,7 +254,7 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess { */ public function getType() { if (!isset($this->data['type'])) { - $this->data['type'] = ($this->getMimetype() === 'httpd/unix-directory') ? self::TYPE_FOLDER : self::TYPE_FILE; + $this->data['type'] = ($this->getMimetype() === self::MIMETYPE_FOLDER) ? self::TYPE_FOLDER : self::TYPE_FILE; } return $this->data['type']; } diff --git a/lib/private/Files/Mount/Manager.php b/lib/private/Files/Mount/Manager.php index ecd97760f17..69285018d17 100644 --- a/lib/private/Files/Mount/Manager.php +++ b/lib/private/Files/Mount/Manager.php @@ -158,7 +158,6 @@ class Manager implements IMountManager { * @return IMountPoint[] */ public function findByStorageId(string $id): array { - \OC_Util::setupFS(); if (\strlen($id) > 64) { $id = md5($id); } @@ -204,4 +203,22 @@ class Manager implements IMountManager { public function getSetupManager(): SetupManager { return $this->setupManager; } + + /** + * Return all mounts in a path from a specific mount provider + * + * @param string $path + * @param string[] $mountProviders + * @return MountPoint[] + */ + public function getMountsByMountProvider(string $path, array $mountProviders) { + $this->getSetupManager()->setupForProvider($path, $mountProviders); + if (in_array('', $mountProviders)) { + return $this->mounts; + } else { + return array_filter($this->mounts, function ($mount) use ($mountProviders) { + return in_array($mount->getMountProvider(), $mountProviders); + }); + } + } } diff --git a/lib/private/Files/Node/Folder.php b/lib/private/Files/Node/Folder.php index 400fd6bedcc..9c15f0edf41 100644 --- a/lib/private/Files/Node/Folder.php +++ b/lib/private/Files/Node/Folder.php @@ -36,6 +36,7 @@ use OC\Files\Cache\Wrapper\CacheJail; use OC\Files\Search\SearchComparison; use OC\Files\Search\SearchOrder; use OC\Files\Search\SearchQuery; +use OC\Files\Utils\PathHelper; use OCP\Files\Cache\ICacheEntry; use OCP\Files\FileInfo; use OCP\Files\Mount\IMountPoint; @@ -76,17 +77,7 @@ class Folder extends Node implements \OCP\Files\Folder { * @return string|null */ public function getRelativePath($path) { - if ($this->path === '' or $this->path === '/') { - return $this->normalizePath($path); - } - if ($path === $this->path) { - return '/'; - } elseif (strpos($path, $this->path . '/') !== 0) { - return null; - } else { - $path = substr($path, strlen($this->path)); - return $this->normalizePath($path); - } + return PathHelper::getRelativePath($this->getPath(), $path); } /** @@ -106,10 +97,10 @@ class Folder extends Node implements \OCP\Files\Folder { * @throws \OCP\Files\NotFoundException */ public function getDirectoryListing() { - $folderContent = $this->view->getDirectoryContent($this->path); + $folderContent = $this->view->getDirectoryContent($this->path, '', $this->getFileInfo()); return array_map(function (FileInfo $info) { - if ($info->getMimetype() === 'httpd/unix-directory') { + if ($info->getMimetype() === FileInfo::MIMETYPE_FOLDER) { return new Folder($this->root, $this->view, $info->getPath(), $info); } else { return new File($this->root, $this->view, $info->getPath(), $info); @@ -342,70 +333,7 @@ class Folder extends Node implements \OCP\Files\Folder { * @return \OC\Files\Node\Node[] */ public function getById($id) { - $mountCache = $this->root->getUserMountCache(); - 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; + return $this->root->getByIdInPath((int)$id, $this->getPath()); } protected function getAppDataDirectoryName(): string { diff --git a/lib/private/Files/Node/LazyFolder.php b/lib/private/Files/Node/LazyFolder.php index 45451e5c53c..7d5038e85a2 100644 --- a/lib/private/Files/Node/LazyFolder.php +++ b/lib/private/Files/Node/LazyFolder.php @@ -25,6 +25,7 @@ declare(strict_types=1); */ namespace OC\Files\Node; +use OC\Files\Utils\PathHelper; use OCP\Constants; /** @@ -382,13 +383,6 @@ class LazyFolder implements \OCP\Files\Folder { /** * @inheritDoc */ - public function getRelativePath($path) { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ public function isSubNode($node) { return $this->__call(__FUNCTION__, func_get_args()); } @@ -518,4 +512,8 @@ class LazyFolder implements \OCP\Files\Folder { public function getUploadTime(): int { return $this->__call(__FUNCTION__, func_get_args()); } + + public function getRelativePath($path) { + return PathHelper::getRelativePath($this->getPath(), $path); + } } diff --git a/lib/private/Files/Node/LazyRoot.php b/lib/private/Files/Node/LazyRoot.php index c4d61f653e4..c01b9fdbb83 100644 --- a/lib/private/Files/Node/LazyRoot.php +++ b/lib/private/Files/Node/LazyRoot.php @@ -39,4 +39,8 @@ class LazyRoot extends LazyFolder implements IRootFolder { public function getUserFolder($userId) { return $this->__call(__FUNCTION__, func_get_args()); } + + public function getByIdInPath(int $id, string $path) { + return $this->__call(__FUNCTION__, func_get_args()); + } } diff --git a/lib/private/Files/Node/LazyUserFolder.php b/lib/private/Files/Node/LazyUserFolder.php index 4c9e89ce233..d91759117c1 100644 --- a/lib/private/Files/Node/LazyUserFolder.php +++ b/lib/private/Files/Node/LazyUserFolder.php @@ -29,28 +29,38 @@ use OCP\Files\NotFoundException; use OCP\IUser; class LazyUserFolder extends LazyFolder { - private IRootFolder $rootFolder; + private IRootFolder $root; private IUser $user; + private string $path; public function __construct(IRootFolder $rootFolder, IUser $user) { - $this->rootFolder = $rootFolder; + $this->root = $rootFolder; $this->user = $user; + $this->path = '/' . $user->getUID() . '/files'; parent::__construct(function () use ($user) { try { - return $this->rootFolder->get('/' . $user->getUID() . '/files'); + return $this->root->get('/' . $user->getUID() . '/files'); } catch (NotFoundException $e) { - if (!$this->rootFolder->nodeExists('/' . $user->getUID())) { - $this->rootFolder->newFolder('/' . $user->getUID()); + if (!$this->root->nodeExists('/' . $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, ]); } 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()); } } diff --git a/lib/private/Files/Node/Node.php b/lib/private/Files/Node/Node.php index b939bfce945..c8975154059 100644 --- a/lib/private/Files/Node/Node.php +++ b/lib/private/Files/Node/Node.php @@ -31,6 +31,7 @@ namespace OC\Files\Node; use OC\Files\Filesystem; use OC\Files\Mount\MoveableMount; +use OC\Files\Utils\PathHelper; use OCP\Files\FileInfo; use OCP\Files\InvalidPathException; use OCP\Files\NotFoundException; @@ -153,12 +154,11 @@ class Node implements \OCP\Files\Node { } } - /** - * @return \OC\Files\Storage\Storage - * @throws \OCP\Files\NotFoundException - */ public function getStorage() { - [$storage,] = $this->view->resolvePath($this->path); + $storage = $this->getMountPoint()->getStorage(); + if (!$storage) { + throw new \Exception("No storage for node"); + } return $storage; } @@ -173,8 +173,7 @@ class Node implements \OCP\Files\Node { * @return string */ public function getInternalPath() { - [, $internalPath] = $this->view->resolvePath($this->path); - return $internalPath; + return $this->getFileInfo()->getInternalPath(); } /** @@ -298,23 +297,7 @@ class Node implements \OCP\Files\Node { * @return string */ protected function normalizePath($path) { - if ($path === '' or $path === '/') { - return '/'; - } - //no windows style slashes - $path = str_replace('\\', '/', $path); - //add leading slash - if ($path[0] !== '/') { - $path = '/' . $path; - } - //remove duplicate slashes - while (strpos($path, '//') !== false) { - $path = str_replace('//', '/', $path); - } - //remove trailing slash - $path = rtrim($path, '/'); - - return $path; + return PathHelper::normalizePath($path); } /** @@ -447,6 +430,15 @@ class Node implements \OCP\Files\Node { if (!$this->view->rename($this->path, $targetPath)) { throw new NotPermittedException('Could not move ' . $this->path . ' to ' . $targetPath); } + + $mountPoint = $this->getMountPoint(); + if ($mountPoint) { + // update the cached fileinfo with the new (internal) path + /** @var \OC\Files\FileInfo $oldFileInfo */ + $oldFileInfo = $this->getFileInfo(); + $this->fileInfo = new \OC\Files\FileInfo($targetPath, $oldFileInfo->getStorage(), $mountPoint->getInternalPath($targetPath), $oldFileInfo->getData(), $mountPoint, $oldFileInfo->getOwner()); + } + $targetNode = $this->root->get($targetPath); $this->sendHooks(['postRename'], [$this, $targetNode]); $this->sendHooks(['postWrite'], [$targetNode]); diff --git a/lib/private/Files/Node/Root.php b/lib/private/Files/Node/Root.php index a8b36ee7731..7592d4caf37 100644 --- a/lib/private/Files/Node/Root.php +++ b/lib/private/Files/Node/Root.php @@ -33,8 +33,10 @@ namespace OC\Files\Node; use OC\Cache\CappedMemoryCache; +use OC\Files\FileInfo; use OC\Files\Mount\Manager; use OC\Files\Mount\MountPoint; +use OC\Files\Utils\PathHelper; use OC\Files\View; use OC\Hooks\PublicEmitter; use OC\User\NoUserException; @@ -42,6 +44,7 @@ use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\Config\IUserMountCache; use OCP\Files\Events\Node\FilesystemTornDownEvent; use OCP\Files\IRootFolder; +use OCP\Files\Mount\IMountPoint; use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; use OCP\IUser; @@ -214,7 +217,7 @@ class Root extends Folder implements IRootFolder { /** * @param string $targetPath - * @return \OC\Files\Node\Node + * @return Node * @throws \OCP\Files\NotPermittedException */ public function rename($targetPath) { @@ -227,7 +230,7 @@ class Root extends Folder implements IRootFolder { /** * @param string $targetPath - * @return \OC\Files\Node\Node + * @return Node * @throws \OCP\Files\NotPermittedException */ public function copy($targetPath) { @@ -402,4 +405,82 @@ class Root extends Folder implements IRootFolder { public function getUserMountCache() { 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; + } } diff --git a/lib/private/Files/SetupManager.php b/lib/private/Files/SetupManager.php index da50983da32..d091b5c5e35 100644 --- a/lib/private/Files/SetupManager.php +++ b/lib/private/Files/SetupManager.php @@ -40,6 +40,7 @@ use OC_Util; use OCP\Constants; use OCP\Diagnostics\IEventLogger; use OCP\EventDispatcher\IEventDispatcher; +use OCP\Files\Config\IHomeMountProvider; use OCP\Files\Config\IMountProvider; use OCP\Files\Config\IUserMountCache; use OCP\Files\Events\InvalidateMountCacheEvent; @@ -263,6 +264,11 @@ class SetupManager { return strpos($mount->getMountPoint(), $userRoot) === 0; }); $this->userMountCache->registerMounts($user, $mounts); + + $cacheDuration = $this->config->getSystemValueInt('fs_mount_cache_duration', 5 * 60); + if ($cacheDuration > 0) { + $this->cache->set($user->getUID(), true, $cacheDuration); + } } /** @@ -321,25 +327,32 @@ class SetupManager { } /** - * Set up the filesystem for the specified path + * Get the user to setup for a path or `null` if the root needs to be setup + * + * @param string $path + * @return IUser|null */ - public function setupForPath(string $path, bool $includeChildren = false): void { + private function getUserForPath(string $path) { if (substr_count($path, '/') < 2) { if ($user = $this->userSession->getUser()) { - $this->setupForUser($user); + return $user; } else { - $this->setupRoot(); + return null; } - return; } elseif (strpos($path, '/appdata_' . \OC_Util::getInstanceId()) === 0 || strpos($path, '/files_external/') === 0) { - $this->setupRoot(); - return; + return null; } else { [, $userId] = explode('/', $path); } - $user = $this->userManager->get($userId); + return $this->userManager->get($userId); + } + /** + * Set up the filesystem for the specified path + */ + public function setupForPath(string $path, bool $includeChildren = false): void { + $user = $this->getUserForPath($path); if (!$user) { $this->setupRoot(); return; @@ -349,17 +362,14 @@ class SetupManager { return; } - // we perform a "cached" setup only after having done the full setup recently - // this is also used to trigger a full setup after handling events that are likely - // to change the available mounts - $cachedSetup = $this->cache->get($user->getUID()); - if (!$cachedSetup) { + if ($this->fullSetupRequired($user)) { $this->setupForUser($user); + return; + } - $cacheDuration = $this->config->getSystemValueInt('fs_mount_cache_duration', 5 * 60); - if ($cacheDuration > 0) { - $this->cache->set($user->getUID(), true, $cacheDuration); - } + // for the user's home folder, it's always the home mount + if (rtrim($path) === "/" . $user->getUID() . "/files" && !$includeChildren) { + $this->oneTimeUserSetup($user); return; } @@ -381,7 +391,7 @@ class SetupManager { $setupProviders[] = $cachedMount->getMountProvider(); $currentProviders[] = $cachedMount->getMountProvider(); if ($cachedMount->getMountProvider()) { - $mounts = $this->mountProviderCollection->getUserMountsForProviderClass($user, $cachedMount->getMountProvider()); + $mounts = $this->mountProviderCollection->getUserMountsForProviderClasses($user, [$cachedMount->getMountProvider()]); } else { $this->logger->debug("mount at " . $cachedMount->getMountPoint() . " has no provider set, performing full setup"); $this->setupForUser($user); @@ -396,7 +406,7 @@ class SetupManager { $setupProviders[] = $cachedMount->getMountProvider(); $currentProviders[] = $cachedMount->getMountProvider(); if ($cachedMount->getMountProvider()) { - $mounts = array_merge($mounts, $this->mountProviderCollection->getUserMountsForProviderClass($user, $cachedMount->getMountProvider())); + $mounts = array_merge($mounts, $this->mountProviderCollection->getUserMountsForProviderClasses($user, [$cachedMount->getMountProvider()])); } else { $this->logger->debug("mount at " . $cachedMount->getMountPoint() . " has no provider set, performing full setup"); $this->setupForUser($user); @@ -416,6 +426,60 @@ class SetupManager { } } + private function fullSetupRequired(IUser $user): bool { + // we perform a "cached" setup only after having done the full setup recently + // this is also used to trigger a full setup after handling events that are likely + // to change the available mounts + return !$this->cache->get($user->getUID()); + } + + /** + * @param string $path + * @param string[] $providers + */ + public function setupForProvider(string $path, array $providers): void { + $user = $this->getUserForPath($path); + if (!$user) { + $this->setupRoot(); + return; + } + + if ($this->isSetupComplete($user)) { + return; + } + + if ($this->fullSetupRequired($user)) { + $this->setupForUser($user); + return; + } + + // home providers are always used + $providers = array_filter($providers, function (string $provider) { + return !is_subclass_of($provider, IHomeMountProvider::class); + }); + + if (in_array('', $providers)) { + $this->setupForUser($user); + } + $setupProviders = $this->setupUserMountProviders[$user->getUID()] ?? []; + + $providers = array_diff($providers, $setupProviders); + if (count($providers) === 0) { + if (!$this->isSetupStarted($user)) { + $this->oneTimeUserSetup($user); + } + return; + } else { + $this->setupUserMountProviders[$user->getUID()] = array_merge($setupProviders, $providers); + $mounts = $this->mountProviderCollection->getUserMountsForProviderClasses($user, $providers); + } + + $this->userMountCache->registerMounts($user, $mounts, $providers); + $this->setupForUserWith($user, function () use ($mounts) { + array_walk($mounts, [$this->mountManager, 'addMount']); + }); + } + public function tearDown() { $this->setupUsers = []; $this->setupUsersComplete = []; diff --git a/lib/private/Files/Utils/PathHelper.php b/lib/private/Files/Utils/PathHelper.php new file mode 100644 index 00000000000..07985e884ce --- /dev/null +++ b/lib/private/Files/Utils/PathHelper.php @@ -0,0 +1,71 @@ +<?php + +declare(strict_types=1); +/** + * @copyright Copyright (c) 2022 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Files\Utils; + +class PathHelper { + /** + * Make a path relative to a root path, or return null if the path is outside the root + * + * @param string $root + * @param string $path + * @return ?string + */ + public static function getRelativePath(string $root, string $path) { + if ($root === '' or $root === '/') { + return self::normalizePath($path); + } + if ($path === $root) { + return '/'; + } elseif (strpos($path, $root . '/') !== 0) { + return null; + } else { + $path = substr($path, strlen($root)); + return self::normalizePath($path); + } + } + + /** + * @param string $path + * @return string + */ + public static function normalizePath(string $path): string { + if ($path === '' or $path === '/') { + return '/'; + } + //no windows style slashes + $path = str_replace('\\', '/', $path); + //add leading slash + if ($path[0] !== '/') { + $path = '/' . $path; + } + //remove duplicate slashes + while (strpos($path, '//') !== false) { + $path = str_replace('//', '/', $path); + } + //remove trailing slash + $path = rtrim($path, '/'); + + return $path; + } +} diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php index 779e0611591..30dc5518be8 100644 --- a/lib/private/Files/View.php +++ b/lib/private/Files/View.php @@ -1099,6 +1099,7 @@ class View { [Filesystem::signal_param_path => $this->getHookPath($path)] ); } + /** @var Storage|null $storage */ [$storage, $internalPath] = Filesystem::resolvePath($absolutePath . $postFix); if ($storage) { return $storage->hash($type, $internalPath, $raw); @@ -1179,7 +1180,7 @@ class View { throw $e; } - if ($result && in_array('delete', $hooks) and $result) { + if ($result && in_array('delete', $hooks)) { $this->removeUpdate($storage, $internalPath); } if ($result && in_array('write', $hooks, true) && $operation !== 'fopen' && $operation !== 'touch') { @@ -1430,129 +1431,134 @@ class View { * @param string $mimetype_filter limit returned content to this mimetype or mimepart * @return FileInfo[] */ - public function getDirectoryContent($directory, $mimetype_filter = '') { + public function getDirectoryContent($directory, $mimetype_filter = '', \OCP\Files\FileInfo $directoryInfo = null) { $this->assertPathLength($directory); if (!Filesystem::isValidPath($directory)) { return []; } + $path = $this->getAbsolutePath($directory); $path = Filesystem::normalizePath($path); $mount = $this->getMount($directory); - if (!$mount) { - return []; - } $storage = $mount->getStorage(); $internalPath = $mount->getInternalPath($path); - if ($storage) { - $cache = $storage->getCache($internalPath); - $user = \OC_User::getUser(); + if (!$storage) { + return []; + } - $data = $this->getCacheEntry($storage, $internalPath, $directory); + $cache = $storage->getCache($internalPath); + $user = \OC_User::getUser(); - if (!$data instanceof ICacheEntry || !isset($data['fileid']) || !($data->getPermissions() && Constants::PERMISSION_READ)) { + if (!$directoryInfo) { + $data = $this->getCacheEntry($storage, $internalPath, $directory); + if (!$data instanceof ICacheEntry || !isset($data['fileid'])) { return []; } + } else { + $data = $directoryInfo; + } - $folderId = $data['fileid']; - $contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter + if (!($data->getPermissions() & Constants::PERMISSION_READ)) { + return []; + } - $sharingDisabled = \OCP\Util::isSharingDisabledForUser(); + $folderId = $data->getId(); + $contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter - $fileNames = array_map(function (ICacheEntry $content) { - return $content->getName(); - }, $contents); - /** - * @var \OC\Files\FileInfo[] $fileInfos - */ - $fileInfos = array_map(function (ICacheEntry $content) use ($path, $storage, $mount, $sharingDisabled) { - if ($sharingDisabled) { - $content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE; - } - $owner = $this->getUserObjectForOwner($storage->getOwner($content['path'])); - return new FileInfo($path . '/' . $content['name'], $storage, $content['path'], $content, $mount, $owner); - }, $contents); - $files = array_combine($fileNames, $fileInfos); - - //add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders - $mounts = Filesystem::getMountManager()->findIn($path); - $dirLength = strlen($path); - foreach ($mounts as $mount) { - $mountPoint = $mount->getMountPoint(); - $subStorage = $mount->getStorage(); - if ($subStorage) { - $subCache = $subStorage->getCache(''); + $sharingDisabled = \OCP\Util::isSharingDisabledForUser(); - $rootEntry = $subCache->get(''); - if (!$rootEntry) { - $subScanner = $subStorage->getScanner(); - try { - $subScanner->scanFile(''); - } catch (\OCP\Files\StorageNotAvailableException $e) { - continue; - } catch (\OCP\Files\StorageInvalidException $e) { - continue; - } catch (\Exception $e) { - // sometimes when the storage is not available it can be any exception - \OC::$server->getLogger()->logException($e, [ - 'message' => 'Exception while scanning storage "' . $subStorage->getId() . '"', - 'level' => ILogger::ERROR, - 'app' => 'lib', - ]); - continue; - } - $rootEntry = $subCache->get(''); + $fileNames = array_map(function (ICacheEntry $content) { + return $content->getName(); + }, $contents); + /** + * @var \OC\Files\FileInfo[] $fileInfos + */ + $fileInfos = array_map(function (ICacheEntry $content) use ($path, $storage, $mount, $sharingDisabled) { + if ($sharingDisabled) { + $content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE; + } + $owner = $this->getUserObjectForOwner($storage->getOwner($content['path'])); + return new FileInfo($path . '/' . $content['name'], $storage, $content['path'], $content, $mount, $owner); + }, $contents); + $files = array_combine($fileNames, $fileInfos); + + //add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders + $mounts = Filesystem::getMountManager()->findIn($path); + $dirLength = strlen($path); + foreach ($mounts as $mount) { + $mountPoint = $mount->getMountPoint(); + $subStorage = $mount->getStorage(); + if ($subStorage) { + $subCache = $subStorage->getCache(''); + + $rootEntry = $subCache->get(''); + if (!$rootEntry) { + $subScanner = $subStorage->getScanner(); + try { + $subScanner->scanFile(''); + } catch (\OCP\Files\StorageNotAvailableException $e) { + continue; + } catch (\OCP\Files\StorageInvalidException $e) { + continue; + } catch (\Exception $e) { + // sometimes when the storage is not available it can be any exception + \OC::$server->getLogger()->logException($e, [ + 'message' => 'Exception while scanning storage "' . $subStorage->getId() . '"', + 'level' => ILogger::ERROR, + 'app' => 'lib', + ]); + continue; } + $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); - foreach ($files as &$entry) { - if ($entry->getName() === $entryName) { - $entry->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 | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE; - } else { - $rootEntry['permissions'] = $permissions & (\OCP\Constants::PERMISSION_ALL - (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE)); + 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); + foreach ($files as &$entry) { + if ($entry->getName() === $entryName) { + $entry->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 | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE; + } else { + $rootEntry['permissions'] = $permissions & (\OCP\Constants::PERMISSION_ALL - (\OCP\Constants::PERMISSION_UPDATE | \OCP\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 (\OCP\Util::isSharingDisabledForUser()) { - $rootEntry['permissions'] = $rootEntry['permissions'] & ~\OCP\Constants::PERMISSION_SHARE; - } + $rootEntry['path'] = substr(Filesystem::normalizePath($path . '/' . $rootEntry['name']), strlen($user) + 2); // full path without /$user/ - $owner = $this->getUserObjectForOwner($subStorage->getOwner('')); - $files[$rootEntry->getName()] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner); + // if sharing was disabled for the user we remove the share permissions + if (\OCP\Util::isSharingDisabledForUser()) { + $rootEntry['permissions'] = $rootEntry['permissions'] & ~\OCP\Constants::PERMISSION_SHARE; } - } - } - } - 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; + $owner = $this->getUserObjectForOwner($subStorage->getOwner('')); + $files[$rootEntry->getName()] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner); } - }); + } } + } - return array_values($files); - } else { - return []; + 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; + } + }); } + + return array_values($files); } /** |