feat(Db): Use SnowflakeId for previews

Allow to get an id for the storing the preview on disk before inserting
the preview on the DB.

Signed-off-by: Carl Schwan <carl.schwan@nextcloud.com>
pull/55728/head
Carl Schwan 2025-10-13 09:52:04 +07:00
parent c9b055a0d0
commit 336cc3fa35
19 changed files with 260 additions and 43 deletions

@ -26,6 +26,7 @@ use OCP\Files\IRootFolder;
use OCP\IAppConfig;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\Snowflake\IGenerator;
use Override;
use Psr\Log\LoggerInterface;
@ -44,6 +45,7 @@ class MovePreviewJob extends TimedJob {
private readonly IMimeTypeDetector $mimeTypeDetector,
private readonly IMimeTypeLoader $mimeTypeLoader,
private readonly LoggerInterface $logger,
private readonly IGenerator $generator,
IAppDataFactory $appDataFactory,
) {
parent::__construct($time);
@ -136,6 +138,7 @@ class MovePreviewJob extends TimedJob {
$path = $fileId . '/' . $previewFile->getName();
/** @var SimpleFile $previewFile */
$preview = Preview::fromPath($path, $this->mimeTypeDetector);
$preview->setId($this->generator->nextId());
if (!$preview) {
$this->logger->error('Unable to import old preview at path.');
continue;

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Migrations;
use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\Migration\Attributes\ModifyColumn;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
/**
* Migrate away from auto-increment
*/
#[ModifyColumn(table: 'preview_locations', name: 'id', description: 'Remove auto-increment')]
#[ModifyColumn(table: 'previews', name: 'id', description: 'Remove auto-increment')]
#[ModifyColumn(table: 'preview_versions', name: 'id', description: 'Remove auto-increment')]
class Version33000Date20251023110529 extends SimpleMigrationStep {
/**
* @param Closure(): ISchemaWrapper $schemaClosure The `\Closure` returns a `ISchemaWrapper`
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
$schema = $schemaClosure();
if ($schema->hasTable('preview_locations')) {
$schema->dropAutoincrementColumn('preview_locations', 'id');
}
if ($schema->hasTable('preview_versions')) {
$schema->dropAutoincrementColumn('preview_versions', 'id');
}
if ($schema->hasTable('previews')) {
$schema->dropAutoincrementColumn('previews', 'id');
}
return $schema;
}
}

@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Migrations;
use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\IDBConnection;
use OCP\Migration\Attributes\AddIndex;
use OCP\Migration\Attributes\IndexType;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
/**
* Use unique index for preview_locations
*/
#[AddIndex(table: 'preview_locations', type: IndexType::UNIQUE)]
class Version33000Date20251023120529 extends SimpleMigrationStep {
public function __construct(
private readonly IDBConnection $connection,
) {
}
/**
* @param Closure(): ISchemaWrapper $schemaClosure The `\Closure` returns a `ISchemaWrapper`
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
if ($schema->hasTable('preview_locations')) {
$table = $schema->getTable('preview_locations');
$table->addUniqueIndex(['bucket_name', 'object_store_name'], 'unique_bucket_store');
}
return $schema;
}
public function preSchemaChange(IOutput $output, \Closure $schemaClosure, array $options) {
// This shouldn't run on a production instance, only daily
$qb = $this->connection->getQueryBuilder();
$qb->select('*')
->from('preview_locations');
$result = $qb->executeQuery();
$set = [];
while ($row = $result->fetch()) {
// Iterate over all the rows with duplicated rows
$id = $row['id'];
if (isset($set[$row['bucket_name'] . '_' . $row['object_store_name']])) {
// duplicate
$authoritativeId = $set[$row['bucket_name'] . '_' . $row['object_store_name']];
$qb = $this->connection->getQueryBuilder();
$qb->select('id')
->from('preview_locations')
->where($qb->expr()->eq('bucket_name', $qb->createNamedParameter($row['bucket_name'])))
->andWhere($qb->expr()->eq('object_store_name', $qb->createNamedParameter($row['object_store_name'])))
->andWhere($qb->expr()->neq('id', $qb->createNamedParameter($authoritativeId)));
$result = $qb->executeQuery();
while ($row = $result->fetch()) {
// Update previews entries to the now de-duplicated id
$qb = $this->connection->getQueryBuilder();
$qb->update('previews')
->set('location_id', $qb->createNamedParameter($id))
->where($qb->expr()->eq('id', $qb->createNamedParameter($row['id'])));
$qb->executeStatement();
$qb = $this->connection->getQueryBuilder();
$qb->delete('preview_locations')
->where($qb->expr()->eq('id', $qb->createNamedParameter($row['id'])));
$qb->executeStatement();
}
break;
}
$set[$row['bucket_name'] . '_' . $row['object_store_name']] = $row['id'];
}
}
}

@ -1530,6 +1530,8 @@ return array(
'OC\\Core\\Migrations\\Version32000Date20250731062008' => $baseDir . '/core/Migrations/Version32000Date20250731062008.php',
'OC\\Core\\Migrations\\Version32000Date20250806110519' => $baseDir . '/core/Migrations/Version32000Date20250806110519.php',
'OC\\Core\\Migrations\\Version33000Date20250819110529' => $baseDir . '/core/Migrations/Version33000Date20250819110529.php',
'OC\\Core\\Migrations\\Version33000Date20251023110529' => $baseDir . '/core/Migrations/Version33000Date20251023110529.php',
'OC\\Core\\Migrations\\Version33000Date20251023120529' => $baseDir . '/core/Migrations/Version33000Date20251023120529.php',
'OC\\Core\\Notification\\CoreNotifier' => $baseDir . '/core/Notification/CoreNotifier.php',
'OC\\Core\\ResponseDefinitions' => $baseDir . '/core/ResponseDefinitions.php',
'OC\\Core\\Service\\CronService' => $baseDir . '/core/Service/CronService.php',

@ -1571,6 +1571,8 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Core\\Migrations\\Version32000Date20250731062008' => __DIR__ . '/../../..' . '/core/Migrations/Version32000Date20250731062008.php',
'OC\\Core\\Migrations\\Version32000Date20250806110519' => __DIR__ . '/../../..' . '/core/Migrations/Version32000Date20250806110519.php',
'OC\\Core\\Migrations\\Version33000Date20250819110529' => __DIR__ . '/../../..' . '/core/Migrations/Version33000Date20250819110529.php',
'OC\\Core\\Migrations\\Version33000Date20251023110529' => __DIR__ . '/../../..' . '/core/Migrations/Version33000Date20251023110529.php',
'OC\\Core\\Migrations\\Version33000Date20251023120529' => __DIR__ . '/../../..' . '/core/Migrations/Version33000Date20251023120529.php',
'OC\\Core\\Notification\\CoreNotifier' => __DIR__ . '/../../..' . '/core/Notification/CoreNotifier.php',
'OC\\Core\\ResponseDefinitions' => __DIR__ . '/../../..' . '/core/ResponseDefinitions.php',
'OC\\Core\\Service\\CronService' => __DIR__ . '/../../..' . '/core/Service/CronService.php',

@ -204,7 +204,7 @@ class Dispatcher {
try {
$response = \call_user_func_array([$controller, $methodName], $arguments);
} catch (\TypeError $e) {
// Only intercept TypeErrors occuring on the first line, meaning that the invocation of the controller method failed.
// Only intercept TypeErrors occurring on the first line, meaning that the invocation of the controller method failed.
// Any other TypeError happens inside the controller method logic and should be logged as normal.
if ($e->getFile() === $this->reflector->getFile() && $e->getLine() === $this->reflector->getStartLine()) {
$this->logger->debug('Failed to call controller method: ' . $e->getMessage(), ['exception' => $e]);

@ -8,8 +8,11 @@ namespace OC\DB;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\OraclePlatform;
use Doctrine\DBAL\Schema\Schema;
use OCP\DB\ISchemaWrapper;
use OCP\Server;
use Psr\Log\LoggerInterface;
class SchemaWrapper implements ISchemaWrapper {
/** @var Connection */
@ -131,4 +134,18 @@ class SchemaWrapper implements ISchemaWrapper {
public function getDatabasePlatform() {
return $this->connection->getDatabasePlatform();
}
public function dropAutoincrementColumn(string $table, string $column): void {
$tableObj = $this->schema->getTable($this->connection->getPrefix() . $table);
$tableObj->modifyColumn('id', ['autoincrement' => false]);
$platform = $this->getDatabasePlatform();
if ($platform instanceof OraclePlatform) {
try {
$this->connection->executeStatement('DROP TRIGGER "' . $this->connection->getPrefix() . $table . '_AI_PK"');
$this->connection->executeStatement('DROP SEQUENCE "' . $this->connection->getPrefix() . $table . '_SEQ"');
} catch (Exception $e) {
Server::get(LoggerInterface::class)->error($e->getMessage(), ['exception' => $e]);
}
}
}
}

@ -17,14 +17,16 @@ use OCP\Files\IMimeTypeDetector;
/**
* Preview entity mapped to the oc_previews and oc_preview_locations table.
*
* @method string getId()
* @method void setId(string $id)
* @method int getFileId() Get the file id of the original file.
* @method void setFileId(int $fileId)
* @method int getStorageId() Get the storage id of the original file.
* @method void setStorageId(int $fileId)
* @method int getOldFileId() Get the old location in the file-cache table, for legacy compatibility.
* @method void setOldFileId(int $oldFileId)
* @method int getLocationId() Get the location id in the preview_locations table. Only set when using an object store as primary storage.
* @method void setLocationId(int $locationId)
* @method string getLocationId() Get the location id in the preview_locations table. Only set when using an object store as primary storage.
* @method void setLocationId(string $locationId)
* @method string|null getBucketName() Get the bucket name where the preview is stored. This is stored in the preview_locations table.
* @method string|null getObjectStoreName() Get the object store name where the preview is stored. This is stored in the preview_locations table.
* @method int getWidth() Get the width of the preview.
@ -46,7 +48,7 @@ use OCP\Files\IMimeTypeDetector;
* @method string getEtag() Get the etag of the preview.
* @method void setEtag(string $etag)
* @method string|null getVersion() Get the version for files_versions_s3
* @method void setVersionId(int $versionId)
* @method void setVersionId(string $versionId)
* @method bool|null getIs() Get the version for files_versions_s3
* @method bool isEncrypted() Get whether the preview is encrypted. At the moment every preview is unencrypted.
* @method void setEncrypted(bool $encrypted)
@ -57,7 +59,7 @@ class Preview extends Entity {
protected ?int $fileId = null;
protected ?int $oldFileId = null;
protected ?int $storageId = null;
protected ?int $locationId = null;
protected ?string $locationId = null;
protected ?string $bucketName = null;
protected ?string $objectStoreName = null;
protected ?int $width = null;
@ -72,14 +74,15 @@ class Preview extends Entity {
protected ?bool $cropped = null;
protected ?string $etag = null;
protected ?string $version = null;
protected ?int $versionId = null;
protected ?string $versionId = null;
protected ?bool $encrypted = null;
public function __construct() {
$this->addType('id', Types::STRING);
$this->addType('fileId', Types::BIGINT);
$this->addType('storageId', Types::BIGINT);
$this->addType('oldFileId', Types::BIGINT);
$this->addType('locationId', Types::BIGINT);
$this->addType('locationId', Types::STRING);
$this->addType('width', Types::INTEGER);
$this->addType('height', Types::INTEGER);
$this->addType('mimetypeId', Types::INTEGER);

@ -15,6 +15,7 @@ use OCP\DB\Exception;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Files\IMimeTypeLoader;
use OCP\IDBConnection;
use OCP\Snowflake\IGenerator;
use Override;
/**
@ -29,6 +30,7 @@ class PreviewMapper extends QBMapper {
public function __construct(
IDBConnection $db,
private readonly IMimeTypeLoader $mimeTypeLoader,
private readonly IGenerator $snowflake,
) {
parent::__construct($db, self::TABLE_NAME, Preview::class);
}
@ -50,13 +52,15 @@ class PreviewMapper extends QBMapper {
if ($preview->getVersion() !== null && $preview->getVersion() !== '') {
$qb = $this->db->getQueryBuilder();
$id = $this->snowflake->nextId();
$qb->insert(self::VERSION_TABLE_NAME)
->values([
'id' => $id,
'version' => $preview->getVersion(),
'file_id' => $preview->getFileId(),
])
->executeStatement();
$entity->setVersionId($qb->getLastInsertId());
$entity->setVersionId($id);
}
return parent::insert($preview);
}
@ -148,7 +152,13 @@ class PreviewMapper extends QBMapper {
));
}
public function getLocationId(string $bucket, string $objectStore): int {
/**
* Get the location id corresponding to the $bucket and $objectStore. Create one
* if not existing yet.
*
* @throws Exception
*/
public function getLocationId(string $bucket, string $objectStore): string {
$qb = $this->db->getQueryBuilder();
$result = $qb->select('id')
->from(self::LOCATION_TABLE_NAME)
@ -157,14 +167,33 @@ class PreviewMapper extends QBMapper {
->executeQuery();
$data = $result->fetchOne();
if ($data) {
return $data;
return (string)$data;
} else {
$qb->insert(self::LOCATION_TABLE_NAME)
->values([
'bucket_name' => $qb->createNamedParameter($bucket),
'object_store_name' => $qb->createNamedParameter($objectStore),
])->executeStatement();
return $qb->getLastInsertId();
try {
$id = $this->snowflake->nextId();
$qb->insert(self::LOCATION_TABLE_NAME)
->values([
'id' => $qb->createNamedParameter($id),
'bucket_name' => $qb->createNamedParameter($bucket),
'object_store_name' => $qb->createNamedParameter($objectStore),
])->executeStatement();
return $id;
} catch (Exception $e) {
if ($e->getReason() === Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
// Fetch again as there seems to be another entry added meanwhile
$result = $qb->select('id')
->from(self::LOCATION_TABLE_NAME)
->where($qb->expr()->eq('bucket_name', $qb->createNamedParameter($bucket)))
->andWhere($qb->expr()->eq('object_store_name', $qb->createNamedParameter($objectStore)))
->executeQuery();
$data = $result->fetchOne();
if ($data) {
return (string)$data;
}
}
throw $e;
}
}
}

@ -23,6 +23,7 @@ use OCP\IPreview;
use OCP\IStreamImage;
use OCP\Preview\BeforePreviewFetchedEvent;
use OCP\Preview\IVersionedPreviewFile;
use OCP\Snowflake\IGenerator;
use Psr\Log\LoggerInterface;
class Generator {
@ -37,6 +38,7 @@ class Generator {
private LoggerInterface $logger,
private PreviewMapper $previewMapper,
private StorageFactory $storageFactory,
private IGenerator $snowflakeGenerator,
) {
}
@ -348,6 +350,7 @@ class Generator {
try {
$previewEntry = new Preview();
$previewEntry->setId($this->snowflakeGenerator->nextId());
$previewEntry->setFileId($file->getId());
$previewEntry->setStorageId($file->getMountPoint()->getNumericStorageId());
$previewEntry->setSourceMimeType($file->getMimeType());
@ -360,7 +363,6 @@ class Generator {
$previewEntry->setMimetype($preview->dataMimeType());
$previewEntry->setEtag($file->getEtag());
$previewEntry->setMtime((new \DateTime())->getTimestamp());
$previewEntry->setSize(0);
return $this->savePreview($previewEntry, $preview);
} catch (NotPermittedException) {
throw new NotFoundException();
@ -502,6 +504,7 @@ class Generator {
}
$previewEntry = new Preview();
$previewEntry->setId($this->snowflakeGenerator->nextId());
$previewEntry->setFileId($file->getId());
$previewEntry->setStorageId($file->getMountPoint()->getNumericStorageId());
$previewEntry->setWidth($width);
@ -514,7 +517,6 @@ class Generator {
$previewEntry->setMimeType($preview->dataMimeType());
$previewEntry->setEtag($file->getEtag());
$previewEntry->setMtime((new \DateTime())->getTimestamp());
$previewEntry->setSize(0);
if ($cacheResult) {
$previewEntry = $this->savePreview($previewEntry, $preview);
return new PreviewFile($previewEntry, $this->storageFactory, $this->previewMapper);
@ -530,26 +532,20 @@ class Generator {
* @throws \OCP\DB\Exception
*/
public function savePreview(Preview $previewEntry, IImage $preview): Preview {
$previewEntry = $this->previewMapper->insert($previewEntry);
// we need to save to DB first
try {
if ($preview instanceof IStreamImage) {
$size = $this->storageFactory->writePreview($previewEntry, $preview->resource());
} else {
$stream = fopen('php://temp', 'w+');
fwrite($stream, $preview->data());
rewind($stream);
$size = $this->storageFactory->writePreview($previewEntry, $stream);
}
if (!$size) {
throw new \RuntimeException('Unable to write preview file');
}
} catch (\Exception $e) {
$this->previewMapper->delete($previewEntry);
throw $e;
if ($preview instanceof IStreamImage) {
$size = $this->storageFactory->writePreview($previewEntry, $preview->resource());
} else {
$stream = fopen('php://temp', 'w+');
fwrite($stream, $preview->data());
rewind($stream);
$size = $this->storageFactory->writePreview($previewEntry, $stream);
}
if (!$size) {
throw new \RuntimeException('Unable to write preview file');
}
$previewEntry->setSize($size);
return $this->previewMapper->update($previewEntry);
return $this->previewMapper->insert($previewEntry);
}
}

@ -22,6 +22,7 @@ use OCP\Files\NotPermittedException;
use OCP\IAppConfig;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\Snowflake\IGenerator;
use Override;
use Psr\Log\LoggerInterface;
use RecursiveDirectoryIterator;
@ -38,6 +39,7 @@ class LocalPreviewStorage implements IPreviewStorage {
private readonly IDBConnection $connection,
private readonly IMimeTypeDetector $mimeTypeDetector,
private readonly LoggerInterface $logger,
private readonly IGenerator $generator,
) {
$this->instanceId = $this->config->getSystemValueString('instanceid');
$this->rootFolder = $this->config->getSystemValue('datadirectory', OC::$SERVERROOT . '/data');
@ -118,6 +120,7 @@ class LocalPreviewStorage implements IPreviewStorage {
$this->logger->error('Unable to parse preview information for ' . $file->getRealPath());
continue;
}
$preview->setId($this->generator->nextId());
try {
$preview->setSize($file->getSize());
$preview->setMtime($file->getMtime());

@ -23,6 +23,7 @@ use OCP\IBinaryFinder;
use OCP\IConfig;
use OCP\IPreview;
use OCP\Preview\IProviderV2;
use OCP\Snowflake\IGenerator;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use Psr\Log\LoggerInterface;
@ -141,6 +142,7 @@ class PreviewManager implements IPreview {
$this->container->get(LoggerInterface::class),
$this->container->get(PreviewMapper::class),
$this->container->get(StorageFactory::class),
$this->container->get(IGenerator::class),
);
}
return $this->generator;

@ -19,10 +19,8 @@ use function substr;
* @psalm-consistent-constructor
*/
abstract class Entity {
/**
* @var int
*/
public $id;
/** @var int $id */
public $id = null;
private array $_updatedFields = [];
/** @var array<string, \OCP\DB\Types::*> */

@ -90,4 +90,11 @@ interface ISchemaWrapper {
* @since 23.0.0
*/
public function getDatabasePlatform();
/**
* Drop autoincrement from an existing table of the database.
*
* @since 33.0.0
*/
public function dropAutoincrementColumn(string $table, string $column): void;
}

@ -22,6 +22,7 @@ use OCP\IPreview;
use OCP\Preview\BeforePreviewFetchedEvent;
use OCP\Preview\IProviderV2;
use OCP\Preview\IVersionedPreviewFile;
use OCP\Snowflake\IGenerator;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\TestWith;
use PHPUnit\Framework\MockObject\MockObject;
@ -41,6 +42,7 @@ class GeneratorTest extends TestCase {
private LoggerInterface&MockObject $logger;
private StorageFactory&MockObject $storageFactory;
private PreviewMapper&MockObject $previewMapper;
private IGenerator&MockObject $snowflakeGenerator;
protected function setUp(): void {
parent::setUp();
@ -52,6 +54,7 @@ class GeneratorTest extends TestCase {
$this->logger = $this->createMock(LoggerInterface::class);
$this->previewMapper = $this->createMock(PreviewMapper::class);
$this->storageFactory = $this->createMock(StorageFactory::class);
$this->snowflakeGenerator = $this->createMock(IGenerator::class);
$this->generator = new Generator(
$this->config,
@ -61,6 +64,7 @@ class GeneratorTest extends TestCase {
$this->logger,
$this->previewMapper,
$this->storageFactory,
$this->snowflakeGenerator,
);
}

@ -24,6 +24,7 @@ use OCP\IAppConfig;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\Server;
use OCP\Snowflake\IGenerator;
use PHPUnit\Framework\Attributes\TestDox;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
@ -123,6 +124,7 @@ class MovePreviewJobTest extends TestCase {
$this->mimeTypeDetector,
$this->mimeTypeLoader,
$this->logger,
Server::get(IGenerator::class),
Server::get(IAppDataFactory::class),
);
$this->invokePrivate($job, 'run', [[]]);
@ -155,6 +157,7 @@ class MovePreviewJobTest extends TestCase {
$this->mimeTypeDetector,
$this->mimeTypeLoader,
$this->logger,
Server::get(IGenerator::class),
Server::get(IAppDataFactory::class)
);
$this->invokePrivate($job, 'run', [[]]);
@ -195,6 +198,7 @@ class MovePreviewJobTest extends TestCase {
$this->mimeTypeDetector,
$this->mimeTypeLoader,
$this->logger,
Server::get(IGenerator::class),
Server::get(IAppDataFactory::class)
);
$this->invokePrivate($job, 'run', [[]]);

@ -14,16 +14,28 @@ use OC\Preview\Db\Preview;
use OC\Preview\Db\PreviewMapper;
use OCP\IDBConnection;
use OCP\Server;
use OCP\Snowflake\IGenerator;
use Test\TestCase;
#[\PHPUnit\Framework\Attributes\Group('DB')]
class PreviewMapperTest extends TestCase {
private PreviewMapper $previewMapper;
private IDBConnection $connection;
private IGenerator $snowflake;
public function setUp(): void {
$this->previewMapper = Server::get(PreviewMapper::class);
$this->connection = Server::get(IDBConnection::class);
$this->snowflake = Server::get(IGenerator::class);
$qb = $this->connection->getQueryBuilder();
$qb->delete('preview_locations')->executeStatement();
$qb = $this->connection->getQueryBuilder();
$qb->delete('preview_versions')->executeStatement();
$qb = $this->connection->getQueryBuilder();
$qb->delete('previews')->executeStatement();
}
public function testGetAvailablePreviews(): void {
@ -51,15 +63,17 @@ class PreviewMapperTest extends TestCase {
$locationId = null;
if ($bucket) {
$qb = $this->connection->getQueryBuilder();
$locationId = $this->snowflake->nextId();
$qb->insert('preview_locations')
->values([
'id' => $locationId,
'bucket_name' => $qb->createNamedParameter('preview-' . $bucket),
'object_store_name' => $qb->createNamedParameter('default'),
]);
$qb->executeStatement();
$locationId = $qb->getLastInsertId();
}
$preview = new Preview();
$preview->setId($this->snowflake->nextId());
$preview->setFileId($fileId);
$preview->setStorageId(1);
$preview->setCropped(true);

@ -14,7 +14,7 @@ use OC\Preview\Db\Preview;
use OC\Preview\Db\PreviewMapper;
use OC\Preview\PreviewService;
use OCP\Server;
use PHPUnit\Framework\Attributes\CoversClass;
use OCP\Snowflake\IGenerator;
use PHPUnit\Framework\TestCase;
#[CoversClass(PreviewService::class)]
@ -22,10 +22,12 @@ use PHPUnit\Framework\TestCase;
class PreviewServiceTest extends TestCase {
private PreviewService $previewService;
private PreviewMapper $previewMapper;
private IGenerator $snowflakeGenerator;
protected function setUp(): void {
$this->previewService = Server::get(PreviewService::class);
$this->previewMapper = Server::get(PreviewMapper::class);
$this->snowflakeGenerator = Server::get(IGenerator::class);
$this->previewService->deleteAll();
}
@ -36,6 +38,7 @@ class PreviewServiceTest extends TestCase {
public function testGetAvailableFileIds(): void {
foreach (range(1, 20) as $i) {
$preview = new Preview();
$preview->setId($this->snowflakeGenerator->nextId());
$preview->setFileId($i % 10);
$preview->setStorageId(1);
$preview->setWidth($i);

@ -9,7 +9,7 @@
// between betas, final and RCs. This is _not_ the public version number. Reset minor/patch level
// when updating major/minor version number.
$OC_Version = [33, 0, 0, 2];
$OC_Version = [33, 0, 0, 3];
// The human-readable string
$OC_VersionString = '33.0.0 dev';