From c69b7ff7e90cfe3dae66f4d1d5aeacfe224ebe4f Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 9 Jul 2026 17:03:50 +0200 Subject: [PATCH] fix(preview): Handle unique constraints violation exception Delete the preview from storage and fetch the actual one from the DB. This should not happen often. Signed-off-by: Carl Schwan --- lib/private/Preview/Db/PreviewMapper.php | 16 +++++++++++++++ lib/private/Preview/Generator.php | 26 +++++++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/lib/private/Preview/Db/PreviewMapper.php b/lib/private/Preview/Db/PreviewMapper.php index e66e2c2ef0a4d..d6508c64afe52 100644 --- a/lib/private/Preview/Db/PreviewMapper.php +++ b/lib/private/Preview/Db/PreviewMapper.php @@ -9,6 +9,7 @@ namespace OC\Preview\Db; +use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\Entity; use OCP\AppFramework\Db\QBMapper; use OCP\DB\Exception; @@ -227,4 +228,19 @@ public function getPreviewsForMimeTypes(array $mimeTypes): \Generator { )); return $this->yieldEntities($qb); } + + public function getPreviewForSpecification(array $parameters): ?Preview { + $qb = $this->db->getQueryBuilder(); + $this->joinLocation($qb); + + foreach ($parameters as $key => $value) { + $qb->andWhere($qb->expr()->eq($key, $qb->createNamedParameter($value))); + } + + try { + return $this->findEntity($qb); + } catch (DoesNotExistException) { + return null; + } + } } diff --git a/lib/private/Preview/Generator.php b/lib/private/Preview/Generator.php index 2549d40213a90..f93fbd4e0ec27 100644 --- a/lib/private/Preview/Generator.php +++ b/lib/private/Preview/Generator.php @@ -590,6 +590,30 @@ public function savePreview(Preview $previewEntry, IImage $preview): Preview { } $previewEntry->setSize($size); $previewEntry->generateId(); - return $this->previewMapper->insert($previewEntry); + try { + return $this->previewMapper->insert($previewEntry); + } catch (DBException $e) { + if ($e->getReason() !== DBException::REASON_UNIQUE_CONSTRAINT_VIOLATION) { + throw $e; + } + + $this->storageFactory->deletePreview($previewEntry); + + $this->logger->debug('Generating a preview but one already exists.', ['exception' => $e]); + + $preview = $this->previewMapper->getPreviewForSpecification([ + 'file_id' => $previewEntry->getFileId(), + 'width' => $previewEntry->getWidth(), + 'height' => $previewEntry->getHeight(), + 'mimetype_id' => $previewEntry->getMimetypeId(), + 'cropped' => $previewEntry->isCropped(), + 'version_id' => $previewEntry->getVersion() ?? '-1', + ]); + + if ($preview === null) { + throw new \RuntimeException('Unable to save a preview because it already exists but fetching it again yield nothing'); + } + return $preview; + } } }