aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/Files
diff options
context:
space:
mode:
authorRobin Appelman <robin@icewind.nl>2024-01-31 19:42:58 +0100
committerGitHub <noreply@github.com>2024-01-31 19:42:58 +0100
commit3066687c7da2835e8b14e5b73e3a9a0987f236c7 (patch)
tree1bf26bebd26650910f2ddc8617bb9c9c6a5ea6ae /lib/private/Files
parenta8e6d89d3be51296199055df1539714e5a4203ef (diff)
parentaff861f4e78d7ed668bb07eaf0cc065446569e3c (diff)
downloadnextcloud-server-3066687c7da2835e8b14e5b73e3a9a0987f236c7.tar.gz
nextcloud-server-3066687c7da2835e8b14e5b73e3a9a0987f236c7.zip
Merge branch 'master' into background-scan-catch-storage-error
Diffstat (limited to 'lib/private/Files')
-rw-r--r--lib/private/Files/AppData/AppData.php6
-rw-r--r--lib/private/Files/AppData/Factory.php2
-rw-r--r--lib/private/Files/Cache/Cache.php26
-rw-r--r--lib/private/Files/Cache/CacheQueryBuilder.php27
-rw-r--r--lib/private/Files/Cache/QuerySearchHelper.php79
-rw-r--r--lib/private/Files/Cache/Scanner.php42
-rw-r--r--lib/private/Files/Cache/SearchBuilder.php107
-rw-r--r--lib/private/Files/Cache/Updater.php26
-rw-r--r--lib/private/Files/Cache/Watcher.php2
-rw-r--r--lib/private/Files/Cache/Wrapper/CacheJail.php2
-rw-r--r--lib/private/Files/Config/CachedMountInfo.php6
-rw-r--r--lib/private/Files/Config/LazyStorageMountInfo.php8
-rw-r--r--lib/private/Files/Config/MountProviderCollection.php5
-rw-r--r--lib/private/Files/Config/UserMountCache.php106
-rw-r--r--lib/private/Files/FileInfo.php44
-rw-r--r--lib/private/Files/Filesystem.php2
-rw-r--r--lib/private/Files/Mount/HomeMountPoint.php49
-rw-r--r--lib/private/Files/Mount/LocalHomeMountProvider.php2
-rw-r--r--lib/private/Files/Mount/Manager.php32
-rw-r--r--lib/private/Files/Mount/ObjectHomeMountProvider.php2
-rw-r--r--lib/private/Files/Node/Folder.php2
-rw-r--r--lib/private/Files/Node/HookConnector.php4
-rw-r--r--lib/private/Files/Node/LazyFolder.php11
-rw-r--r--lib/private/Files/Node/LazyUserFolder.php6
-rw-r--r--lib/private/Files/Node/Node.php20
-rw-r--r--lib/private/Files/Node/Root.php2
-rw-r--r--lib/private/Files/ObjectStore/ObjectStoreScanner.php2
-rw-r--r--lib/private/Files/ObjectStore/ObjectStoreStorage.php7
-rw-r--r--lib/private/Files/ObjectStore/S3ConnectionTrait.php7
-rw-r--r--lib/private/Files/ObjectStore/S3ObjectTrait.php34
-rw-r--r--lib/private/Files/Search/QueryOptimizer/PathPrefixOptimizer.php7
-rw-r--r--lib/private/Files/Search/SearchComparison.php44
-rw-r--r--lib/private/Files/Search/SearchOrder.php30
-rw-r--r--lib/private/Files/SetupManager.php41
-rw-r--r--lib/private/Files/SimpleFS/SimpleFolder.php2
-rw-r--r--lib/private/Files/Storage/DAV.php4
-rw-r--r--lib/private/Files/Storage/Local.php27
-rw-r--r--lib/private/Files/Storage/Wrapper/Encoding.php2
-rw-r--r--lib/private/Files/Storage/Wrapper/Jail.php5
-rw-r--r--lib/private/Files/Stream/Encryption.php24
-rw-r--r--lib/private/Files/Template/TemplateManager.php7
-rw-r--r--lib/private/Files/Type/Detection.php10
-rw-r--r--lib/private/Files/Utils/Scanner.php4
-rw-r--r--lib/private/Files/View.php14
44 files changed, 593 insertions, 298 deletions
diff --git a/lib/private/Files/AppData/AppData.php b/lib/private/Files/AppData/AppData.php
index 237fcb42e03..1c632c3062f 100644
--- a/lib/private/Files/AppData/AppData.php
+++ b/lib/private/Files/AppData/AppData.php
@@ -26,9 +26,9 @@ declare(strict_types=1);
*/
namespace OC\Files\AppData;
-use OCP\Cache\CappedMemoryCache;
use OC\Files\SimpleFS\SimpleFolder;
use OC\SystemConfig;
+use OCP\Cache\CappedMemoryCache;
use OCP\Files\Folder;
use OCP\Files\IAppData;
use OCP\Files\IRootFolder;
@@ -53,8 +53,8 @@ class AppData implements IAppData {
* @param string $appId
*/
public function __construct(IRootFolder $rootFolder,
- SystemConfig $systemConfig,
- string $appId) {
+ SystemConfig $systemConfig,
+ string $appId) {
$this->rootFolder = $rootFolder;
$this->config = $systemConfig;
$this->appId = $appId;
diff --git a/lib/private/Files/AppData/Factory.php b/lib/private/Files/AppData/Factory.php
index 03f8fdedcbd..a16c3df327d 100644
--- a/lib/private/Files/AppData/Factory.php
+++ b/lib/private/Files/AppData/Factory.php
@@ -39,7 +39,7 @@ class Factory implements IAppDataFactory {
private array $folders = [];
public function __construct(IRootFolder $rootFolder,
- SystemConfig $systemConfig) {
+ SystemConfig $systemConfig) {
$this->rootFolder = $rootFolder;
$this->config = $systemConfig;
}
diff --git a/lib/private/Files/Cache/Cache.php b/lib/private/Files/Cache/Cache.php
index 67d01bb6999..052b3c75ce8 100644
--- a/lib/private/Files/Cache/Cache.php
+++ b/lib/private/Files/Cache/Cache.php
@@ -47,9 +47,9 @@ use OC\Files\Storage\Wrapper\Encryption;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Cache\CacheEntryInsertedEvent;
+use OCP\Files\Cache\CacheEntryRemovedEvent;
use OCP\Files\Cache\CacheEntryUpdatedEvent;
use OCP\Files\Cache\CacheInsertEvent;
-use OCP\Files\Cache\CacheEntryRemovedEvent;
use OCP\Files\Cache\CacheUpdateEvent;
use OCP\Files\Cache\ICache;
use OCP\Files\Cache\ICacheEntry;
@@ -59,6 +59,7 @@ use OCP\Files\Search\ISearchComparison;
use OCP\Files\Search\ISearchOperator;
use OCP\Files\Search\ISearchQuery;
use OCP\Files\Storage\IStorage;
+use OCP\FilesMetadata\IFilesMetadataManager;
use OCP\IDBConnection;
use OCP\Util;
use Psr\Log\LoggerInterface;
@@ -132,7 +133,8 @@ class Cache implements ICache {
return new CacheQueryBuilder(
$this->connection,
\OC::$server->getSystemConfig(),
- \OC::$server->get(LoggerInterface::class)
+ \OC::$server->get(LoggerInterface::class),
+ \OC::$server->get(IFilesMetadataManager::class),
);
}
@@ -154,6 +156,7 @@ class Cache implements ICache {
public function get($file) {
$query = $this->getQueryBuilder();
$query->selectFileCache();
+ $metadataQuery = $query->selectMetadata();
if (is_string($file) || $file == '') {
// normalize file
@@ -175,6 +178,7 @@ class Cache implements ICache {
} elseif (!$data) {
return $data;
} else {
+ $data['metadata'] = $metadataQuery?->extractMetadata($data)->asArray() ?? [];
return self::cacheEntryFromData($data, $this->mimetypeLoader);
}
}
@@ -239,11 +243,14 @@ class Cache implements ICache {
->whereParent($fileId)
->orderBy('name', 'ASC');
+ $metadataQuery = $query->selectMetadata();
+
$result = $query->execute();
$files = $result->fetchAll();
$result->closeCursor();
- return array_map(function (array $data) {
+ return array_map(function (array $data) use ($metadataQuery) {
+ $data['metadata'] = $metadataQuery?->extractMetadata($data)->asArray() ?? [];
return self::cacheEntryFromData($data, $this->mimetypeLoader);
}, $files);
}
@@ -447,7 +454,7 @@ class Cache implements ICache {
$params = [];
$extensionParams = [];
foreach ($data as $name => $value) {
- if (array_search($name, $fields) !== false) {
+ if (in_array($name, $fields)) {
if ($name === 'path') {
$params['path_hash'] = md5($value);
} elseif ($name === 'mimetype') {
@@ -467,7 +474,7 @@ class Cache implements ICache {
}
$params[$name] = $value;
}
- if (array_search($name, $extensionFields) !== false) {
+ if (in_array($name, $extensionFields)) {
$extensionParams[$name] = $value;
}
}
@@ -599,9 +606,12 @@ class Cache implements ICache {
}
/** @var ICacheEntry[] $childFolders */
- $childFolders = array_filter($children, function ($child) {
- return $child->getMimeType() == FileInfo::MIMETYPE_FOLDER;
- });
+ $childFolders = [];
+ foreach ($children as $child) {
+ if ($child->getMimeType() == FileInfo::MIMETYPE_FOLDER) {
+ $childFolders[] = $child;
+ }
+ }
foreach ($childFolders as $folder) {
$parentIds[] = $folder->getId();
$queue[] = $folder->getId();
diff --git a/lib/private/Files/Cache/CacheQueryBuilder.php b/lib/private/Files/Cache/CacheQueryBuilder.php
index 34d2177b84e..365d28fc8c5 100644
--- a/lib/private/Files/Cache/CacheQueryBuilder.php
+++ b/lib/private/Files/Cache/CacheQueryBuilder.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Robin Appelman <robin@icewind.nl>
*
+ * @author Maxence Lange <maxence@artificial-owl.com>
* @author Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
@@ -28,6 +29,8 @@ namespace OC\Files\Cache;
use OC\DB\QueryBuilder\QueryBuilder;
use OC\SystemConfig;
use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\FilesMetadata\IFilesMetadataManager;
+use OCP\FilesMetadata\IMetadataQuery;
use OCP\IDBConnection;
use Psr\Log\LoggerInterface;
@@ -35,9 +38,14 @@ use Psr\Log\LoggerInterface;
* Query builder with commonly used helpers for filecache queries
*/
class CacheQueryBuilder extends QueryBuilder {
- private $alias = null;
-
- public function __construct(IDBConnection $connection, SystemConfig $systemConfig, LoggerInterface $logger) {
+ private ?string $alias = null;
+
+ public function __construct(
+ IDBConnection $connection,
+ SystemConfig $systemConfig,
+ LoggerInterface $logger,
+ private IFilesMetadataManager $filesMetadataManager,
+ ) {
parent::__construct($connection, $systemConfig, $logger);
}
@@ -63,7 +71,7 @@ class CacheQueryBuilder extends QueryBuilder {
public function selectFileCache(string $alias = null, bool $joinExtendedCache = true) {
$name = $alias ?: 'filecache';
$this->select("$name.fileid", 'storage', 'path', 'path_hash', "$name.parent", "$name.name", 'mimetype', 'mimepart', 'size', 'mtime',
- 'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum', 'unencrypted_size')
+ 'storage_mtime', 'encrypted', 'etag', "$name.permissions", 'checksum', 'unencrypted_size')
->from('filecache', $name);
if ($joinExtendedCache) {
@@ -126,4 +134,15 @@ class CacheQueryBuilder extends QueryBuilder {
return $this;
}
+
+ /**
+ * join metadata to current query builder and returns an helper
+ *
+ * @return IMetadataQuery|null NULL if no metadata have never been generated
+ */
+ public function selectMetadata(): ?IMetadataQuery {
+ $metadataQuery = $this->filesMetadataManager->getMetadataQuery($this, $this->alias, 'fileid');
+ $metadataQuery?->retrieveMetadata();
+ return $metadataQuery;
+ }
}
diff --git a/lib/private/Files/Cache/QuerySearchHelper.php b/lib/private/Files/Cache/QuerySearchHelper.php
index 15c089a0f11..d8c5e66e129 100644
--- a/lib/private/Files/Cache/QuerySearchHelper.php
+++ b/lib/private/Files/Cache/QuerySearchHelper.php
@@ -3,6 +3,7 @@
* @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
+ * @author Maxence Lange <maxence@artificial-owl.com>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Tobias Kaminsky <tobias@kaminsky.me>
@@ -37,52 +38,47 @@ use OCP\Files\IRootFolder;
use OCP\Files\Mount\IMountPoint;
use OCP\Files\Search\ISearchBinaryOperator;
use OCP\Files\Search\ISearchQuery;
+use OCP\FilesMetadata\IFilesMetadataManager;
+use OCP\FilesMetadata\IMetadataQuery;
use OCP\IDBConnection;
use OCP\IGroupManager;
use OCP\IUser;
use Psr\Log\LoggerInterface;
class QuerySearchHelper {
- /** @var IMimeTypeLoader */
- private $mimetypeLoader;
- /** @var IDBConnection */
- private $connection;
- /** @var SystemConfig */
- private $systemConfig;
- private LoggerInterface $logger;
- /** @var SearchBuilder */
- private $searchBuilder;
- /** @var QueryOptimizer */
- private $queryOptimizer;
- private IGroupManager $groupManager;
-
public function __construct(
- IMimeTypeLoader $mimetypeLoader,
- IDBConnection $connection,
- SystemConfig $systemConfig,
- LoggerInterface $logger,
- SearchBuilder $searchBuilder,
- QueryOptimizer $queryOptimizer,
- IGroupManager $groupManager,
+ private IMimeTypeLoader $mimetypeLoader,
+ private IDBConnection $connection,
+ private SystemConfig $systemConfig,
+ private LoggerInterface $logger,
+ private SearchBuilder $searchBuilder,
+ private QueryOptimizer $queryOptimizer,
+ private IGroupManager $groupManager,
+ private IFilesMetadataManager $filesMetadataManager,
) {
- $this->mimetypeLoader = $mimetypeLoader;
- $this->connection = $connection;
- $this->systemConfig = $systemConfig;
- $this->logger = $logger;
- $this->searchBuilder = $searchBuilder;
- $this->queryOptimizer = $queryOptimizer;
- $this->groupManager = $groupManager;
}
protected function getQueryBuilder() {
return new CacheQueryBuilder(
$this->connection,
$this->systemConfig,
- $this->logger
+ $this->logger,
+ $this->filesMetadataManager,
);
}
- protected function applySearchConstraints(CacheQueryBuilder $query, ISearchQuery $searchQuery, array $caches): void {
+ /**
+ * @param CacheQueryBuilder $query
+ * @param ISearchQuery $searchQuery
+ * @param array $caches
+ * @param IMetadataQuery|null $metadataQuery
+ */
+ protected function applySearchConstraints(
+ CacheQueryBuilder $query,
+ ISearchQuery $searchQuery,
+ array $caches,
+ ?IMetadataQuery $metadataQuery = null
+ ): void {
$storageFilters = array_values(array_map(function (ICache $cache) {
return $cache->getQueryFilterForStorage();
}, $caches));
@@ -90,12 +86,12 @@ class QuerySearchHelper {
$filter = new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_AND, [$searchQuery->getSearchOperation(), $storageFilter]);
$this->queryOptimizer->processOperator($filter);
- $searchExpr = $this->searchBuilder->searchOperatorToDBExpr($query, $filter);
+ $searchExpr = $this->searchBuilder->searchOperatorToDBExpr($query, $filter, $metadataQuery);
if ($searchExpr) {
$query->andWhere($searchExpr);
}
- $this->searchBuilder->addSearchOrdersToQuery($query, $searchQuery->getOrder());
+ $this->searchBuilder->addSearchOrdersToQuery($query, $searchQuery->getOrder(), $metadataQuery);
if ($searchQuery->getLimit()) {
$query->setMaxResults($searchQuery->getLimit());
@@ -144,6 +140,11 @@ class QuerySearchHelper {
));
}
+
+ protected function equipQueryForShares(CacheQueryBuilder $query): void {
+ $query->join('file', 'share', 's', $query->expr()->eq('file.fileid', 's.file_source'));
+ }
+
/**
* Perform a file system search in multiple caches
*
@@ -175,19 +176,31 @@ class QuerySearchHelper {
$query = $builder->selectFileCache('file', false);
$requestedFields = $this->searchBuilder->extractRequestedFields($searchQuery->getSearchOperation());
+
if (in_array('systemtag', $requestedFields)) {
$this->equipQueryForSystemTags($query, $this->requireUser($searchQuery));
}
if (in_array('tagname', $requestedFields) || in_array('favorite', $requestedFields)) {
$this->equipQueryForDavTags($query, $this->requireUser($searchQuery));
}
+ if (in_array('owner', $requestedFields) || in_array('share_with', $requestedFields) || in_array('share_type', $requestedFields)) {
+ $this->equipQueryForShares($query);
+ }
- $this->applySearchConstraints($query, $searchQuery, $caches);
+ $metadataQuery = $query->selectMetadata();
+
+ $this->applySearchConstraints($query, $searchQuery, $caches, $metadataQuery);
$result = $query->execute();
$files = $result->fetchAll();
- $rawEntries = array_map(function (array $data) {
+ $rawEntries = array_map(function (array $data) use ($metadataQuery) {
+ // migrate to null safe ...
+ if ($metadataQuery === null) {
+ $data['metadata'] = [];
+ } else {
+ $data['metadata'] = $metadataQuery->extractMetadata($data)->asArray();
+ }
return Cache::cacheEntryFromData($data, $this->mimetypeLoader);
}, $files);
diff --git a/lib/private/Files/Cache/Scanner.php b/lib/private/Files/Cache/Scanner.php
index 074e88e7639..0c82e21e30d 100644
--- a/lib/private/Files/Cache/Scanner.php
+++ b/lib/private/Files/Cache/Scanner.php
@@ -37,14 +37,14 @@ namespace OC\Files\Cache;
use Doctrine\DBAL\Exception;
use OC\Files\Storage\Wrapper\Encryption;
+use OC\Files\Storage\Wrapper\Jail;
+use OC\Hooks\BasicEmitter;
use OCP\Files\Cache\IScanner;
use OCP\Files\ForbiddenException;
use OCP\Files\NotFoundException;
use OCP\Files\Storage\IReliableEtagStorage;
use OCP\IDBConnection;
use OCP\Lock\ILockingProvider;
-use OC\Files\Storage\Wrapper\Jail;
-use OC\Hooks\BasicEmitter;
use Psr\Log\LoggerInterface;
/**
@@ -203,7 +203,9 @@ class Scanner extends BasicEmitter implements IScanner {
$fileId = $cacheData['fileid'];
$data['fileid'] = $fileId;
// only reuse data if the file hasn't explicitly changed
- if (isset($data['storage_mtime']) && isset($cacheData['storage_mtime']) && $data['storage_mtime'] === $cacheData['storage_mtime']) {
+ $mtimeUnchanged = isset($data['storage_mtime']) && isset($cacheData['storage_mtime']) && $data['storage_mtime'] === $cacheData['storage_mtime'];
+ // if the folder is marked as unscanned, never reuse etags
+ if ($mtimeUnchanged && $cacheData['size'] !== -1) {
$data['mtime'] = $cacheData['mtime'];
if (($reuseExisting & self::REUSE_SIZE) && ($data['size'] === -1)) {
$data['size'] = $cacheData['size'];
@@ -220,6 +222,11 @@ class Scanner extends BasicEmitter implements IScanner {
// Only update metadata that has changed
$newData = array_diff_assoc($data, $cacheData->getData());
+
+ // make it known to the caller that etag has been changed and needs propagation
+ if (isset($newData['etag'])) {
+ $data['etag_changed'] = true;
+ }
} else {
// we only updated unencrypted_size if it's already set
unset($data['unencrypted_size']);
@@ -388,16 +395,20 @@ class Scanner extends BasicEmitter implements IScanner {
* @param int|float $oldSize the size of the folder before (re)scanning the children
* @return int|float the size of the scanned folder or -1 if the size is unknown at this stage
*/
- protected function scanChildren(string $path, $recursive, int $reuse, int $folderId, bool $lock, int|float $oldSize) {
+ protected function scanChildren(string $path, $recursive, int $reuse, int $folderId, bool $lock, int|float $oldSize, &$etagChanged = false) {
if ($reuse === -1) {
$reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : self::REUSE_ETAG;
}
$this->emit('\OC\Files\Cache\Scanner', 'scanFolder', [$path, $this->storageId]);
$size = 0;
- $childQueue = $this->handleChildren($path, $recursive, $reuse, $folderId, $lock, $size);
+ $childQueue = $this->handleChildren($path, $recursive, $reuse, $folderId, $lock, $size, $etagChanged);
foreach ($childQueue as $child => [$childId, $childSize]) {
- $childSize = $this->scanChildren($child, $recursive, $reuse, $childId, $lock, $childSize);
+ // "etag changed" propagates up, but not down, so we pass `false` to the children even if we already know that the etag of the current folder changed
+ $childEtagChanged = false;
+ $childSize = $this->scanChildren($child, $recursive, $reuse, $childId, $lock, $childSize, $childEtagChanged);
+ $etagChanged |= $childEtagChanged;
+
if ($childSize === -1) {
$size = -1;
} elseif ($size !== -1) {
@@ -410,8 +421,17 @@ class Scanner extends BasicEmitter implements IScanner {
if ($this->storage->instanceOfStorage(Encryption::class)) {
$this->cache->calculateFolderSize($path);
} else {
- if ($this->cacheActive && $oldSize !== $size) {
- $this->cache->update($folderId, ['size' => $size]);
+ if ($this->cacheActive) {
+ $updatedData = [];
+ if ($oldSize !== $size) {
+ $updatedData['size'] = $size;
+ }
+ if ($etagChanged) {
+ $updatedData['etag'] = uniqid();
+ }
+ if ($updatedData) {
+ $this->cache->update($folderId, $updatedData);
+ }
}
}
$this->emit('\OC\Files\Cache\Scanner', 'postScanFolder', [$path, $this->storageId]);
@@ -421,7 +441,7 @@ class Scanner extends BasicEmitter implements IScanner {
/**
* @param bool|IScanner::SCAN_RECURSIVE_INCOMPLETE $recursive
*/
- private function handleChildren(string $path, $recursive, int $reuse, int $folderId, bool $lock, int|float &$size): array {
+ private function handleChildren(string $path, $recursive, int $reuse, int $folderId, bool $lock, int|float &$size, bool &$etagChanged): array {
// we put this in it's own function so it cleans up the memory before we start recursing
$existingChildren = $this->getExistingChildren($folderId);
$newChildren = iterator_to_array($this->storage->getDirectoryContent($path));
@@ -469,6 +489,10 @@ class Scanner extends BasicEmitter implements IScanner {
} elseif ($size !== -1) {
$size += $data['size'];
}
+
+ if (isset($data['etag_changed']) && $data['etag_changed']) {
+ $etagChanged = true;
+ }
}
} catch (Exception $ex) {
// might happen if inserting duplicate while a scanning
diff --git a/lib/private/Files/Cache/SearchBuilder.php b/lib/private/Files/Cache/SearchBuilder.php
index b9a70bbd39b..38161ec9cc6 100644
--- a/lib/private/Files/Cache/SearchBuilder.php
+++ b/lib/private/Files/Cache/SearchBuilder.php
@@ -3,6 +3,7 @@
* @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
+ * @author Maxence Lange <maxence@artificial-owl.com>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Tobias Kaminsky <tobias@kaminsky.me>
@@ -32,6 +33,7 @@ use OCP\Files\Search\ISearchBinaryOperator;
use OCP\Files\Search\ISearchComparison;
use OCP\Files\Search\ISearchOperator;
use OCP\Files\Search\ISearchOrder;
+use OCP\FilesMetadata\IMetadataQuery;
/**
* Tools for transforming search queries into database queries
@@ -45,6 +47,7 @@ class SearchBuilder {
ISearchComparison::COMPARE_GREATER_THAN_EQUAL => 'gte',
ISearchComparison::COMPARE_LESS_THAN => 'lt',
ISearchComparison::COMPARE_LESS_THAN_EQUAL => 'lte',
+ ISearchComparison::COMPARE_DEFINED => 'isNotNull',
];
protected static $searchOperatorNegativeMap = [
@@ -55,6 +58,7 @@ class SearchBuilder {
ISearchComparison::COMPARE_GREATER_THAN_EQUAL => 'lt',
ISearchComparison::COMPARE_LESS_THAN => 'gte',
ISearchComparison::COMPARE_LESS_THAN_EQUAL => 'gt',
+ ISearchComparison::COMPARE_DEFINED => 'isNull',
];
public const TAG_FAVORITE = '_$!<Favorite>!$_';
@@ -76,7 +80,7 @@ class SearchBuilder {
return array_reduce($operator->getArguments(), function (array $fields, ISearchOperator $operator) {
return array_unique(array_merge($fields, $this->extractRequestedFields($operator)));
}, []);
- } elseif ($operator instanceof ISearchComparison) {
+ } elseif ($operator instanceof ISearchComparison && !$operator->getExtra()) {
return [$operator->getField()];
}
return [];
@@ -86,13 +90,21 @@ class SearchBuilder {
* @param IQueryBuilder $builder
* @param ISearchOperator[] $operators
*/
- public function searchOperatorArrayToDBExprArray(IQueryBuilder $builder, array $operators) {
- return array_filter(array_map(function ($operator) use ($builder) {
- return $this->searchOperatorToDBExpr($builder, $operator);
+ public function searchOperatorArrayToDBExprArray(
+ IQueryBuilder $builder,
+ array $operators,
+ ?IMetadataQuery $metadataQuery = null
+ ) {
+ return array_filter(array_map(function ($operator) use ($builder, $metadataQuery) {
+ return $this->searchOperatorToDBExpr($builder, $operator, $metadataQuery);
}, $operators));
}
- public function searchOperatorToDBExpr(IQueryBuilder $builder, ISearchOperator $operator) {
+ public function searchOperatorToDBExpr(
+ IQueryBuilder $builder,
+ ISearchOperator $operator,
+ ?IMetadataQuery $metadataQuery = null
+ ) {
$expr = $builder->expr();
if ($operator instanceof ISearchBinaryOperator) {
@@ -104,29 +116,37 @@ class SearchBuilder {
case ISearchBinaryOperator::OPERATOR_NOT:
$negativeOperator = $operator->getArguments()[0];
if ($negativeOperator instanceof ISearchComparison) {
- return $this->searchComparisonToDBExpr($builder, $negativeOperator, self::$searchOperatorNegativeMap);
+ return $this->searchComparisonToDBExpr($builder, $negativeOperator, self::$searchOperatorNegativeMap, $metadataQuery);
} else {
throw new \InvalidArgumentException('Binary operators inside "not" is not supported');
}
// no break
case ISearchBinaryOperator::OPERATOR_AND:
- return call_user_func_array([$expr, 'andX'], $this->searchOperatorArrayToDBExprArray($builder, $operator->getArguments()));
+ return call_user_func_array([$expr, 'andX'], $this->searchOperatorArrayToDBExprArray($builder, $operator->getArguments(), $metadataQuery));
case ISearchBinaryOperator::OPERATOR_OR:
- return call_user_func_array([$expr, 'orX'], $this->searchOperatorArrayToDBExprArray($builder, $operator->getArguments()));
+ return call_user_func_array([$expr, 'orX'], $this->searchOperatorArrayToDBExprArray($builder, $operator->getArguments(), $metadataQuery));
default:
throw new \InvalidArgumentException('Invalid operator type: ' . $operator->getType());
}
} elseif ($operator instanceof ISearchComparison) {
- return $this->searchComparisonToDBExpr($builder, $operator, self::$searchOperatorMap);
+ return $this->searchComparisonToDBExpr($builder, $operator, self::$searchOperatorMap, $metadataQuery);
} else {
throw new \InvalidArgumentException('Invalid operator type: ' . get_class($operator));
}
}
- private function searchComparisonToDBExpr(IQueryBuilder $builder, ISearchComparison $comparison, array $operatorMap) {
- $this->validateComparison($comparison);
+ private function searchComparisonToDBExpr(
+ IQueryBuilder $builder,
+ ISearchComparison $comparison,
+ array $operatorMap,
+ ?IMetadataQuery $metadataQuery = null
+ ) {
+ if ($comparison->getExtra()) {
+ [$field, $value, $type] = $this->getExtraOperatorField($comparison, $metadataQuery);
+ } else {
+ [$field, $value, $type] = $this->getOperatorFieldAndValue($comparison);
+ }
- [$field, $value, $type] = $this->getOperatorFieldAndValue($comparison);
if (isset($operatorMap[$type])) {
$queryOperator = $operatorMap[$type];
return $builder->expr()->$queryOperator($field, $this->getParameterForValue($builder, $value));
@@ -136,9 +156,12 @@ class SearchBuilder {
}
private function getOperatorFieldAndValue(ISearchComparison $operator) {
+ $this->validateComparison($operator);
+
$field = $operator->getField();
$value = $operator->getValue();
$type = $operator->getType();
+
if ($field === 'mimetype') {
$value = (string)$value;
if ($operator->getType() === ISearchComparison::COMPARE_EQUAL) {
@@ -171,6 +194,8 @@ class SearchBuilder {
} elseif ($field === 'path' && $type === ISearchComparison::COMPARE_EQUAL && $operator->getQueryHint(ISearchComparison::HINT_PATH_EQ_HASH, true)) {
$field = 'path_hash';
$value = md5((string)$value);
+ } elseif ($field === 'owner') {
+ $field = 'uid_owner';
}
return [$field, $value, $type];
}
@@ -187,6 +212,9 @@ class SearchBuilder {
'favorite' => 'boolean',
'fileid' => 'integer',
'storage' => 'integer',
+ 'share_with' => 'string',
+ 'share_type' => 'integer',
+ 'owner' => 'string',
];
$comparisons = [
'mimetype' => ['eq', 'like'],
@@ -199,6 +227,9 @@ class SearchBuilder {
'favorite' => ['eq'],
'fileid' => ['eq'],
'storage' => ['eq'],
+ 'share_with' => ['eq'],
+ 'share_type' => ['eq'],
+ 'owner' => ['eq'],
];
if (!isset($types[$operator->getField()])) {
@@ -213,6 +244,24 @@ class SearchBuilder {
}
}
+
+ private function getExtraOperatorField(ISearchComparison $operator, IMetadataQuery $metadataQuery): array {
+ $field = $operator->getField();
+ $value = $operator->getValue();
+ $type = $operator->getType();
+
+ switch($operator->getExtra()) {
+ case IMetadataQuery::EXTRA:
+ $metadataQuery->joinIndex($field); // join index table if not joined yet
+ $field = $metadataQuery->getMetadataValueField($field);
+ break;
+ default:
+ throw new \InvalidArgumentException('Invalid extra type: ' . $operator->getExtra());
+ }
+
+ return [$field, $value, $type];
+ }
+
private function getParameterForValue(IQueryBuilder $builder, $value) {
if ($value instanceof \DateTime) {
$value = $value->getTimestamp();
@@ -228,24 +277,32 @@ class SearchBuilder {
/**
* @param IQueryBuilder $query
* @param ISearchOrder[] $orders
+ * @param IMetadataQuery|null $metadataQuery
*/
- public function addSearchOrdersToQuery(IQueryBuilder $query, array $orders) {
+ public function addSearchOrdersToQuery(IQueryBuilder $query, array $orders, ?IMetadataQuery $metadataQuery = null): void {
foreach ($orders as $order) {
$field = $order->getField();
- if ($field === 'fileid') {
- $field = 'file.fileid';
- }
+ switch ($order->getExtra()) {
+ case IMetadataQuery::EXTRA:
+ $metadataQuery->joinIndex($field); // join index table if not joined yet
+ $field = $metadataQuery->getMetadataValueField($order->getField());
+ break;
- // Mysql really likes to pick an index for sorting if it can't fully satisfy the where
- // filter with an index, since search queries pretty much never are fully filtered by index
- // mysql often picks an index for sorting instead of the much more useful index for filtering.
- //
- // By changing the order by to an expression, mysql isn't smart enough to see that it could still
- // use the index, so it instead picks an index for the filtering
- if ($field === 'mtime') {
- $field = $query->func()->add($field, $query->createNamedParameter(0));
- }
+ default:
+ if ($field === 'fileid') {
+ $field = 'file.fileid';
+ }
+ // Mysql really likes to pick an index for sorting if it can't fully satisfy the where
+ // filter with an index, since search queries pretty much never are fully filtered by index
+ // mysql often picks an index for sorting instead of the much more useful index for filtering.
+ //
+ // By changing the order by to an expression, mysql isn't smart enough to see that it could still
+ // use the index, so it instead picks an index for the filtering
+ if ($field === 'mtime') {
+ $field = $query->func()->add($field, $query->createNamedParameter(0));
+ }
+ }
$query->addOrderBy($field, $order->getDirection());
}
}
diff --git a/lib/private/Files/Cache/Updater.php b/lib/private/Files/Cache/Updater.php
index 457dd207e9d..a6f2f3375a4 100644
--- a/lib/private/Files/Cache/Updater.php
+++ b/lib/private/Files/Cache/Updater.php
@@ -119,7 +119,7 @@ class Updater implements IUpdater {
* @param string $path
* @param int $time
*/
- public function update($path, $time = null) {
+ public function update($path, $time = null, ?int $sizeDifference = null) {
if (!$this->enabled or Scanner::isPartialFile($path)) {
return;
}
@@ -128,20 +128,22 @@ class Updater implements IUpdater {
}
$data = $this->scanner->scan($path, Scanner::SCAN_SHALLOW, -1, false);
- if (
- isset($data['oldSize']) && isset($data['size']) &&
- !$data['encrypted'] // encryption is a pita and touches the cache itself
- ) {
+
+ if (isset($data['oldSize']) && isset($data['size'])) {
$sizeDifference = $data['size'] - $data['oldSize'];
- } else {
- // scanner didn't provide size info, fallback to full size calculation
- $sizeDifference = 0;
- if ($this->cache instanceof Cache) {
- $this->cache->correctFolderSize($path, $data);
- }
+ }
+
+ // encryption is a pita and touches the cache itself
+ if (isset($data['encrypted']) && !!$data['encrypted']) {
+ $sizeDifference = null;
+ }
+
+ // scanner didn't provide size info, fallback to full size calculation
+ if ($this->cache instanceof Cache && $sizeDifference === null) {
+ $this->cache->correctFolderSize($path, $data);
}
$this->correctParentStorageMtime($path);
- $this->propagator->propagateChange($path, $time, $sizeDifference);
+ $this->propagator->propagateChange($path, $time, $sizeDifference ?? 0);
}
/**
diff --git a/lib/private/Files/Cache/Watcher.php b/lib/private/Files/Cache/Watcher.php
index acc76f263dc..61ea5b2f848 100644
--- a/lib/private/Files/Cache/Watcher.php
+++ b/lib/private/Files/Cache/Watcher.php
@@ -129,7 +129,7 @@ class Watcher implements IWatcher {
* @return bool
*/
public function needsUpdate($path, $cachedData) {
- if ($this->watchPolicy === self::CHECK_ALWAYS or ($this->watchPolicy === self::CHECK_ONCE and array_search($path, $this->checkedPaths) === false)) {
+ if ($this->watchPolicy === self::CHECK_ALWAYS or ($this->watchPolicy === self::CHECK_ONCE and !in_array($path, $this->checkedPaths))) {
$this->checkedPaths[] = $path;
return $this->storage->hasUpdated($path, $cachedData['storage_mtime']);
}
diff --git a/lib/private/Files/Cache/Wrapper/CacheJail.php b/lib/private/Files/Cache/Wrapper/CacheJail.php
index d8cf3eb61d7..73c9a017019 100644
--- a/lib/private/Files/Cache/Wrapper/CacheJail.php
+++ b/lib/private/Files/Cache/Wrapper/CacheJail.php
@@ -52,8 +52,6 @@ class CacheJail extends CacheWrapper {
public function __construct($cache, $root) {
parent::__construct($cache);
$this->root = $root;
- $this->connection = \OC::$server->getDatabaseConnection();
- $this->mimetypeLoader = \OC::$server->getMimeTypeLoader();
if ($cache instanceof CacheJail) {
$this->unjailedRoot = $cache->getSourcePath($root);
diff --git a/lib/private/Files/Config/CachedMountInfo.php b/lib/private/Files/Config/CachedMountInfo.php
index 43c9fae63ec..7c97135a565 100644
--- a/lib/private/Files/Config/CachedMountInfo.php
+++ b/lib/private/Files/Config/CachedMountInfo.php
@@ -35,6 +35,7 @@ class CachedMountInfo implements ICachedMountInfo {
protected ?int $mountId;
protected string $rootInternalPath;
protected string $mountProvider;
+ protected string $key;
/**
* CachedMountInfo constructor.
@@ -65,6 +66,7 @@ class CachedMountInfo implements ICachedMountInfo {
throw new \Exception("Mount provider $mountProvider name exceeds the limit of 128 characters");
}
$this->mountProvider = $mountProvider;
+ $this->key = $rootId . '::' . $mountPoint;
}
/**
@@ -132,4 +134,8 @@ class CachedMountInfo implements ICachedMountInfo {
public function getMountProvider(): string {
return $this->mountProvider;
}
+
+ public function getKey(): string {
+ return $this->key;
+ }
}
diff --git a/lib/private/Files/Config/LazyStorageMountInfo.php b/lib/private/Files/Config/LazyStorageMountInfo.php
index 78055a2cdb8..7e4acb2e129 100644
--- a/lib/private/Files/Config/LazyStorageMountInfo.php
+++ b/lib/private/Files/Config/LazyStorageMountInfo.php
@@ -39,6 +39,7 @@ class LazyStorageMountInfo extends CachedMountInfo {
$this->rootId = 0;
$this->storageId = 0;
$this->mountPoint = '';
+ $this->key = '';
}
/**
@@ -87,4 +88,11 @@ class LazyStorageMountInfo extends CachedMountInfo {
public function getMountProvider(): string {
return $this->mount->getMountProvider();
}
+
+ public function getKey(): string {
+ if (!$this->key) {
+ $this->key = $this->getRootId() . '::' . $this->getMountPoint();
+ }
+ return $this->key;
+ }
}
diff --git a/lib/private/Files/Config/MountProviderCollection.php b/lib/private/Files/Config/MountProviderCollection.php
index ae6481e45bb..d251199fd43 100644
--- a/lib/private/Files/Config/MountProviderCollection.php
+++ b/lib/private/Files/Config/MountProviderCollection.php
@@ -238,6 +238,11 @@ class MountProviderCollection implements IMountProviderCollection, Emitter {
$mounts = array_reduce($mounts, function (array $mounts, array $providerMounts) {
return array_merge($mounts, $providerMounts);
}, []);
+
+ if (count($mounts) === 0) {
+ throw new \Exception("No root mounts provided by any provider");
+ }
+
return $mounts;
}
diff --git a/lib/private/Files/Config/UserMountCache.php b/lib/private/Files/Config/UserMountCache.php
index 8a6b818d413..2fb7a7d83f4 100644
--- a/lib/private/Files/Config/UserMountCache.php
+++ b/lib/private/Files/Config/UserMountCache.php
@@ -29,13 +29,11 @@
namespace OC\Files\Config;
use OCP\Cache\CappedMemoryCache;
-use OCA\Files_Sharing\SharedMount;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Diagnostics\IEventLogger;
use OCP\Files\Config\ICachedMountFileInfo;
use OCP\Files\Config\ICachedMountInfo;
use OCP\Files\Config\IUserMountCache;
-use OCP\Files\Mount\IMountPoint;
use OCP\Files\NotFoundException;
use OCP\IDBConnection;
use OCP\IUser;
@@ -78,41 +76,27 @@ class UserMountCache implements IUserMountCache {
public function registerMounts(IUser $user, array $mounts, array $mountProviderClasses = null) {
$this->eventLogger->start('fs:setup:user:register', 'Registering mounts for user');
- // filter out non-proper storages coming from unit tests
- $mounts = array_filter($mounts, function (IMountPoint $mount) {
- return $mount instanceof SharedMount || ($mount->getStorage() && $mount->getStorage()->getCache());
- });
- /** @var ICachedMountInfo[] $newMounts */
- $newMounts = array_map(function (IMountPoint $mount) use ($user) {
+ /** @var array<string, ICachedMountInfo> $newMounts */
+ $newMounts = [];
+ foreach ($mounts as $mount) {
// filter out any storages which aren't scanned yet since we aren't interested in files from those storages (yet)
- if ($mount->getStorageRootId() === -1) {
- return null;
- } else {
- return new LazyStorageMountInfo($user, $mount);
+ if ($mount->getStorageRootId() !== -1) {
+ $mountInfo = new LazyStorageMountInfo($user, $mount);
+ $newMounts[$mountInfo->getKey()] = $mountInfo;
}
- }, $mounts);
- $newMounts = array_values(array_filter($newMounts));
- $newMountKeys = array_map(function (ICachedMountInfo $mount) {
- return $mount->getRootId() . '::' . $mount->getMountPoint();
- }, $newMounts);
- $newMounts = array_combine($newMountKeys, $newMounts);
+ }
$cachedMounts = $this->getMountsForUser($user);
if (is_array($mountProviderClasses)) {
$cachedMounts = array_filter($cachedMounts, function (ICachedMountInfo $mountInfo) use ($mountProviderClasses, $newMounts) {
// for existing mounts that didn't have a mount provider set
// we still want the ones that map to new mounts
- $mountKey = $mountInfo->getRootId() . '::' . $mountInfo->getMountPoint();
- if ($mountInfo->getMountProvider() === '' && isset($newMounts[$mountKey])) {
+ if ($mountInfo->getMountProvider() === '' && isset($newMounts[$mountInfo->getKey()])) {
return true;
}
return in_array($mountInfo->getMountProvider(), $mountProviderClasses);
});
}
- $cachedRootKeys = array_map(function (ICachedMountInfo $mount) {
- return $mount->getRootId() . '::' . $mount->getMountPoint();
- }, $cachedMounts);
- $cachedMounts = array_combine($cachedRootKeys, $cachedMounts);
$addedMounts = [];
$removedMounts = [];
@@ -131,46 +115,44 @@ class UserMountCache implements IUserMountCache {
$changedMounts = $this->findChangedMounts($newMounts, $cachedMounts);
- $this->connection->beginTransaction();
- try {
- foreach ($addedMounts as $mount) {
- $this->addToCache($mount);
- /** @psalm-suppress InvalidArgument */
- $this->mountsForUsers[$user->getUID()][] = $mount;
- }
- foreach ($removedMounts as $mount) {
- $this->removeFromCache($mount);
- $index = array_search($mount, $this->mountsForUsers[$user->getUID()]);
- unset($this->mountsForUsers[$user->getUID()][$index]);
- }
- foreach ($changedMounts as $mount) {
- $this->updateCachedMount($mount);
+ if ($addedMounts || $removedMounts || $changedMounts) {
+ $this->connection->beginTransaction();
+ $userUID = $user->getUID();
+ try {
+ foreach ($addedMounts as $mount) {
+ $this->addToCache($mount);
+ /** @psalm-suppress InvalidArgument */
+ $this->mountsForUsers[$userUID][$mount->getKey()] = $mount;
+ }
+ foreach ($removedMounts as $mount) {
+ $this->removeFromCache($mount);
+ unset($this->mountsForUsers[$userUID][$mount->getKey()]);
+ }
+ foreach ($changedMounts as $mount) {
+ $this->updateCachedMount($mount);
+ /** @psalm-suppress InvalidArgument */
+ $this->mountsForUsers[$userUID][$mount->getKey()] = $mount;
+ }
+ $this->connection->commit();
+ } catch (\Throwable $e) {
+ $this->connection->rollBack();
+ throw $e;
}
- $this->connection->commit();
- } catch (\Throwable $e) {
- $this->connection->rollBack();
- throw $e;
}
$this->eventLogger->end('fs:setup:user:register');
}
/**
- * @param ICachedMountInfo[] $newMounts
- * @param ICachedMountInfo[] $cachedMounts
+ * @param array<string, ICachedMountInfo> $newMounts
+ * @param array<string, ICachedMountInfo> $cachedMounts
* @return ICachedMountInfo[]
*/
private function findChangedMounts(array $newMounts, array $cachedMounts) {
- $new = [];
- foreach ($newMounts as $mount) {
- $new[$mount->getRootId() . '::' . $mount->getMountPoint()] = $mount;
- }
$changed = [];
- foreach ($cachedMounts as $cachedMount) {
- $key = $cachedMount->getRootId() . '::' . $cachedMount->getMountPoint();
- if (isset($new[$key])) {
- $newMount = $new[$key];
+ foreach ($cachedMounts as $key => $cachedMount) {
+ if (isset($newMounts[$key])) {
+ $newMount = $newMounts[$key];
if (
- $newMount->getMountPoint() !== $cachedMount->getMountPoint() ||
$newMount->getStorageId() !== $cachedMount->getStorageId() ||
$newMount->getMountId() !== $cachedMount->getMountId() ||
$newMount->getMountProvider() !== $cachedMount->getMountProvider()
@@ -247,20 +229,28 @@ class UserMountCache implements IUserMountCache {
* @return ICachedMountInfo[]
*/
public function getMountsForUser(IUser $user) {
- if (!isset($this->mountsForUsers[$user->getUID()])) {
+ $userUID = $user->getUID();
+ if (!isset($this->mountsForUsers[$userUID])) {
$builder = $this->connection->getQueryBuilder();
$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class')
->from('mounts', 'm')
->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
- ->where($builder->expr()->eq('user_id', $builder->createPositionalParameter($user->getUID())));
+ ->where($builder->expr()->eq('user_id', $builder->createPositionalParameter($userUID)));
$result = $query->execute();
$rows = $result->fetchAll();
$result->closeCursor();
- $this->mountsForUsers[$user->getUID()] = array_filter(array_map([$this, 'dbRowToMountInfo'], $rows));
+ $this->mountsForUsers[$userUID] = [];
+ /** @var array<string, ICachedMountInfo> $mounts */
+ foreach ($rows as $row) {
+ $mount = $this->dbRowToMountInfo($row);
+ if ($mount !== null) {
+ $this->mountsForUsers[$userUID][$mount->getKey()] = $mount;
+ }
+ }
}
- return $this->mountsForUsers[$user->getUID()];
+ return $this->mountsForUsers[$userUID];
}
/**
@@ -463,7 +453,7 @@ class UserMountCache implements IUserMountCache {
}, $mounts);
$mounts = array_combine($mountPoints, $mounts);
- $current = $path;
+ $current = rtrim($path, '/');
// walk up the directory tree until we find a path that has a mountpoint set
// the loop will return if a mountpoint is found or break if none are found
while (true) {
diff --git a/lib/private/Files/FileInfo.php b/lib/private/Files/FileInfo.php
index 7800074460b..5ba2f27b78b 100644
--- a/lib/private/Files/FileInfo.php
+++ b/lib/private/Files/FileInfo.php
@@ -6,6 +6,7 @@
* @author Joas Schilling <coding@schilljs.com>
* @author Julius Härtl <jus@bitgrid.net>
* @author Lukas Reschke <lukas@statuscode.ch>
+ * @author Maxence Lange <maxence@artificial-owl.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Piotr M <mrow4a@yahoo.com>
* @author Robin Appelman <robin@icewind.nl>
@@ -32,9 +33,10 @@
*/
namespace OC\Files;
-use OCA\Files_Sharing\ISharedStorage;
+use OC\Files\Mount\HomeMountPoint;
+use OCA\Files_Sharing\External\Mount;
+use OCA\Files_Sharing\ISharedMountPoint;
use OCP\Files\Cache\ICacheEntry;
-use OCP\Files\IHomeStorage;
use OCP\Files\Mount\IMountPoint;
use OCP\IUser;
@@ -121,21 +123,14 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess {
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset) {
- if ($offset === 'type') {
- return $this->getType();
- } elseif ($offset === 'etag') {
- return $this->getEtag();
- } elseif ($offset === 'size') {
- return $this->getSize();
- } elseif ($offset === 'mtime') {
- return $this->getMTime();
- } elseif ($offset === 'permissions') {
- return $this->getPermissions();
- } elseif (isset($this->data[$offset])) {
- return $this->data[$offset];
- } else {
- return null;
- }
+ return match ($offset) {
+ 'type' => $this->getType(),
+ 'etag' => $this->getEtag(),
+ 'size' => $this->getSize(),
+ 'mtime' => $this->getMTime(),
+ 'permissions' => $this->getPermissions(),
+ default => $this->data[$offset] ?? null,
+ };
}
/**
@@ -311,13 +306,12 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess {
* @return bool
*/
public function isShared() {
- $storage = $this->getStorage();
- return $storage->instanceOfStorage(ISharedStorage::class);
+ return $this->mount instanceof ISharedMountPoint;
}
public function isMounted() {
- $storage = $this->getStorage();
- return !($storage->instanceOfStorage(IHomeStorage::class) || $storage->instanceOfStorage(ISharedStorage::class));
+ $isHome = $this->mount instanceof HomeMountPoint;
+ return !$isHome && !$this->isShared();
}
/**
@@ -416,4 +410,12 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess {
public function getParentId(): int {
return $this->data['parent'] ?? -1;
}
+
+ /**
+ * @inheritDoc
+ * @return array<string, int|string|bool|float|string[]|int[]>
+ */
+ public function getMetadata(): array {
+ return $this->data['metadata'] ?? [];
+ }
}
diff --git a/lib/private/Files/Filesystem.php b/lib/private/Files/Filesystem.php
index 5f7c0c403db..9f0d89052be 100644
--- a/lib/private/Files/Filesystem.php
+++ b/lib/private/Files/Filesystem.php
@@ -37,9 +37,9 @@
*/
namespace OC\Files;
-use OCP\Cache\CappedMemoryCache;
use OC\Files\Mount\MountPoint;
use OC\User\NoUserException;
+use OCP\Cache\CappedMemoryCache;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Events\Node\FilesystemTornDownEvent;
use OCP\Files\Mount\IMountManager;
diff --git a/lib/private/Files/Mount/HomeMountPoint.php b/lib/private/Files/Mount/HomeMountPoint.php
new file mode 100644
index 00000000000..0bec12af5c2
--- /dev/null
+++ b/lib/private/Files/Mount/HomeMountPoint.php
@@ -0,0 +1,49 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2023 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\Mount;
+
+use OCP\Files\Storage\IStorageFactory;
+use OCP\IUser;
+
+class HomeMountPoint extends MountPoint {
+ private IUser $user;
+
+ public function __construct(
+ IUser $user,
+ $storage,
+ string $mountpoint,
+ array $arguments = null,
+ IStorageFactory $loader = null,
+ array $mountOptions = null,
+ int $mountId = null,
+ string $mountProvider = null
+ ) {
+ parent::__construct($storage, $mountpoint, $arguments, $loader, $mountOptions, $mountId, $mountProvider);
+ $this->user = $user;
+ }
+
+ public function getUser(): IUser {
+ return $this->user;
+ }
+}
diff --git a/lib/private/Files/Mount/LocalHomeMountProvider.php b/lib/private/Files/Mount/LocalHomeMountProvider.php
index 25a67fc1574..964b607d152 100644
--- a/lib/private/Files/Mount/LocalHomeMountProvider.php
+++ b/lib/private/Files/Mount/LocalHomeMountProvider.php
@@ -38,6 +38,6 @@ class LocalHomeMountProvider implements IHomeMountProvider {
*/
public function getHomeMountForUser(IUser $user, IStorageFactory $loader) {
$arguments = ['user' => $user];
- return new MountPoint('\OC\Files\Storage\Home', '/' . $user->getUID(), $arguments, $loader, null, null, self::class);
+ return new HomeMountPoint($user, '\OC\Files\Storage\Home', '/' . $user->getUID(), $arguments, $loader, null, null, self::class);
}
}
diff --git a/lib/private/Files/Mount/Manager.php b/lib/private/Files/Mount/Manager.php
index 805cce658a6..2b2de1fbff1 100644
--- a/lib/private/Files/Mount/Manager.php
+++ b/lib/private/Files/Mount/Manager.php
@@ -10,6 +10,7 @@ declare(strict_types=1);
* @author Robin Appelman <robin@icewind.nl>
* @author Robin McCorkell <robin@mccorkell.me.uk>
* @author Roeland Jago Douma <roeland@famdouma.nl>
+ * @author Jonas <jonas@freesources.org>
*
* @license AGPL-3.0
*
@@ -29,10 +30,11 @@ declare(strict_types=1);
namespace OC\Files\Mount;
-use OCP\Cache\CappedMemoryCache;
use OC\Files\Filesystem;
use OC\Files\SetupManager;
use OC\Files\SetupManagerFactory;
+use OCP\Cache\CappedMemoryCache;
+use OCP\Files\Config\ICachedMountInfo;
use OCP\Files\Mount\IMountManager;
use OCP\Files\Mount\IMountPoint;
use OCP\Files\NotFoundException;
@@ -99,6 +101,15 @@ class Manager implements IMountManager {
return $this->pathCache[$path];
}
+
+
+ if (count($this->mounts) === 0) {
+ $this->setupManager->setupRoot();
+ if (count($this->mounts) === 0) {
+ throw new \Exception("No mounts even after explicitly setting up the root mounts");
+ }
+ }
+
$current = $path;
while (true) {
$mountPoint = $current . '/';
@@ -115,7 +126,7 @@ class Manager implements IMountManager {
}
}
- throw new NotFoundException("No mount for path " . $path . " existing mounts: " . implode(",", array_keys($this->mounts)));
+ throw new NotFoundException("No mount for path " . $path . " existing mounts (" . count($this->mounts) ."): " . implode(",", array_keys($this->mounts)));
}
/**
@@ -226,4 +237,21 @@ class Manager implements IMountManager {
});
}
}
+
+ /**
+ * Return the mount matching a cached mount info (or mount file info)
+ *
+ * @param ICachedMountInfo $info
+ *
+ * @return IMountPoint|null
+ */
+ public function getMountFromMountInfo(ICachedMountInfo $info): ?IMountPoint {
+ $this->setupManager->setupForPath($info->getMountPoint());
+ foreach ($this->mounts as $mount) {
+ if ($mount->getMountPoint() === $info->getMountPoint()) {
+ return $mount;
+ }
+ }
+ return null;
+ }
}
diff --git a/lib/private/Files/Mount/ObjectHomeMountProvider.php b/lib/private/Files/Mount/ObjectHomeMountProvider.php
index 889a39fbd9e..3593a95c311 100644
--- a/lib/private/Files/Mount/ObjectHomeMountProvider.php
+++ b/lib/private/Files/Mount/ObjectHomeMountProvider.php
@@ -65,7 +65,7 @@ class ObjectHomeMountProvider implements IHomeMountProvider {
return null;
}
- return new MountPoint('\OC\Files\ObjectStore\HomeObjectStoreStorage', '/' . $user->getUID(), $config['arguments'], $loader, null, null, self::class);
+ return new HomeMountPoint($user, '\OC\Files\ObjectStore\HomeObjectStoreStorage', '/' . $user->getUID(), $config['arguments'], $loader, null, null, self::class);
}
/**
diff --git a/lib/private/Files/Node/Folder.php b/lib/private/Files/Node/Folder.php
index ccd10da9d0c..c7462572fed 100644
--- a/lib/private/Files/Node/Folder.php
+++ b/lib/private/Files/Node/Folder.php
@@ -177,7 +177,7 @@ class Folder extends Node implements \OCP\Files\Folder {
* @throws \OCP\Files\NotPermittedException
*/
public function newFile($path, $content = null) {
- if (empty($path)) {
+ if ($path === '') {
throw new NotPermittedException('Could not create as provided path is empty');
}
if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
diff --git a/lib/private/Files/Node/HookConnector.php b/lib/private/Files/Node/HookConnector.php
index a8e76d95c22..f61eedee66e 100644
--- a/lib/private/Files/Node/HookConnector.php
+++ b/lib/private/Files/Node/HookConnector.php
@@ -133,7 +133,7 @@ class HookConnector {
$this->root->emit('\OC\Files', 'preDelete', [$node]);
$this->dispatcher->dispatch('\OCP\Files::preDelete', new GenericEvent($node));
- $event = new BeforeNodeDeletedEvent($node);
+ $event = new BeforeNodeDeletedEvent($node, $arguments['run']);
$this->dispatcher->dispatchTyped($event);
}
@@ -171,7 +171,7 @@ class HookConnector {
$this->root->emit('\OC\Files', 'preRename', [$source, $target]);
$this->dispatcher->dispatch('\OCP\Files::preRename', new GenericEvent([$source, $target]));
- $event = new BeforeNodeRenamedEvent($source, $target);
+ $event = new BeforeNodeRenamedEvent($source, $target, $arguments['run']);
$this->dispatcher->dispatchTyped($event);
}
diff --git a/lib/private/Files/Node/LazyFolder.php b/lib/private/Files/Node/LazyFolder.php
index f13cdc0c4f9..e30cfea693e 100644
--- a/lib/private/Files/Node/LazyFolder.php
+++ b/lib/private/Files/Node/LazyFolder.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
/**
* @copyright Copyright (c) 2020 Robin Appelman <robin@icewind.nl>
*
+ * @author Maxence Lange <maxence@artificial-owl.com>
* @author Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
@@ -28,8 +29,8 @@ namespace OC\Files\Node;
use OC\Files\Filesystem;
use OC\Files\Utils\PathHelper;
-use OCP\Files\Folder;
use OCP\Constants;
+use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\Files\Mount\IMountPoint;
use OCP\Files\NotPermittedException;
@@ -574,4 +575,12 @@ class LazyFolder implements Folder {
}
return $this->__call(__FUNCTION__, func_get_args());
}
+
+ /**
+ * @inheritDoc
+ * @return array<string, int|string|bool|float|string[]|int[]>
+ */
+ public function getMetadata(): array {
+ return $this->data['metadata'] ?? $this->__call(__FUNCTION__, func_get_args());
+ }
}
diff --git a/lib/private/Files/Node/LazyUserFolder.php b/lib/private/Files/Node/LazyUserFolder.php
index 503b0af8921..917ab80f366 100644
--- a/lib/private/Files/Node/LazyUserFolder.php
+++ b/lib/private/Files/Node/LazyUserFolder.php
@@ -23,13 +23,13 @@ declare(strict_types=1);
namespace OC\Files\Node;
-use OCP\Files\FileInfo;
use OCP\Constants;
+use OCP\Files\File;
+use OCP\Files\FileInfo;
+use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\Files\Mount\IMountManager;
use OCP\Files\NotFoundException;
-use OCP\Files\Folder;
-use OCP\Files\File;
use OCP\IUser;
use Psr\Log\LoggerInterface;
diff --git a/lib/private/Files/Node/Node.php b/lib/private/Files/Node/Node.php
index 385d45f1e3e..acd91c56d3f 100644
--- a/lib/private/Files/Node/Node.php
+++ b/lib/private/Files/Node/Node.php
@@ -7,6 +7,7 @@
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Julius Härtl <jus@bitgrid.net>
+ * @author Maxence Lange <maxence@artificial-owl.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
@@ -43,7 +44,7 @@ use OCP\Files\NotPermittedException;
use OCP\Lock\LockedException;
use OCP\PreConditionNotMetException;
-// FIXME: this class really should be abstract
+// FIXME: this class really should be abstract (+1)
class Node implements INode {
/**
* @var \OC\Files\View $view
@@ -131,7 +132,14 @@ class Node implements INode {
if (method_exists($this->root, 'emit')) {
$this->root->emit('\OC\Files', $hook, $args);
}
- $dispatcher->dispatch('\OCP\Files::' . $hook, new GenericEvent($args));
+
+ if (in_array($hook, ['preWrite', 'postWrite', 'preCreate', 'postCreate', 'preTouch', 'postTouch', 'preDelete', 'postDelete'], true)) {
+ $event = new GenericEvent($args[0]);
+ } else {
+ $event = new GenericEvent($args);
+ }
+
+ $dispatcher->dispatch('\OCP\Files::' . $hook, $event);
}
}
@@ -490,4 +498,12 @@ class Node implements INode {
public function getParentId(): int {
return $this->fileInfo->getParentId();
}
+
+ /**
+ * @inheritDoc
+ * @return array<string, int|string|bool|float|string[]|int[]>
+ */
+ public function getMetadata(): array {
+ return $this->fileInfo->getMetadata();
+ }
}
diff --git a/lib/private/Files/Node/Root.php b/lib/private/Files/Node/Root.php
index 1195b644083..ee344f9be8b 100644
--- a/lib/private/Files/Node/Root.php
+++ b/lib/private/Files/Node/Root.php
@@ -32,7 +32,6 @@
namespace OC\Files\Node;
-use OCP\Cache\CappedMemoryCache;
use OC\Files\FileInfo;
use OC\Files\Mount\Manager;
use OC\Files\Mount\MountPoint;
@@ -40,6 +39,7 @@ use OC\Files\Utils\PathHelper;
use OC\Files\View;
use OC\Hooks\PublicEmitter;
use OC\User\NoUserException;
+use OCP\Cache\CappedMemoryCache;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Cache\ICacheEntry;
use OCP\Files\Config\IUserMountCache;
diff --git a/lib/private/Files/ObjectStore/ObjectStoreScanner.php b/lib/private/Files/ObjectStore/ObjectStoreScanner.php
index d827662ae0b..8a9b844c47f 100644
--- a/lib/private/Files/ObjectStore/ObjectStoreScanner.php
+++ b/lib/private/Files/ObjectStore/ObjectStoreScanner.php
@@ -39,7 +39,7 @@ class ObjectStoreScanner extends Scanner {
return [];
}
- protected function scanChildren(string $path, $recursive, int $reuse, int $folderId, bool $lock, int|float $oldSize) {
+ protected function scanChildren(string $path, $recursive, int $reuse, int $folderId, bool $lock, int|float $oldSize, &$etagChanged = false) {
return 0;
}
diff --git a/lib/private/Files/ObjectStore/ObjectStoreStorage.php b/lib/private/Files/ObjectStore/ObjectStoreStorage.php
index 4dceee9a58b..eb8aaffe1e0 100644
--- a/lib/private/Files/ObjectStore/ObjectStoreStorage.php
+++ b/lib/private/Files/ObjectStore/ObjectStoreStorage.php
@@ -68,6 +68,8 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
private $logger;
+ private bool $handleCopiesAsOwned;
+
/** @var bool */
protected $validateWrites = true;
@@ -88,6 +90,7 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
if (isset($params['validateWrites'])) {
$this->validateWrites = (bool)$params['validateWrites'];
}
+ $this->handleCopiesAsOwned = (bool)($params['handleCopiesAsOwned'] ?? false);
$this->logger = \OC::$server->getLogger();
}
@@ -651,6 +654,10 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
try {
$this->objectStore->copyObject($sourceUrn, $targetUrn);
+ if ($this->handleCopiesAsOwned) {
+ // Copied the file thus we gain all permissions as we are the owner now ! warning while this aligns with local storage it should not be used and instead fix local storage !
+ $cache->update($targetId, ['permissions' => \OCP\Constants::PERMISSION_ALL]);
+ }
} catch (\Exception $e) {
$cache->remove($to);
diff --git a/lib/private/Files/ObjectStore/S3ConnectionTrait.php b/lib/private/Files/ObjectStore/S3ConnectionTrait.php
index 044c3cdc900..a1edfa1eb99 100644
--- a/lib/private/Files/ObjectStore/S3ConnectionTrait.php
+++ b/lib/private/Files/ObjectStore/S3ConnectionTrait.php
@@ -71,6 +71,11 @@ trait S3ConnectionTrait {
/** @var int */
private $putSizeLimit;
+ /** @var int */
+ private $copySizeLimit;
+
+ private bool $useMultipartCopy = true;
+
protected $test;
protected function parseParams($params) {
@@ -87,6 +92,8 @@ trait S3ConnectionTrait {
$this->storageClass = !empty($params['storageClass']) ? $params['storageClass'] : 'STANDARD';
$this->uploadPartSize = $params['uploadPartSize'] ?? 524288000;
$this->putSizeLimit = $params['putSizeLimit'] ?? 104857600;
+ $this->copySizeLimit = $params['copySizeLimit'] ?? 5242880000;
+ $this->useMultipartCopy = (bool)($params['useMultipartCopy'] ?? true);
$params['region'] = empty($params['region']) ? 'eu-west-1' : $params['region'];
$params['hostname'] = empty($params['hostname']) ? 's3.' . $params['region'] . '.amazonaws.com' : $params['hostname'];
if (!isset($params['port']) || $params['port'] === '') {
diff --git a/lib/private/Files/ObjectStore/S3ObjectTrait.php b/lib/private/Files/ObjectStore/S3ObjectTrait.php
index e9c52f11936..2ef9614ac85 100644
--- a/lib/private/Files/ObjectStore/S3ObjectTrait.php
+++ b/lib/private/Files/ObjectStore/S3ObjectTrait.php
@@ -191,15 +191,29 @@ trait S3ObjectTrait {
}
public function copyObject($from, $to, array $options = []) {
- $copy = new MultipartCopy($this->getConnection(), [
- "source_bucket" => $this->getBucket(),
- "source_key" => $from
- ], array_merge([
- "bucket" => $this->getBucket(),
- "key" => $to,
- "acl" => "private",
- "params" => $this->getSSECParameters() + $this->getSSECParameters(true)
- ], $options));
- $copy->copy();
+ $sourceMetadata = $this->getConnection()->headObject([
+ 'Bucket' => $this->getBucket(),
+ 'Key' => $from,
+ ] + $this->getSSECParameters());
+
+ $size = (int)($sourceMetadata->get('Size') ?? $sourceMetadata->get('ContentLength'));
+
+ if ($this->useMultipartCopy && $size > $this->copySizeLimit) {
+ $copy = new MultipartCopy($this->getConnection(), [
+ "source_bucket" => $this->getBucket(),
+ "source_key" => $from
+ ], array_merge([
+ "bucket" => $this->getBucket(),
+ "key" => $to,
+ "acl" => "private",
+ "params" => $this->getSSECParameters() + $this->getSSECParameters(true),
+ "source_metadata" => $sourceMetadata
+ ], $options));
+ $copy->copy();
+ } else {
+ $this->getConnection()->copy($this->getBucket(), $from, $this->getBucket(), $to, 'private', array_merge([
+ 'params' => $this->getSSECParameters() + $this->getSSECParameters(true)
+ ], $options));
+ }
}
}
diff --git a/lib/private/Files/Search/QueryOptimizer/PathPrefixOptimizer.php b/lib/private/Files/Search/QueryOptimizer/PathPrefixOptimizer.php
index 0caa9b12a02..664402f1238 100644
--- a/lib/private/Files/Search/QueryOptimizer/PathPrefixOptimizer.php
+++ b/lib/private/Files/Search/QueryOptimizer/PathPrefixOptimizer.php
@@ -4,6 +4,9 @@ declare(strict_types=1);
/**
* @copyright Copyright (c) 2021 Robin Appelman <robin@icewind.nl>
*
+ * @author Maxence Lange <maxence@artificial-owl.com>
+ * @author 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
@@ -48,7 +51,7 @@ class PathPrefixOptimizer extends QueryOptimizerStep {
}
public function processOperator(ISearchOperator &$operator) {
- if (!$this->useHashEq && $operator instanceof ISearchComparison && $operator->getField() === 'path' && $operator->getType() === ISearchComparison::COMPARE_EQUAL) {
+ if (!$this->useHashEq && $operator instanceof ISearchComparison && !$operator->getExtra() && $operator->getField() === 'path' && $operator->getType() === ISearchComparison::COMPARE_EQUAL) {
$operator->setQueryHint(ISearchComparison::HINT_PATH_EQ_HASH, false);
}
@@ -69,7 +72,7 @@ class PathPrefixOptimizer extends QueryOptimizerStep {
private function operatorPairIsPathPrefix(ISearchOperator $like, ISearchOperator $equal): bool {
return (
$like instanceof ISearchComparison && $equal instanceof ISearchComparison &&
- $like->getField() === 'path' && $equal->getField() === 'path' &&
+ !$like->getExtra() && !$equal->getExtra() && $like->getField() === 'path' && $equal->getField() === 'path' &&
$like->getType() === ISearchComparison::COMPARE_LIKE_CASE_SENSITIVE && $equal->getType() === ISearchComparison::COMPARE_EQUAL
&& $like->getValue() === SearchComparison::escapeLikeParameter($equal->getValue()) . '/%'
);
diff --git a/lib/private/Files/Search/SearchComparison.php b/lib/private/Files/Search/SearchComparison.php
index 122a1f730b4..d94b3e9dfab 100644
--- a/lib/private/Files/Search/SearchComparison.php
+++ b/lib/private/Files/Search/SearchComparison.php
@@ -1,7 +1,10 @@
<?php
+
+declare(strict_types=1);
/**
* @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl>
*
+ * @author Maxence Lange <maxence@artificial-owl.com>
* @author Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
@@ -25,48 +28,45 @@ namespace OC\Files\Search;
use OCP\Files\Search\ISearchComparison;
class SearchComparison implements ISearchComparison {
- /** @var string */
- private $type;
- /** @var string */
- private $field;
- /** @var string|integer|\DateTime */
- private $value;
- private $hints = [];
+ private array $hints = [];
- /**
- * SearchComparison constructor.
- *
- * @param string $type
- * @param string $field
- * @param \DateTime|int|string $value
- */
- public function __construct($type, $field, $value) {
- $this->type = $type;
- $this->field = $field;
- $this->value = $value;
+ public function __construct(
+ private string $type,
+ private string $field,
+ private \DateTime|int|string|bool $value,
+ private string $extra = ''
+ ) {
}
/**
* @return string
*/
- public function getType() {
+ public function getType(): string {
return $this->type;
}
/**
* @return string
*/
- public function getField() {
+ public function getField(): string {
return $this->field;
}
/**
- * @return \DateTime|int|string
+ * @return \DateTime|int|string|bool
*/
- public function getValue() {
+ public function getValue(): string|int|bool|\DateTime {
return $this->value;
}
+ /**
+ * @return string
+ * @since 28.0.0
+ */
+ public function getExtra(): string {
+ return $this->extra;
+ }
+
public function getQueryHint(string $name, $default) {
return $this->hints[$name] ?? $default;
}
diff --git a/lib/private/Files/Search/SearchOrder.php b/lib/private/Files/Search/SearchOrder.php
index 1395a87ac72..de514262bf5 100644
--- a/lib/private/Files/Search/SearchOrder.php
+++ b/lib/private/Files/Search/SearchOrder.php
@@ -2,6 +2,7 @@
/**
* @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl>
*
+ * @author Maxence Lange <maxence@artificial-owl.com>
* @author Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
@@ -26,34 +27,33 @@ use OCP\Files\FileInfo;
use OCP\Files\Search\ISearchOrder;
class SearchOrder implements ISearchOrder {
- /** @var string */
- private $direction;
- /** @var string */
- private $field;
+ public function __construct(
+ private string $direction,
+ private string $field,
+ private string $extra = ''
+ ) {
+ }
/**
- * SearchOrder constructor.
- *
- * @param string $direction
- * @param string $field
+ * @return string
*/
- public function __construct($direction, $field) {
- $this->direction = $direction;
- $this->field = $field;
+ public function getDirection(): string {
+ return $this->direction;
}
/**
* @return string
*/
- public function getDirection() {
- return $this->direction;
+ public function getField(): string {
+ return $this->field;
}
/**
* @return string
+ * @since 28.0.0
*/
- public function getField() {
- return $this->field;
+ public function getExtra(): string {
+ return $this->extra;
}
public function sortFileInfo(FileInfo $a, FileInfo $b): int {
diff --git a/lib/private/Files/SetupManager.php b/lib/private/Files/SetupManager.php
index cae0fd2f232..511e80bd7d9 100644
--- a/lib/private/Files/SetupManager.php
+++ b/lib/private/Files/SetupManager.php
@@ -24,8 +24,8 @@ declare(strict_types=1);
namespace OC\Files;
use OC\Files\Config\MountProviderCollection;
+use OC\Files\Mount\HomeMountPoint;
use OC\Files\Mount\MountPoint;
-use OC\Files\ObjectStore\HomeObjectStoreStorage;
use OC\Files\Storage\Common;
use OC\Files\Storage\Home;
use OC\Files\Storage\Storage;
@@ -39,7 +39,10 @@ use OC\Share20\ShareDisableChecker;
use OC_App;
use OC_Hook;
use OC_Util;
-use OCA\Files_Sharing\ISharedStorage;
+use OCA\Files_External\Config\ConfigAdapter;
+use OCA\Files_Sharing\External\Mount;
+use OCA\Files_Sharing\ISharedMountPoint;
+use OCA\Files_Sharing\SharedMount;
use OCP\Constants;
use OCP\Diagnostics\IEventLogger;
use OCP\EventDispatcher\IEventDispatcher;
@@ -117,7 +120,7 @@ class SetupManager {
$prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false);
Filesystem::addStorageWrapper('mount_options', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
- if ($storage->instanceOfStorage(Common::class)) {
+ if ($mount->getOptions() && $storage->instanceOfStorage(Common::class)) {
$storage->setMountOptions($mount->getOptions());
}
return $storage;
@@ -130,7 +133,7 @@ class SetupManager {
'sharing_mask',
function ($mountPoint, IStorage $storage, IMountPoint $mount) use ($reSharingEnabled, $sharingEnabledForUser) {
$sharingEnabledForMount = $mount->getOption('enable_sharing', true);
- $isShared = $storage->instanceOfStorage(ISharedStorage::class);
+ $isShared = $mount instanceof ISharedMountPoint;
if (!$sharingEnabledForMount || !$sharingEnabledForUser || (!$reSharingEnabled && $isShared)) {
return new PermissionsMask([
'storage' => $storage,
@@ -142,35 +145,30 @@ class SetupManager {
);
// install storage availability wrapper, before most other wrappers
- Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, IStorage $storage) {
- if (!$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage') && !$storage->isLocal()) {
+ Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
+ $externalMount = $mount instanceof ConfigAdapter || $mount instanceof Mount;
+ if ($externalMount && !$storage->isLocal()) {
return new Availability(['storage' => $storage]);
}
return $storage;
});
Filesystem::addStorageWrapper('oc_encoding', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
- if ($mount->getOption('encoding_compatibility', false) && !$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) {
+ if ($mount->getOption('encoding_compatibility', false) && !$mount instanceof SharedMount) {
return new Encoding(['storage' => $storage]);
}
return $storage;
});
$quotaIncludeExternal = $this->config->getSystemValue('quota_include_external_storage', false);
- Filesystem::addStorageWrapper('oc_quota', function ($mountPoint, $storage) use ($quotaIncludeExternal) {
+ Filesystem::addStorageWrapper('oc_quota', function ($mountPoint, $storage, IMountPoint $mount) use ($quotaIncludeExternal) {
// set up quota for home storages, even for other users
// which can happen when using sharing
-
- /**
- * @var Storage $storage
- */
- if ($storage->instanceOfStorage(HomeObjectStoreStorage::class) || $storage->instanceOfStorage(Home::class)) {
- if (is_object($storage->getUser())) {
- $user = $storage->getUser();
- return new Quota(['storage' => $storage, 'quotaCallback' => function () use ($user) {
- return OC_Util::getUserQuota($user);
- }, 'root' => 'files', 'include_external_storage' => $quotaIncludeExternal]);
- }
+ if ($mount instanceof HomeMountPoint) {
+ $user = $mount->getUser();
+ return new Quota(['storage' => $storage, 'quotaCallback' => function () use ($user) {
+ return OC_Util::getUserQuota($user);
+ }, 'root' => 'files', 'include_external_storage' => $quotaIncludeExternal]);
}
return $storage;
@@ -337,12 +335,13 @@ class SetupManager {
if ($this->rootSetup) {
return;
}
+
+ $this->setupBuiltinWrappers();
+
$this->rootSetup = true;
$this->eventLogger->start('fs:setup:root', 'Setup root filesystem');
- $this->setupBuiltinWrappers();
-
$rootMounts = $this->mountProviderCollection->getRootMounts();
foreach ($rootMounts as $rootMountProvider) {
$this->mountManager->addMount($rootMountProvider);
diff --git a/lib/private/Files/SimpleFS/SimpleFolder.php b/lib/private/Files/SimpleFS/SimpleFolder.php
index 4d24aa138c1..2c1f23f8e44 100644
--- a/lib/private/Files/SimpleFS/SimpleFolder.php
+++ b/lib/private/Files/SimpleFS/SimpleFolder.php
@@ -28,8 +28,8 @@ use OCP\Files\File;
use OCP\Files\Folder;
use OCP\Files\Node;
use OCP\Files\NotFoundException;
-use OCP\Files\SimpleFS\ISimpleFolder;
use OCP\Files\SimpleFS\ISimpleFile;
+use OCP\Files\SimpleFS\ISimpleFolder;
class SimpleFolder implements ISimpleFolder {
/** @var Folder */
diff --git a/lib/private/Files/Storage/DAV.php b/lib/private/Files/Storage/DAV.php
index 2d2bb52635b..e5bbbb560da 100644
--- a/lib/private/Files/Storage/DAV.php
+++ b/lib/private/Files/Storage/DAV.php
@@ -55,11 +55,11 @@ use OCP\ICertificateManager;
use OCP\IConfig;
use OCP\Util;
use Psr\Http\Message\ResponseInterface;
+use Psr\Log\LoggerInterface;
use Sabre\DAV\Client;
use Sabre\DAV\Xml\Property\ResourceType;
use Sabre\HTTP\ClientException;
use Sabre\HTTP\ClientHttpException;
-use Psr\Log\LoggerInterface;
use Sabre\HTTP\RequestInterface;
/**
@@ -921,7 +921,7 @@ class DAV extends Common {
}
foreach ($responses as $file => $response) {
- $file = urldecode($file);
+ $file = rawurldecode($file);
$file = substr($file, strlen($this->root));
$file = $this->cleanPath($file);
$this->statCache->set($file, $response);
diff --git a/lib/private/Files/Storage/Local.php b/lib/private/Files/Storage/Local.php
index eeb9e11b24e..0fca853da59 100644
--- a/lib/private/Files/Storage/Local.php
+++ b/lib/private/Files/Storage/Local.php
@@ -74,6 +74,8 @@ class Local extends \OC\Files\Storage\Common {
protected bool $unlinkOnTruncate;
+ protected bool $caseInsensitive = false;
+
public function __construct($arguments) {
if (!isset($arguments['datadir']) || !is_string($arguments['datadir'])) {
throw new \InvalidArgumentException('No data directory set for local storage');
@@ -93,6 +95,7 @@ class Local extends \OC\Files\Storage\Common {
$this->config = \OC::$server->get(IConfig::class);
$this->mimeTypeDetector = \OC::$server->get(IMimeTypeDetector::class);
$this->defUMask = $this->config->getSystemValue('localstorage.umask', 0022);
+ $this->caseInsensitive = $this->config->getSystemValueBool('localstorage.case_insensitive', false);
// support Write-Once-Read-Many file systems
$this->unlinkOnTruncate = $this->config->getSystemValueBool('localstorage.unlink_on_truncate', false);
@@ -162,6 +165,9 @@ class Local extends \OC\Files\Storage\Common {
}
public function is_dir($path) {
+ if ($this->caseInsensitive && !$this->file_exists($path)) {
+ return false;
+ }
if (str_ends_with($path, '/')) {
$path = substr($path, 0, -1);
}
@@ -169,6 +175,9 @@ class Local extends \OC\Files\Storage\Common {
}
public function is_file($path) {
+ if ($this->caseInsensitive && !$this->file_exists($path)) {
+ return false;
+ }
return is_file($this->getSourcePath($path));
}
@@ -271,7 +280,13 @@ class Local extends \OC\Files\Storage\Common {
}
public function file_exists($path) {
- return file_exists($this->getSourcePath($path));
+ if ($this->caseInsensitive) {
+ $fullPath = $this->getSourcePath($path);
+ $content = scandir(dirname($fullPath), SCANDIR_SORT_NONE);
+ return is_array($content) && array_search(basename($fullPath), $content) !== false;
+ } else {
+ return file_exists($this->getSourcePath($path));
+ }
}
public function filemtime($path) {
@@ -372,6 +387,11 @@ class Local extends \OC\Files\Storage\Common {
}
if (@rename($this->getSourcePath($source), $this->getSourcePath($target))) {
+ if ($this->caseInsensitive) {
+ if (mb_strtolower($target) === mb_strtolower($source) && !$this->file_exists($target)) {
+ return false;
+ }
+ }
return true;
}
@@ -388,6 +408,11 @@ class Local extends \OC\Files\Storage\Common {
}
$result = copy($this->getSourcePath($source), $this->getSourcePath($target));
umask($oldMask);
+ if ($this->caseInsensitive) {
+ if (mb_strtolower($target) === mb_strtolower($source) && !$this->file_exists($target)) {
+ return false;
+ }
+ }
return $result;
}
}
diff --git a/lib/private/Files/Storage/Wrapper/Encoding.php b/lib/private/Files/Storage/Wrapper/Encoding.php
index 6633cbf41e3..1bdb0e39f14 100644
--- a/lib/private/Files/Storage/Wrapper/Encoding.php
+++ b/lib/private/Files/Storage/Wrapper/Encoding.php
@@ -28,8 +28,8 @@
*/
namespace OC\Files\Storage\Wrapper;
-use OCP\Cache\CappedMemoryCache;
use OC\Files\Filesystem;
+use OCP\Cache\CappedMemoryCache;
use OCP\Files\Storage\IStorage;
use OCP\ICache;
diff --git a/lib/private/Files/Storage/Wrapper/Jail.php b/lib/private/Files/Storage/Wrapper/Jail.php
index 1921ac27848..592acd418ec 100644
--- a/lib/private/Files/Storage/Wrapper/Jail.php
+++ b/lib/private/Files/Storage/Wrapper/Jail.php
@@ -396,10 +396,7 @@ class Jail extends Wrapper {
* @return \OC\Files\Cache\Cache
*/
public function getCache($path = '', $storage = null) {
- if (!$storage) {
- $storage = $this->getWrapperStorage();
- }
- $sourceCache = $this->getWrapperStorage()->getCache($this->getUnjailedPath($path), $storage);
+ $sourceCache = $this->getWrapperStorage()->getCache($this->getUnjailedPath($path));
return new CacheJail($sourceCache, $this->rootPath);
}
diff --git a/lib/private/Files/Stream/Encryption.php b/lib/private/Files/Stream/Encryption.php
index bcf0a10740b..c57991f35a9 100644
--- a/lib/private/Files/Stream/Encryption.php
+++ b/lib/private/Files/Stream/Encryption.php
@@ -153,18 +153,18 @@ class Encryption extends Wrapper {
* @throws \BadMethodCallException
*/
public static function wrap($source, $internalPath, $fullPath, array $header,
- $uid,
- \OCP\Encryption\IEncryptionModule $encryptionModule,
- \OC\Files\Storage\Storage $storage,
- \OC\Files\Storage\Wrapper\Encryption $encStorage,
- \OC\Encryption\Util $util,
- \OC\Encryption\File $file,
- $mode,
- $size,
- $unencryptedSize,
- $headerSize,
- $signed,
- $wrapper = Encryption::class) {
+ $uid,
+ \OCP\Encryption\IEncryptionModule $encryptionModule,
+ \OC\Files\Storage\Storage $storage,
+ \OC\Files\Storage\Wrapper\Encryption $encStorage,
+ \OC\Encryption\Util $util,
+ \OC\Encryption\File $file,
+ $mode,
+ $size,
+ $unencryptedSize,
+ $headerSize,
+ $signed,
+ $wrapper = Encryption::class) {
$context = stream_context_create([
'ocencryption' => [
'source' => $source,
diff --git a/lib/private/Files/Template/TemplateManager.php b/lib/private/Files/Template/TemplateManager.php
index 878680caa4e..9d9f6416208 100644
--- a/lib/private/Files/Template/TemplateManager.php
+++ b/lib/private/Files/Template/TemplateManager.php
@@ -31,8 +31,8 @@ use OC\AppFramework\Bootstrap\Coordinator;
use OC\Files\Cache\Scanner;
use OC\Files\Filesystem;
use OCP\EventDispatcher\IEventDispatcher;
-use OCP\Files\Folder;
use OCP\Files\File;
+use OCP\Files\Folder;
use OCP\Files\GenericFileException;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
@@ -275,6 +275,11 @@ class TemplateManager implements ITemplateManager {
$isDefaultTemplates = $skeletonTemplatePath === $defaultTemplateDirectory;
$userLang = $this->l10nFactory->getUserLanguage($this->userManager->get($this->userId));
+ if ($skeletonTemplatePath === '') {
+ $this->setTemplatePath('');
+ return '';
+ }
+
try {
$l10n = $this->l10nFactory->get('lib', $userLang);
$userFolder = $this->rootFolder->getUserFolder($this->userId);
diff --git a/lib/private/Files/Type/Detection.php b/lib/private/Files/Type/Detection.php
index 9a61aa93b95..71b8cb986d7 100644
--- a/lib/private/Files/Type/Detection.php
+++ b/lib/private/Files/Type/Detection.php
@@ -75,9 +75,9 @@ class Detection implements IMimeTypeDetector {
private $defaultConfigDir;
public function __construct(IURLGenerator $urlGenerator,
- LoggerInterface $logger,
- string $customConfigDir,
- string $defaultConfigDir) {
+ LoggerInterface $logger,
+ string $customConfigDir,
+ string $defaultConfigDir) {
$this->urlGenerator = $urlGenerator;
$this->logger = $logger;
$this->customConfigDir = $customConfigDir;
@@ -96,8 +96,8 @@ class Detection implements IMimeTypeDetector {
* @param string|null $secureMimeType
*/
public function registerType(string $extension,
- string $mimetype,
- ?string $secureMimeType = null): void {
+ string $mimetype,
+ ?string $secureMimeType = null): void {
$this->mimetypes[$extension] = [$mimetype, $secureMimeType];
$this->secureMimeTypes[$mimetype] = $secureMimeType ?: $mimetype;
}
diff --git a/lib/private/Files/Utils/Scanner.php b/lib/private/Files/Utils/Scanner.php
index b482c89ffd5..6348427cb3b 100644
--- a/lib/private/Files/Utils/Scanner.php
+++ b/lib/private/Files/Utils/Scanner.php
@@ -41,11 +41,11 @@ use OCA\Files_Sharing\SharedStorage;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Events\BeforeFileScannedEvent;
use OCP\Files\Events\BeforeFolderScannedEvent;
-use OCP\Files\Events\NodeAddedToCache;
use OCP\Files\Events\FileCacheUpdated;
-use OCP\Files\Events\NodeRemovedFromCache;
use OCP\Files\Events\FileScannedEvent;
use OCP\Files\Events\FolderScannedEvent;
+use OCP\Files\Events\NodeAddedToCache;
+use OCP\Files\Events\NodeRemovedFromCache;
use OCP\Files\NotFoundException;
use OCP\Files\Storage\IStorage;
use OCP\Files\StorageNotAvailableException;
diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php
index 86651ab3e1a..df8990790bb 100644
--- a/lib/private/Files/View.php
+++ b/lib/private/Files/View.php
@@ -49,10 +49,10 @@ namespace OC\Files;
use Icewind\Streams\CallbackWrapper;
use OC\Files\Mount\MoveableMount;
use OC\Files\Storage\Storage;
-use OC\User\LazyUser;
use OC\Share\Share;
-use OC\User\User;
+use OC\User\LazyUser;
use OC\User\Manager as UserManager;
+use OC\User\User;
use OCA\Files_Sharing\SharedMount;
use OCP\Constants;
use OCP\Files\Cache\ICacheEntry;
@@ -287,12 +287,12 @@ class View {
$this->updaterEnabled = true;
}
- protected function writeUpdate(Storage $storage, string $internalPath, ?int $time = null): void {
+ protected function writeUpdate(Storage $storage, string $internalPath, ?int $time = null, ?int $sizeDifference = null): void {
if ($this->updaterEnabled) {
if (is_null($time)) {
$time = time();
}
- $storage->getUpdater()->update($internalPath, $time);
+ $storage->getUpdater()->update($internalPath, $time, $sizeDifference);
}
}
@@ -1173,7 +1173,9 @@ class View {
$this->removeUpdate($storage, $internalPath);
}
if ($result !== false && in_array('write', $hooks, true) && $operation !== 'fopen' && $operation !== 'touch') {
- $this->writeUpdate($storage, $internalPath);
+ $isCreateOperation = $operation === 'mkdir' || ($operation === 'file_put_contents' && in_array('create', $hooks, true));
+ $sizeDifference = $operation === 'mkdir' ? 0 : $result;
+ $this->writeUpdate($storage, $internalPath, null, $isCreateOperation ? $sizeDifference : null);
}
if ($result !== false && in_array('touch', $hooks)) {
$this->writeUpdate($storage, $internalPath, $extraParam);
@@ -1525,7 +1527,7 @@ class View {
$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()) {
+ if ($sharingDisabled) {
$rootEntry['permissions'] = $rootEntry['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
}