diff --git a/lib/private/FilesMetadata/FilesMetadataManager.php b/lib/private/FilesMetadata/FilesMetadataManager.php index 1a35f56999e22..8ec49013d5cf8 100644 --- a/lib/private/FilesMetadata/FilesMetadataManager.php +++ b/lib/private/FilesMetadata/FilesMetadataManager.php @@ -13,6 +13,7 @@ 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; @@ -107,23 +108,19 @@ public function saveMetadata(IFilesMetadata $filesMetadata): void { } try { - // if update request changed no rows, means that new entry is needed, or sync_token not valid anymore - $updated = $this->metadataRequestService->updateMetadata($filesMetadata); - if ($updated === 0) { + if ($filesMetadata->getSyncToken() === '') { $this->metadataRequestService->store($filesMetadata); + } else { + $this->metadataRequestService->updateMetadata($filesMetadata); } - } catch (\OCP\DB\Exception $e) { - // if duplicate, only means a desync during update. cancel update process. - if ($e->getReason() !== Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) { - $this->logger->warning( - 'issue while saveMetadata', ['exception' => $e, 'metadata' => $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; } -// $this->removeDeprecatedMetadata($filesMetadata); foreach ($filesMetadata->getIndexes() as $index) { try { $this->indexRequestService->updateIndex($filesMetadata, $index); diff --git a/lib/private/FilesMetadata/Model/FilesMetadata.php b/lib/private/FilesMetadata/Model/FilesMetadata.php index 1d63c5fde3b77..47048d6535fe5 100644 --- a/lib/private/FilesMetadata/Model/FilesMetadata.php +++ b/lib/private/FilesMetadata/Model/FilesMetadata.php @@ -59,6 +59,7 @@ public function import(array $data): IFilesMetadata { */ public function importFromDatabase(array $data, string $prefix = ''): IFilesMetadata { try { + $this->syncToken = $data[$prefix . 'sync_token']; return $this->import( json_decode($data[$prefix . 'json'] ?? '[]', true, @@ -346,7 +347,6 @@ public function getValueWrapper(string $key): MetadataValueWrapper { return $this->metadata[$key]; } - public function jsonSerialize(): array { $data = []; foreach ($this->metadata as $metaKey => $metaValueWrapper) { diff --git a/lib/private/FilesMetadata/Service/MetadataRequestService.php b/lib/private/FilesMetadata/Service/MetadataRequestService.php index 0c82d80b760a8..a4821b7276592 100644 --- a/lib/private/FilesMetadata/Service/MetadataRequestService.php +++ b/lib/private/FilesMetadata/Service/MetadataRequestService.php @@ -4,7 +4,6 @@ namespace OC\FilesMetadata\Service; -use JsonException; use OC\FilesMetadata\Model\FilesMetadata; use OCP\DB\Exception; use OCP\DB\QueryBuilder\IQueryBuilder; @@ -34,7 +33,7 @@ public function store(IFilesMetadata $filesMetadata): void { $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($filesMetadata->getSyncToken())) + ->setValue('sync_token', $qb->createNamedParameter($this->generateSyncToken())) ->setValue('last_update', $qb->createFunction('NOW()')); $qb->executeStatement(); } @@ -48,13 +47,17 @@ public function store(IFilesMetadata $filesMetadata): void { public function getMetadataFromFileId(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))); + $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]); + $this->logger->warning( + 'exception while getMetadataFromDatabase()', ['exception' => $e, 'fileId' => $fileId] + ); throw new FilesMetadataNotFoundException(); } @@ -68,7 +71,6 @@ public function getMetadataFromFileId(int $fileId): IFilesMetadata { return $metadata; } - /** * @param int $fileId * @@ -82,17 +84,6 @@ public function dropMetadata(int $fileId): void { $qb->executeStatement(); } - private function removeDeprecatedMetadata(IFilesMetadata $filesMetadata): void { - // TODO delete aussi les index generate a partir d'une string[] - - $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->getIndexes(), IQueryBuilder::PARAM_STR_ARRAY)); - $qb->executeStatement(); - } - - /** * @param IFilesMetadata $filesMetadata * @@ -100,14 +91,36 @@ private function removeDeprecatedMetadata(IFilesMetadata $filesMetadata): void { * @throws Exception */ public function updateMetadata(IFilesMetadata $filesMetadata): int { - // TODO check sync_token on update $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('abc')) + ->set('sync_token', $qb->createNamedParameter($this->generateSyncToken())) ->set('last_update', $qb->createFunction('NOW()')) - ->where($qb->expr()->eq('file_id', $qb->createNamedParameter($filesMetadata->getFileId(), IQueryBuilder::PARAM_INT))); + ->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(); } + + + 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) { + } + } + + return $str; + } } diff --git a/lib/public/FilesMetadata/Model/IFilesMetadata.php b/lib/public/FilesMetadata/Model/IFilesMetadata.php index b5f0a34c50f2d..faa4629e10b08 100644 --- a/lib/public/FilesMetadata/Model/IFilesMetadata.php +++ b/lib/public/FilesMetadata/Model/IFilesMetadata.php @@ -69,9 +69,9 @@ public function getArray(string $key): array; public function getStringList(string $key): array; public function getIntList(string $key): array; public function getType(string $key): string; - public function set(string $key, string $value): self; - public function setInt(string $key, int $value): self; - public function setFloat(string $key, float $value): self; + public function set(string $key, string $value, bool $index = false): self; + public function setInt(string $key, int $value, bool $index = false): self; + public function setFloat(string $key, float $value, bool $index = false): self; public function setBool(string $key, bool $value): self; public function setArray(string $key, array $value): self; public function setStringList(string $key, array $value): self;