Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,15 @@
use NCU\FullTextSearch\IManager;
use OC;
use OC\FullTextSearch\FullTextSearchManager;
use OCA\Files_FullTextSearch\Listeners\FileChanged;
use OCA\Files_FullTextSearch\Listeners\FileCreated;
use OCA\Files_FullTextSearch\Listeners\FileDeleted;
use OCA\Files_FullTextSearch\Listeners\FileRenamed;
use OCA\Files_FullTextSearch\Listeners\ShareCreated;
use OCA\Files_FullTextSearch\Listeners\ShareDeleted;
use OCA\FullTextSearch\Capabilities;
use OCA\FullTextSearch\ConfigLexicon;
use OCA\FullTextSearch\Files\FileEvents;
use OCA\FullTextSearch\Provider\FilesContentProvider;
use OCA\FullTextSearch\Search\UnifiedSearchProvider;
use OCA\FullTextSearch\Service\FullTextSearchService;
Expand All @@ -26,11 +33,17 @@
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\Files\Events\Node\NodeCreatedEvent;
use OCP\Files\Events\Node\NodeDeletedEvent;
use OCP\Files\Events\Node\NodeRenamedEvent;
use OCP\Files\Events\Node\NodeWrittenEvent;
use OCP\FullTextSearch\IFullTextSearchManager;
use OCP\IAppConfig;
use OCP\INavigationManager;
use OCP\IServerContainer;
use OCP\IURLGenerator;
use OCP\Share\Events\ShareCreatedEvent;
use OCP\Share\Events\ShareDeletedEvent;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
use Psr\Container\ContainerInterface;
use Throwable;
Expand All @@ -47,6 +60,15 @@ public function register(IRegistrationContext $context): void {
$context->registerConfigLexicon(ConfigLexicon::class);

$context->registerFullTextSearchService(FullTextSearchService::class);
$context->registerFullTextSearchContentProvider(FilesContentProvider::class);

// files life cycle events
$context->registerEventListener(NodeCreatedEvent::class, FileEvents::class);
$context->registerEventListener(NodeWrittenEvent::class, FileEvents::class);
$context->registerEventListener(NodeRenamedEvent::class, FileEvents::class);
$context->registerEventListener(NodeDeletedEvent::class, FileEvents::class);
$context->registerEventListener(ShareCreatedEvent::class, FileEvents::class);
$context->registerEventListener(ShareDeletedEvent::class, FileEvents::class);

$this->registerServices($this->getContainer());
}
Expand Down
28 changes: 6 additions & 22 deletions lib/Command/Sync.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,16 @@

namespace OCA\FullTextSearch\Command;

use Exception;
use OC\Core\Command\Base;
use OCA\FullTextSearch\Exceptions\PlatformTemporaryException;
use OCA\FullTextSearch\Exceptions\LockException;
use OCA\FullTextSearch\Service\FullTextSearchService;
use OCA\FullTextSearch\Service\LockService;
use OCA\FullTextSearch\Service\LoggerService;
use OCA\FullTextSearch\Service\SyncService;
use OCA\FullTextSearch\Tools\Traits\TArrayTools;
use Exception;
use OC\Core\Command\InterruptedException;
use OCA\FullTextSearch\ACommandBase;
use OCA\FullTextSearch\Exceptions\TickDoesNotExistException;
use OCA\FullTextSearch\Model\Index as ModelIndex;
use OCA\FullTextSearch\Model\Runner;
use OCA\FullTextSearch\Service\CliService;
use OCA\FullTextSearch\Service\ConfigService;
use OCA\FullTextSearch\Service\IndexService;
use OCA\FullTextSearch\Service\PlatformService;
use OCA\FullTextSearch\Service\ProviderService;
use OCA\FullTextSearch\Service\RunningService;
use OCP\IUserManager;
use OutOfBoundsException;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Terminal;
use Throwable;

class Sync extends Base {
public function __construct(
Expand All @@ -52,7 +34,7 @@ protected function configure() {
parent::configure();
$this->setName('fulltextsearch:sync')
->setDescription('Index files')
->addOption('info', '', InputOption::VALUE_NONE, 'display info entries')
->addOption('info', '', InputOption::VALUE_NONE, 'display info entries')
->addOption('no-output', '', InputOption::VALUE_NONE, 'no output, use nextcloud logs');
}

Expand All @@ -67,10 +49,12 @@ protected function execute(InputInterface $input, OutputInterface $output) {
try {
$this->lockService->update();
$this->syncProcess();
} catch (LockException $e) {
throw $e;
} catch (Exception $e) {
$this->loggerService->error('Exception while running fulltextsearch:sync', ['exception' => $e]);
}
sleep(10);
sleep(15);
}

return 0;
Expand Down
1 change: 1 addition & 0 deletions lib/ConfigLexicon.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use OCP\Config\Lexicon\ILexicon;
use OCP\Config\Lexicon\Strictness;
use OCP\Config\ValueType;
use OCP\IAppConfig;

/**
* Config Lexicon for fulltextsearch.
Expand Down
2 changes: 1 addition & 1 deletion lib/Db/SyncMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public function __construct(
/**
* @return DocumentSync[]
*/
public function getForcedSyncs(int $limit = 100): array {
public function getRequestedSyncs(int $limit = 100): array {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
Expand Down
4 changes: 2 additions & 2 deletions lib/Enum/SessionType.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
namespace OCA\FullTextSearch\Enum;

enum SessionType: string {
case UNKNOWN = '';
case FORCED = 'forced';
case CLOSED = '';
case REQUESTED = 'requested';
case SYNC = 'sync';
case RESYNC = 'sync_recent';
}
14 changes: 14 additions & 0 deletions lib/Files/Exceptions/NodeNotFoundException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\FullTextSearch\Files\Exceptions;

use \Exception;

class NodeNotFoundException extends Exception {
}
68 changes: 68 additions & 0 deletions lib/Files/FileEvents.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\FullTextSearch\Files;

use NCU\FullTextSearch\Exceptions\ServiceNotFoundException;
use NCU\FullTextSearch\IManager as IFullTextSearchManager;
use OCA\FullTextSearch\Service\LoggerService;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Files\Events\Node\NodeCreatedEvent;
use OCP\Files\Events\Node\NodeDeletedEvent;
use OCP\Files\Events\Node\NodeRenamedEvent;
use OCP\Files\Events\Node\NodeWrittenEvent;
use OCP\Files\InvalidPathException;
use OCP\Files\NotFoundException;
use OCP\Share\Events\ShareCreatedEvent;
use OCP\Share\Events\ShareDeletedEvent;
use Psr\Log\LoggerInterface;

readonly class FileEvents implements IEventListener {
public function __construct(
private IFullTextSearchManager $fullTextSearchManager,
private LoggerInterface $logger,
) {
}

public function handle(Event $event): void {
$this->logger->warning('handle 1');
try {
$service = $this->fullTextSearchManager->getService();
} catch (ServiceNotFoundException) {
// no service available
return;
}
$this->logger->warning('handle 2');

if ($event instanceof NodeCreatedEvent
|| $event instanceof NodeWrittenEvent
|| $event instanceof NodeRenamedEvent
|| $event instanceof NodeDeletedEvent) {
try {
$node = $event->getNode();
$service->requestIndex('files', (string)$node->getId());
} catch (InvalidPathException|NotFoundException) {
// cannot reach document
return;
}
}

if ($event instanceof ShareCreatedEvent
|| $event instanceof ShareDeletedEvent) {
try {
$node = $event->getShare()->getNode();
$service->requestIndex('files', (string)$node->getId());
} catch (NotFoundException|InvalidPathException) {
// cannot reach document
return;
}
}
}
}
66 changes: 66 additions & 0 deletions lib/Files/Service/FilesService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\FullTextSearch\Files\Service;

use NCU\FullTextSearch\Model\Document;
use NCU\FullTextSearch\Model\DocumentAccess;
use OC\User\NoUserException;
use OCA\FullTextSearch\Files\Exceptions\NodeNotFoundException;
use OCA\FullTextSearch\Service\LoggerService;
use OCP\Files\Config\IUserMountCache;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
use OCP\Files\NotPermittedException;
use OCP\FullTextSearch\Model\IIndexDocument;

class FilesService {
public function __construct(
private readonly IRootFolder $rootFolder,
private readonly IUserMountCache $userMountCache,
private readonly SharesService $sharesService,
private readonly LoggerService $logger,
) {
}

public function getNode(int $nodeId): ?Node {
$mounts = $this->userMountCache->getMountsForFileId($nodeId);
if (empty($mounts ?? [])) {
$this->logger->warning('empty mount for nodeId=' . $nodeId);
return null;
}

$mount = reset($mounts);
try {
return $this->rootFolder->getUserFolder($mount->getUser()->getUID())->getFirstNodeById($nodeId);
} catch (NotPermittedException|NoUserException $e) {
$this->logger->error('could not find node', ['exception' => $e]);
}

return null;
}

public function generateDocument(Node $node): ?Document {
if ($node->getType() !== 'file') {
return null;
}

$document = new Document();
// $document->setId((string)$node->getId());`

$document->setFlags(8);
$document->setContent(base64_encode($node->getContent()), true);
$document->setAccess($this->sharesService->getDocumentAccess($node));

return $document;
}

}


77 changes: 77 additions & 0 deletions lib/Files/Service/SharesService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\FullTextSearch\Files\Service;

use NCU\FullTextSearch\Model\DocumentAccess;
use OCA\FullTextSearch\Service\LoggerService;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Files\Node;
use OCP\IDBConnection;
use OCP\Share\IShare;

class SharesService {
public function __construct(
private readonly IDBConnection $connection,
private readonly LoggerService $logger,
) {
}

public function getDocumentAccess(Node $node): DocumentAccess {
$owner = $node->getOwner()?->getUID() ?? '';
$users = $groups = $circles = $links = [];
foreach($this->getShares($node->getId()) as $share) {
switch ($share['share_type']) {
case IShare::TYPE_USER:
$users[] = $share['share_with'];
break;
case IShare::TYPE_GROUP:
$groups[] = $share['share_with'];
break;
case IShare::TYPE_CIRCLE:
$circles[] = $share['share_with'];
break;
case IShare::TYPE_LINK:
$links[] = $share['token'];
break;
}
}

return new DocumentAccess(
$owner,
$users,
$groups,
$circles,
$links,
);
}

private function getShares(int $nodeId): array {
$qb = $this->connection->getQueryBuilder();
$qb->select('share_type', 'share_with', 'token')
->from('share')
->where(
$qb->expr()->eq('item_source', $qb->createNamedParameter($nodeId, IQueryBuilder::PARAM_INT)),
$qb->expr()->isNotNull('parent')
);

$shares = [];
$cursor = $qb->executeQuery();
while ($row = $cursor->fetch()) {
$shares[] = [
'share_type' => $row['share_type'],
'share_with' => $row['share_with'],
];
}
$cursor->closeCursor();

return $shares;
}
}

2 changes: 1 addition & 1 deletion lib/Migration/Version33001Date202511271645.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt
]);
$table->addColumn('checksum', Types::STRING, [
'length' => 16,
'notnull' => true,
'default' => '',
]);

$table->setPrimaryKey(['provider_id', 'document_id'], 'fts_i_pd');
Expand Down
Loading