Skip to content

Commit af5acc3

Browse files
Merge pull request #51810 from nextcloud/feat/getByAncestorInStorage
feat: Add new methods to list distinct mounts and retrieve all files in a mount
2 parents 764b582 + 43be97d commit af5acc3

File tree

3 files changed

+595
-0
lines changed

3 files changed

+595
-0
lines changed

lib/private/Files/Cache/FileAccess.php

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
use OC\FilesMetadata\FilesMetadataManager;
1212
use OC\SystemConfig;
13+
use OCP\DB\Exception;
1314
use OCP\DB\QueryBuilder\IQueryBuilder;
1415
use OCP\Files\Cache\IFileAccess;
1516
use OCP\Files\IMimeTypeLoader;
@@ -94,4 +95,128 @@ public function getByFileIdsInStorage(array $fileIds, int $storageId): array {
9495
$rows = $query->executeQuery()->fetchAll();
9596
return $this->rowsToEntries($rows);
9697
}
98+
99+
public function getByAncestorInStorage(int $storageId, int $folderId, int $fileIdCursor = 0, int $maxResults = 100, array $mimeTypeIds = [], bool $endToEndEncrypted = true, bool $serverSideEncrypted = true): \Generator {
100+
$qb = $this->getQuery();
101+
$qb->select('path')
102+
->from('filecache')
103+
->where($qb->expr()->eq('fileid', $qb->createNamedParameter($folderId, IQueryBuilder::PARAM_INT)));
104+
$result = $qb->executeQuery();
105+
/** @var array{path:string}|false $root */
106+
$root = $result->fetch();
107+
$result->closeCursor();
108+
109+
if ($root === false) {
110+
throw new Exception('Could not fetch storage root');
111+
}
112+
113+
$qb = $this->getQuery();
114+
115+
$path = $root['path'] === '' ? '' : $root['path'] . '/';
116+
117+
$qb->selectDistinct('*')
118+
->from('filecache', 'f')
119+
->where($qb->expr()->like('f.path', $qb->createNamedParameter($this->connection->escapeLikeParameter($path) . '%')))
120+
->andWhere($qb->expr()->eq('f.storage', $qb->createNamedParameter($storageId)))
121+
->andWhere($qb->expr()->gt('f.fileid', $qb->createNamedParameter($fileIdCursor, IQueryBuilder::PARAM_INT)));
122+
123+
if (!$endToEndEncrypted) {
124+
// End to end encrypted files are descendants of a folder with encrypted=1
125+
// Use a subquery to check the `encrypted` status of the parent folder
126+
$subQuery = $this->getQuery()->select('p.encrypted')
127+
->from('filecache', 'p')
128+
->andWhere($qb->expr()->eq('p.fileid', 'f.parent'))
129+
->getSQL();
130+
131+
$qb->andWhere(
132+
$qb->expr()->eq($qb->createFunction(sprintf('(%s)', $subQuery)), $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT))
133+
);
134+
}
135+
136+
if (!$serverSideEncrypted) {
137+
// Server side encrypted files have encrypted=1 directly
138+
$qb->andWhere($qb->expr()->eq('f.encrypted', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
139+
}
140+
141+
if (count($mimeTypeIds) > 0) {
142+
$qb->andWhere($qb->expr()->in('f.mimetype', $qb->createNamedParameter($mimeTypeIds, IQueryBuilder::PARAM_INT_ARRAY)));
143+
}
144+
145+
if ($maxResults !== 0) {
146+
$qb->setMaxResults($maxResults);
147+
}
148+
$qb->orderBy('f.fileid', 'ASC');
149+
$files = $qb->executeQuery();
150+
151+
while (
152+
/** @var array */
153+
$row = $files->fetch()
154+
) {
155+
yield Cache::cacheEntryFromData($row, $this->mimeTypeLoader);
156+
}
157+
158+
$files->closeCursor();
159+
}
160+
161+
public function getDistinctMounts(array $mountProviders = [], bool $onlyUserFilesMounts = true): \Generator {
162+
$qb = $this->connection->getQueryBuilder();
163+
$qb->selectDistinct(['root_id', 'storage_id', 'mount_provider_class'])
164+
->from('mounts');
165+
if ($onlyUserFilesMounts) {
166+
$qb->andWhere(
167+
$qb->expr()->orX(
168+
$qb->expr()->like('mount_point', $qb->createNamedParameter('/%/files/%')),
169+
$qb->expr()->in('mount_provider_class', $qb->createNamedParameter([
170+
\OC\Files\Mount\LocalHomeMountProvider::class,
171+
\OC\Files\Mount\ObjectHomeMountProvider::class,
172+
], IQueryBuilder::PARAM_STR_ARRAY))
173+
)
174+
);
175+
}
176+
if (count($mountProviders) > 0) {
177+
$qb->andWhere($qb->expr()->in('mount_provider_class', $qb->createNamedParameter($mountProviders, IQueryBuilder::PARAM_STR_ARRAY)));
178+
}
179+
$qb->orderBy('root_id', 'ASC');
180+
$result = $qb->executeQuery();
181+
182+
while (
183+
/** @var array{storage_id:int, root_id:int,mount_provider_class:string} $row */
184+
$row = $result->fetch()
185+
) {
186+
$storageId = (int)$row['storage_id'];
187+
$rootId = (int)$row['root_id'];
188+
$overrideRoot = $rootId;
189+
// LocalHomeMountProvider is the default provider for user home directories
190+
// ObjectHomeMountProvider is the home directory provider for when S3 primary storage is used
191+
if ($onlyUserFilesMounts && in_array($row['mount_provider_class'], [
192+
\OC\Files\Mount\LocalHomeMountProvider::class,
193+
\OC\Files\Mount\ObjectHomeMountProvider::class,
194+
], true)) {
195+
// Only crawl files, not cache or trashbin
196+
$qb = $this->getQuery();
197+
try {
198+
$qb->select('fileid')
199+
->from('filecache')
200+
->where($qb->expr()->eq('storage', $qb->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
201+
->andWhere($qb->expr()->eq('parent', $qb->createNamedParameter($rootId, IQueryBuilder::PARAM_INT)))
202+
->andWhere($qb->expr()->eq('path', $qb->createNamedParameter('files')));
203+
/** @var array|false $root */
204+
$root = $qb->executeQuery()->fetch();
205+
if ($root !== false) {
206+
$overrideRoot = (int)$root['fileid'];
207+
}
208+
} catch (Exception $e) {
209+
$this->logger->error('Could not fetch home storage files root for storage ' . $storageId, ['exception' => $e]);
210+
continue;
211+
}
212+
}
213+
// Reference to root_id is still necessary even if we have the overridden_root_id, because storage_id and root_id uniquely identify a mount
214+
yield [
215+
'storage_id' => $storageId,
216+
'root_id' => $rootId,
217+
'overridden_root' => $overrideRoot,
218+
];
219+
}
220+
$result->closeCursor();
221+
}
97222
}

lib/public/Files/Cache/IFileAccess.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,36 @@ public function getByFileIds(array $fileIds): array;
7979
* @since 29.0.0
8080
*/
8181
public function getByFileIdsInStorage(array $fileIds, int $storageId): array;
82+
83+
/**
84+
* Retrieves files stored in a specific storage that have a specified ancestor in the file hierarchy.
85+
* Allows filtering by mime types, encryption status, and limits the number of results.
86+
*
87+
* @param int $storageId The ID of the storage to search within.
88+
* @param int $folderId The file ID of the ancestor to base the search on.
89+
* @param int $fileIdCursor The last processed file ID. Only files with a higher ID will be included. Defaults to 0.
90+
* @param int $maxResults The maximum number of results to retrieve. If set to 0, all matching files will be retrieved.
91+
* @param list<int> $mimeTypeIds An array of mime types to filter the results. If empty, no mime type filtering will be applied.
92+
* @param bool $endToEndEncrypted Whether to include EndToEndEncrypted files
93+
* @param bool $serverSideEncrypted Whether to include ServerSideEncrypted files
94+
* @return \Generator<ICacheEntry> A generator yielding matching files as cache entries.
95+
* @throws \OCP\DB\Exception
96+
*
97+
* @since 32.0.0
98+
*/
99+
public function getByAncestorInStorage(int $storageId, int $folderId, int $fileIdCursor = 0, int $maxResults = 100, array $mimeTypeIds = [], bool $endToEndEncrypted = true, bool $serverSideEncrypted = true): \Generator;
100+
101+
/**
102+
* Retrieves a list of all distinct mounts.
103+
* Allows filtering by specific mount providers.
104+
* Optionally rewrites home directory root paths to avoid cache and trashbin.
105+
*
106+
* @param list<string> $mountProviders An array of mount provider class names to filter. If empty, all providers will be included.
107+
* @param bool $onlyUserFilesMounts Whether to rewrite the root IDs for home directories to only include user files and to only consider mounts with mount points in the user files.
108+
* @return \Generator<array{storage_id: int, root_id: int, overridden_root: int}> A generator yielding mount configurations as an array containing 'storage_id', 'root_id', and 'override_root'.
109+
* @throws \OCP\DB\Exception
110+
*
111+
* @since 32.0.0
112+
*/
113+
public function getDistinctMounts(array $mountProviders = [], bool $onlyUserFilesMounts = true): \Generator;
82114
}

0 commit comments

Comments
 (0)