summaryrefslogtreecommitdiffstats
path: root/lib/private/Files
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private/Files')
-rw-r--r--lib/private/Files/Cache/Storage.php17
-rw-r--r--lib/private/Files/Cache/StorageGlobal.php32
-rw-r--r--lib/private/Files/Config/MountProviderCollection.php4
-rw-r--r--lib/private/Files/FileInfo.php2
-rw-r--r--lib/private/Files/Mount/Manager.php19
-rw-r--r--lib/private/Files/Node/Folder.php82
-rw-r--r--lib/private/Files/Node/LazyFolder.php12
-rw-r--r--lib/private/Files/Node/LazyRoot.php4
-rw-r--r--lib/private/Files/Node/LazyUserFolder.php26
-rw-r--r--lib/private/Files/Node/Node.php40
-rw-r--r--lib/private/Files/Node/Root.php85
-rw-r--r--lib/private/Files/SetupManager.php102
-rw-r--r--lib/private/Files/Utils/PathHelper.php71
-rw-r--r--lib/private/Files/View.php198
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);
}
/**