-
-
Notifications
You must be signed in to change notification settings - Fork 4.1k
Commit
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace OC\Core\Command\FilesMetadata; | ||
|
||
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 IFilesMetadataManager $filesMetadataManager, | ||
) { | ||
parent::__construct(); | ||
} | ||
|
||
protected function configure() { | ||
Check notice Code scanning / Psalm MissingReturnType Note
Method OC\Core\Command\FilesMetadata\Get::configure does not have a return type, expecting void
|
||
$this->setName('metadata:get') | ||
->setDescription('update and returns up-to-date metadata') | ||
->addArgument( | ||
'fileId', | ||
InputArgument::REQUIRED, | ||
'id of the file document' | ||
) | ||
->addOption( | ||
'background', | ||
'', | ||
InputOption::VALUE_NONE, | ||
'emulate background jobs env' | ||
); | ||
} | ||
|
||
protected function execute(InputInterface $input, OutputInterface $output): int { | ||
$fileId = (int) $input->getArgument('fileId'); | ||
$metadata = $this->filesMetadataManager->refreshMetadata($fileId); | ||
$output->writeln(json_encode($metadata, JSON_PRETTY_PRINT)); | ||
|
||
return 0; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
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(); | ||
|
||
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'); | ||
} | ||
|
||
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('k', Types::STRING, [ | ||
'notnull' => false, | ||
'length' => 31, | ||
]); | ||
$table->addColumn('v', Types::STRING, [ | ||
'notnull' => false, | ||
'length' => 63, | ||
]); | ||
$table->addColumn('v_int', Types::BIGINT, [ | ||
'notnull' => false, | ||
'length' => 11, | ||
]); | ||
$table->addColumn('last_update', Types::DATETIME); | ||
|
||
$table->setPrimaryKey(['id']); | ||
$table->addIndex(['k', 'v', 'v_int'], 'files_meta_indexes'); | ||
} | ||
|
||
return $schema; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace OC\FilesMetadata\Event; | ||
|
||
use OCP\EventDispatcher\Event; | ||
use OCP\FilesMetadata\IFilesMetadata; | ||
|
||
class FilesMetadataEvent extends Event { | ||
private bool $runAsBackgroundJob = false; | ||
|
||
public function __construct( | ||
private int $fileId, | ||
private IFilesMetadata $metadata, | ||
) { | ||
parent::__construct(); | ||
} | ||
|
||
/** | ||
* If an app prefer to update metadata on a background job, instead of | ||
* live process, just call this method. | ||
* A new event will be generated on next cron tick | ||
* | ||
* @return void | ||
*/ | ||
public function requestBackgroundJob() { | ||
$this->runAsBackgroundJob = true; | ||
} | ||
|
||
/** | ||
* return fileId | ||
* | ||
* @return int | ||
*/ | ||
public function getFileId(): int { | ||
return $this->fileId; | ||
} | ||
|
||
/** | ||
* return Metadata | ||
* | ||
* @return IFilesMetadata | ||
*/ | ||
public function getMetadata(): IFilesMetadata { | ||
return $this->metadata; | ||
} | ||
|
||
/** | ||
* return true if any app that catch this event requested a re-run as background job | ||
* | ||
* @return bool | ||
*/ | ||
public function isRunAsBackgroundJobRequested(): bool { | ||
return $this->runAsBackgroundJob; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace OC\FilesMetadata; | ||
|
||
use Doctrine\DBAL\Exception\UniqueConstraintViolationException; | ||
use OC\FilesMetadata\Event\FilesMetadataEvent; | ||
use OC\FilesMetadata\Model\FilesMetadata; | ||
use OCP\DB\Exception; | ||
use OCP\DB\QueryBuilder\IQueryBuilder; | ||
use OCP\EventDispatcher\IEventDispatcher; | ||
use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException; | ||
use OCP\FilesMetadata\IFilesMetadata; | ||
use OCP\FilesMetadata\IFilesMetadataManager; | ||
use OCP\FilesMetadata\IFilesMetadataQueryHelper; | ||
use OCP\IDBConnection; | ||
use Psr\Log\LoggerInterface; | ||
|
||
class FilesMetadataManager implements IFilesMetadataManager { | ||
public const TABLE_METADATA = 'files_metadata'; | ||
public const TABLE_METADATA_INDEX = 'files_metadata_index'; | ||
|
||
public function __construct( | ||
private IDBConnection $dbConnection, | ||
private IEventDispatcher $eventDispatcher, | ||
private FilesMetadataQueryHelper $filesMetadataQueryHelper, | ||
private LoggerInterface $logger | ||
) { | ||
} | ||
|
||
public function refreshMetadata( | ||
int $fileId, | ||
bool $asBackgroundJob = false, | ||
bool $fromScratch = false | ||
): IFilesMetadata { | ||
$metadata = null; | ||
if (!$fromScratch) { | ||
try { | ||
$metadata = $this->selectMetadata($fileId); | ||
} catch (FilesMetadataNotFoundException $e) { | ||
} | ||
} | ||
|
||
if (is_null($metadata)) { | ||
$metadata = new FilesMetadata($fileId); | ||
} | ||
|
||
$event = new FilesMetadataEvent($fileId, $metadata); | ||
$this->eventDispatcher->dispatchTyped($event); | ||
$this->saveMetadata($event->getMetadata()); | ||
|
||
return $metadata; | ||
} | ||
|
||
/** | ||
* @param int $fileId | ||
* @param bool $createIfNeeded | ||
* | ||
* @return IFilesMetadata | ||
* @throws FilesMetadataNotFoundException | ||
*/ | ||
public function getMetadata(int $fileId, bool $createIfNeeded = false): IFilesMetadata { | ||
try { | ||
return $this->selectMetadata($fileId); | ||
} catch (FilesMetadataNotFoundException $e) { | ||
if ($createIfNeeded) { | ||
return $this->refreshMetadata($fileId); | ||
} | ||
|
||
throw $e; | ||
} | ||
} | ||
|
||
|
||
public function saveMetadata(IFilesMetadata $filesMetadata): void { | ||
if ($filesMetadata->getFileId() === 0 || !$filesMetadata->updated()) { | ||
return; | ||
} | ||
|
||
try { | ||
try { | ||
$this->insertMetadata($filesMetadata); | ||
} catch (UniqueConstraintViolationException $e) { | ||
$this->updateMetadata($filesMetadata); | ||
} | ||
} catch (Exception $e) { | ||
$this->logger->warning('exception while saveMetadata()', ['exception' => $e]); | ||
|
||
return; | ||
} | ||
|
||
// $this->removeDeprecatedMetadata($filesMetadata); | ||
// remove indexes from metadata_index that are not in the list of indexes anymore. | ||
foreach ($filesMetadata->listIndexes() as $index) { | ||
// foreach index, update entry in table metadata_index | ||
// if no update, insert as new row | ||
// !! we might want to get the type of the value to be indexed at one point !! | ||
} | ||
} | ||
|
||
private function removeDeprecatedMetadata(IFilesMetadata $filesMetadata): void { | ||
$qb = $this->dbConnection->getQueryBuilder(); | ||
$qb->delete(self::TABLE_METADATA_INDEX) | ||
->where($qb->expr()->eq('file_id', $qb->createNamedParameter($filesMetadata->getFileId(), IQueryBuilder::PARAM_INT))) | ||
->andWhere($qb->expr()->notIn('file_id', $filesMetadata->listIndexes(), IQueryBuilder::PARAM_STR_ARRAY)); | ||
$qb->executeStatement(); | ||
} | ||
|
||
|
||
public function getQueryHelper(): IFilesMetadataQueryHelper { | ||
return $this->filesMetadataQueryHelper; | ||
} | ||
|
||
private function insertMetadata(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('abc')) | ||
->setValue('last_update', $qb->createFunction('NOW()')); | ||
Check failure on line 121 in lib/private/FilesMetadata/FilesMetadataManager.php GitHub Actions / static-code-analysisImplicitToStringCast
Check failure Code scanning / Psalm ImplicitToStringCast Error
Argument 2 of OCP\DB\QueryBuilder\IQueryBuilder::setValue expects OCP\DB\QueryBuilder\IParameter|string, but OCP\DB\QueryBuilder\IQueryFunction provided with a __toString method
|
||
$qb->execute(); | ||
} | ||
|
||
/** | ||
* @param IFilesMetadata $filesMetadata | ||
* | ||
* @return bool | ||
Check failure on line 128 in lib/private/FilesMetadata/FilesMetadataManager.php GitHub Actions / static-code-analysisMismatchingDocblockReturnType
Check failure on line 128 in lib/private/FilesMetadata/FilesMetadataManager.php GitHub Actions / static-code-analysisInvalidReturnType
Check failure Code scanning / Psalm MismatchingDocblockReturnType Error
Docblock has incorrect return type 'bool', should be 'void'
Check failure Code scanning / Psalm InvalidReturnType Error
No return statements were found for method OC\FilesMetadata\FilesMetadataManager::updateMetadata but return type 'bool' was expected
|
||
* @throws Exception | ||
*/ | ||
private function updateMetadata(IFilesMetadata $filesMetadata): void { | ||
// TODO check sync_token on update | ||
$qb = $this->dbConnection->getQueryBuilder(); | ||
$qb->update(self::TABLE_METADATA) | ||
->set('json', $qb->createNamedParameter(json_encode($filesMetadata->jsonSerialize()))) | ||
->set('sync_token', $qb->createNamedParameter('abc')) | ||
->set('last_update', $qb->createFunction('NOW()')) | ||
->where($qb->expr()->eq('file_id', $qb->createNamedParameter($filesMetadata->getFileId(), IQueryBuilder::PARAM_INT))); | ||
$qb->executeStatement(); | ||
} | ||
|
||
/** | ||
* @param int $fileId | ||
* | ||
* @return IFilesMetadata | ||
* @throws FilesMetadataNotFoundException | ||
*/ | ||
private function selectMetadata(int $fileId): IFilesMetadata { | ||
try { | ||
$qb = $this->dbConnection->getQueryBuilder(); | ||
$qb->select('json')->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->import($data['json'] ?? ''); | ||
|
||
return $metadata; | ||
} | ||
} |