Skip to content

Commit f45eda7

Browse files
committed
files content provider
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
1 parent de1b933 commit f45eda7

File tree

5 files changed

+240
-1
lines changed

5 files changed

+240
-1
lines changed

lib/AppInfo/Application.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ public function register(IRegistrationContext $context): void {
4747
$context->registerConfigLexicon(ConfigLexicon::class);
4848

4949
$context->registerFullTextSearchService(FullTextSearchService::class);
50-
50+
$context->registerFullTextSearchContentProvider(FilesContentProvider::class);
51+
5152
$this->registerServices($this->getContainer());
5253
}
5354

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
namespace OCA\FullTextSearch\Files\Exceptions;
10+
11+
use \Exception;
12+
13+
class NodeNotFoundException extends Exception {
14+
}

lib/Files/Service/FilesService.php

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\FullTextSearch\Files\Service;
11+
12+
use NCU\FullTextSearch\Model\Document;
13+
use NCU\FullTextSearch\Model\DocumentAccess;
14+
use OC\User\NoUserException;
15+
use OCA\FullTextSearch\Files\Exceptions\NodeNotFoundException;
16+
use OCA\FullTextSearch\Service\LoggerService;
17+
use OCP\Files\Config\IUserMountCache;
18+
use OCP\Files\IRootFolder;
19+
use OCP\Files\Node;
20+
use OCP\Files\NotPermittedException;
21+
use OCP\FullTextSearch\Model\IIndexDocument;
22+
23+
class FilesService {
24+
public function __construct(
25+
private readonly IRootFolder $rootFolder,
26+
private readonly IUserMountCache $userMountCache,
27+
private readonly SharesService $sharesService,
28+
private readonly LoggerService $logger,
29+
) {
30+
}
31+
32+
public function getNode(int $nodeId): ?Node {
33+
$mounts = $this->userMountCache->getMountsForFileId($nodeId);
34+
if (empty($mounts ?? [])) {
35+
$this->logger->warning('empty mount for nodeId=' . $nodeId);
36+
return null;
37+
}
38+
39+
$mount = reset($mounts);
40+
try {
41+
return $this->rootFolder->getUserFolder($mount->getUser()->getUID())->getFirstNodeById($nodeId);
42+
} catch (NotPermittedException|NoUserException $e) {
43+
$this->logger->error('could not find node', ['exception' => $e]);
44+
}
45+
46+
return null;
47+
}
48+
49+
public function generateDocument(Node $node): ?Document {
50+
if ($node->getType() !== 'file') {
51+
return null;
52+
}
53+
54+
$document = new Document();
55+
// $document->setId((string)$node->getId());`
56+
57+
$document->setFlags(8);
58+
$document->setContent(base64_encode($node->getContent()), true);
59+
$document->setAccess($this->sharesService->getDocumentAccess($node));
60+
61+
return $document;
62+
}
63+
64+
}
65+
66+
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\FullTextSearch\Files\Service;
11+
12+
use NCU\FullTextSearch\Model\DocumentAccess;
13+
use OCA\FullTextSearch\Service\LoggerService;
14+
use OCP\DB\QueryBuilder\IQueryBuilder;
15+
use OCP\Files\Node;
16+
use OCP\IDBConnection;
17+
use OCP\Share\IShare;
18+
19+
class SharesService {
20+
public function __construct(
21+
private readonly IDBConnection $connection,
22+
private readonly LoggerService $logger,
23+
) {
24+
}
25+
26+
public function getDocumentAccess(Node $node): DocumentAccess {
27+
$owner = $node->getOwner()?->getUID() ?? '';
28+
$users = $groups = $circles = $links = [];
29+
foreach($this->getShares($node->getId()) as $share) {
30+
switch ($share['share_type']) {
31+
case IShare::TYPE_USER:
32+
$users[] = $share['share_with'];
33+
break;
34+
case IShare::TYPE_GROUP:
35+
$groups[] = $share['share_with'];
36+
break;
37+
case IShare::TYPE_CIRCLE:
38+
$circles[] = $share['share_with'];
39+
break;
40+
case IShare::TYPE_LINK:
41+
$links[] = $share['token'];
42+
break;
43+
}
44+
}
45+
46+
return new DocumentAccess(
47+
$owner,
48+
$users,
49+
$groups,
50+
$circles,
51+
$links,
52+
);
53+
}
54+
55+
private function getShares(int $nodeId): array {
56+
$qb = $this->connection->getQueryBuilder();
57+
$qb->select('share_type', 'share_with', 'token')
58+
->from('share')
59+
->where(
60+
$qb->expr()->eq('item_source', $qb->createNamedParameter($nodeId, IQueryBuilder::PARAM_INT)),
61+
$qb->expr()->isNotNull('parent')
62+
);
63+
64+
$shares = [];
65+
$cursor = $qb->executeQuery();
66+
while ($row = $cursor->fetch()) {
67+
$shares[] = [
68+
'share_type' => $row['share_type'],
69+
'share_with' => $row['share_with'],
70+
];
71+
}
72+
$cursor->closeCursor();
73+
74+
return $shares;
75+
}
76+
}
77+
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\FullTextSearch\Provider;
11+
12+
use Generator;
13+
use NCU\FullTextSearch\Model\Document;
14+
use OCA\FullTextSearch\Files\Service\FilesService;
15+
use OCA\FullTextSearch\Files\Service\SharesService;
16+
use NCU\FullTextSearch\IContentProvider;
17+
use NCU\FullTextSearch\IContentProviderImprovedSearch;
18+
use NCU\FullTextSearch\IContentProviderSyncIndex;
19+
use NCU\FullTextSearch\IIndexQueryHelper;
20+
use NCU\FullTextSearch\Model\UnindexedDocument;
21+
use OCP\FullTextSearch\Model\ISearchRequest;
22+
use OCP\FullTextSearch\Model\ISearchResult;
23+
use OCP\FullTextSearch\Model\ISearchTemplate;
24+
use OCP\IDBConnection;
25+
26+
class FilesContentProvider implements
27+
IContentProvider,
28+
IContentProviderSyncIndex,
29+
IContentProviderImprovedSearch {
30+
public function __construct(
31+
private readonly IDBConnection $connection,
32+
private readonly FilesService $filesService,
33+
private readonly SharesService $sharesService,
34+
) {
35+
}
36+
37+
public function getId(): string {
38+
return 'files';
39+
}
40+
41+
public function getConfiguration(): array {
42+
return [];
43+
}
44+
45+
public function getDocument(string $documentId): ?Document {
46+
$nodeId = (int)$documentId;
47+
$node = $this->filesService->getNode($nodeId);
48+
if ($node === null) {
49+
return null;
50+
}
51+
52+
return $this->filesService->generateDocument($node);
53+
}
54+
55+
// IContentProviderImprovedSearch
56+
public function getSearchTemplate(): ?ISearchTemplate {
57+
return null;
58+
}
59+
60+
// IContentProviderImprovedSearch
61+
public function improveSearchRequest(ISearchRequest $searchRequest): void {
62+
}
63+
64+
// IContentProviderImprovedSearch
65+
public function improveSearchResult(ISearchResult $searchResult): void {
66+
}
67+
68+
// IContentProviderSyncIndex
69+
public function getUnindexedDocuments(IIndexQueryHelper $qh): Generator {
70+
$qh->notNeeded();
71+
$qb = $this->connection->getQueryBuilder();
72+
$qb->select('fileid', 'mtime')
73+
->from('filecache');
74+
$result = $qb->executeQuery();
75+
while ($row = $result->fetch()) {
76+
yield new UnindexedDocument($row['fileid'], $row['mtime']);
77+
}
78+
$result->closeCursor();
79+
}
80+
}
81+

0 commit comments

Comments
 (0)