Browse Source
Merge pull request #40761 from nextcloud/enh/noid/files-metadata
Merge pull request #40761 from nextcloud/enh/noid/files-metadata
IFilesMetadatapull/41333/head
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 3723 additions and 150 deletions
-
9apps/dav/lib/Connector/Sabre/FilesPlugin.php
-
102apps/dav/lib/Files/FileSearchBackend.php
-
5apps/dav/lib/Server.php
-
5apps/dav/tests/unit/Files/FileSearchBackendTest.php
-
15apps/files/lib/Command/Scan.php
-
10apps/files_trashbin/lib/Trash/TrashItem.php
-
119core/Command/FilesMetadata/Get.php
-
83core/Migrations/Version28000Date20231004103301.php
-
2core/register_command.php
-
22lib/composer/composer/autoload_classmap.php
-
22lib/composer/composer/autoload_static.php
-
3lib/private/Files/Cache/CacheQueryBuilder.php
-
70lib/private/Files/Cache/QuerySearchHelper.php
-
97lib/private/Files/Cache/SearchBuilder.php
-
9lib/private/Files/FileInfo.php
-
9lib/private/Files/Node/LazyFolder.php
-
11lib/private/Files/Node/Node.php
-
7lib/private/Files/Search/QueryOptimizer/PathPrefixOptimizer.php
-
42lib/private/Files/Search/SearchComparison.php
-
30lib/private/Files/Search/SearchOrder.php
-
281lib/private/FilesMetadata/FilesMetadataManager.php
-
67lib/private/FilesMetadata/Job/UpdateSingleMetadata.php
-
64lib/private/FilesMetadata/Listener/MetadataDelete.php
-
64lib/private/FilesMetadata/Listener/MetadataUpdate.php
-
589lib/private/FilesMetadata/Model/FilesMetadata.php
-
165lib/private/FilesMetadata/Model/MetadataQuery.php
-
397lib/private/FilesMetadata/Model/MetadataValueWrapper.php
-
195lib/private/FilesMetadata/Service/IndexRequestService.php
-
160lib/private/FilesMetadata/Service/MetadataRequestService.php
-
5lib/private/Server.php
-
9lib/public/Files/FileInfo.php
-
15lib/public/Files/Search/ISearchComparison.php
-
13lib/public/Files/Search/ISearchOrder.php
-
68lib/public/FilesMetadata/AMetadataEvent.php
-
40lib/public/FilesMetadata/Event/MetadataBackgroundEvent.php
-
67lib/public/FilesMetadata/Event/MetadataLiveEvent.php
-
34lib/public/FilesMetadata/Exceptions/FilesMetadataException.php
-
32lib/public/FilesMetadata/Exceptions/FilesMetadataKeyFormatException.php
-
32lib/public/FilesMetadata/Exceptions/FilesMetadataNotFoundException.php
-
32lib/public/FilesMetadata/Exceptions/FilesMetadataTypeException.php
-
139lib/public/FilesMetadata/IFilesMetadataManager.php
-
343lib/public/FilesMetadata/Model/IFilesMetadata.php
-
90lib/public/FilesMetadata/Model/IMetadataQuery.php
-
300lib/public/FilesMetadata/Model/IMetadataValueWrapper.php
@ -0,0 +1,119 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2023 Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @author Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @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\Core\Command\FilesMetadata; |
|||
|
|||
use OC\User\NoUserException; |
|||
use OCP\Files\IRootFolder; |
|||
use OCP\Files\NotFoundException; |
|||
use OCP\Files\NotPermittedException; |
|||
use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException; |
|||
use OCP\FilesMetadata\IFilesMetadataManager; |
|||
use Symfony\Component\Console\Command\Command; |
|||
use Symfony\Component\Console\Input\InputArgument; |
|||
use Symfony\Component\Console\Input\InputInterface; |
|||
use Symfony\Component\Console\Input\InputOption; |
|||
use Symfony\Component\Console\Output\OutputInterface; |
|||
|
|||
class Get extends Command { |
|||
public function __construct( |
|||
private IRootFolder $rootFolder, |
|||
private IFilesMetadataManager $filesMetadataManager, |
|||
) { |
|||
parent::__construct(); |
|||
} |
|||
|
|||
protected function configure(): void { |
|||
$this->setName('metadata:get') |
|||
->setDescription('get stored metadata about a file, by its id') |
|||
->addArgument( |
|||
'fileId', |
|||
InputArgument::REQUIRED, |
|||
'id of the file document' |
|||
) |
|||
->addArgument( |
|||
'userId', |
|||
InputArgument::OPTIONAL, |
|||
'file owner' |
|||
) |
|||
->addOption( |
|||
'as-array', |
|||
'', |
|||
InputOption::VALUE_NONE, |
|||
'display metadata as a simple key=>value array' |
|||
) |
|||
->addOption( |
|||
'refresh', |
|||
'', |
|||
InputOption::VALUE_NONE, |
|||
'refresh metadata' |
|||
) |
|||
->addOption( |
|||
'reset', |
|||
'', |
|||
InputOption::VALUE_NONE, |
|||
'refresh metadata from scratch' |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* @throws NotPermittedException |
|||
* @throws FilesMetadataNotFoundException |
|||
* @throws NoUserException |
|||
* @throws NotFoundException |
|||
*/ |
|||
protected function execute(InputInterface $input, OutputInterface $output): int { |
|||
$fileId = (int)$input->getArgument('fileId'); |
|||
|
|||
if ($input->getOption('reset')) { |
|||
$this->filesMetadataManager->deleteMetadata($fileId); |
|||
if (!$input->getOption('refresh')) { |
|||
return self::SUCCESS; |
|||
} |
|||
} |
|||
|
|||
if ($input->getOption('refresh')) { |
|||
$node = $this->rootFolder->getUserFolder($input->getArgument('userId'))->getById($fileId); |
|||
if (count($node) === 0) { |
|||
throw new NotFoundException(); |
|||
} |
|||
$metadata = $this->filesMetadataManager->refreshMetadata( |
|||
$node[0], |
|||
IFilesMetadataManager::PROCESS_LIVE | IFilesMetadataManager::PROCESS_BACKGROUND |
|||
); |
|||
} else { |
|||
$metadata = $this->filesMetadataManager->getMetadata($fileId); |
|||
} |
|||
|
|||
if ($input->getOption('as-array')) { |
|||
$output->writeln(json_encode($metadata->asArray(), JSON_PRETTY_PRINT)); |
|||
} else { |
|||
$output->writeln(json_encode($metadata, JSON_PRETTY_PRINT)); |
|||
} |
|||
|
|||
return self::SUCCESS; |
|||
} |
|||
} |
@ -0,0 +1,83 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
/** |
|||
* @copyright 2023 Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @author Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @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\Core\Migrations; |
|||
|
|||
use Closure; |
|||
use OCP\DB\ISchemaWrapper; |
|||
use OCP\DB\Types; |
|||
use OCP\Migration\IOutput; |
|||
use OCP\Migration\SimpleMigrationStep; |
|||
|
|||
class Version28000Date20231004103301 extends SimpleMigrationStep { |
|||
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper { |
|||
/** @var ISchemaWrapper $schema */ |
|||
$schema = $schemaClosure(); |
|||
$updated = false; |
|||
|
|||
if (!$schema->hasTable('files_metadata')) { |
|||
$table = $schema->createTable('files_metadata'); |
|||
$table->addColumn('id', Types::BIGINT, [ |
|||
'autoincrement' => true, |
|||
'notnull' => true, |
|||
'length' => 15, |
|||
'unsigned' => true, |
|||
]); |
|||
$table->addColumn('file_id', Types::BIGINT, ['notnull' => false, 'length' => 15,]); |
|||
$table->addColumn('json', Types::TEXT); |
|||
$table->addColumn('sync_token', Types::STRING, ['length' => 15]); |
|||
$table->addColumn('last_update', Types::DATETIME); |
|||
|
|||
$table->setPrimaryKey(['id']); |
|||
$table->addUniqueIndex(['file_id'], 'files_meta_fileid'); |
|||
$updated = true; |
|||
} |
|||
|
|||
if (!$schema->hasTable('files_metadata_index')) { |
|||
$table = $schema->createTable('files_metadata_index'); |
|||
$table->addColumn('id', Types::BIGINT, [ |
|||
'autoincrement' => true, |
|||
'notnull' => true, |
|||
'length' => 15, |
|||
'unsigned' => true, |
|||
]); |
|||
$table->addColumn('file_id', Types::BIGINT, ['notnull' => false, 'length' => 15]); |
|||
$table->addColumn('meta_key', Types::STRING, ['notnull' => false, 'length' => 31]); |
|||
$table->addColumn('meta_value_string', Types::STRING, ['notnull' => false, 'length' => 63]); |
|||
$table->addColumn('meta_value_int', Types::BIGINT, ['notnull' => false, 'length' => 11]); |
|||
|
|||
$table->setPrimaryKey(['id']); |
|||
$table->addIndex(['file_id', 'meta_key', 'meta_value_string'], 'f_meta_index'); |
|||
$table->addIndex(['file_id', 'meta_key', 'meta_value_int'], 'f_meta_index_i'); |
|||
$updated = true; |
|||
} |
|||
|
|||
if (!$updated) { |
|||
return null; |
|||
} |
|||
|
|||
return $schema; |
|||
} |
|||
} |
@ -0,0 +1,281 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
/** |
|||
* @copyright 2023 Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @author Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @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\FilesMetadata; |
|||
|
|||
use JsonException; |
|||
use OC\FilesMetadata\Job\UpdateSingleMetadata; |
|||
use OC\FilesMetadata\Listener\MetadataDelete; |
|||
use OC\FilesMetadata\Listener\MetadataUpdate; |
|||
use OC\FilesMetadata\Model\FilesMetadata; |
|||
use OC\FilesMetadata\Model\MetadataQuery; |
|||
use OC\FilesMetadata\Service\IndexRequestService; |
|||
use OC\FilesMetadata\Service\MetadataRequestService; |
|||
use OCP\BackgroundJob\IJobList; |
|||
use OCP\DB\Exception; |
|||
use OCP\DB\Exception as DBException; |
|||
use OCP\DB\QueryBuilder\IQueryBuilder; |
|||
use OCP\EventDispatcher\IEventDispatcher; |
|||
use OCP\Files\Events\Node\NodeCreatedEvent; |
|||
use OCP\Files\Events\Node\NodeDeletedEvent; |
|||
use OCP\Files\Events\Node\NodeWrittenEvent; |
|||
use OCP\Files\InvalidPathException; |
|||
use OCP\Files\Node; |
|||
use OCP\Files\NotFoundException; |
|||
use OCP\FilesMetadata\Event\MetadataBackgroundEvent; |
|||
use OCP\FilesMetadata\Event\MetadataLiveEvent; |
|||
use OCP\FilesMetadata\Exceptions\FilesMetadataException; |
|||
use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException; |
|||
use OCP\FilesMetadata\IFilesMetadataManager; |
|||
use OCP\FilesMetadata\Model\IFilesMetadata; |
|||
use OCP\FilesMetadata\Model\IMetadataQuery; |
|||
use OCP\FilesMetadata\Model\IMetadataValueWrapper; |
|||
use OCP\IConfig; |
|||
use Psr\Log\LoggerInterface; |
|||
|
|||
/** |
|||
* @inheritDoc |
|||
* @since 28.0.0 |
|||
*/ |
|||
class FilesMetadataManager implements IFilesMetadataManager { |
|||
public const CONFIG_KEY = 'files_metadata'; |
|||
private const JSON_MAXSIZE = 100000; |
|||
|
|||
private ?IFilesMetadata $all = null; |
|||
|
|||
public function __construct( |
|||
private IEventDispatcher $eventDispatcher, |
|||
private IJobList $jobList, |
|||
private IConfig $config, |
|||
private LoggerInterface $logger, |
|||
private MetadataRequestService $metadataRequestService, |
|||
private IndexRequestService $indexRequestService, |
|||
) { |
|||
} |
|||
|
|||
/** |
|||
* @inheritDoc |
|||
* |
|||
* @param Node $node related node |
|||
* @param int $process type of process |
|||
* |
|||
* @return IFilesMetadata |
|||
* @throws FilesMetadataException if metadata are invalid |
|||
* @throws InvalidPathException if path to file is not valid |
|||
* @throws NotFoundException if file cannot be found |
|||
* @see self::PROCESS_BACKGROUND |
|||
* @see self::PROCESS_LIVE |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function refreshMetadata( |
|||
Node $node, |
|||
int $process = self::PROCESS_LIVE |
|||
): IFilesMetadata { |
|||
try { |
|||
$metadata = $this->metadataRequestService->getMetadataFromFileId($node->getId()); |
|||
} catch (FilesMetadataNotFoundException) { |
|||
$metadata = new FilesMetadata($node->getId()); |
|||
} |
|||
|
|||
// if $process is LIVE, we enforce LIVE
|
|||
if ((self::PROCESS_LIVE & $process) !== 0) { |
|||
$event = new MetadataLiveEvent($node, $metadata); |
|||
} else { |
|||
$event = new MetadataBackgroundEvent($node, $metadata); |
|||
} |
|||
|
|||
$this->eventDispatcher->dispatchTyped($event); |
|||
$this->saveMetadata($event->getMetadata()); |
|||
|
|||
// if requested, we add a new job for next cron to refresh metadata out of main thread
|
|||
// if $process was set to LIVE+BACKGROUND, we run background process directly
|
|||
if ($event instanceof MetadataLiveEvent && $event->isRunAsBackgroundJobRequested()) { |
|||
if ((self::PROCESS_BACKGROUND & $process) !== 0) { |
|||
return $this->refreshMetadata($node, self::PROCESS_BACKGROUND); |
|||
} |
|||
|
|||
$this->jobList->add(UpdateSingleMetadata::class, [$node->getOwner()->getUID(), $node->getId()]); |
|||
} |
|||
|
|||
return $metadata; |
|||
} |
|||
|
|||
/** |
|||
* @param int $fileId file id |
|||
* |
|||
* @inheritDoc |
|||
* @return IFilesMetadata |
|||
* @throws FilesMetadataNotFoundException if not found |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getMetadata(int $fileId): IFilesMetadata { |
|||
return $this->metadataRequestService->getMetadataFromFileId($fileId); |
|||
} |
|||
|
|||
/** |
|||
* @param IFilesMetadata $filesMetadata metadata |
|||
* |
|||
* @inheritDoc |
|||
* @throws FilesMetadataException if metadata seems malformed |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function saveMetadata(IFilesMetadata $filesMetadata): void { |
|||
if ($filesMetadata->getFileId() === 0 || !$filesMetadata->updated()) { |
|||
return; |
|||
} |
|||
|
|||
$json = json_encode($filesMetadata->jsonSerialize()); |
|||
if (strlen($json) > self::JSON_MAXSIZE) { |
|||
throw new FilesMetadataException('json cannot exceed ' . self::JSON_MAXSIZE . ' characters long'); |
|||
} |
|||
|
|||
try { |
|||
if ($filesMetadata->getSyncToken() === '') { |
|||
$this->metadataRequestService->store($filesMetadata); |
|||
} else { |
|||
$this->metadataRequestService->updateMetadata($filesMetadata); |
|||
} |
|||
} catch (DBException $e) { |
|||
// most of the logged exception are the result of race condition
|
|||
// between 2 simultaneous process trying to create/update metadata
|
|||
$this->logger->warning('issue while saveMetadata', ['exception' => $e, 'metadata' => $filesMetadata]); |
|||
|
|||
return; |
|||
} |
|||
|
|||
// update indexes
|
|||
foreach ($filesMetadata->getIndexes() as $index) { |
|||
try { |
|||
$this->indexRequestService->updateIndex($filesMetadata, $index); |
|||
} catch (DBException $e) { |
|||
$this->logger->warning('issue while updateIndex', ['exception' => $e]); |
|||
} |
|||
} |
|||
|
|||
// update metadata types list
|
|||
$current = $this->getKnownMetadata(); |
|||
$current->import($filesMetadata->jsonSerialize(true)); |
|||
$this->config->setAppValue('core', self::CONFIG_KEY, json_encode($current)); |
|||
} |
|||
|
|||
/** |
|||
* @param int $fileId file id |
|||
* |
|||
* @inheritDoc |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function deleteMetadata(int $fileId): void { |
|||
try { |
|||
$this->metadataRequestService->dropMetadata($fileId); |
|||
} catch (Exception $e) { |
|||
$this->logger->warning('issue while deleteMetadata', ['exception' => $e, 'fileId' => $fileId]); |
|||
} |
|||
|
|||
try { |
|||
$this->indexRequestService->dropIndex($fileId); |
|||
} catch (Exception $e) { |
|||
$this->logger->warning('issue while deleteMetadata', ['exception' => $e, 'fileId' => $fileId]); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @param IQueryBuilder $qb |
|||
* @param string $fileTableAlias alias of the table that contains data about files |
|||
* @param string $fileIdField alias of the field that contains file ids |
|||
* |
|||
* @inheritDoc |
|||
* @return IMetadataQuery |
|||
* @see IMetadataQuery |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getMetadataQuery( |
|||
IQueryBuilder $qb, |
|||
string $fileTableAlias, |
|||
string $fileIdField |
|||
): IMetadataQuery { |
|||
return new MetadataQuery($qb, $this->getKnownMetadata(), $fileTableAlias, $fileIdField); |
|||
} |
|||
|
|||
/** |
|||
* @inheritDoc |
|||
* @return IFilesMetadata |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getKnownMetadata(): IFilesMetadata { |
|||
if (null !== $this->all) { |
|||
return $this->all; |
|||
} |
|||
$this->all = new FilesMetadata(); |
|||
|
|||
try { |
|||
$data = json_decode($this->config->getAppValue('core', self::CONFIG_KEY, '[]'), true, 127, JSON_THROW_ON_ERROR); |
|||
$this->all->import($data); |
|||
} catch (JsonException) { |
|||
$this->logger->warning('issue while reading stored list of metadata. Advised to run ./occ files:scan --all --generate-metadata'); |
|||
} |
|||
|
|||
return $this->all; |
|||
} |
|||
|
|||
/** |
|||
* @param string $key metadata key |
|||
* @param string $type metadata type |
|||
* @param bool $indexed TRUE if metadata can be search |
|||
* |
|||
* @inheritDoc |
|||
* @since 28.0.0 |
|||
* @see IMetadataValueWrapper::TYPE_INT |
|||
* @see IMetadataValueWrapper::TYPE_FLOAT |
|||
* @see IMetadataValueWrapper::TYPE_BOOL |
|||
* @see IMetadataValueWrapper::TYPE_ARRAY |
|||
* @see IMetadataValueWrapper::TYPE_STRING_LIST |
|||
* @see IMetadataValueWrapper::TYPE_INT_LIST |
|||
* @see IMetadataValueWrapper::TYPE_STRING |
|||
*/ |
|||
public function initMetadata(string $key, string $type, bool $indexed): void { |
|||
$current = $this->getKnownMetadata(); |
|||
try { |
|||
if ($current->getType($key) === $type && $indexed === $current->isIndex($key)) { |
|||
return; // if key exists, with same type and indexed, we do nothing.
|
|||
} |
|||
} catch (FilesMetadataNotFoundException) { |
|||
// if value does not exist, we keep on the writing of course
|
|||
} |
|||
|
|||
$current->import([$key => ['type' => $type, 'indexed' => $indexed]]); |
|||
$this->config->setAppValue('core', self::CONFIG_KEY, json_encode($current)); |
|||
} |
|||
|
|||
/** |
|||
* load listeners |
|||
* |
|||
* @param IEventDispatcher $eventDispatcher |
|||
*/ |
|||
public static function loadListeners(IEventDispatcher $eventDispatcher): void { |
|||
$eventDispatcher->addServiceListener(NodeCreatedEvent::class, MetadataUpdate::class); |
|||
$eventDispatcher->addServiceListener(NodeWrittenEvent::class, MetadataUpdate::class); |
|||
$eventDispatcher->addServiceListener(NodeDeletedEvent::class, MetadataDelete::class); |
|||
} |
|||
} |
@ -0,0 +1,67 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
/** |
|||
* @copyright 2023 Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @author Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @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\FilesMetadata\Job; |
|||
|
|||
use OC\FilesMetadata\FilesMetadataManager; |
|||
use OCP\AppFramework\Utility\ITimeFactory; |
|||
use OCP\BackgroundJob\QueuedJob; |
|||
use OCP\Files\IRootFolder; |
|||
use OCP\FilesMetadata\Event\MetadataLiveEvent; |
|||
use OCP\FilesMetadata\IFilesMetadataManager; |
|||
use Psr\Log\LoggerInterface; |
|||
|
|||
/** |
|||
* Simple background job, created when requested by an app during the |
|||
* dispatch of MetadataLiveEvent. |
|||
* This background job will re-run the event to refresh metadata on a non-live thread. |
|||
* |
|||
* @see MetadataLiveEvent::requestBackgroundJob() |
|||
* @since 28.0.0 |
|||
*/ |
|||
class UpdateSingleMetadata extends QueuedJob { |
|||
public function __construct( |
|||
ITimeFactory $time, |
|||
private IRootFolder $rootFolder, |
|||
private FilesMetadataManager $filesMetadataManager, |
|||
private LoggerInterface $logger |
|||
) { |
|||
parent::__construct($time); |
|||
} |
|||
|
|||
protected function run($argument) { |
|||
[$userId, $fileId] = $argument; |
|||
|
|||
try { |
|||
$node = $this->rootFolder->getUserFolder($userId)->getById($fileId); |
|||
if (count($node) > 0) { |
|||
$file = array_shift($node); |
|||
$this->filesMetadataManager->refreshMetadata($file, IFilesMetadataManager::PROCESS_BACKGROUND); |
|||
} |
|||
} catch (\Exception $e) { |
|||
$this->logger->warning('issue while running UpdateSingleMetadata', ['exception' => $e, 'userId' => $userId, 'fileId' => $fileId]); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,64 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
/** |
|||
* @copyright 2023 Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @author Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @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\FilesMetadata\Listener; |
|||
|
|||
use Exception; |
|||
use OCP\EventDispatcher\Event; |
|||
use OCP\EventDispatcher\IEventListener; |
|||
use OCP\Files\Events\Node\NodeDeletedEvent; |
|||
use OCP\FilesMetadata\IFilesMetadataManager; |
|||
use Psr\Log\LoggerInterface; |
|||
|
|||
/** |
|||
* Handle file deletion event and remove stored metadata related to the deleted file |
|||
* |
|||
* @template-implements IEventListener<NodeDeletedEvent> |
|||
*/ |
|||
class MetadataDelete implements IEventListener { |
|||
public function __construct( |
|||
private IFilesMetadataManager $filesMetadataManager, |
|||
private LoggerInterface $logger |
|||
) { |
|||
} |
|||
|
|||
/** |
|||
* @param Event $event |
|||
*/ |
|||
public function handle(Event $event): void { |
|||
if (!($event instanceof NodeDeletedEvent)) { |
|||
return; |
|||
} |
|||
|
|||
try { |
|||
$nodeId = (int)$event->getNode()->getId(); |
|||
if ($nodeId > 0) { |
|||
$this->filesMetadataManager->deleteMetadata($nodeId); |
|||
} |
|||
} catch (Exception $e) { |
|||
$this->logger->warning('issue while running MetadataDelete', ['exception' => $e]); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,64 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
/** |
|||
* @copyright 2023 Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @author Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @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\FilesMetadata\Listener; |
|||
|
|||
use Exception; |
|||
use OCP\EventDispatcher\Event; |
|||
use OCP\EventDispatcher\IEventListener; |
|||
use OCP\Files\Events\Node\NodeCreatedEvent; |
|||
use OCP\Files\Events\Node\NodeWrittenEvent; |
|||
use OCP\FilesMetadata\IFilesMetadataManager; |
|||
use Psr\Log\LoggerInterface; |
|||
|
|||
/** |
|||
* Handle file creation/modification events and initiate a new event related to the created/edited file. |
|||
* The generated new event is broadcast in order to obtain file related metadata from other apps. |
|||
* metadata will be stored in database. |
|||
* |
|||
* @template-implements IEventListener<NodeCreatedEvent|NodeWrittenEvent> |
|||
*/ |
|||
class MetadataUpdate implements IEventListener { |
|||
public function __construct( |
|||
private IFilesMetadataManager $filesMetadataManager, |
|||
private LoggerInterface $logger |
|||
) { |
|||
} |
|||
|
|||
/** |
|||
* @param Event $event |
|||
*/ |
|||
public function handle(Event $event): void { |
|||
if (!($event instanceof NodeCreatedEvent) && !($event instanceof NodeWrittenEvent)) { |
|||
return; |
|||
} |
|||
|
|||
try { |
|||
$this->filesMetadataManager->refreshMetadata($event->getNode()); |
|||
} catch (Exception $e) { |
|||
$this->logger->warning('issue while running MetadataUpdate', ['exception' => $e]); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,589 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
/** |
|||
* @copyright 2023 Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @author Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @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\FilesMetadata\Model; |
|||
|
|||
use JsonException; |
|||
use OCP\FilesMetadata\Exceptions\FilesMetadataKeyFormatException; |
|||
use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException; |
|||
use OCP\FilesMetadata\Exceptions\FilesMetadataTypeException; |
|||
use OCP\FilesMetadata\Model\IFilesMetadata; |
|||
use OCP\FilesMetadata\Model\IMetadataValueWrapper; |
|||
|
|||
/** |
|||
* Model that represent metadata linked to a specific file. |
|||
* |
|||
* @inheritDoc |
|||
* @since 28.0.0 |
|||
*/ |
|||
class FilesMetadata implements IFilesMetadata { |
|||
/** @var array<string, MetadataValueWrapper> */ |
|||
private array $metadata = []; |
|||
private bool $updated = false; |
|||
private int $lastUpdate = 0; |
|||
private string $syncToken = ''; |
|||
|
|||
public function __construct( |
|||
private int $fileId = 0 |
|||
) { |
|||
} |
|||
|
|||
/** |
|||
* @inheritDoc |
|||
* @return int related file id |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getFileId(): int { |
|||
return $this->fileId; |
|||
} |
|||
|
|||
/** |
|||
* @inheritDoc |
|||
* @return int timestamp |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function lastUpdateTimestamp(): int { |
|||
return $this->lastUpdate; |
|||
} |
|||
|
|||
/** |
|||
* @inheritDoc |
|||
* @return string token |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getSyncToken(): string { |
|||
return $this->syncToken; |
|||
} |
|||
|
|||
/** |
|||
* @inheritDoc |
|||
* @return string[] list of keys |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getKeys(): array { |
|||
return array_keys($this->metadata); |
|||
} |
|||
|
|||
/** |
|||
* @param string $needle metadata key to search |
|||
* |
|||
* @inheritDoc |
|||
* @return bool TRUE if key exist |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function hasKey(string $needle): bool { |
|||
return (in_array($needle, $this->getKeys())); |
|||
} |
|||
|
|||
/** |
|||
* @inheritDoc |
|||
* @return string[] list of indexes |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getIndexes(): array { |
|||
$indexes = []; |
|||
foreach ($this->getKeys() as $key) { |
|||
if ($this->metadata[$key]->isIndexed()) { |
|||
$indexes[] = $key; |
|||
} |
|||
} |
|||
|
|||
return $indexes; |
|||
} |
|||
|
|||
/** |
|||
* @param string $key metadata key |
|||
* |
|||
* @inheritDoc |
|||
* @return bool TRUE if key exists and is set as indexed |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function isIndex(string $key): bool { |
|||
return $this->metadata[$key]?->isIndexed() ?? false; |
|||
} |
|||
|
|||
/** |
|||
* @param string $key metadata key |
|||
* |
|||
* @inheritDoc |
|||
* @return string metadata value |
|||
* @throws FilesMetadataNotFoundException |
|||
* @throws FilesMetadataTypeException |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function get(string $key): string { |
|||
if (!array_key_exists($key, $this->metadata)) { |
|||
throw new FilesMetadataNotFoundException(); |
|||
} |
|||
|
|||
return $this->metadata[$key]->getValueString(); |
|||
} |
|||
|
|||
/** |
|||
* @param string $key metadata key |
|||
* |
|||
* @inheritDoc |
|||
* @return int metadata value |
|||
* @throws FilesMetadataNotFoundException |
|||
* @throws FilesMetadataTypeException |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getInt(string $key): int { |
|||
if (!array_key_exists($key, $this->metadata)) { |
|||
throw new FilesMetadataNotFoundException(); |
|||
} |
|||
|
|||
return $this->metadata[$key]->getValueInt(); |
|||
} |
|||
|
|||
/** |
|||
* @param string $key metadata key |
|||
* |
|||
* @inheritDoc |
|||
* @return float metadata value |
|||
* @throws FilesMetadataNotFoundException |
|||
* @throws FilesMetadataTypeException |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getFloat(string $key): float { |
|||
if (!array_key_exists($key, $this->metadata)) { |
|||
throw new FilesMetadataNotFoundException(); |
|||
} |
|||
|
|||
return $this->metadata[$key]->getValueFloat(); |
|||
} |
|||
|
|||
/** |
|||
* @param string $key metadata key |
|||
* |
|||
* @inheritDoc |
|||
* @return bool metadata value |
|||
* @throws FilesMetadataNotFoundException |
|||
* @throws FilesMetadataTypeException |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getBool(string $key): bool { |
|||
if (!array_key_exists($key, $this->metadata)) { |
|||
throw new FilesMetadataNotFoundException(); |
|||
} |
|||
|
|||
return $this->metadata[$key]->getValueBool(); |
|||
} |
|||
|
|||
/** |
|||
* @param string $key metadata key |
|||
* |
|||
* @inheritDoc |
|||
* @return array metadata value |
|||
* @throws FilesMetadataNotFoundException |
|||
* @throws FilesMetadataTypeException |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getArray(string $key): array { |
|||
if (!array_key_exists($key, $this->metadata)) { |
|||
throw new FilesMetadataNotFoundException(); |
|||
} |
|||
|
|||
return $this->metadata[$key]->getValueArray(); |
|||
} |
|||
|
|||
/** |
|||
* @param string $key metadata key |
|||
* |
|||
* @inheritDoc |
|||
* @return string[] metadata value |
|||
* @throws FilesMetadataNotFoundException |
|||
* @throws FilesMetadataTypeException |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getStringList(string $key): array { |
|||
if (!array_key_exists($key, $this->metadata)) { |
|||
throw new FilesMetadataNotFoundException(); |
|||
} |
|||
|
|||
return $this->metadata[$key]->getValueStringList(); |
|||
} |
|||
|
|||
/** |
|||
* @param string $key metadata key |
|||
* |
|||
* @inheritDoc |
|||
* @return int[] metadata value |
|||
* @throws FilesMetadataNotFoundException |
|||
* @throws FilesMetadataTypeException |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getIntList(string $key): array { |
|||
if (!array_key_exists($key, $this->metadata)) { |
|||
throw new FilesMetadataNotFoundException(); |
|||
} |
|||
|
|||
return $this->metadata[$key]->getValueIntList(); |
|||
} |
|||
|
|||
/** |
|||
* @param string $key metadata key |
|||
* |
|||
* @inheritDoc |
|||
* @return string value type |
|||
* @throws FilesMetadataNotFoundException |
|||
* @see IMetadataValueWrapper::TYPE_STRING |
|||
* @see IMetadataValueWrapper::TYPE_INT |
|||
* @see IMetadataValueWrapper::TYPE_FLOAT |
|||
* @see IMetadataValueWrapper::TYPE_BOOL |
|||
* @see IMetadataValueWrapper::TYPE_ARRAY |
|||
* @see IMetadataValueWrapper::TYPE_STRING_LIST |
|||
* @see IMetadataValueWrapper::TYPE_INT_LIST |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getType(string $key): string { |
|||
if (!array_key_exists($key, $this->metadata)) { |
|||
throw new FilesMetadataNotFoundException(); |
|||
} |
|||
|
|||
return $this->metadata[$key]->getType(); |
|||
} |
|||
|
|||
/** |
|||
* @param string $key metadata key |
|||
* @param string $value metadata value |
|||
* @param bool $index set TRUE if value must be indexed |
|||
* |
|||
* @inheritDoc |
|||
* @return self |
|||
* @throws FilesMetadataKeyFormatException |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function set(string $key, string $value, bool $index = false): IFilesMetadata { |
|||
$this->confirmKeyFormat($key); |
|||
try { |
|||
if ($this->get($key) === $value && $index === $this->isIndex($key)) { |
|||
return $this; // we ignore if value and index have not changed
|
|||
} |
|||
} catch (FilesMetadataNotFoundException|FilesMetadataTypeException $e) { |
|||
// if value does not exist, or type has changed, we keep on the writing
|
|||
} |
|||
|
|||
$meta = new MetadataValueWrapper(IMetadataValueWrapper::TYPE_STRING); |
|||
$this->updated = true; |
|||
$this->metadata[$key] = $meta->setValueString($value)->setIndexed($index); |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* @param string $key metadata key |
|||
* @param int $value metadata value |
|||
* @param bool $index set TRUE if value must be indexed |
|||
* |
|||
* @inheritDoc |
|||
* @return self |
|||
* @throws FilesMetadataKeyFormatException |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function setInt(string $key, int $value, bool $index = false): IFilesMetadata { |
|||
$this->confirmKeyFormat($key); |
|||
try { |
|||
if ($this->getInt($key) === $value && $index === $this->isIndex($key)) { |
|||
return $this; // we ignore if value have not changed
|
|||
} |
|||
} catch (FilesMetadataNotFoundException|FilesMetadataTypeException $e) { |
|||
// if value does not exist, or type has changed, we keep on the writing
|
|||
} |
|||
|
|||
$meta = new MetadataValueWrapper(IMetadataValueWrapper::TYPE_INT); |
|||
$this->metadata[$key] = $meta->setValueInt($value)->setIndexed($index); |
|||
$this->updated = true; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* @param string $key metadata key |
|||
* @param float $value metadata value |
|||
* |
|||
* @inheritDoc |
|||
* @return self |
|||
* @throws FilesMetadataKeyFormatException |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function setFloat(string $key, float $value, bool $index = false): IFilesMetadata { |
|||
$this->confirmKeyFormat($key); |
|||
try { |
|||
if ($this->getFloat($key) === $value && $index === $this->isIndex($key)) { |
|||
return $this; // we ignore if value have not changed
|
|||
} |
|||
} catch (FilesMetadataNotFoundException|FilesMetadataTypeException $e) { |
|||
// if value does not exist, or type has changed, we keep on the writing
|
|||
} |
|||
|
|||
$meta = new MetadataValueWrapper(IMetadataValueWrapper::TYPE_FLOAT); |
|||
$this->metadata[$key] = $meta->setValueFloat($value)->setIndexed($index); |
|||
$this->updated = true; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* @param string $key metadata key |
|||
* @param bool $value metadata value |
|||
* @param bool $index set TRUE if value must be indexed |
|||
* |
|||
* @inheritDoc |
|||
* @return self |
|||
* @throws FilesMetadataKeyFormatException |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function setBool(string $key, bool $value, bool $index = false): IFilesMetadata { |
|||
$this->confirmKeyFormat($key); |
|||
try { |
|||
if ($this->getBool($key) === $value && $index === $this->isIndex($key)) { |
|||
return $this; // we ignore if value have not changed
|
|||
} |
|||
} catch (FilesMetadataNotFoundException|FilesMetadataTypeException $e) { |
|||
// if value does not exist, or type has changed, we keep on the writing
|
|||
} |
|||
|
|||
$meta = new MetadataValueWrapper(IMetadataValueWrapper::TYPE_BOOL); |
|||
$this->metadata[$key] = $meta->setValueBool($value)->setIndexed($index); |
|||
$this->updated = true; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* @param string $key metadata key |
|||
* @param array $value metadata value |
|||
* |
|||
* @inheritDoc |
|||
* @return self |
|||
* @throws FilesMetadataKeyFormatException |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function setArray(string $key, array $value): IFilesMetadata { |
|||
$this->confirmKeyFormat($key); |
|||
try { |
|||
if ($this->getArray($key) === $value) { |
|||
return $this; // we ignore if value have not changed
|
|||
} |
|||
} catch (FilesMetadataNotFoundException|FilesMetadataTypeException $e) { |
|||
// if value does not exist, or type has changed, we keep on the writing
|
|||
} |
|||
|
|||
$meta = new MetadataValueWrapper(IMetadataValueWrapper::TYPE_ARRAY); |
|||
$this->metadata[$key] = $meta->setValueArray($value); |
|||
$this->updated = true; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* @param string $key metadata key |
|||
* @param string[] $value metadata value |
|||
* @param bool $index set TRUE if each values from the list must be indexed |
|||
* |
|||
* @inheritDoc |
|||
* @return self |
|||
* @throws FilesMetadataKeyFormatException |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function setStringList(string $key, array $value, bool $index = false): IFilesMetadata { |
|||
$this->confirmKeyFormat($key); |
|||
try { |
|||
if ($this->getStringList($key) === $value) { |
|||
return $this; // we ignore if value have not changed
|
|||
} |
|||
} catch (FilesMetadataNotFoundException|FilesMetadataTypeException $e) { |
|||
// if value does not exist, or type has changed, we keep on the writing
|
|||
} |
|||
|
|||
$meta = new MetadataValueWrapper(IMetadataValueWrapper::TYPE_STRING_LIST); |
|||
$this->metadata[$key] = $meta->setValueStringList($value)->setIndexed($index); |
|||
$this->updated = true; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* @param string $key metadata key |
|||
* @param int[] $value metadata value |
|||
* @param bool $index set TRUE if each values from the list must be indexed |
|||
* |
|||
* @inheritDoc |
|||
* @return self |
|||
* @throws FilesMetadataKeyFormatException |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function setIntList(string $key, array $value, bool $index = false): IFilesMetadata { |
|||
$this->confirmKeyFormat($key); |
|||
try { |
|||
if ($this->getIntList($key) === $value) { |
|||
return $this; // we ignore if value have not changed
|
|||
} |
|||
} catch (FilesMetadataNotFoundException|FilesMetadataTypeException $e) { |
|||
// if value does not exist, or type has changed, we keep on the writing
|
|||
} |
|||
|
|||
$valueWrapper = new MetadataValueWrapper(IMetadataValueWrapper::TYPE_STRING_LIST); |
|||
$this->metadata[$key] = $valueWrapper->setValueIntList($value)->setIndexed($index); |
|||
$this->updated = true; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* @param string $key metadata key |
|||
* |
|||
* @inheritDoc |
|||
* @return self |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function unset(string $key): IFilesMetadata { |
|||
if (!array_key_exists($key, $this->metadata)) { |
|||
return $this; |
|||
} |
|||
|
|||
unset($this->metadata[$key]); |
|||
$this->updated = true; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* @param string $keyPrefix metadata key prefix |
|||
* |
|||
* @inheritDoc |
|||
* @return self |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function removeStartsWith(string $keyPrefix): IFilesMetadata { |
|||
if ($keyPrefix === '') { |
|||
return $this; |
|||
} |
|||
|
|||
foreach ($this->getKeys() as $key) { |
|||
if (str_starts_with($key, $keyPrefix)) { |
|||
$this->unset($key); |
|||
} |
|||
} |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* @param string $key |
|||
* |
|||
* @return void |
|||
* @throws FilesMetadataKeyFormatException |
|||
*/ |
|||
private function confirmKeyFormat(string $key): void { |
|||
$acceptedChars = ['-', '_']; |
|||
if (ctype_alnum(str_replace($acceptedChars, '', $key))) { |
|||
return; |
|||
} |
|||
|
|||
throw new FilesMetadataKeyFormatException('key can only contains alphanumerical characters, and dash (-)'); |
|||
} |
|||
|
|||
/** |
|||
* @inheritDoc |
|||
* @return bool TRUE if metadata have been modified |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function updated(): bool { |
|||
return $this->updated; |
|||
} |
|||
|
|||
public function jsonSerialize(bool $emptyValues = false): array { |
|||
$data = []; |
|||
foreach ($this->metadata as $metaKey => $metaValueWrapper) { |
|||
$data[$metaKey] = $metaValueWrapper->jsonSerialize($emptyValues); |
|||
} |
|||
|
|||
return $data; |
|||
} |
|||
|
|||
/** |
|||
* @return array<string, string|int|bool|float|string[]|int[]> |
|||
*/ |
|||
public function asArray(): array { |
|||
$data = []; |
|||
foreach ($this->metadata as $metaKey => $metaValueWrapper) { |
|||
try { |
|||
$data[$metaKey] = $metaValueWrapper->getValueAny(); |
|||
} catch (FilesMetadataNotFoundException $e) { |
|||
// ignore exception
|
|||
} |
|||
} |
|||
|
|||
return $data; |
|||
} |
|||
|
|||
/** |
|||
* @param array $data |
|||
* |
|||
* @inheritDoc |
|||
* @return IFilesMetadata |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function import(array $data): IFilesMetadata { |
|||
foreach ($data as $k => $v) { |
|||
$valueWrapper = new MetadataValueWrapper(); |
|||
$this->metadata[$k] = $valueWrapper->import($v); |
|||
} |
|||
$this->updated = false; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* import data from database to configure this model |
|||
* |
|||
* @param array $data |
|||
* @param string $prefix |
|||
* |
|||
* @return IFilesMetadata |
|||
* @throws FilesMetadataNotFoundException |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function importFromDatabase(array $data, string $prefix = ''): IFilesMetadata { |
|||
try { |
|||
$this->syncToken = $data[$prefix . 'sync_token'] ?? ''; |
|||
|
|||
return $this->import( |
|||
json_decode( |
|||
$data[$prefix . 'json'] ?? '[]', |
|||
true, |
|||
512, |
|||
JSON_THROW_ON_ERROR |
|||
) |
|||
); |
|||
} catch (JsonException $e) { |
|||
throw new FilesMetadataNotFoundException(); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,165 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
/** |
|||
* @copyright 2023 Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @author Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @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\FilesMetadata\Model; |
|||
|
|||
use OC\FilesMetadata\Service\IndexRequestService; |
|||
use OC\FilesMetadata\Service\MetadataRequestService; |
|||
use OCP\DB\QueryBuilder\IQueryBuilder; |
|||
use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException; |
|||
use OCP\FilesMetadata\Exceptions\FilesMetadataTypeException; |
|||
use OCP\FilesMetadata\Model\IFilesMetadata; |
|||
use OCP\FilesMetadata\Model\IMetadataQuery; |
|||
use OCP\FilesMetadata\Model\IMetadataValueWrapper; |
|||
|
|||
/** |
|||
* @inheritDoc |
|||
* @since 28.0.0 |
|||
*/ |
|||
class MetadataQuery implements IMetadataQuery { |
|||
private array $knownJoinedIndex = []; |
|||
public function __construct( |
|||
private IQueryBuilder $queryBuilder, |
|||
private IFilesMetadata $knownMetadata, |
|||
private string $fileTableAlias = 'fc', |
|||
private string $fileIdField = 'fileid', |
|||
private string $alias = 'meta', |
|||
private string $aliasIndexPrefix = 'meta_index' |
|||
) { |
|||
} |
|||
|
|||
/** |
|||
* @inheritDoc |
|||
* @see self::extractMetadata() |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function retrieveMetadata(): void { |
|||
$this->queryBuilder->selectAlias($this->alias . '.json', 'meta_json'); |
|||
$this->queryBuilder->leftJoin( |
|||
$this->fileTableAlias, MetadataRequestService::TABLE_METADATA, $this->alias, |
|||
$this->queryBuilder->expr()->eq($this->fileTableAlias . '.' . $this->fileIdField, $this->alias . '.file_id') |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* @param array $row result row |
|||
* |
|||
* @inheritDoc |
|||
* @return IFilesMetadata metadata |
|||
* @see self::retrieveMetadata() |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function extractMetadata(array $row): IFilesMetadata { |
|||
$fileId = (array_key_exists($this->fileIdField, $row)) ? $row[$this->fileIdField] : 0; |
|||
$metadata = new FilesMetadata((int)$fileId); |
|||
try { |
|||
$metadata->importFromDatabase($row, $this->alias . '_'); |
|||
} catch (FilesMetadataNotFoundException) { |
|||
// can be ignored as files' metadata are optional and might not exist in database
|
|||
} |
|||
|
|||
return $metadata; |
|||
} |
|||
|
|||
/** |
|||
* @param string $metadataKey metadata key |
|||
* @param bool $enforce limit the request only to existing metadata |
|||
* |
|||
* @inheritDoc |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function joinIndex(string $metadataKey, bool $enforce = false): string { |
|||
if (array_key_exists($metadataKey, $this->knownJoinedIndex)) { |
|||
return $this->knownJoinedIndex[$metadataKey]; |
|||
} |
|||
|
|||
$aliasIndex = $this->aliasIndexPrefix . '_' . count($this->knownJoinedIndex); |
|||
$this->knownJoinedIndex[$metadataKey] = $aliasIndex; |
|||
|
|||
$expr = $this->queryBuilder->expr(); |
|||
$andX = $expr->andX($expr->eq($aliasIndex . '.file_id', $this->fileTableAlias . '.' . $this->fileIdField)); |
|||
$andX->add($expr->eq($this->getMetadataKeyField($metadataKey), $this->queryBuilder->createNamedParameter($metadataKey))); |
|||
|
|||
if ($enforce) { |
|||
$this->queryBuilder->rightJoin( |
|||
$this->fileTableAlias, |
|||
IndexRequestService::TABLE_METADATA_INDEX, |
|||
$aliasIndex, |
|||
$andX |
|||
); |
|||
} else { |
|||
$this->queryBuilder->leftJoin( |
|||
$this->fileTableAlias, |
|||
IndexRequestService::TABLE_METADATA_INDEX, |
|||
$aliasIndex, |
|||
$andX |
|||
); |
|||
} |
|||
|
|||
return $aliasIndex; |
|||
} |
|||
|
|||
/** |
|||
* @throws FilesMetadataNotFoundException |
|||
*/ |
|||
public function joinedTableAlias(string $metadataKey): string { |
|||
if (!array_key_exists($metadataKey, $this->knownJoinedIndex)) { |
|||
throw new FilesMetadataNotFoundException('table related to ' . $metadataKey . ' not initiated, you need to use leftJoin() first.'); |
|||
} |
|||
|
|||
return $this->knownJoinedIndex[$metadataKey]; |
|||
} |
|||
|
|||
/** |
|||
* @inheritDoc |
|||
* |
|||
* @param string $metadataKey metadata key |
|||
* |
|||
* @return string table field |
|||
* @throws FilesMetadataNotFoundException |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getMetadataKeyField(string $metadataKey): string { |
|||
return $this->joinedTableAlias($metadataKey) . '.meta_key'; |
|||
} |
|||
|
|||
/** |
|||
* @inheritDoc |
|||
* |
|||
* @param string $metadataKey metadata key |
|||
* |
|||
* @return string table field |
|||
* @throws FilesMetadataNotFoundException if metadataKey is not known |
|||
* @throws FilesMetadataTypeException is metadataKey is not set as indexed |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getMetadataValueField(string $metadataKey): string { |
|||
return match ($this->knownMetadata->getType($metadataKey)) { |
|||
IMetadataValueWrapper::TYPE_STRING => $this->joinedTableAlias($metadataKey) . '.meta_value_string', |
|||
IMetadataValueWrapper::TYPE_INT, IMetadataValueWrapper::TYPE_BOOL => $this->joinedTableAlias($metadataKey) . '.meta_value_int', |
|||
default => throw new FilesMetadataTypeException('metadata is not set as indexed'), |
|||
}; |
|||
} |
|||
} |
@ -0,0 +1,397 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
/** |
|||
* @copyright 2023 Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @author Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @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\FilesMetadata\Model; |
|||
|
|||
use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException; |
|||
use OCP\FilesMetadata\Exceptions\FilesMetadataTypeException; |
|||
use OCP\FilesMetadata\Model\IMetadataValueWrapper; |
|||
|
|||
/** |
|||
* @inheritDoc |
|||
* @see IFilesMetadata |
|||
* @since 28.0.0 |
|||
*/ |
|||
class MetadataValueWrapper implements IMetadataValueWrapper { |
|||
private string $type; |
|||
/** @var string|int|float|bool|array|string[]|int[] */ |
|||
private mixed $value = null; |
|||
private bool $indexed = false; |
|||
|
|||
/** |
|||
* @param string $type value type |
|||
* |
|||
* @inheritDoc |
|||
* @see self::TYPE_INT |
|||
* @see self::TYPE_FLOAT |
|||
* @see self::TYPE_BOOL |
|||
* @see self::TYPE_ARRAY |
|||
* @see self::TYPE_STRING_LIST |
|||
* @see self::TYPE_INT_LIST |
|||
* @see self::TYPE_STRING |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function __construct(string $type = '') { |
|||
$this->type = $type; |
|||
} |
|||
|
|||
/** |
|||
* @inheritDoc |
|||
* @return string value type |
|||
* @see self::TYPE_INT |
|||
* @see self::TYPE_FLOAT |
|||
* @see self::TYPE_BOOL |
|||
* @see self::TYPE_ARRAY |
|||
* @see self::TYPE_STRING_LIST |
|||
* @see self::TYPE_INT_LIST |
|||
* @see self::TYPE_STRING |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getType(): string { |
|||
return $this->type; |
|||
} |
|||
|
|||
/** |
|||
* @param string $type value type |
|||
* |
|||
* @inheritDoc |
|||
* @return bool |
|||
* @see self::TYPE_INT |
|||
* @see self::TYPE_FLOAT |
|||
* @see self::TYPE_BOOL |
|||
* @see self::TYPE_ARRAY |
|||
* @see self::TYPE_STRING_LIST |
|||
* @see self::TYPE_INT_LIST |
|||
* @see self::TYPE_STRING |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function isType(string $type): bool { |
|||
return (strtolower($type) === strtolower($this->type)); |
|||
} |
|||
|
|||
/** |
|||
* @param string $type value type |
|||
* |
|||
* @inheritDoc |
|||
* @return self |
|||
* @throws FilesMetadataTypeException if type cannot be confirmed |
|||
* @see self::TYPE_INT |
|||
* @see self::TYPE_BOOL |
|||
* @see self::TYPE_ARRAY |
|||
* @see self::TYPE_STRING_LIST |
|||
* @see self::TYPE_INT_LIST |
|||
* @see self::TYPE_STRING |
|||
* @see self::TYPE_FLOAT |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function assertType(string $type): self { |
|||
if (!$this->isType($type)) { |
|||
throw new FilesMetadataTypeException('type is \'' . $this->getType() . '\', expecting \'' . $type . '\''); |
|||
} |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* @param string $value string to be set as value |
|||
* |
|||
* @inheritDoc |
|||
* @return self |
|||
* @throws FilesMetadataTypeException if wrapper was not set to store a string |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function setValueString(string $value): self { |
|||
$this->assertType(self::TYPE_STRING); |
|||
$this->value = $value; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* @param int $value int to be set as value |
|||
* |
|||
* @inheritDoc |
|||
* @return self |
|||
* @throws FilesMetadataTypeException if wrapper was not set to store an int |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function setValueInt(int $value): self { |
|||
$this->assertType(self::TYPE_INT); |
|||
$this->value = $value; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* @param float $value float to be set as value |
|||
* |
|||
* @inheritDoc |
|||
* @return self |
|||
* @throws FilesMetadataTypeException if wrapper was not set to store a float |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function setValueFloat(float $value): self { |
|||
$this->assertType(self::TYPE_FLOAT); |
|||
$this->value = $value; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* @param bool $value bool to be set as value |
|||
* |
|||
* @inheritDoc |
|||
* @return self |
|||
* @throws FilesMetadataTypeException if wrapper was not set to store a bool |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function setValueBool(bool $value): self { |
|||
$this->assertType(self::TYPE_BOOL); |
|||
$this->value = $value; |
|||
|
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* @param array $value array to be set as value |
|||
* |
|||
* @inheritDoc |
|||
* @return self |
|||
* @throws FilesMetadataTypeException if wrapper was not set to store an array |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function setValueArray(array $value): self { |
|||
$this->assertType(self::TYPE_ARRAY); |
|||
$this->value = $value; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* @param string[] $value string list to be set as value |
|||
* |
|||
* @inheritDoc |
|||
* @return self |
|||
* @throws FilesMetadataTypeException if wrapper was not set to store a string list |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function setValueStringList(array $value): self { |
|||
$this->assertType(self::TYPE_STRING_LIST); |
|||
// TODO confirm value is an array or string ?
|
|||
$this->value = $value; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* @param int[] $value int list to be set as value |
|||
* |
|||
* @inheritDoc |
|||
* @return self |
|||
* @throws FilesMetadataTypeException if wrapper was not set to store an int list |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function setValueIntList(array $value): self { |
|||
$this->assertType(self::TYPE_INT_LIST); |
|||
// TODO confirm value is an array of int ?
|
|||
$this->value = $value; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* @inheritDoc |
|||
* @return string set value |
|||
* @throws FilesMetadataTypeException if wrapper was not set to store a string |
|||
* @throws FilesMetadataNotFoundException if value is not set |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getValueString(): string { |
|||
$this->assertType(self::TYPE_STRING); |
|||
if (null === $this->value) { |
|||
throw new FilesMetadataNotFoundException('value is not set'); |
|||
} |
|||
|
|||
return (string)$this->value; |
|||
} |
|||
|
|||
/** |
|||
* @inheritDoc |
|||
* @return int set value |
|||
* @throws FilesMetadataTypeException if wrapper was not set to store an int |
|||
* @throws FilesMetadataNotFoundException if value is not set |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getValueInt(): int { |
|||
$this->assertType(self::TYPE_INT); |
|||
if (null === $this->value) { |
|||
throw new FilesMetadataNotFoundException('value is not set'); |
|||
} |
|||
|
|||
return (int)$this->value; |
|||
} |
|||
|
|||
/** |
|||
* @inheritDoc |
|||
* @return float set value |
|||
* @throws FilesMetadataTypeException if wrapper was not set to store a float |
|||
* @throws FilesMetadataNotFoundException if value is not set |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getValueFloat(): float { |
|||
$this->assertType(self::TYPE_FLOAT); |
|||
if (null === $this->value) { |
|||
throw new FilesMetadataNotFoundException('value is not set'); |
|||
} |
|||
|
|||
return (float)$this->value; |
|||
} |
|||
|
|||
/** |
|||
* @inheritDoc |
|||
* @return bool set value |
|||
* @throws FilesMetadataTypeException if wrapper was not set to store a bool |
|||
* @throws FilesMetadataNotFoundException if value is not set |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getValueBool(): bool { |
|||
$this->assertType(self::TYPE_BOOL); |
|||
if (null === $this->value) { |
|||
throw new FilesMetadataNotFoundException('value is not set'); |
|||
} |
|||
|
|||
return (bool)$this->value; |
|||
} |
|||
|
|||
/** |
|||
* @inheritDoc |
|||
* @return array set value |
|||
* @throws FilesMetadataTypeException if wrapper was not set to store an array |
|||
* @throws FilesMetadataNotFoundException if value is not set |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getValueArray(): array { |
|||
$this->assertType(self::TYPE_ARRAY); |
|||
if (null === $this->value) { |
|||
throw new FilesMetadataNotFoundException('value is not set'); |
|||
} |
|||
|
|||
return (array)$this->value; |
|||
} |
|||
|
|||
/** |
|||
* @inheritDoc |
|||
* @return string[] set value |
|||
* @throws FilesMetadataTypeException if wrapper was not set to store a string list |
|||
* @throws FilesMetadataNotFoundException if value is not set |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getValueStringList(): array { |
|||
$this->assertType(self::TYPE_STRING_LIST); |
|||
if (null === $this->value) { |
|||
throw new FilesMetadataNotFoundException('value is not set'); |
|||
} |
|||
|
|||
return (array)$this->value; |
|||
} |
|||
|
|||
/** |
|||
* @inheritDoc |
|||
* @return int[] set value |
|||
* @throws FilesMetadataTypeException if wrapper was not set to store an int list |
|||
* @throws FilesMetadataNotFoundException if value is not set |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getValueIntList(): array { |
|||
$this->assertType(self::TYPE_INT_LIST); |
|||
if (null === $this->value) { |
|||
throw new FilesMetadataNotFoundException('value is not set'); |
|||
} |
|||
|
|||
return (array)$this->value; |
|||
} |
|||
|
|||
/** |
|||
* @inheritDoc |
|||
* @return string|int|float|bool|array|string[]|int[] set value |
|||
* @throws FilesMetadataNotFoundException if value is not set |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getValueAny(): mixed { |
|||
if (null === $this->value) { |
|||
throw new FilesMetadataNotFoundException('value is not set'); |
|||
} |
|||
|
|||
return $this->value; |
|||
} |
|||
|
|||
/** |
|||
* @param bool $indexed TRUE to set the stored value as an indexed value |
|||
* |
|||
* @inheritDoc |
|||
* @return self |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function setIndexed(bool $indexed): self { |
|||
$this->indexed = $indexed; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* @inheritDoc |
|||
* @return bool TRUE if value is an indexed value |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function isIndexed(): bool { |
|||
return $this->indexed; |
|||
} |
|||
|
|||
/** |
|||
* @param array $data serialized version of the object |
|||
* |
|||
* @inheritDoc |
|||
* @return self |
|||
* @see jsonSerialize |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function import(array $data): self { |
|||
$this->value = $data['value'] ?? null; |
|||
$this->type = $data['type'] ?? ''; |
|||
$this->setIndexed($data['indexed'] ?? false); |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
public function jsonSerialize(bool $emptyValues = false): array { |
|||
return [ |
|||
'value' => ($emptyValues) ? null : $this->value, |
|||
'type' => $this->getType(), |
|||
'indexed' => $this->isIndexed() |
|||
]; |
|||
} |
|||
} |
@ -0,0 +1,195 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
/** |
|||
* @copyright 2023 Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @author Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @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\FilesMetadata\Service; |
|||
|
|||
use OCP\DB\Exception as DbException; |
|||
use OCP\DB\QueryBuilder\IQueryBuilder; |
|||
use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException; |
|||
use OCP\FilesMetadata\Exceptions\FilesMetadataTypeException; |
|||
use OCP\FilesMetadata\Model\IFilesMetadata; |
|||
use OCP\FilesMetadata\Model\IMetadataValueWrapper; |
|||
use OCP\IDBConnection; |
|||
use Psr\Log\LoggerInterface; |
|||
|
|||
/** |
|||
* manage sql request to the metadata_index table |
|||
*/ |
|||
class IndexRequestService { |
|||
public const TABLE_METADATA_INDEX = 'files_metadata_index'; |
|||
|
|||
public function __construct( |
|||
private IDBConnection $dbConnection, |
|||
private LoggerInterface $logger |
|||
) { |
|||
} |
|||
|
|||
/** |
|||
* update the index for a specific metadata key |
|||
* |
|||
* @param IFilesMetadata $filesMetadata metadata |
|||
* @param string $key metadata key to update |
|||
* |
|||
* @throws DbException |
|||
*/ |
|||
public function updateIndex(IFilesMetadata $filesMetadata, string $key): void { |
|||
$fileId = $filesMetadata->getFileId(); |
|||
try { |
|||
$metadataType = $filesMetadata->getType($key); |
|||
} catch (FilesMetadataNotFoundException $e) { |
|||
return; |
|||
} |
|||
|
|||
/** |
|||
* might look harsh, but a lot simpler than comparing current indexed data, as we can expect |
|||
* conflict with a change of types. |
|||
* We assume that each time one random metadata were modified we can drop all index for this |
|||
* key and recreate them. |
|||
* To make it slightly cleaner, we'll use transaction |
|||
*/ |
|||
$this->dbConnection->beginTransaction(); |
|||
try { |
|||
$this->dropIndex($fileId, $key); |
|||
match ($metadataType) { |
|||
IMetadataValueWrapper::TYPE_STRING => $this->insertIndexString($fileId, $key, $filesMetadata->get($key)), |
|||
IMetadataValueWrapper::TYPE_INT => $this->insertIndexInt($fileId, $key, $filesMetadata->getInt($key)), |
|||
IMetadataValueWrapper::TYPE_BOOL => $this->insertIndexBool($fileId, $key, $filesMetadata->getBool($key)), |
|||
IMetadataValueWrapper::TYPE_STRING_LIST => $this->insertIndexStringList($fileId, $key, $filesMetadata->getStringList($key)), |
|||
IMetadataValueWrapper::TYPE_INT_LIST => $this->insertIndexIntList($fileId, $key, $filesMetadata->getIntList($key)) |
|||
}; |
|||
} catch (FilesMetadataNotFoundException|FilesMetadataTypeException|DbException $e) { |
|||
$this->dbConnection->rollBack(); |
|||
$this->logger->warning('issue while updateIndex', ['exception' => $e, 'fileId' => $fileId, 'key' => $key]); |
|||
} |
|||
|
|||
$this->dbConnection->commit(); |
|||
} |
|||
|
|||
/** |
|||
* insert a new entry in the metadata_index table for a string value |
|||
* |
|||
* @param int $fileId file id |
|||
* @param string $key metadata key |
|||
* @param string $value metadata value |
|||
* |
|||
* @throws DbException |
|||
*/ |
|||
private function insertIndexString(int $fileId, string $key, string $value): void { |
|||
$qb = $this->dbConnection->getQueryBuilder(); |
|||
$qb->insert(self::TABLE_METADATA_INDEX) |
|||
->setValue('meta_key', $qb->createNamedParameter($key)) |
|||
->setValue('meta_value_string', $qb->createNamedParameter($value)) |
|||
->setValue('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)); |
|||
$qb->executeStatement(); |
|||
} |
|||
|
|||
/** |
|||
* insert a new entry in the metadata_index table for an int value |
|||
* |
|||
* @param int $fileId file id |
|||
* @param string $key metadata key |
|||
* @param int $value metadata value |
|||
* |
|||
* @throws DbException |
|||
*/ |
|||
public function insertIndexInt(int $fileId, string $key, int $value): void { |
|||
$qb = $this->dbConnection->getQueryBuilder(); |
|||
$qb->insert(self::TABLE_METADATA_INDEX) |
|||
->setValue('meta_key', $qb->createNamedParameter($key)) |
|||
->setValue('meta_value_int', $qb->createNamedParameter($value, IQueryBuilder::PARAM_INT)) |
|||
->setValue('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)); |
|||
$qb->executeStatement(); |
|||
} |
|||
|
|||
/** |
|||
* insert a new entry in the metadata_index table for a bool value |
|||
* |
|||
* @param int $fileId file id |
|||
* @param string $key metadata key |
|||
* @param bool $value metadata value |
|||
* |
|||
* @throws DbException |
|||
*/ |
|||
public function insertIndexBool(int $fileId, string $key, bool $value): void { |
|||
$qb = $this->dbConnection->getQueryBuilder(); |
|||
$qb->insert(self::TABLE_METADATA_INDEX) |
|||
->setValue('meta_key', $qb->createNamedParameter($key)) |
|||
->setValue('meta_value_int', $qb->createNamedParameter(($value) ? '1' : '0', IQueryBuilder::PARAM_INT)) |
|||
->setValue('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)); |
|||
$qb->executeStatement(); |
|||
} |
|||
|
|||
/** |
|||
* insert entries in the metadata_index table for list of string |
|||
* |
|||
* @param int $fileId file id |
|||
* @param string $key metadata key |
|||
* @param string[] $values metadata values |
|||
* |
|||
* @throws DbException |
|||
*/ |
|||
public function insertIndexStringList(int $fileId, string $key, array $values): void { |
|||
foreach ($values as $value) { |
|||
$this->insertIndexString($fileId, $key, $value); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* insert entries in the metadata_index table for list of int |
|||
* |
|||
* @param int $fileId file id |
|||
* @param string $key metadata key |
|||
* @param int[] $values metadata values |
|||
* |
|||
* @throws DbException |
|||
*/ |
|||
public function insertIndexIntList(int $fileId, string $key, array $values): void { |
|||
foreach ($values as $value) { |
|||
$this->insertIndexInt($fileId, $key, $value); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* drop indexes related to a file id |
|||
* if a key is specified, only drop entries related to it |
|||
* |
|||
* @param int $fileId file id |
|||
* @param string $key metadata key |
|||
* |
|||
* @throws DbException |
|||
*/ |
|||
public function dropIndex(int $fileId, string $key = ''): void { |
|||
$qb = $this->dbConnection->getQueryBuilder(); |
|||
$expr = $qb->expr(); |
|||
$qb->delete(self::TABLE_METADATA_INDEX) |
|||
->where($expr->eq('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT))); |
|||
|
|||
if ($key !== '') { |
|||
$qb->andWhere($expr->eq('meta_key', $qb->createNamedParameter($key))); |
|||
} |
|||
|
|||
$qb->executeStatement(); |
|||
} |
|||
} |
@ -0,0 +1,160 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
/** |
|||
* @copyright 2023 Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @author Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @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\FilesMetadata\Service; |
|||
|
|||
use OC\FilesMetadata\Model\FilesMetadata; |
|||
use OCP\DB\Exception; |
|||
use OCP\DB\QueryBuilder\IQueryBuilder; |
|||
use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException; |
|||
use OCP\FilesMetadata\Model\IFilesMetadata; |
|||
use OCP\IDBConnection; |
|||
use Psr\Log\LoggerInterface; |
|||
|
|||
/** |
|||
* manage sql request to the metadata table |
|||
*/ |
|||
class MetadataRequestService { |
|||
public const TABLE_METADATA = 'files_metadata'; |
|||
|
|||
public function __construct( |
|||
private IDBConnection $dbConnection, |
|||
private LoggerInterface $logger |
|||
) { |
|||
} |
|||
|
|||
/** |
|||
* store metadata into database |
|||
* |
|||
* @param IFilesMetadata $filesMetadata |
|||
* |
|||
* @throws Exception |
|||
*/ |
|||
public function store(IFilesMetadata $filesMetadata): void { |
|||
$qb = $this->dbConnection->getQueryBuilder(); |
|||
$qb->insert(self::TABLE_METADATA) |
|||
->setValue('file_id', $qb->createNamedParameter($filesMetadata->getFileId(), IQueryBuilder::PARAM_INT)) |
|||
->setValue('json', $qb->createNamedParameter(json_encode($filesMetadata->jsonSerialize()))) |
|||
->setValue('sync_token', $qb->createNamedParameter($this->generateSyncToken())) |
|||
->setValue('last_update', (string) $qb->createFunction('NOW()')); |
|||
$qb->executeStatement(); |
|||
} |
|||
|
|||
/** |
|||
* returns metadata for a file id |
|||
* |
|||
* @param int $fileId file id |
|||
* |
|||
* @return IFilesMetadata |
|||
* @throws FilesMetadataNotFoundException if no metadata are found in database |
|||
*/ |
|||
public function getMetadataFromFileId(int $fileId): IFilesMetadata { |
|||
try { |
|||
$qb = $this->dbConnection->getQueryBuilder(); |
|||
$qb->select('json', 'sync_token')->from(self::TABLE_METADATA); |
|||
$qb->where( |
|||
$qb->expr()->eq('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)) |
|||
); |
|||
$result = $qb->executeQuery(); |
|||
$data = $result->fetch(); |
|||
$result->closeCursor(); |
|||
} catch (Exception $e) { |
|||
$this->logger->warning( |
|||
'exception while getMetadataFromDatabase()', ['exception' => $e, 'fileId' => $fileId] |
|||
); |
|||
throw new FilesMetadataNotFoundException(); |
|||
} |
|||
|
|||
if ($data === false) { |
|||
throw new FilesMetadataNotFoundException(); |
|||
} |
|||
|
|||
$metadata = new FilesMetadata($fileId); |
|||
$metadata->importFromDatabase($data); |
|||
|
|||
return $metadata; |
|||
} |
|||
|
|||
/** |
|||
* drop metadata related to a file id |
|||
* |
|||
* @param int $fileId file id |
|||
* |
|||
* @return void |
|||
* @throws Exception |
|||
*/ |
|||
public function dropMetadata(int $fileId): void { |
|||
$qb = $this->dbConnection->getQueryBuilder(); |
|||
$qb->delete(self::TABLE_METADATA) |
|||
->where($qb->expr()->eq('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT))); |
|||
$qb->executeStatement(); |
|||
} |
|||
|
|||
/** |
|||
* update metadata in the database |
|||
* |
|||
* @param IFilesMetadata $filesMetadata metadata |
|||
* |
|||
* @return int number of affected rows |
|||
* @throws Exception |
|||
*/ |
|||
public function updateMetadata(IFilesMetadata $filesMetadata): int { |
|||
$qb = $this->dbConnection->getQueryBuilder(); |
|||
$expr = $qb->expr(); |
|||
|
|||
$qb->update(self::TABLE_METADATA) |
|||
->set('json', $qb->createNamedParameter(json_encode($filesMetadata->jsonSerialize()))) |
|||
->set('sync_token', $qb->createNamedParameter($this->generateSyncToken())) |
|||
->set('last_update', $qb->createFunction('NOW()')) |
|||
->where( |
|||
$expr->andX( |
|||
$expr->eq('file_id', $qb->createNamedParameter($filesMetadata->getFileId(), IQueryBuilder::PARAM_INT)), |
|||
$expr->eq('sync_token', $qb->createNamedParameter($filesMetadata->getSyncToken())) |
|||
) |
|||
); |
|||
|
|||
return $qb->executeStatement(); |
|||
} |
|||
|
|||
/** |
|||
* generate a random token |
|||
* @return string |
|||
*/ |
|||
private function generateSyncToken(): string { |
|||
$chars = 'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890'; |
|||
|
|||
$str = ''; |
|||
$max = strlen($chars); |
|||
for ($i = 0; $i < 7; $i++) { |
|||
try { |
|||
$str .= $chars[random_int(0, $max - 2)]; |
|||
} catch (\Exception $e) { |
|||
$this->logger->warning('exception during generateSyncToken', ['exception' => $e]); |
|||
} |
|||
} |
|||
|
|||
return $str; |
|||
} |
|||
} |
@ -0,0 +1,68 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
/** |
|||
* @copyright 2023 Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @author Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @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 OCP\FilesMetadata; |
|||
|
|||
use OCP\EventDispatcher\Event; |
|||
use OCP\Files\Node; |
|||
use OCP\FilesMetadata\Model\IFilesMetadata; |
|||
|
|||
/** |
|||
* @since 28.0.0 |
|||
*/ |
|||
abstract class AMetadataEvent extends Event { |
|||
/** |
|||
* @param Node $node |
|||
* @param IFilesMetadata $metadata |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function __construct( |
|||
private Node $node, |
|||
private IFilesMetadata $metadata |
|||
) { |
|||
parent::__construct(); |
|||
} |
|||
|
|||
/** |
|||
* returns related node |
|||
* |
|||
* @return Node |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getNode(): Node { |
|||
return $this->node; |
|||
} |
|||
|
|||
/** |
|||
* returns metadata. if known, it already contains data from the database. |
|||
* If the object is modified using its setters, changes are stored in database at the end of the event. |
|||
* |
|||
* @return IFilesMetadata |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getMetadata(): IFilesMetadata { |
|||
return $this->metadata; |
|||
} |
|||
} |
@ -0,0 +1,40 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
/** |
|||
* @copyright 2023 Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @author Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @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 OCP\FilesMetadata\Event; |
|||
|
|||
use OCP\FilesMetadata\AMetadataEvent; |
|||
|
|||
/** |
|||
* MetadataBackgroundEvent is an event similar to MetadataLiveEvent but dispatched |
|||
* on a background thread instead of live thread. Meaning there is no limit to |
|||
* the time required for the generation of your metadata. |
|||
* |
|||
* @see AMetadataEvent::getMetadata() |
|||
* @see AMetadataEvent::getNode() |
|||
* @since 28.0.0 |
|||
*/ |
|||
class MetadataBackgroundEvent extends AMetadataEvent { |
|||
} |
@ -0,0 +1,67 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
/** |
|||
* @copyright 2023 Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @author Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @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 OCP\FilesMetadata\Event; |
|||
|
|||
use OCP\FilesMetadata\AMetadataEvent; |
|||
|
|||
/** |
|||
* MetadataLiveEvent is an event initiated when a file is created or updated. |
|||
* The app contains the Node related to the created/updated file, and a FilesMetadata that already |
|||
* contains the currently known metadata. |
|||
* |
|||
* Setting new metadata, or modifying already existing metadata with different value, will trigger |
|||
* the save of the metadata in the database. |
|||
* |
|||
* @see AMetadataEvent::getMetadata() |
|||
* @see AMetadataEvent::getNode() |
|||
* @see MetadataLiveEvent::requestBackgroundJob() |
|||
* @since 28.0.0 |
|||
*/ |
|||
class MetadataLiveEvent extends AMetadataEvent { |
|||
private bool $runAsBackgroundJob = false; |
|||
|
|||
/** |
|||
* For heavy process, call this method if your app prefers to update metadata on a |
|||
* background/cron job, instead of the live process. |
|||
* A similar MetadataBackgroundEvent will be broadcast on next cron tick. |
|||
* |
|||
* @return void |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function requestBackgroundJob(): void { |
|||
$this->runAsBackgroundJob = true; |
|||
} |
|||
|
|||
/** |
|||
* return true if any app that catch this event requested a re-run as background job |
|||
* |
|||
* @return bool |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function isRunAsBackgroundJobRequested(): bool { |
|||
return $this->runAsBackgroundJob; |
|||
} |
|||
} |
@ -0,0 +1,34 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
/** |
|||
* @copyright 2023 Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @author Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @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 OCP\FilesMetadata\Exceptions; |
|||
|
|||
use Exception; |
|||
|
|||
/** |
|||
* @since 28.0.0 |
|||
*/ |
|||
class FilesMetadataException extends Exception { |
|||
} |
@ -0,0 +1,32 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
/** |
|||
* @copyright 2023 Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @author Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @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 OCP\FilesMetadata\Exceptions; |
|||
|
|||
/** |
|||
* @since 28.0.0 |
|||
*/ |
|||
class FilesMetadataKeyFormatException extends FilesMetadataException { |
|||
} |
@ -0,0 +1,32 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
/** |
|||
* @copyright 2023 Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @author Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @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 OCP\FilesMetadata\Exceptions; |
|||
|
|||
/** |
|||
* @since 28.0.0 |
|||
*/ |
|||
class FilesMetadataNotFoundException extends FilesMetadataException { |
|||
} |
@ -0,0 +1,32 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
/** |
|||
* @copyright 2023 Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @author Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @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 OCP\FilesMetadata\Exceptions; |
|||
|
|||
/** |
|||
* @since 28.0.0 |
|||
*/ |
|||
class FilesMetadataTypeException extends FilesMetadataException { |
|||
} |
@ -0,0 +1,139 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
/** |
|||
* @copyright 2023 Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @author Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @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 OCP\FilesMetadata; |
|||
|
|||
use OCP\DB\QueryBuilder\IQueryBuilder; |
|||
use OCP\Files\Node; |
|||
use OCP\FilesMetadata\Exceptions\FilesMetadataException; |
|||
use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException; |
|||
use OCP\FilesMetadata\Model\IFilesMetadata; |
|||
use OCP\FilesMetadata\Model\IMetadataQuery; |
|||
|
|||
/** |
|||
* Manager for FilesMetadata; manage files' metadata. |
|||
* |
|||
* @since 28.0.0 |
|||
*/ |
|||
interface IFilesMetadataManager { |
|||
/** @since 28.0.0 */ |
|||
public const PROCESS_LIVE = 1; |
|||
/** @since 28.0.0 */ |
|||
public const PROCESS_BACKGROUND = 2; |
|||
|
|||
/** |
|||
* initiate the process of refreshing the metadata in relation to a node |
|||
* usually, this process: |
|||
* - get current metadata from database, if available, or create a new one |
|||
* - dispatch a MetadataLiveEvent, |
|||
* - save new metadata in database, if metadata have been changed during the event |
|||
* - refresh metadata indexes if needed, |
|||
* - prep a new cronjob if an app request it during the event, |
|||
* |
|||
* @param Node $node related node |
|||
* @param int $process type of process |
|||
* |
|||
* @return IFilesMetadata |
|||
* @see self::PROCESS_BACKGROUND |
|||
* @see self::PROCESS_LIVE |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function refreshMetadata( |
|||
Node $node, |
|||
int $process = self::PROCESS_LIVE |
|||
): IFilesMetadata; |
|||
|
|||
/** |
|||
* returns metadata from a file id |
|||
* |
|||
* @param int $fileId file id |
|||
* |
|||
* @return IFilesMetadata |
|||
* @throws FilesMetadataNotFoundException if not found |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getMetadata(int $fileId): IFilesMetadata; |
|||
|
|||
/** |
|||
* save metadata to database and refresh indexes. |
|||
* metadata are saved if new data are available. |
|||
* on update, a check on syncToken is done to avoid conflict (race condition) |
|||
* |
|||
* @param IFilesMetadata $filesMetadata |
|||
* |
|||
* @throws FilesMetadataException if metadata seems malformed |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function saveMetadata(IFilesMetadata $filesMetadata): void; |
|||
|
|||
/** |
|||
* delete metadata and its indexes |
|||
* |
|||
* @param int $fileId file id |
|||
* |
|||
* @return void |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function deleteMetadata(int $fileId): void; |
|||
|
|||
/** |
|||
* generate and return a MetadataQuery to help building sql queries |
|||
* |
|||
* @param IQueryBuilder $qb |
|||
* @param string $fileTableAlias alias of the table that contains data about files |
|||
* @param string $fileIdField alias of the field that contains file ids |
|||
* |
|||
* @return IMetadataQuery |
|||
* @see IMetadataQuery |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getMetadataQuery( |
|||
IQueryBuilder $qb, |
|||
string $fileTableAlias, |
|||
string $fileIdField |
|||
): IMetadataQuery; |
|||
|
|||
/** |
|||
* returns all type of metadata currently available. |
|||
* The list is stored in a IFilesMetadata with null values but correct type. |
|||
* |
|||
* @return IFilesMetadata |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getKnownMetadata(): IFilesMetadata; |
|||
|
|||
/** |
|||
* initiate a metadata key with its type. |
|||
* The call is mandatory before using the metadata property in a webdav request. |
|||
* It is not needed to only use this method when the app is enabled: the method can be |
|||
* called each time during the app loading as the metadata will only be initiated if not known |
|||
* |
|||
* @param string $key metadata key |
|||
* @param string $type metadata type |
|||
* @param bool $indexed TRUE if metadata can be search |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function initMetadata(string $key, string $type, bool $indexed): void; |
|||
} |
@ -0,0 +1,343 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
/** |
|||
* @copyright 2023 Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @author Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @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 OCP\FilesMetadata\Model; |
|||
|
|||
use JsonSerializable; |
|||
use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException; |
|||
use OCP\FilesMetadata\Exceptions\FilesMetadataTypeException; |
|||
|
|||
/** |
|||
* Model that represent metadata linked to a specific file. |
|||
* |
|||
* Example of json stored in the database |
|||
* { |
|||
* "mymeta": { |
|||
* "value": "this is a test", |
|||
* "type": "string", |
|||
* "indexed": false |
|||
* }, |
|||
* "myapp-anothermeta": { |
|||
* "value": 42, |
|||
* "type": "int", |
|||
* "indexed": true |
|||
* } |
|||
* } |
|||
* |
|||
* @see IMetadataValueWrapper |
|||
* @since 28.0.0 |
|||
*/ |
|||
interface IFilesMetadata extends JsonSerializable { |
|||
/** |
|||
* returns the file id linked to this metadata |
|||
* |
|||
* @return int related file id |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getFileId(): int; |
|||
|
|||
/** |
|||
* returns last time metadata were updated in the database |
|||
* |
|||
* @return int timestamp |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function lastUpdateTimestamp(): int; |
|||
|
|||
/** |
|||
* returns the token known at the time the metadata were extracted from database |
|||
* |
|||
* @return string token |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getSyncToken(): string; |
|||
|
|||
/** |
|||
* returns all current metadata keys |
|||
* |
|||
* @return string[] list of keys |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getKeys(): array; |
|||
|
|||
/** |
|||
* returns true if search metadata key exists |
|||
* |
|||
* @param string $needle metadata key to search |
|||
* |
|||
* @return bool TRUE if key exist |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function hasKey(string $needle): bool; |
|||
|
|||
/** |
|||
* return the list of metadata keys set as indexed |
|||
* |
|||
* @return string[] list of indexes |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getIndexes(): array; |
|||
|
|||
/** |
|||
* returns true if key exists and is set as indexed |
|||
* |
|||
* @param string $key metadata key |
|||
* |
|||
* @return bool |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function isIndex(string $key): bool; |
|||
|
|||
/** |
|||
* returns string value for a metadata key |
|||
* |
|||
* @param string $key metadata key |
|||
* |
|||
* @return string metadata value |
|||
* @throws FilesMetadataNotFoundException |
|||
* @throws FilesMetadataTypeException |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function get(string $key): string; |
|||
|
|||
/** |
|||
* returns int value for a metadata key |
|||
* |
|||
* @param string $key metadata key |
|||
* |
|||
* @return int metadata value |
|||
* @throws FilesMetadataNotFoundException |
|||
* @throws FilesMetadataTypeException |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getInt(string $key): int; |
|||
|
|||
/** |
|||
* returns float value for a metadata key |
|||
* |
|||
* @param string $key metadata key |
|||
* |
|||
* @return float metadata value |
|||
* @throws FilesMetadataNotFoundException |
|||
* @throws FilesMetadataTypeException |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getFloat(string $key): float; |
|||
|
|||
/** |
|||
* returns bool value for a metadata key |
|||
* |
|||
* @param string $key metadata key |
|||
* |
|||
* @return bool metadata value |
|||
* @throws FilesMetadataNotFoundException |
|||
* @throws FilesMetadataTypeException |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getBool(string $key): bool; |
|||
|
|||
/** |
|||
* returns array for a metadata key |
|||
* |
|||
* @param string $key metadata key |
|||
* |
|||
* @return array metadata value |
|||
* @throws FilesMetadataNotFoundException |
|||
* @throws FilesMetadataTypeException |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getArray(string $key): array; |
|||
|
|||
/** |
|||
* returns string[] value for a metadata key |
|||
* |
|||
* @param string $key metadata key |
|||
* |
|||
* @return string[] metadata value |
|||
* @throws FilesMetadataNotFoundException |
|||
* @throws FilesMetadataTypeException |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getStringList(string $key): array; |
|||
|
|||
/** |
|||
* returns int[] value for a metadata key |
|||
* |
|||
* @param string $key metadata key |
|||
* |
|||
* @return int[] metadata value |
|||
* @throws FilesMetadataNotFoundException |
|||
* @throws FilesMetadataTypeException |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getIntList(string $key): array; |
|||
|
|||
/** |
|||
* returns the value type of the metadata (string, int, ...) |
|||
* |
|||
* @param string $key metadata key |
|||
* |
|||
* @return string value type |
|||
* @throws FilesMetadataNotFoundException |
|||
* @see IMetadataValueWrapper::TYPE_STRING |
|||
* @see IMetadataValueWrapper::TYPE_INT |
|||
* @see IMetadataValueWrapper::TYPE_FLOAT |
|||
* @see IMetadataValueWrapper::TYPE_BOOL |
|||
* @see IMetadataValueWrapper::TYPE_ARRAY |
|||
* @see IMetadataValueWrapper::TYPE_STRING_LIST |
|||
* @see IMetadataValueWrapper::TYPE_INT_LIST |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getType(string $key): string; |
|||
|
|||
/** |
|||
* set a metadata key/value pair for string value |
|||
* |
|||
* @param string $key metadata key |
|||
* @param string $value metadata value |
|||
* @param bool $index set TRUE if value must be indexed |
|||
* |
|||
* @return self |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function set(string $key, string $value, bool $index = false): self; |
|||
|
|||
/** |
|||
* set a metadata key/value pair for int value |
|||
* |
|||
* @param string $key metadata key |
|||
* @param int $value metadata value |
|||
* @param bool $index set TRUE if value must be indexed |
|||
* |
|||
* @return self |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function setInt(string $key, int $value, bool $index = false): self; |
|||
|
|||
/** |
|||
* set a metadata key/value pair for float value |
|||
* |
|||
* @param string $key metadata key |
|||
* @param float $value metadata value |
|||
* |
|||
* @return self |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function setFloat(string $key, float $value): self; |
|||
|
|||
/** |
|||
* set a metadata key/value pair for bool value |
|||
* |
|||
* @param string $key metadata key |
|||
* @param bool $value metadata value |
|||
* @param bool $index set TRUE if value must be indexed |
|||
* |
|||
* @return self |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function setBool(string $key, bool $value, bool $index = false): self; |
|||
|
|||
/** |
|||
* set a metadata key/value pair for array |
|||
* |
|||
* @param string $key metadata key |
|||
* @param array $value metadata value |
|||
* |
|||
* @return self |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function setArray(string $key, array $value): self; |
|||
|
|||
/** |
|||
* set a metadata key/value pair for list of string |
|||
* |
|||
* @param string $key metadata key |
|||
* @param string[] $value metadata value |
|||
* @param bool $index set TRUE if each values from the list must be indexed |
|||
* |
|||
* @return self |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function setStringList(string $key, array $value, bool $index = false): self; |
|||
|
|||
/** |
|||
* set a metadata key/value pair for list of int |
|||
* |
|||
* @param string $key metadata key |
|||
* @param int[] $value metadata value |
|||
* @param bool $index set TRUE if each values from the list must be indexed |
|||
* |
|||
* @return self |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function setIntList(string $key, array $value, bool $index = false): self; |
|||
|
|||
/** |
|||
* unset a metadata |
|||
* |
|||
* @param string $key metadata key |
|||
* |
|||
* @return self |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function unset(string $key): self; |
|||
|
|||
/** |
|||
* unset metadata with key starting with prefix |
|||
* |
|||
* @param string $keyPrefix metadata key prefix |
|||
* |
|||
* @return self |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function removeStartsWith(string $keyPrefix): self; |
|||
|
|||
/** |
|||
* returns true if object have been updated since last import |
|||
* |
|||
* @return bool TRUE if metadata have been modified |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function updated(): bool; |
|||
|
|||
/** |
|||
* returns metadata in a simple array with METADATA_KEY => METADATA_VALUE |
|||
* |
|||
* @return array metadata |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function asArray(): array; |
|||
|
|||
/** |
|||
* deserialize the object from a json |
|||
* |
|||
* @param array $data serialized version of the object |
|||
* |
|||
* @return self |
|||
* @see jsonSerialize |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function import(array $data): self; |
|||
} |
@ -0,0 +1,90 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
/** |
|||
* @copyright 2023 Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @author Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @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 OCP\FilesMetadata\Model; |
|||
|
|||
/** |
|||
* Model that help building queries with metadata and metadata indexes |
|||
* |
|||
* @since 28.0.0 |
|||
*/ |
|||
interface IMetadataQuery { |
|||
/** @since 28.0.0 */ |
|||
public const EXTRA = 'metadata'; |
|||
|
|||
/** |
|||
* Add metadata linked to file id to the query |
|||
* |
|||
* @see self::extractMetadata() |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function retrieveMetadata(): void; |
|||
|
|||
/** |
|||
* extract metadata from a result row |
|||
* |
|||
* @param array $row result row |
|||
* |
|||
* @return IFilesMetadata metadata |
|||
* @see self::retrieveMetadata() |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function extractMetadata(array $row): IFilesMetadata; |
|||
|
|||
/** |
|||
* join the metadata_index table, based on a metadataKey. |
|||
* This will prep the query for condition based on this specific metadataKey. |
|||
* If a link to the metadataKey already exists, returns known alias. |
|||
* |
|||
* TODO: investigate how to support a search done on multiple values for same key (AND). |
|||
* |
|||
* @param string $metadataKey metadata key |
|||
* @param bool $enforce limit the request only to existing metadata |
|||
* |
|||
* @return string generated table alias |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function joinIndex(string $metadataKey, bool $enforce = false): string; |
|||
|
|||
/** |
|||
* returns the name of the field for metadata key to be used in query expressions |
|||
* |
|||
* @param string $metadataKey metadata key |
|||
* |
|||
* @return string table field |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getMetadataKeyField(string $metadataKey): string; |
|||
|
|||
/** |
|||
* returns the name of the field for metadata string value to be used in query expressions |
|||
* |
|||
* @param string $metadataKey metadata key |
|||
* |
|||
* @return string table field |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getMetadataValueField(string $metadataKey): string; |
|||
} |
@ -0,0 +1,300 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
/** |
|||
* @copyright 2023 Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @author Maxence Lange <maxence@artificial-owl.com> |
|||
* |
|||
* @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 OCP\FilesMetadata\Model; |
|||
|
|||
use JsonSerializable; |
|||
use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException; |
|||
use OCP\FilesMetadata\Exceptions\FilesMetadataTypeException; |
|||
|
|||
/** |
|||
* Model that store the value of a single metadata. |
|||
* It stores the value, its type and the index status. |
|||
* |
|||
* @see IFilesMetadata |
|||
* @since 28.0.0 |
|||
*/ |
|||
interface IMetadataValueWrapper extends JsonSerializable { |
|||
/** |
|||
* @since 28.0.0 |
|||
*/ |
|||
public const TYPE_STRING = 'string'; |
|||
public const TYPE_INT = 'int'; |
|||
public const TYPE_FLOAT = 'float'; |
|||
public const TYPE_BOOL = 'bool'; |
|||
public const TYPE_ARRAY = 'array'; |
|||
public const TYPE_STRING_LIST = 'string[]'; |
|||
public const TYPE_INT_LIST = 'int[]'; |
|||
|
|||
/** |
|||
* Unless a call of import() to deserialize an object is expected, a valid value type is needed here. |
|||
* |
|||
* @param string $type value type |
|||
* |
|||
* @see self::TYPE_INT |
|||
* @see self::TYPE_FLOAT |
|||
* @see self::TYPE_BOOL |
|||
* @see self::TYPE_ARRAY |
|||
* @see self::TYPE_STRING_LIST |
|||
* @see self::TYPE_INT_LIST |
|||
* @see self::TYPE_STRING |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function __construct(string $type); |
|||
|
|||
/** |
|||
* returns the value type |
|||
* |
|||
* @return string value type |
|||
* @see self::TYPE_INT |
|||
* @see self::TYPE_FLOAT |
|||
* @see self::TYPE_BOOL |
|||
* @see self::TYPE_ARRAY |
|||
* @see self::TYPE_STRING_LIST |
|||
* @see self::TYPE_INT_LIST |
|||
* @see self::TYPE_STRING |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getType(): string; |
|||
|
|||
/** |
|||
* returns if the set value type is the one expected |
|||
* |
|||
* @param string $type value type |
|||
* |
|||
* @return bool |
|||
* @see self::TYPE_INT |
|||
* @see self::TYPE_FLOAT |
|||
* @see self::TYPE_BOOL |
|||
* @see self::TYPE_ARRAY |
|||
* @see self::TYPE_STRING_LIST |
|||
* @see self::TYPE_INT_LIST |
|||
* @see self::TYPE_STRING |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function isType(string $type): bool; |
|||
|
|||
/** |
|||
* throws an exception if the type is not correctly set |
|||
* |
|||
* @param string $type value type |
|||
* |
|||
* @return self |
|||
* @throws FilesMetadataTypeException if type cannot be confirmed |
|||
* @see self::TYPE_INT |
|||
* @see self::TYPE_BOOL |
|||
* @see self::TYPE_ARRAY |
|||
* @see self::TYPE_STRING_LIST |
|||
* @see self::TYPE_INT_LIST |
|||
* @see self::TYPE_STRING |
|||
* @see self::TYPE_FLOAT |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function assertType(string $type): self; |
|||
|
|||
/** |
|||
* set a string value |
|||
* |
|||
* @param string $value string to be set as value |
|||
* |
|||
* @return self |
|||
* @throws FilesMetadataTypeException if wrapper was not set to store a string |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function setValueString(string $value): self; |
|||
|
|||
/** |
|||
* set a int value |
|||
* |
|||
* @param int $value int to be set as value |
|||
* |
|||
* @return self |
|||
* @throws FilesMetadataTypeException if wrapper was not set to store an int |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function setValueInt(int $value): self; |
|||
|
|||
/** |
|||
* set a float value |
|||
* |
|||
* @param float $value float to be set as value |
|||
* |
|||
* @return self |
|||
* @throws FilesMetadataTypeException if wrapper was not set to store a float |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function setValueFloat(float $value): self; |
|||
|
|||
/** |
|||
* set a bool value |
|||
* |
|||
* @param bool $value bool to be set as value |
|||
* |
|||
* @return self |
|||
* @throws FilesMetadataTypeException if wrapper was not set to store a bool |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function setValueBool(bool $value): self; |
|||
|
|||
/** |
|||
* set an array value |
|||
* |
|||
* @param array $value array to be set as value |
|||
* |
|||
* @return self |
|||
* @throws FilesMetadataTypeException if wrapper was not set to store an array |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function setValueArray(array $value): self; |
|||
|
|||
/** |
|||
* set a string list value |
|||
* |
|||
* @param string[] $value string list to be set as value |
|||
* |
|||
* @return self |
|||
* @throws FilesMetadataTypeException if wrapper was not set to store a string list |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function setValueStringList(array $value): self; |
|||
|
|||
/** |
|||
* set an int list value |
|||
* |
|||
* @param int[] $value int list to be set as value |
|||
* |
|||
* @return self |
|||
* @throws FilesMetadataTypeException if wrapper was not set to store an int list |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function setValueIntList(array $value): self; |
|||
|
|||
|
|||
/** |
|||
* get stored value |
|||
* |
|||
* @return string set value |
|||
* @throws FilesMetadataTypeException if wrapper was not set to store a string |
|||
* @throws FilesMetadataNotFoundException if value is not set |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getValueString(): string; |
|||
|
|||
/** |
|||
* get stored value |
|||
* |
|||
* @return int set value |
|||
* @throws FilesMetadataTypeException if wrapper was not set to store an int |
|||
* @throws FilesMetadataNotFoundException if value is not set |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getValueInt(): int; |
|||
|
|||
/** |
|||
* get stored value |
|||
* |
|||
* @return float set value |
|||
* @throws FilesMetadataTypeException if wrapper was not set to store a float |
|||
* @throws FilesMetadataNotFoundException if value is not set |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getValueFloat(): float; |
|||
|
|||
/** |
|||
* get stored value |
|||
* |
|||
* @return bool set value |
|||
* @throws FilesMetadataTypeException if wrapper was not set to store a bool |
|||
* @throws FilesMetadataNotFoundException if value is not set |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getValueBool(): bool; |
|||
|
|||
/** |
|||
* get stored value |
|||
* |
|||
* @return array set value |
|||
* @throws FilesMetadataTypeException if wrapper was not set to store an array |
|||
* @throws FilesMetadataNotFoundException if value is not set |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getValueArray(): array; |
|||
|
|||
/** |
|||
* get stored value |
|||
* |
|||
* @return string[] set value |
|||
* @throws FilesMetadataTypeException if wrapper was not set to store a string list |
|||
* @throws FilesMetadataNotFoundException if value is not set |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getValueStringList(): array; |
|||
|
|||
/** |
|||
* get stored value |
|||
* |
|||
* @return int[] set value |
|||
* @throws FilesMetadataTypeException if wrapper was not set to store an int list |
|||
* @throws FilesMetadataNotFoundException if value is not set |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getValueIntList(): array; |
|||
|
|||
/** |
|||
* get stored value |
|||
* |
|||
* @return string|int|float|bool|array|string[]|int[] set value |
|||
* @throws FilesMetadataNotFoundException if value is not set |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function getValueAny(): mixed; |
|||
|
|||
/** |
|||
* @param bool $indexed TRUE to set the stored value as an indexed value |
|||
* |
|||
* @return self |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function setIndexed(bool $indexed): self; |
|||
|
|||
/** |
|||
* returns if value is an indexed value |
|||
* |
|||
* @return bool TRUE if value is an indexed value |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function isIndexed(): bool; |
|||
|
|||
/** |
|||
* deserialize the object from a json |
|||
* |
|||
* @param array $data serialized version of the object |
|||
* |
|||
* @return self |
|||
* @see jsonSerialize |
|||
* @since 28.0.0 |
|||
*/ |
|||
public function import(array $data): self; |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue