mirror of
https://github.com/nextcloud/server.git
synced 2026-02-18 18:28:50 -05:00
fix(preview): Make version column a string
And move it to a different table so that we don't have to pay the storage cost when not using it (most of the times). Signed-off-by: Carl Schwan <carl.schwan@nextcloud.com>
This commit is contained in:
parent
66f50bd585
commit
bef3996c3e
23 changed files with 404 additions and 246 deletions
|
|
@ -17,24 +17,26 @@ use OC\Preview\Storage\StorageFactory;
|
|||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\BackgroundJob\TimedJob;
|
||||
use OCP\DB\Exception;
|
||||
use OCP\DB\IResult;
|
||||
use OCP\Files\AppData\IAppDataFactory;
|
||||
use OCP\Files\IAppData;
|
||||
use OCP\Files\IMimeTypeDetector;
|
||||
use OCP\Files\IMimeTypeLoader;
|
||||
use OCP\Files\IRootFolder;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\Files\SimpleFS\ISimpleFolder;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IConfig;
|
||||
use OCP\IDBConnection;
|
||||
use Override;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class MovePreviewJob extends TimedJob {
|
||||
private IAppData $appData;
|
||||
private string $previewRootPath;
|
||||
|
||||
public function __construct(
|
||||
ITimeFactory $time,
|
||||
private readonly IAppConfig $appConfig,
|
||||
private readonly IConfig $config,
|
||||
private readonly PreviewMapper $previewMapper,
|
||||
private readonly StorageFactory $storageFactory,
|
||||
private readonly IDBConnection $connection,
|
||||
|
|
@ -49,6 +51,7 @@ class MovePreviewJob extends TimedJob {
|
|||
$this->appData = $appDataFactory->get('preview');
|
||||
$this->setTimeSensitivity(self::TIME_INSENSITIVE);
|
||||
$this->setInterval(24 * 60 * 60);
|
||||
$this->previewRootPath = 'appdata_' . $this->config->getSystemValueString('instanceid') . '/preview/';
|
||||
}
|
||||
|
||||
#[Override]
|
||||
|
|
@ -57,49 +60,22 @@ class MovePreviewJob extends TimedJob {
|
|||
return;
|
||||
}
|
||||
|
||||
$emptyHierarchicalPreviewFolders = false;
|
||||
|
||||
$startTime = time();
|
||||
while (true) {
|
||||
// Check new hierarchical preview folders first
|
||||
if (!$emptyHierarchicalPreviewFolders) {
|
||||
$qb = $this->connection->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from('filecache')
|
||||
->where($qb->expr()->like('path', $qb->createNamedParameter('appdata_%/preview/%/%/%/%/%/%/%/%/%')))
|
||||
->hintShardKey('storage', $this->rootFolder->getMountPoint()->getNumericStorageId())
|
||||
->setMaxResults(100);
|
||||
|
||||
$result = $qb->executeQuery();
|
||||
while ($row = $result->fetch()) {
|
||||
$pathSplit = explode('/', $row['path']);
|
||||
assert(count($pathSplit) >= 2);
|
||||
$fileId = $pathSplit[count($pathSplit) - 2];
|
||||
$this->processPreviews($fileId, false);
|
||||
}
|
||||
}
|
||||
|
||||
// And then the flat preview folder (legacy)
|
||||
$emptyHierarchicalPreviewFolders = true;
|
||||
$qb = $this->connection->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
$qb->select('path')
|
||||
->from('filecache')
|
||||
->where($qb->expr()->like('path', $qb->createNamedParameter('appdata_%/preview/%/%.%')))
|
||||
// Hierarchical preview folder structure
|
||||
->where($qb->expr()->like('path', $qb->createNamedParameter($this->previewRootPath . '%/%/%/%/%/%/%/%/%')))
|
||||
// Legacy flat preview folder structure
|
||||
->orWhere($qb->expr()->like('path', $qb->createNamedParameter($this->previewRootPath . '%/%.%')))
|
||||
->hintShardKey('storage', $this->rootFolder->getMountPoint()->getNumericStorageId())
|
||||
->setMaxResults(100);
|
||||
|
||||
$result = $qb->executeQuery();
|
||||
$foundOldPreview = false;
|
||||
while ($row = $result->fetch()) {
|
||||
$pathSplit = explode('/', $row['path']);
|
||||
assert(count($pathSplit) >= 2);
|
||||
$fileId = $pathSplit[count($pathSplit) - 2];
|
||||
array_pop($pathSplit);
|
||||
$this->processPreviews($fileId, true);
|
||||
$foundOldPreview = true;
|
||||
}
|
||||
$foundPreviews = $this->processQueryResult($result);
|
||||
|
||||
if (!$foundOldPreview) {
|
||||
if (!$foundPreviews) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -109,20 +85,46 @@ class MovePreviewJob extends TimedJob {
|
|||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Delete any leftover preview directory
|
||||
$this->appData->getFolder('.')->delete();
|
||||
} catch (NotFoundException) {
|
||||
// ignore
|
||||
}
|
||||
$this->appConfig->setValueBool('core', 'previewMovedDone', true);
|
||||
}
|
||||
|
||||
private function processQueryResult(IResult $result): bool {
|
||||
$foundPreview = false;
|
||||
$fileIds = [];
|
||||
$flatFileIds = [];
|
||||
while ($row = $result->fetch()) {
|
||||
$pathSplit = explode('/', $row['path']);
|
||||
assert(count($pathSplit) >= 2);
|
||||
$fileId = (int)$pathSplit[count($pathSplit) - 2];
|
||||
if (count($pathSplit) === 11) {
|
||||
// Hierarchical structure
|
||||
if (!in_array($fileId, $fileIds)) {
|
||||
$fileIds[] = $fileId;
|
||||
}
|
||||
} else {
|
||||
// Flat structure
|
||||
if (!in_array($fileId, $flatFileIds)) {
|
||||
$flatFileIds[] = $fileId;
|
||||
}
|
||||
}
|
||||
$foundPreview = true;
|
||||
}
|
||||
|
||||
foreach ($fileIds as $fileId) {
|
||||
$this->processPreviews($fileId, flatPath: false);
|
||||
}
|
||||
|
||||
foreach ($flatFileIds as $fileId) {
|
||||
$this->processPreviews($fileId, flatPath: true);
|
||||
}
|
||||
return $foundPreview;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string|int, string[]> $previewFolders
|
||||
*/
|
||||
private function processPreviews(int|string $fileId, bool $simplePaths): void {
|
||||
$internalPath = $this->getInternalFolder((string)$fileId, $simplePaths);
|
||||
private function processPreviews(int $fileId, bool $flatPath): void {
|
||||
$internalPath = $this->getInternalFolder((string)$fileId, $flatPath);
|
||||
$folder = $this->appData->getFolder($internalPath);
|
||||
|
||||
/**
|
||||
|
|
@ -133,7 +135,7 @@ class MovePreviewJob extends TimedJob {
|
|||
foreach ($folder->getDirectoryListing() as $previewFile) {
|
||||
$path = $fileId . '/' . $previewFile->getName();
|
||||
/** @var SimpleFile $previewFile */
|
||||
$preview = Preview::fromPath($path, $this->mimeTypeDetector, $this->mimeTypeLoader);
|
||||
$preview = Preview::fromPath($path, $this->mimeTypeDetector);
|
||||
if (!$preview) {
|
||||
$this->logger->error('Unable to import old preview at path.');
|
||||
continue;
|
||||
|
|
@ -160,23 +162,30 @@ class MovePreviewJob extends TimedJob {
|
|||
|
||||
if (count($result) > 0) {
|
||||
foreach ($previewFiles as $previewFile) {
|
||||
/** @var Preview $preview */
|
||||
$preview = $previewFile['preview'];
|
||||
/** @var SimpleFile $file */
|
||||
$file = $previewFile['file'];
|
||||
$preview->setStorageId($result[0]['storage']);
|
||||
$preview->setEtag($result[0]['etag']);
|
||||
$preview->setSourceMimetype($result[0]['mimetype']);
|
||||
$preview->setSourceMimeType($this->mimeTypeLoader->getMimetypeById((int)$result[0]['mimetype']));
|
||||
try {
|
||||
$preview = $this->previewMapper->insert($preview);
|
||||
} catch (Exception $e) {
|
||||
} catch (Exception) {
|
||||
// We already have this preview in the preview table, skip
|
||||
$qb->delete('filecache')
|
||||
->where($qb->expr()->eq('fileid', $qb->createNamedParameter($file->getId())))
|
||||
->hintShardKey('storage', $this->rootFolder->getMountPoint()->getNumericStorageId())
|
||||
->executeStatement();
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->storageFactory->migratePreview($preview, $file);
|
||||
$qb = $this->connection->getQueryBuilder();
|
||||
$qb->delete('filecache')
|
||||
->where($qb->expr()->eq('fileid', $qb->createNamedParameter($file->getId())))
|
||||
->hintShardKey('storage', $this->rootFolder->getMountPoint()->getNumericStorageId())
|
||||
->executeStatement();
|
||||
// Do not call $file->delete() as this will also delete the file from the file system
|
||||
} catch (\Exception $e) {
|
||||
|
|
@ -184,35 +193,51 @@ class MovePreviewJob extends TimedJob {
|
|||
throw $e;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No matching fileId, delete preview
|
||||
try {
|
||||
$this->connection->beginTransaction();
|
||||
foreach ($previewFiles as $previewFile) {
|
||||
/** @var SimpleFile $file */
|
||||
$file = $previewFile['file'];
|
||||
$file->delete();
|
||||
}
|
||||
$this->connection->commit();
|
||||
} catch (Exception) {
|
||||
$this->connection->rollback();
|
||||
}
|
||||
}
|
||||
|
||||
$this->deleteFolder($internalPath, $folder);
|
||||
$this->deleteFolder($internalPath);
|
||||
}
|
||||
|
||||
public static function getInternalFolder(string $name, bool $simplePaths): string {
|
||||
if ($simplePaths) {
|
||||
return '/' . $name;
|
||||
public static function getInternalFolder(string $name, bool $flatPath): string {
|
||||
if ($flatPath) {
|
||||
return $name;
|
||||
}
|
||||
return implode('/', str_split(substr(md5($name), 0, 7))) . '/' . $name;
|
||||
}
|
||||
|
||||
private function deleteFolder(string $path, ISimpleFolder $folder): void {
|
||||
$folder->delete();
|
||||
|
||||
private function deleteFolder(string $path): void {
|
||||
$current = $path;
|
||||
|
||||
while (true) {
|
||||
$appDataPath = $this->previewRootPath . $current;
|
||||
$qb = $this->connection->getQueryBuilder();
|
||||
$qb->delete('filecache')
|
||||
->where($qb->expr()->eq('path_hash', $qb->createNamedParameter(md5($appDataPath))))
|
||||
->hintShardKey('storage', $this->rootFolder->getMountPoint()->getNumericStorageId())
|
||||
->executeStatement();
|
||||
|
||||
$current = dirname($current);
|
||||
if ($current === '/' || $current === '.' || $current === '') {
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
$folder = $this->appData->getFolder($current);
|
||||
if (count($folder->getDirectoryListing()) !== 0) {
|
||||
break;
|
||||
}
|
||||
$folder->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ namespace OC\Core\Command\Preview;
|
|||
|
||||
use OC\Preview\Db\Preview;
|
||||
use OC\Preview\PreviewService;
|
||||
use OCP\Files\IMimeTypeLoader;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\Files\NotPermittedException;
|
||||
use OCP\IAvatarManager;
|
||||
|
|
@ -28,7 +27,6 @@ class ResetRenderedTexts extends Command {
|
|||
protected readonly IUserManager $userManager,
|
||||
protected readonly IAvatarManager $avatarManager,
|
||||
private readonly PreviewService $previewService,
|
||||
private readonly IMimeTypeLoader $mimeTypeLoader,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
|
@ -93,7 +91,7 @@ class ResetRenderedTexts extends Command {
|
|||
$previewsToDeleteCount = 0;
|
||||
|
||||
foreach ($this->getPreviewsToDelete() as $preview) {
|
||||
$output->writeln('Deleting preview ' . $preview->getName($this->mimeTypeLoader) . ' for fileId ' . $preview->getFileId(), OutputInterface::VERBOSITY_VERBOSE);
|
||||
$output->writeln('Deleting preview ' . $preview->getName() . ' for fileId ' . $preview->getFileId(), OutputInterface::VERBOSITY_VERBOSE);
|
||||
|
||||
$previewsToDeleteCount++;
|
||||
|
||||
|
|
@ -112,9 +110,9 @@ class ResetRenderedTexts extends Command {
|
|||
*/
|
||||
private function getPreviewsToDelete(): \Generator {
|
||||
return $this->previewService->getPreviewsForMimeTypes([
|
||||
$this->mimeTypeLoader->getId('text/plain'),
|
||||
$this->mimeTypeLoader->getId('text/markdown'),
|
||||
$this->mimeTypeLoader->getId('text/x-markdown'),
|
||||
'text/plain',
|
||||
'text/markdown',
|
||||
'text/x-markdown'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,14 @@ class Version33000Date20250819110529 extends SimpleMigrationStep {
|
|||
$table->setPrimaryKey(['id']);
|
||||
}
|
||||
|
||||
if (!$schema->hasTable('preview_versions')) {
|
||||
$table = $schema->createTable('preview_versions');
|
||||
$table->addColumn('id', Types::BIGINT, ['autoincrement' => true, 'notnull' => true, 'length' => 20, 'unsigned' => true]);
|
||||
$table->addColumn('file_id', Types::BIGINT, ['notnull' => true, 'length' => 20, 'unsigned' => true]);
|
||||
$table->addColumn('version', Types::STRING, ['notnull' => true, 'default' => '', 'length' => 1024]);
|
||||
$table->setPrimaryKey(['id']);
|
||||
}
|
||||
|
||||
if (!$schema->hasTable('previews')) {
|
||||
$table = $schema->createTable('previews');
|
||||
$table->addColumn('id', Types::BIGINT, ['autoincrement' => true, 'notnull' => true, 'length' => 20, 'unsigned' => true]);
|
||||
|
|
@ -46,18 +54,18 @@ class Version33000Date20250819110529 extends SimpleMigrationStep {
|
|||
$table->addColumn('location_id', Types::BIGINT, ['notnull' => false, 'length' => 20, 'unsigned' => true]);
|
||||
$table->addColumn('width', Types::INTEGER, ['notnull' => true, 'unsigned' => true]);
|
||||
$table->addColumn('height', Types::INTEGER, ['notnull' => true, 'unsigned' => true]);
|
||||
$table->addColumn('mimetype', Types::INTEGER, ['notnull' => true]);
|
||||
$table->addColumn('source_mimetype', Types::INTEGER, ['notnull' => true]);
|
||||
$table->addColumn('mimetype_id', Types::INTEGER, ['notnull' => true]);
|
||||
$table->addColumn('source_mimetype_id', Types::INTEGER, ['notnull' => true]);
|
||||
$table->addColumn('max', Types::BOOLEAN, ['notnull' => true, 'default' => false]);
|
||||
$table->addColumn('cropped', Types::BOOLEAN, ['notnull' => true, 'default' => false]);
|
||||
$table->addColumn('encrypted', Types::BOOLEAN, ['notnull' => true, 'default' => false]);
|
||||
$table->addColumn('etag', Types::STRING, ['notnull' => true, 'length' => 40, 'fixed' => true]);
|
||||
$table->addColumn('mtime', Types::INTEGER, ['notnull' => true, 'unsigned' => true]);
|
||||
$table->addColumn('size', Types::INTEGER, ['notnull' => true, 'unsigned' => true]);
|
||||
$table->addColumn('version', Types::BIGINT, ['notnull' => true, 'default' => -1]); // can not be null otherwise unique index doesn't work
|
||||
$table->addColumn('version_id', Types::BIGINT, ['notnull' => true, 'default' => -1]);
|
||||
$table->setPrimaryKey(['id']);
|
||||
$table->addIndex(['file_id']);
|
||||
$table->addUniqueIndex(['file_id', 'width', 'height', 'mimetype', 'cropped', 'version'], 'previews_file_uniq_idx');
|
||||
$table->addUniqueIndex(['file_id', 'width', 'height', 'mimetype_id', 'cropped', 'version_id'], 'previews_file_uniq_idx');
|
||||
}
|
||||
|
||||
return $schema;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ namespace OC\Files\Cache;
|
|||
|
||||
use OCP\IConfig;
|
||||
use OCP\Server;
|
||||
use Override;
|
||||
|
||||
class LocalRootScanner extends Scanner {
|
||||
private string $previewFolder;
|
||||
|
|
@ -20,6 +21,7 @@ class LocalRootScanner extends Scanner {
|
|||
$this->previewFolder = 'appdata_' . $config->getSystemValueString('instanceid', '') . '/preview';
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true, $data = null) {
|
||||
if ($this->shouldScanPath($file)) {
|
||||
return parent::scanFile($file, $reuseExisting, $parentId, $cacheData, $lock, $data);
|
||||
|
|
@ -28,6 +30,7 @@ class LocalRootScanner extends Scanner {
|
|||
}
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $lock = true) {
|
||||
if ($this->shouldScanPath($path)) {
|
||||
return parent::scan($path, $recursive, $reuse, $lock);
|
||||
|
|
@ -36,11 +39,16 @@ class LocalRootScanner extends Scanner {
|
|||
}
|
||||
}
|
||||
|
||||
#[Override]
|
||||
protected function scanChildren(string $path, $recursive, int $reuse, int $folderId, bool $lock, int|float $oldSize, &$etagChanged = false) {
|
||||
if (str_starts_with($path, $this->previewFolder)) {
|
||||
return 0;
|
||||
}
|
||||
return parent::scanChildren($path, $recursive, $reuse, $folderId, $lock, $oldSize, $etagChanged);
|
||||
}
|
||||
|
||||
private function shouldScanPath(string $path): bool {
|
||||
$path = trim($path, '/');
|
||||
if (str_starts_with($path, $this->previewFolder)) {
|
||||
return false;
|
||||
}
|
||||
return $path === '' || str_starts_with($path, 'appdata_') || str_starts_with($path, '__groupfolders');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class BackgroundCleanupJob extends TimedJob {
|
|||
public function run($argument): void {
|
||||
foreach ($this->getDeletedFiles() as $fileId) {
|
||||
$previewIds = [];
|
||||
foreach ($this->previewService->getAvailablePreviewForFile($fileId) as $preview) {
|
||||
foreach ($this->previewService->getAvailablePreviewsForFile($fileId) as $preview) {
|
||||
$this->previewService->deletePreview($preview);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,6 @@ namespace OC\Preview\Db;
|
|||
use OCP\AppFramework\Db\Entity;
|
||||
use OCP\DB\Types;
|
||||
use OCP\Files\IMimeTypeDetector;
|
||||
use OCP\Files\IMimeTypeLoader;
|
||||
use OCP\Server;
|
||||
|
||||
/**
|
||||
* Preview entity mapped to the oc_previews and oc_preview_locations table.
|
||||
|
|
@ -35,10 +33,10 @@ use OCP\Server;
|
|||
* @method void setHeight(int $height)
|
||||
* @method bool isCropped() Get whether the preview is cropped or not.
|
||||
* @method void setCropped(bool $cropped)
|
||||
* @method void setMimetype(int $mimetype) Set the mimetype of the preview.
|
||||
* @method int getMimetype() Get the mimetype of the preview.
|
||||
* @method void setSourceMimetype(int $sourceMimetype) Set the mimetype of the source file.
|
||||
* @method int getSourceMimetype() Get the mimetype of the source file.
|
||||
* @method void setMimetypeId(int $mimetype) Set the mimetype of the preview.
|
||||
* @method int getMimetypeId() Get the mimetype of the preview.
|
||||
* @method void setSourceMimetypeId(int $sourceMimetype) Set the mimetype of the source file.
|
||||
* @method int getSourceMimetypeId() Get the mimetype of the source file.
|
||||
* @method int getMtime() Get the modification time of the preview.
|
||||
* @method void setMtime(int $mtime)
|
||||
* @method int getSize() Get the size of the preview.
|
||||
|
|
@ -47,8 +45,8 @@ use OCP\Server;
|
|||
* @method void setMax(bool $max)
|
||||
* @method string getEtag() Get the etag of the preview.
|
||||
* @method void setEtag(string $etag)
|
||||
* @method int|null getVersion() Get the version for files_versions_s3
|
||||
* @method void setVersion(?int $version)
|
||||
* @method string|null getVersion() Get the version for files_versions_s3
|
||||
* @method void setVersionId(int $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)
|
||||
|
|
@ -64,15 +62,17 @@ class Preview extends Entity {
|
|||
protected ?string $objectStoreName = null;
|
||||
protected ?int $width = null;
|
||||
protected ?int $height = null;
|
||||
protected ?int $mimetype = null;
|
||||
|
||||
protected ?int $sourceMimetype = null;
|
||||
protected ?int $mimetypeId = null;
|
||||
protected ?int $sourceMimetypeId = null;
|
||||
protected string $mimetype = 'application/octet-stream';
|
||||
protected string $sourceMimetype = 'application/octet-stream';
|
||||
protected ?int $mtime = null;
|
||||
protected ?int $size = null;
|
||||
protected ?bool $max = null;
|
||||
protected ?bool $cropped = null;
|
||||
protected ?string $etag = null;
|
||||
protected ?int $version = null;
|
||||
protected ?string $version = null;
|
||||
protected ?int $versionId = null;
|
||||
protected ?bool $encrypted = null;
|
||||
|
||||
public function __construct() {
|
||||
|
|
@ -82,23 +82,23 @@ class Preview extends Entity {
|
|||
$this->addType('locationId', Types::BIGINT);
|
||||
$this->addType('width', Types::INTEGER);
|
||||
$this->addType('height', Types::INTEGER);
|
||||
$this->addType('mimetype', Types::INTEGER);
|
||||
$this->addType('sourceMimetype', Types::INTEGER);
|
||||
$this->addType('mimetypeId', Types::INTEGER);
|
||||
$this->addType('sourceMimetypeId', Types::INTEGER);
|
||||
$this->addType('mtime', Types::INTEGER);
|
||||
$this->addType('size', Types::INTEGER);
|
||||
$this->addType('max', Types::BOOLEAN);
|
||||
$this->addType('cropped', Types::BOOLEAN);
|
||||
$this->addType('encrypted', Types::BOOLEAN);
|
||||
$this->addType('etag', Types::STRING);
|
||||
$this->addType('version', Types::BIGINT);
|
||||
$this->addType('versionId', Types::STRING);
|
||||
}
|
||||
|
||||
public static function fromPath(string $path, IMimeTypeDetector $mimeTypeDetector, IMimeTypeLoader $mimeTypeLoader): Preview|false {
|
||||
public static function fromPath(string $path, IMimeTypeDetector $mimeTypeDetector): Preview|false {
|
||||
$preview = new self();
|
||||
$preview->setFileId((int)basename(dirname($path)));
|
||||
|
||||
$fileName = pathinfo($path, PATHINFO_FILENAME) . '.' . pathinfo($path, PATHINFO_EXTENSION);
|
||||
$ok = preg_match('/(([0-9]+)-)?([0-9]+)-([0-9]+)(-(max))?(-(crop))?\.([a-z]{3,4})/', $fileName, $matches);
|
||||
$ok = preg_match('/(([A-Za-z0-9\+\/]+)-)?([0-9]+)-([0-9]+)(-(max))?(-(crop))?\.([a-z]{3,4})/', $fileName, $matches);
|
||||
|
||||
if ($ok !== 1) {
|
||||
return false;
|
||||
|
|
@ -108,11 +108,11 @@ class Preview extends Entity {
|
|||
2 => $version,
|
||||
3 => $width,
|
||||
4 => $height,
|
||||
6 => $crop,
|
||||
8 => $max,
|
||||
6 => $max,
|
||||
8 => $crop,
|
||||
] = $matches;
|
||||
|
||||
$preview->setMimetype($mimeTypeLoader->getId($mimeTypeDetector->detectPath($fileName)));
|
||||
$preview->setMimeType($mimeTypeDetector->detectPath($fileName));
|
||||
|
||||
$preview->setWidth((int)$width);
|
||||
$preview->setHeight((int)$height);
|
||||
|
|
@ -120,12 +120,12 @@ class Preview extends Entity {
|
|||
$preview->setMax($max === 'max');
|
||||
|
||||
if (!empty($version)) {
|
||||
$preview->setVersion((int)$version);
|
||||
$preview->setVersion($version);
|
||||
}
|
||||
return $preview;
|
||||
}
|
||||
|
||||
public function getName(IMimeTypeLoader $mimeTypeLoader): string {
|
||||
public function getName(): string {
|
||||
$path = ($this->getVersion() > -1 ? $this->getVersion() . '-' : '') . $this->getWidth() . '-' . $this->getHeight();
|
||||
if ($this->isCropped()) {
|
||||
$path .= '-crop';
|
||||
|
|
@ -134,13 +134,13 @@ class Preview extends Entity {
|
|||
$path .= '-max';
|
||||
}
|
||||
|
||||
$ext = $this->getExtension($mimeTypeLoader);
|
||||
$ext = $this->getExtension();
|
||||
$path .= '.' . $ext;
|
||||
return $path;
|
||||
}
|
||||
|
||||
public function getExtension(IMimeTypeLoader $mimeTypeLoader): string {
|
||||
return match ($this->getMimetypeValue($mimeTypeLoader)) {
|
||||
public function getExtension(): string {
|
||||
return match ($this->getMimeType()) {
|
||||
'image/png' => 'png',
|
||||
'image/gif' => 'gif',
|
||||
'image/jpeg' => 'jpg',
|
||||
|
|
@ -149,10 +149,6 @@ class Preview extends Entity {
|
|||
};
|
||||
}
|
||||
|
||||
public function getMimetypeValue(IMimeTypeLoader $mimeTypeLoader): string {
|
||||
return $mimeTypeLoader->getMimetypeById($this->mimetype) ?? 'image/jpeg';
|
||||
}
|
||||
|
||||
public function setBucketName(string $bucketName): void {
|
||||
$this->bucketName = $bucketName;
|
||||
}
|
||||
|
|
@ -160,4 +156,24 @@ class Preview extends Entity {
|
|||
public function setObjectStoreName(string $objectStoreName): void {
|
||||
$this->objectStoreName = $objectStoreName;
|
||||
}
|
||||
|
||||
public function setVersion(?string $version): void {
|
||||
$this->version = $version;
|
||||
}
|
||||
|
||||
public function getMimeType(): string {
|
||||
return $this->mimetype;
|
||||
}
|
||||
|
||||
public function setMimeType(string $mimeType): void {
|
||||
$this->mimetype = $mimeType;
|
||||
}
|
||||
|
||||
public function getSourceMimeType(): string {
|
||||
return $this->sourceMimetype;
|
||||
}
|
||||
|
||||
public function setSourceMimeType(string $mimeType): void {
|
||||
$this->sourceMimetype = $mimeType;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,13 +9,13 @@ declare(strict_types=1);
|
|||
|
||||
namespace OC\Preview\Db;
|
||||
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Db\Entity;
|
||||
use OCP\AppFramework\Db\QBMapper;
|
||||
use OCP\DB\Exception;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\Files\IMimeTypeLoader;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IPreview;
|
||||
use Override;
|
||||
|
||||
/**
|
||||
* @template-extends QBMapper<Preview>
|
||||
|
|
@ -24,18 +24,74 @@ class PreviewMapper extends QBMapper {
|
|||
|
||||
private const TABLE_NAME = 'previews';
|
||||
private const LOCATION_TABLE_NAME = 'preview_locations';
|
||||
private const VERSION_TABLE_NAME = 'preview_versions';
|
||||
|
||||
public function __construct(
|
||||
IDBConnection $db,
|
||||
private readonly IMimeTypeLoader $mimeTypeLoader,
|
||||
) {
|
||||
parent::__construct($db, self::TABLE_NAME, Preview::class);
|
||||
}
|
||||
|
||||
protected function mapRowToEntity(array $row): Entity {
|
||||
$row['mimetype'] = $this->mimeTypeLoader->getMimetypeById((int)$row['mimetype_id']);
|
||||
$row['source_mimetype'] = $this->mimeTypeLoader->getMimetypeById((int)$row['source_mimetype_id']);
|
||||
|
||||
return parent::mapRowToEntity($row);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function insert(Entity $entity): Entity {
|
||||
/** @var Preview $preview */
|
||||
$preview = $entity;
|
||||
|
||||
$preview->setMimetypeId($this->mimeTypeLoader->getId($preview->getMimeType()));
|
||||
$preview->setSourceMimetypeId($this->mimeTypeLoader->getId($preview->getSourceMimeType()));
|
||||
|
||||
if ($preview->getVersion() !== null && $preview->getVersion() !== '') {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->insert(self::VERSION_TABLE_NAME)
|
||||
->values([
|
||||
'version' => $preview->getVersion(),
|
||||
'file_id' => $preview->getFileId(),
|
||||
])
|
||||
->executeStatement();
|
||||
$entity->setVersionId($qb->getLastInsertId());
|
||||
}
|
||||
return parent::insert($preview);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function update(Entity $entity): Entity {
|
||||
/** @var Preview $preview */
|
||||
$preview = $entity;
|
||||
|
||||
$preview->setMimetypeId($this->mimeTypeLoader->getId($preview->getMimeType()));
|
||||
$preview->setSourceMimetypeId($this->mimeTypeLoader->getId($preview->getSourceMimeType()));
|
||||
|
||||
return parent::update($preview);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function delete(Entity $entity): Entity {
|
||||
/** @var Preview $preview */
|
||||
$preview = $entity;
|
||||
if ($preview->getVersion() !== null && $preview->getVersion() !== '') {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->delete(self::VERSION_TABLE_NAME)
|
||||
->where($qb->expr()->eq('file_id', $qb->createNamedParameter($preview->getFileId())))
|
||||
->andWhere($qb->expr()->eq('version', $qb->createNamedParameter($preview->getVersion())))
|
||||
->executeStatement();
|
||||
}
|
||||
|
||||
return parent::delete($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Generator<Preview>
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getAvailablePreviewForFile(int $fileId): \Generator {
|
||||
public function getAvailablePreviewsForFile(int $fileId): \Generator {
|
||||
$selectQb = $this->db->getQueryBuilder();
|
||||
$this->joinLocation($selectQb)
|
||||
->where($selectQb->expr()->eq('p.file_id', $selectQb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
|
||||
|
|
@ -82,10 +138,13 @@ class PreviewMapper extends QBMapper {
|
|||
}
|
||||
|
||||
protected function joinLocation(IQueryBuilder $qb): IQueryBuilder {
|
||||
return $qb->select('p.*', 'l.bucket_name', 'l.object_store_name')
|
||||
return $qb->select('p.*', 'l.bucket_name', 'l.object_store_name', 'v.version')
|
||||
->from(self::TABLE_NAME, 'p')
|
||||
->leftJoin('p', 'preview_locations', 'l', $qb->expr()->eq(
|
||||
->leftJoin('p', self::LOCATION_TABLE_NAME, 'l', $qb->expr()->eq(
|
||||
'p.location_id', 'l.id'
|
||||
))
|
||||
->leftJoin('p', self::VERSION_TABLE_NAME, 'v', $qb->expr()->eq(
|
||||
'p.version_id', 'v.id'
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -127,15 +186,15 @@ class PreviewMapper extends QBMapper {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param int[] $mimeTypes
|
||||
* @param string[] $mimeTypes
|
||||
* @return \Generator<Preview>
|
||||
*/
|
||||
public function getPreviewsForMimeTypes(array $mimeTypes): \Generator {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$this->joinLocation($qb)
|
||||
->where($qb->expr()->orX(
|
||||
...array_map(function (int $mimeType) use ($qb) {
|
||||
return $qb->expr()->eq('source_mimetype', $qb->createNamedParameter($mimeType, IQueryBuilder::PARAM_INT));
|
||||
...array_map(function (string $mimeType) use ($qb): string {
|
||||
return $qb->expr()->eq('source_mimetype_id', $qb->createNamedParameter($this->mimeTypeLoader->getId($mimeType), IQueryBuilder::PARAM_INT));
|
||||
}, $mimeTypes)
|
||||
));
|
||||
return $this->yieldEntities($qb);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ use OC\Preview\Storage\PreviewFile;
|
|||
use OC\Preview\Storage\StorageFactory;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\Files\File;
|
||||
use OCP\Files\IMimeTypeLoader;
|
||||
use OCP\Files\InvalidPathException;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\Files\NotPermittedException;
|
||||
|
|
@ -39,7 +38,6 @@ class Generator {
|
|||
private LoggerInterface $logger,
|
||||
private PreviewMapper $previewMapper,
|
||||
private StorageFactory $storageFactory,
|
||||
private IMimeTypeLoader $mimeTypeLoader,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -111,9 +109,9 @@ class Generator {
|
|||
|
||||
[$file->getId() => $previews] = $this->previewMapper->getAvailablePreviews([$file->getId()]);
|
||||
|
||||
$previewVersion = -1;
|
||||
$previewVersion = null;
|
||||
if ($file instanceof IVersionedPreviewFile) {
|
||||
$previewVersion = (int)$file->getPreviewVersion();
|
||||
$previewVersion = $file->getPreviewVersion();
|
||||
}
|
||||
|
||||
// Get the max preview and infer the max preview sizes from that
|
||||
|
|
@ -152,7 +150,7 @@ class Generator {
|
|||
// No need to generate a preview that is just the max preview
|
||||
if ($width === $maxWidth && $height === $maxHeight) {
|
||||
// ensure correct return value if this was the last one
|
||||
$previewFile = new PreviewFile($maxPreview, $this->storageFactory, $this->previewMapper, $this->mimeTypeLoader);
|
||||
$previewFile = new PreviewFile($maxPreview, $this->storageFactory, $this->previewMapper);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -163,14 +161,14 @@ class Generator {
|
|||
&& $preview->getVersion() === $previewVersion && $preview->isCropped() === $crop);
|
||||
|
||||
if ($preview) {
|
||||
$previewFile = new PreviewFile($preview, $this->storageFactory, $this->previewMapper, $this->mimeTypeLoader);
|
||||
$previewFile = new PreviewFile($preview, $this->storageFactory, $this->previewMapper);
|
||||
} else {
|
||||
if (!$this->previewManager->isMimeSupported($mimeType)) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
if ($maxPreviewImage === null) {
|
||||
$maxPreviewImage = $this->helper->getImage(new PreviewFile($maxPreview, $this->storageFactory, $this->previewMapper, $this->mimeTypeLoader));
|
||||
$maxPreviewImage = $this->helper->getImage(new PreviewFile($maxPreview, $this->storageFactory, $this->previewMapper));
|
||||
}
|
||||
|
||||
$this->logger->debug('Cached preview not found for file {path}, generating a new preview.', ['path' => $file->getPath()]);
|
||||
|
|
@ -298,7 +296,7 @@ class Generator {
|
|||
* @param Preview[] $previews
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
private function getMaxPreview(array $previews, File $file, string $mimeType, int $version): Preview {
|
||||
private function getMaxPreview(array $previews, File $file, string $mimeType, ?string $version): Preview {
|
||||
// We don't know the max preview size, so we can't use getCachedPreview.
|
||||
// It might have been generated with a higher resolution than the current value.
|
||||
foreach ($previews as $preview) {
|
||||
|
|
@ -313,7 +311,7 @@ class Generator {
|
|||
return $this->generateProviderPreview($file, $maxWidth, $maxHeight, false, true, $mimeType, $version);
|
||||
}
|
||||
|
||||
private function generateProviderPreview(File $file, int $width, int $height, bool $crop, bool $max, string $mimeType, int $version): Preview {
|
||||
private function generateProviderPreview(File $file, int $width, int $height, bool $crop, bool $max, string $mimeType, ?string $version): Preview {
|
||||
$previewProviders = $this->previewManager->getProviders();
|
||||
foreach ($previewProviders as $supportedMimeType => $providers) {
|
||||
// Filter out providers that does not support this mime
|
||||
|
|
@ -353,14 +351,14 @@ class Generator {
|
|||
$previewEntry = new Preview();
|
||||
$previewEntry->setFileId($file->getId());
|
||||
$previewEntry->setStorageId($file->getMountPoint()->getNumericStorageId());
|
||||
$previewEntry->setSourceMimetype($this->mimeTypeLoader->getId($file->getMimeType()));
|
||||
$previewEntry->setSourceMimeType($file->getMimeType());
|
||||
$previewEntry->setWidth($preview->width());
|
||||
$previewEntry->setHeight($preview->height());
|
||||
$previewEntry->setVersion($version);
|
||||
$previewEntry->setMax($max);
|
||||
$previewEntry->setCropped($crop);
|
||||
$previewEntry->setEncrypted(false);
|
||||
$previewEntry->setMimetype($this->mimeTypeLoader->getId($preview->dataMimeType()));
|
||||
$previewEntry->setMimetype($preview->dataMimeType());
|
||||
$previewEntry->setEtag($file->getEtag());
|
||||
$previewEntry->setMtime((new \DateTime())->getTimestamp());
|
||||
$previewEntry->setSize(0);
|
||||
|
|
@ -468,7 +466,7 @@ class Generator {
|
|||
bool $crop,
|
||||
int $maxWidth,
|
||||
int $maxHeight,
|
||||
?int $version,
|
||||
?string $version,
|
||||
bool $cacheResult,
|
||||
): ISimpleFile {
|
||||
$preview = $maxPreview;
|
||||
|
|
@ -508,21 +506,21 @@ class Generator {
|
|||
$previewEntry->setFileId($file->getId());
|
||||
$previewEntry->setStorageId($file->getMountPoint()->getNumericStorageId());
|
||||
$previewEntry->setWidth($width);
|
||||
$previewEntry->setSourceMimetype($this->mimeTypeLoader->getId($file->getMimeType()));
|
||||
$previewEntry->setSourceMimeType($file->getMimeType());
|
||||
$previewEntry->setHeight($height);
|
||||
$previewEntry->setVersion($version);
|
||||
$previewEntry->setMax(false);
|
||||
$previewEntry->setCropped($crop);
|
||||
$previewEntry->setEncrypted(false);
|
||||
$previewEntry->setMimetype($this->mimeTypeLoader->getId($preview->dataMimeType()));
|
||||
$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, $this->mimeTypeLoader);
|
||||
return new PreviewFile($previewEntry, $this->storageFactory, $this->previewMapper);
|
||||
} else {
|
||||
return new InMemoryFile($previewEntry->getName($this->mimeTypeLoader), $preview->data());
|
||||
return new InMemoryFile($previewEntry->getName(), $preview->data());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -540,7 +538,10 @@ class Generator {
|
|||
if ($preview instanceof IStreamImage) {
|
||||
$size = $this->storageFactory->writePreview($previewEntry, $preview->resource());
|
||||
} else {
|
||||
$size = $this->storageFactory->writePreview($previewEntry, $preview->data());
|
||||
$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');
|
||||
|
|
|
|||
|
|
@ -70,12 +70,12 @@ class PreviewService {
|
|||
/**
|
||||
* @return \Generator<Preview>
|
||||
*/
|
||||
public function getAvailablePreviewForFile(int $fileId): \Generator {
|
||||
return $this->previewMapper->getAvailablePreviewForFile($fileId);
|
||||
public function getAvailablePreviewsForFile(int $fileId): \Generator {
|
||||
return $this->previewMapper->getAvailablePreviewsForFile($fileId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $mimeTypes
|
||||
* @param string[] $mimeTypes
|
||||
* @return \Generator<Preview>
|
||||
*/
|
||||
public function getPreviewsForMimeTypes(array $mimeTypes): \Generator {
|
||||
|
|
|
|||
|
|
@ -10,32 +10,44 @@ declare(strict_types=1);
|
|||
|
||||
namespace OC\Preview\Storage;
|
||||
|
||||
use Exception;
|
||||
use OC\Files\SimpleFS\SimpleFile;
|
||||
use OC\Preview\Db\Preview;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\Files\NotPermittedException;
|
||||
|
||||
interface IPreviewStorage {
|
||||
/**
|
||||
* @param resource|string $stream
|
||||
* @param resource $stream
|
||||
* @throws NotPermittedException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function writePreview(Preview $preview, mixed $stream): false|int;
|
||||
public function writePreview(Preview $preview, mixed $stream): int;
|
||||
|
||||
/**
|
||||
* @param Preview $preview
|
||||
* @return resource|false
|
||||
* @return resource
|
||||
* @throws NotPermittedException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function readPreview(Preview $preview): mixed;
|
||||
|
||||
/**
|
||||
* @throws NotPermittedException
|
||||
*/
|
||||
public function deletePreview(Preview $preview): void;
|
||||
|
||||
/**
|
||||
* Migration helper
|
||||
*
|
||||
* To remove at some point
|
||||
* @throw \Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
public function migratePreview(Preview $preview, SimpleFile $file): void;
|
||||
|
||||
/**
|
||||
* @throws NotPermittedException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function scan(): int;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ use OC\Preview\Db\Preview;
|
|||
use OC\Preview\Db\PreviewMapper;
|
||||
use OCP\DB\Exception;
|
||||
use OCP\Files\IMimeTypeDetector;
|
||||
use OCP\Files\IMimeTypeLoader;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\Files\NotPermittedException;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IConfig;
|
||||
use OCP\IDBConnection;
|
||||
|
|
@ -33,10 +34,8 @@ class LocalPreviewStorage implements IPreviewStorage {
|
|||
public function __construct(
|
||||
private readonly IConfig $config,
|
||||
private readonly PreviewMapper $previewMapper,
|
||||
private readonly StorageFactory $previewStorage,
|
||||
private readonly IAppConfig $appConfig,
|
||||
private readonly IDBConnection $connection,
|
||||
private readonly IMimeTypeLoader $mimeTypeLoader,
|
||||
private readonly IMimeTypeDetector $mimeTypeDetector,
|
||||
private readonly LoggerInterface $logger,
|
||||
) {
|
||||
|
|
@ -45,22 +44,28 @@ class LocalPreviewStorage implements IPreviewStorage {
|
|||
}
|
||||
|
||||
#[Override]
|
||||
public function writePreview(Preview $preview, mixed $stream): false|int {
|
||||
public function writePreview(Preview $preview, mixed $stream): int {
|
||||
$previewPath = $this->constructPath($preview);
|
||||
if (!$this->createParentFiles($previewPath)) {
|
||||
return false;
|
||||
}
|
||||
$this->createParentFiles($previewPath);
|
||||
return file_put_contents($previewPath, $stream);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function readPreview(Preview $preview): mixed {
|
||||
return @fopen($this->constructPath($preview), 'r');
|
||||
$previewPath = $this->constructPath($preview);
|
||||
$resource = @fopen($previewPath, 'r');
|
||||
if ($resource === false) {
|
||||
throw new NotFoundException('Unable to open preview stream at ' . $previewPath);
|
||||
}
|
||||
return $resource;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function deletePreview(Preview $preview): void {
|
||||
@unlink($this->constructPath($preview));
|
||||
$previewPath = $this->constructPath($preview);
|
||||
if (!@unlink($previewPath) && is_file($previewPath)) {
|
||||
throw new NotPermittedException('Unable to delete preview at ' . $previewPath);
|
||||
}
|
||||
}
|
||||
|
||||
public function getPreviewRootFolder(): string {
|
||||
|
|
@ -68,19 +73,21 @@ class LocalPreviewStorage implements IPreviewStorage {
|
|||
}
|
||||
|
||||
private function constructPath(Preview $preview): string {
|
||||
return $this->getPreviewRootFolder() . implode('/', str_split(substr(md5((string)$preview->getFileId()), 0, 7))) . '/' . $preview->getFileId() . '/' . $preview->getName($this->mimeTypeLoader);
|
||||
return $this->getPreviewRootFolder() . implode('/', str_split(substr(md5((string)$preview->getFileId()), 0, 7))) . '/' . $preview->getFileId() . '/' . $preview->getName();
|
||||
}
|
||||
|
||||
private function createParentFiles(string $path): bool {
|
||||
private function createParentFiles(string $path): void {
|
||||
$dirname = dirname($path);
|
||||
@mkdir($dirname, recursive: true);
|
||||
return is_dir($dirname);
|
||||
if (!is_dir($dirname)) {
|
||||
throw new NotPermittedException("Unable to create directory '$dirname'");
|
||||
}
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function migratePreview(Preview $preview, SimpleFile $file): void {
|
||||
// legacy flat directory
|
||||
$sourcePath = $this->getPreviewRootFolder() . $preview->getFileId() . '/' . $preview->getName($this->mimeTypeLoader);
|
||||
$sourcePath = $this->getPreviewRootFolder() . $preview->getFileId() . '/' . $preview->getName();
|
||||
if (!file_exists($sourcePath)) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -106,7 +113,7 @@ class LocalPreviewStorage implements IPreviewStorage {
|
|||
$previewsFound = 0;
|
||||
foreach (new RecursiveIteratorIterator($scanner) as $file) {
|
||||
if ($file->isFile()) {
|
||||
$preview = Preview::fromPath((string)$file, $this->mimeTypeDetector, $this->mimeTypeLoader);
|
||||
$preview = Preview::fromPath((string)$file, $this->mimeTypeDetector);
|
||||
if ($preview === false) {
|
||||
$this->logger->error('Unable to parse preview information for ' . $file->getRealPath());
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ use OC\Files\ObjectStore\PrimaryObjectStoreConfig;
|
|||
use OC\Files\SimpleFS\SimpleFile;
|
||||
use OC\Preview\Db\Preview;
|
||||
use OC\Preview\Db\PreviewMapper;
|
||||
use OCP\Files\NotPermittedException;
|
||||
use OCP\Files\ObjectStore\IObjectStore;
|
||||
use OCP\IConfig;
|
||||
use Override;
|
||||
|
|
@ -41,15 +42,7 @@ class ObjectStorePreviewStorage implements IPreviewStorage {
|
|||
}
|
||||
|
||||
#[Override]
|
||||
public function writePreview(Preview $preview, mixed $stream): false|int {
|
||||
if (!is_resource($stream)) {
|
||||
$fh = fopen('php://temp', 'w+');
|
||||
fwrite($fh, $stream);
|
||||
rewind($fh);
|
||||
|
||||
$stream = $fh;
|
||||
}
|
||||
|
||||
public function writePreview(Preview $preview, mixed $stream): int {
|
||||
$size = 0;
|
||||
$countStream = CountWrapper::wrap($stream, function (int $writtenSize) use (&$size): void {
|
||||
$size = $writtenSize;
|
||||
|
|
@ -61,7 +54,11 @@ class ObjectStorePreviewStorage implements IPreviewStorage {
|
|||
'config' => $config,
|
||||
] = $this->getObjectStoreForPreview($preview);
|
||||
|
||||
$store->writeObject($this->constructUrn($objectPrefix, $preview->getId()), $countStream);
|
||||
try {
|
||||
$store->writeObject($this->constructUrn($objectPrefix, $preview->getId()), $countStream);
|
||||
} catch (\Exception $exception) {
|
||||
throw new NotPermittedException('Unable to save preview to object store', previous: $exception);
|
||||
}
|
||||
return $size;
|
||||
}
|
||||
|
||||
|
|
@ -71,7 +68,11 @@ class ObjectStorePreviewStorage implements IPreviewStorage {
|
|||
'objectPrefix' => $objectPrefix,
|
||||
'store' => $store,
|
||||
] = $this->getObjectStoreForPreview($preview);
|
||||
return $store->readObject($this->constructUrn($objectPrefix, $preview->getId()));
|
||||
try {
|
||||
return $store->readObject($this->constructUrn($objectPrefix, $preview->getId()));
|
||||
} catch (\Exception $exception) {
|
||||
throw new NotPermittedException('Unable to read preview from object store', previous: $exception);
|
||||
}
|
||||
}
|
||||
|
||||
#[Override]
|
||||
|
|
@ -80,7 +81,11 @@ class ObjectStorePreviewStorage implements IPreviewStorage {
|
|||
'objectPrefix' => $objectPrefix,
|
||||
'store' => $store,
|
||||
] = $this->getObjectStoreForPreview($preview);
|
||||
$store->deleteObject($this->constructUrn($objectPrefix, $preview->getId()));
|
||||
try {
|
||||
$store->deleteObject($this->constructUrn($objectPrefix, $preview->getId()));
|
||||
} catch (\Exception $exception) {
|
||||
throw new NotPermittedException('Unable to delete preview from object store', previous: $exception);
|
||||
}
|
||||
}
|
||||
|
||||
#[Override]
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ namespace OC\Preview\Storage;
|
|||
|
||||
use OC\Preview\Db\Preview;
|
||||
use OC\Preview\Db\PreviewMapper;
|
||||
use OCP\Files\IMimeTypeLoader;
|
||||
use OCP\Files\SimpleFS\ISimpleFile;
|
||||
use Override;
|
||||
|
||||
|
|
@ -21,13 +20,12 @@ class PreviewFile implements ISimpleFile {
|
|||
private readonly Preview $preview,
|
||||
private readonly IPreviewStorage $storage,
|
||||
private readonly PreviewMapper $previewMapper,
|
||||
private readonly IMimeTypeLoader $mimeTypeLoader,
|
||||
) {
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function getName(): string {
|
||||
return $this->preview->getName($this->mimeTypeLoader);
|
||||
return $this->preview->getName();
|
||||
}
|
||||
|
||||
#[Override]
|
||||
|
|
@ -63,12 +61,12 @@ class PreviewFile implements ISimpleFile {
|
|||
|
||||
#[Override]
|
||||
public function getMimeType(): string {
|
||||
return $this->preview->getMimetypeValue($this->mimeTypeLoader);
|
||||
return $this->preview->getMimetype();
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function getExtension(): string {
|
||||
return $this->preview->getExtension($this->mimeTypeLoader);
|
||||
return $this->preview->getExtension();
|
||||
}
|
||||
|
||||
#[Override]
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class StorageFactory implements IPreviewStorage {
|
|||
}
|
||||
|
||||
#[Override]
|
||||
public function writePreview(Preview $preview, mixed $stream): false|int {
|
||||
public function writePreview(Preview $preview, mixed $stream): int {
|
||||
return $this->getBackend()->writePreview($preview, $stream);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ use OC\Preview\Storage\StorageFactory;
|
|||
use OCP\Files\FileInfo;
|
||||
use OCP\Files\Folder;
|
||||
use OCP\Files\Node;
|
||||
use OCP\IDBConnection;
|
||||
|
||||
/**
|
||||
* Class Watcher
|
||||
|
|
@ -26,8 +27,9 @@ class Watcher {
|
|||
* Watcher constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
readonly private StorageFactory $storageFactory,
|
||||
readonly private PreviewMapper $previewMapper,
|
||||
private readonly StorageFactory $storageFactory,
|
||||
private readonly PreviewMapper $previewMapper,
|
||||
private readonly IDBConnection $connection,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -47,8 +49,14 @@ class Watcher {
|
|||
}
|
||||
|
||||
[$node->getId() => $previews] = $this->previewMapper->getAvailablePreviews([$nodeId]);
|
||||
foreach ($previews as $preview) {
|
||||
$this->storageFactory->deletePreview($preview);
|
||||
$this->connection->beginTransaction();
|
||||
try {
|
||||
foreach ($previews as $preview) {
|
||||
$this->storageFactory->deletePreview($preview);
|
||||
$this->previewMapper->delete($preview);
|
||||
}
|
||||
} finally {
|
||||
$this->connection->commit();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ use OC\Preview\Storage\StorageFactory;
|
|||
use OCP\AppFramework\QueryException;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\Files\File;
|
||||
use OCP\Files\IMimeTypeLoader;
|
||||
use OCP\Files\IRootFolder;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\Files\SimpleFS\ISimpleFile;
|
||||
|
|
@ -140,7 +139,6 @@ class PreviewManager implements IPreview {
|
|||
$this->container->get(LoggerInterface::class),
|
||||
$this->container->get(PreviewMapper::class),
|
||||
$this->container->get(StorageFactory::class),
|
||||
$this->container->get(IMimeTypeLoader::class),
|
||||
);
|
||||
}
|
||||
return $this->generator;
|
||||
|
|
|
|||
|
|
@ -308,6 +308,7 @@ class Server extends ServerContainer implements IServerContainer {
|
|||
return new Watcher(
|
||||
$c->get(\OC\Preview\Storage\StorageFactory::class),
|
||||
$c->get(PreviewMapper::class),
|
||||
$c->get(IDBConnection::class),
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -18,8 +18,7 @@ namespace OCP\Preview;
|
|||
*/
|
||||
interface IVersionedPreviewFile {
|
||||
/**
|
||||
* @return numeric
|
||||
* @since 17.0.0
|
||||
*/
|
||||
public function getPreviewVersion();
|
||||
public function getPreviewVersion(): string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ class BackgroundCleanupJobTest extends \Test\TestCase {
|
|||
|
||||
$this->logout();
|
||||
|
||||
foreach ($this->previewService->getAvailablePreviewForFile(5) as $preview) {
|
||||
foreach ($this->previewService->getAvailablePreviewsForFile(5) as $preview) {
|
||||
$this->previewService->deletePreview($preview);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ use OC\Preview\GeneratorHelper;
|
|||
use OC\Preview\Storage\StorageFactory;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\Files\File;
|
||||
use OCP\Files\IMimeTypeLoader;
|
||||
use OCP\Files\Mount\IMountPoint;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\IConfig;
|
||||
|
|
@ -22,10 +21,17 @@ use OCP\IImage;
|
|||
use OCP\IPreview;
|
||||
use OCP\Preview\BeforePreviewFetchedEvent;
|
||||
use OCP\Preview\IProviderV2;
|
||||
use OCP\Preview\IVersionedPreviewFile;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\TestWith;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Test\TestCase;
|
||||
|
||||
abstract class VersionedPreviewFile implements IVersionedPreviewFile, File {
|
||||
|
||||
}
|
||||
|
||||
class GeneratorTest extends TestCase {
|
||||
private IConfig&MockObject $config;
|
||||
private IPreview&MockObject $previewManager;
|
||||
|
|
@ -35,7 +41,6 @@ class GeneratorTest extends TestCase {
|
|||
private LoggerInterface&MockObject $logger;
|
||||
private StorageFactory&MockObject $storageFactory;
|
||||
private PreviewMapper&MockObject $previewMapper;
|
||||
private IMimeTypeLoader&MockObject $mimeTypeLoader;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
|
@ -47,12 +52,6 @@ class GeneratorTest extends TestCase {
|
|||
$this->logger = $this->createMock(LoggerInterface::class);
|
||||
$this->previewMapper = $this->createMock(PreviewMapper::class);
|
||||
$this->storageFactory = $this->createMock(StorageFactory::class);
|
||||
$this->mimeTypeLoader = $this->createMock(IMimeTypeLoader::class);
|
||||
$this->mimeTypeLoader->method('getId')
|
||||
->willReturnCallback(fn ($mimeType) => $mimeType === 'image/png' ? 42 : 43);
|
||||
$this->mimeTypeLoader->method('getMimetypeById')
|
||||
->with(42)
|
||||
->willReturn('image/png');
|
||||
|
||||
$this->generator = new Generator(
|
||||
$this->config,
|
||||
|
|
@ -62,14 +61,18 @@ class GeneratorTest extends TestCase {
|
|||
$this->logger,
|
||||
$this->previewMapper,
|
||||
$this->storageFactory,
|
||||
$this->mimeTypeLoader,
|
||||
);
|
||||
}
|
||||
|
||||
private function getFile(int $fileId, string $mimeType): File {
|
||||
private function getFile(int $fileId, string $mimeType, bool $hasVersion = false): File {
|
||||
$mountPoint = $this->createMock(IMountPoint::class);
|
||||
$mountPoint->method('getNumericStorageId')->willReturn(42);
|
||||
$file = $this->createMock(File::class);
|
||||
if ($hasVersion) {
|
||||
$file = $this->createMock(VersionedPreviewFile::class);
|
||||
$file->method('getPreviewVersion')->willReturn('abc');
|
||||
} else {
|
||||
$file = $this->createMock(File::class);
|
||||
}
|
||||
$file->method('isReadable')
|
||||
->willReturn(true);
|
||||
$file->method('getMimeType')
|
||||
|
|
@ -81,8 +84,10 @@ class GeneratorTest extends TestCase {
|
|||
return $file;
|
||||
}
|
||||
|
||||
public function testGetCachedPreview(): void {
|
||||
$file = $this->getFile(42, 'myMimeType');
|
||||
#[TestWith([true])]
|
||||
#[TestWith([false])]
|
||||
public function testGetCachedPreview(bool $hasPreview): void {
|
||||
$file = $this->getFile(42, 'myMimeType', $hasPreview);
|
||||
|
||||
$this->previewManager->method('isMimeSupported')
|
||||
->with($this->equalTo('myMimeType'))
|
||||
|
|
@ -93,20 +98,20 @@ class GeneratorTest extends TestCase {
|
|||
$maxPreview->setHeight(1000);
|
||||
$maxPreview->setMax(true);
|
||||
$maxPreview->setSize(1000);
|
||||
$maxPreview->setVersion(-1);
|
||||
$maxPreview->setCropped(false);
|
||||
$maxPreview->setStorageId(1);
|
||||
$maxPreview->setMimetype($this->mimeTypeLoader->getId('image/png'));
|
||||
$maxPreview->setVersion($hasPreview ? 'abc' : null);
|
||||
$maxPreview->setMimeType('image/png');
|
||||
|
||||
$previewFile = new Preview();
|
||||
$previewFile->setWidth(256);
|
||||
$previewFile->setHeight(256);
|
||||
$previewFile->setMax(false);
|
||||
$previewFile->setSize(1000);
|
||||
$previewFile->setVersion(-1);
|
||||
$previewFile->setVersion($hasPreview ? 'abc' : null);
|
||||
$previewFile->setCropped(false);
|
||||
$previewFile->setStorageId(1);
|
||||
$previewFile->setMimetype($this->mimeTypeLoader->getId('image/png'));
|
||||
$previewFile->setMimeType('image/png');
|
||||
|
||||
$this->previewMapper->method('getAvailablePreviews')
|
||||
->with($this->equalTo([42]))
|
||||
|
|
@ -120,12 +125,14 @@ class GeneratorTest extends TestCase {
|
|||
->with(new BeforePreviewFetchedEvent($file, 100, 100, false, IPreview::MODE_FILL, null));
|
||||
|
||||
$result = $this->generator->getPreview($file, 100, 100);
|
||||
$this->assertSame('256-256.png', $result->getName());
|
||||
$this->assertSame($hasPreview ? 'abc-256-256.png' : '256-256.png', $result->getName());
|
||||
$this->assertSame(1000, $result->getSize());
|
||||
}
|
||||
|
||||
public function testGetNewPreview(): void {
|
||||
$file = $this->getFile(42, 'myMimeType');
|
||||
#[TestWith([true])]
|
||||
#[TestWith([false])]
|
||||
public function testGetNewPreview(bool $hasVersion): void {
|
||||
$file = $this->getFile(42, 'myMimeType', $hasVersion);
|
||||
|
||||
$this->previewManager->method('isMimeSupported')
|
||||
->with($this->equalTo('myMimeType'))
|
||||
|
|
@ -196,13 +203,6 @@ class GeneratorTest extends TestCase {
|
|||
$image->method('data')
|
||||
->willReturn('my data');
|
||||
|
||||
$maxPreview = new Preview();
|
||||
$maxPreview->setWidth(2048);
|
||||
$maxPreview->setHeight(2048);
|
||||
$maxPreview->setMax(true);
|
||||
$maxPreview->setSize(1000);
|
||||
$maxPreview->setMimetype($this->mimeTypeLoader->getId('image/png'));
|
||||
|
||||
$this->previewMapper->method('insert')
|
||||
->willReturnCallback(fn (Preview $preview): Preview => $preview);
|
||||
|
||||
|
|
@ -210,16 +210,28 @@ class GeneratorTest extends TestCase {
|
|||
->willReturnCallback(fn (Preview $preview): Preview => $preview);
|
||||
|
||||
$this->storageFactory->method('writePreview')
|
||||
->willReturnCallback(function (Preview $preview, string $data): int {
|
||||
switch ($preview->getName($this->mimeTypeLoader)) {
|
||||
case '2048-2048-max.png':
|
||||
$this->assertSame('my data', $data);
|
||||
return 1000;
|
||||
case '256-256.png':
|
||||
$this->assertSame('my resized data', $data);
|
||||
return 1000;
|
||||
->willReturnCallback(function (Preview $preview, mixed $data) use ($hasVersion): int {
|
||||
$data = stream_get_contents($data);
|
||||
if ($hasVersion) {
|
||||
switch ($preview->getName()) {
|
||||
case 'abc-2048-2048-max.png':
|
||||
$this->assertSame('my data', $data);
|
||||
return 1000;
|
||||
case 'abc-256-256.png':
|
||||
$this->assertSame('my resized data', $data);
|
||||
return 1000;
|
||||
}
|
||||
} else {
|
||||
switch ($preview->getName()) {
|
||||
case '2048-2048-max.png':
|
||||
$this->assertSame('my data', $data);
|
||||
return 1000;
|
||||
case '256-256.png':
|
||||
$this->assertSame('my resized data', $data);
|
||||
return 1000;
|
||||
}
|
||||
}
|
||||
$this->fail('file name is wrong:' . $preview->getName($this->mimeTypeLoader));
|
||||
$this->fail('file name is wrong:' . $preview->getName());
|
||||
});
|
||||
|
||||
$image = $this->getMockImage(2048, 2048, 'my resized data');
|
||||
|
|
@ -231,7 +243,7 @@ class GeneratorTest extends TestCase {
|
|||
->with(new BeforePreviewFetchedEvent($file, 100, 100, false, IPreview::MODE_FILL, null));
|
||||
|
||||
$result = $this->generator->getPreview($file, 100, 100);
|
||||
$this->assertSame('256-256.png', $result->getName());
|
||||
$this->assertSame($hasVersion ? 'abc-256-256.png' : '256-256.png', $result->getName());
|
||||
$this->assertSame(1000, $result->getSize());
|
||||
}
|
||||
|
||||
|
|
@ -249,8 +261,8 @@ class GeneratorTest extends TestCase {
|
|||
$maxPreview->setHeight(2048);
|
||||
$maxPreview->setMax(true);
|
||||
$maxPreview->setSize(1000);
|
||||
$maxPreview->setVersion(-1);
|
||||
$maxPreview->setMimetype(42);
|
||||
$maxPreview->setVersion(null);
|
||||
$maxPreview->setMimetype('image/png');
|
||||
|
||||
$this->previewMapper->method('getAvailablePreviews')
|
||||
->with($this->equalTo([42]))
|
||||
|
|
@ -273,8 +285,8 @@ class GeneratorTest extends TestCase {
|
|||
$maxPreview->setHeight(2048);
|
||||
$maxPreview->setMax(true);
|
||||
$maxPreview->setSize(1000);
|
||||
$maxPreview->setVersion(-1);
|
||||
$maxPreview->setMimetype($this->mimeTypeLoader->getId('image/png'));
|
||||
$maxPreview->setVersion(null);
|
||||
$maxPreview->setMimeType('image/png');
|
||||
|
||||
$previewFile = new Preview();
|
||||
$previewFile->setWidth(1024);
|
||||
|
|
@ -282,8 +294,8 @@ class GeneratorTest extends TestCase {
|
|||
$previewFile->setMax(false);
|
||||
$previewFile->setSize(1000);
|
||||
$previewFile->setCropped(true);
|
||||
$previewFile->setVersion(-1);
|
||||
$previewFile->setMimetype($this->mimeTypeLoader->getId('image/png'));
|
||||
$previewFile->setVersion(null);
|
||||
$previewFile->setMimeType('image/png');
|
||||
|
||||
$this->previewMapper->method('getAvailablePreviews')
|
||||
->with($this->equalTo([42]))
|
||||
|
|
@ -321,7 +333,7 @@ class GeneratorTest extends TestCase {
|
|||
$this->generator->getPreview($file, 100, 100);
|
||||
}
|
||||
|
||||
private function getMockImage(int $width, int $height, $data = null) {
|
||||
private function getMockImage(int $width, int $height, string $data = '') {
|
||||
$image = $this->createMock(IImage::class);
|
||||
$image->method('height')->willReturn($width);
|
||||
$image->method('width')->willReturn($height);
|
||||
|
|
@ -378,7 +390,7 @@ class GeneratorTest extends TestCase {
|
|||
];
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('dataSize')]
|
||||
#[DataProvider('dataSize')]
|
||||
public function testCorrectSize(int $maxX, int $maxY, int $reqX, int $reqY, bool $crop, string $mode, int $expectedX, int $expectedY): void {
|
||||
$file = $this->getFile(42, 'myMimeType');
|
||||
|
||||
|
|
@ -391,11 +403,11 @@ class GeneratorTest extends TestCase {
|
|||
$maxPreview->setHeight($maxY);
|
||||
$maxPreview->setMax(true);
|
||||
$maxPreview->setSize(1000);
|
||||
$maxPreview->setVersion(-1);
|
||||
$maxPreview->setMimetype(42);
|
||||
$maxPreview->setVersion(null);
|
||||
$maxPreview->setMimeType('image/png');
|
||||
|
||||
$this->assertSame($maxPreview->getName($this->mimeTypeLoader), $maxX . '-' . $maxY . '-max.png');
|
||||
$this->assertSame($maxPreview->getMimetypeValue($this->mimeTypeLoader), 'image/png');
|
||||
$this->assertSame($maxPreview->getName(), $maxX . '-' . $maxY . '-max.png');
|
||||
$this->assertSame($maxPreview->getMimeType(), 'image/png');
|
||||
|
||||
$this->previewMapper->method('getAvailablePreviews')
|
||||
->with($this->equalTo([42]))
|
||||
|
|
@ -415,7 +427,7 @@ class GeneratorTest extends TestCase {
|
|||
|
||||
$this->previewMapper->method('insert')
|
||||
->willReturnCallback(function (Preview $preview) use ($filename): Preview {
|
||||
$this->assertSame($preview->getName($this->mimeTypeLoader), $filename);
|
||||
$this->assertSame($preview->getName(), $filename);
|
||||
return $preview;
|
||||
});
|
||||
|
||||
|
|
@ -431,7 +443,7 @@ class GeneratorTest extends TestCase {
|
|||
|
||||
$result = $this->generator->getPreview($file, $reqX, $reqY, $crop, $mode);
|
||||
if ($expectedX === $maxX && $expectedY === $maxY) {
|
||||
$this->assertSame($maxPreview->getName($this->mimeTypeLoader), $result->getName());
|
||||
$this->assertSame($maxPreview->getName(), $result->getName());
|
||||
} else {
|
||||
$this->assertSame($filename, $result->getName());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ use OCP\Files\IMimeTypeDetector;
|
|||
use OCP\Files\IMimeTypeLoader;
|
||||
use OCP\Files\IRootFolder;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IConfig;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\Server;
|
||||
use PHPUnit\Framework\Attributes\TestDox;
|
||||
|
|
@ -35,6 +36,7 @@ class MovePreviewJobTest extends TestCase {
|
|||
private IAppData $previewAppData;
|
||||
private PreviewMapper $previewMapper;
|
||||
private IAppConfig&MockObject $appConfig;
|
||||
private IConfig $config;
|
||||
private StorageFactory $storageFactory;
|
||||
private PreviewService $previewService;
|
||||
private IDBConnection $db;
|
||||
|
|
@ -46,6 +48,7 @@ class MovePreviewJobTest extends TestCase {
|
|||
parent::setUp();
|
||||
$this->previewAppData = Server::get(IAppDataFactory::class)->get('preview');
|
||||
$this->previewMapper = Server::get(PreviewMapper::class);
|
||||
$this->config = Server::get(IConfig::class);
|
||||
$this->appConfig = $this->createMock(IAppConfig::class);
|
||||
$this->appConfig->expects($this->any())
|
||||
->method('getValueBool')
|
||||
|
|
@ -71,7 +74,7 @@ class MovePreviewJobTest extends TestCase {
|
|||
'path_hash' => $qb->createNamedParameter(md5('test')),
|
||||
'parent' => $qb->createNamedParameter(0),
|
||||
'name' => $qb->createNamedParameter('abc'),
|
||||
'mimetype' => $qb->createNamedParameter(0),
|
||||
'mimetype' => $qb->createNamedParameter(42),
|
||||
'size' => $qb->createNamedParameter(1000),
|
||||
'mtime' => $qb->createNamedParameter(1000),
|
||||
'storage_mtime' => $qb->createNamedParameter(1000),
|
||||
|
|
@ -86,6 +89,7 @@ class MovePreviewJobTest extends TestCase {
|
|||
$this->mimeTypeDetector->method('detectPath')->willReturn('image/png');
|
||||
$this->mimeTypeLoader = $this->createMock(IMimeTypeLoader::class);
|
||||
$this->mimeTypeLoader->method('getId')->with('image/png')->willReturn(42);
|
||||
$this->mimeTypeLoader->method('getMimetypeById')->with(42)->willReturn('image/png');
|
||||
$this->logger = $this->createMock(LoggerInterface::class);
|
||||
}
|
||||
|
||||
|
|
@ -108,11 +112,12 @@ class MovePreviewJobTest extends TestCase {
|
|||
$folder->newFile('128-128-crop.png', 'abcdefg');
|
||||
$this->assertEquals(1, count($this->previewAppData->getDirectoryListing()));
|
||||
$this->assertEquals(2, count($folder->getDirectoryListing()));
|
||||
$this->assertEquals(0, count(iterator_to_array($this->previewMapper->getAvailablePreviewForFile(5))));
|
||||
$this->assertEquals(0, count(iterator_to_array($this->previewMapper->getAvailablePreviewsForFile(5))));
|
||||
|
||||
$job = new MovePreviewJob(
|
||||
Server::get(ITimeFactory::class),
|
||||
$this->appConfig,
|
||||
$this->config,
|
||||
$this->previewMapper,
|
||||
$this->storageFactory,
|
||||
Server::get(IDBConnection::class),
|
||||
|
|
@ -124,7 +129,7 @@ class MovePreviewJobTest extends TestCase {
|
|||
);
|
||||
$this->invokePrivate($job, 'run', [[]]);
|
||||
$this->assertEquals(0, count($this->previewAppData->getDirectoryListing()));
|
||||
$this->assertEquals(2, count(iterator_to_array($this->previewMapper->getAvailablePreviewForFile(5))));
|
||||
$this->assertEquals(2, count(iterator_to_array($this->previewMapper->getAvailablePreviewsForFile(5))));
|
||||
}
|
||||
|
||||
private static function getInternalFolder(string $name): string {
|
||||
|
|
@ -139,11 +144,12 @@ class MovePreviewJobTest extends TestCase {
|
|||
|
||||
$folder = $this->previewAppData->getFolder(self::getInternalFolder((string)5));
|
||||
$this->assertEquals(2, count($folder->getDirectoryListing()));
|
||||
$this->assertEquals(0, count(iterator_to_array($this->previewMapper->getAvailablePreviewForFile(5))));
|
||||
$this->assertEquals(0, count(iterator_to_array($this->previewMapper->getAvailablePreviewsForFile(5))));
|
||||
|
||||
$job = new MovePreviewJob(
|
||||
Server::get(ITimeFactory::class),
|
||||
$this->appConfig,
|
||||
$this->config,
|
||||
$this->previewMapper,
|
||||
$this->storageFactory,
|
||||
Server::get(IDBConnection::class),
|
||||
|
|
@ -155,7 +161,7 @@ class MovePreviewJobTest extends TestCase {
|
|||
);
|
||||
$this->invokePrivate($job, 'run', [[]]);
|
||||
$this->assertEquals(0, count($this->previewAppData->getDirectoryListing()));
|
||||
$this->assertEquals(2, count(iterator_to_array($this->previewMapper->getAvailablePreviewForFile(5))));
|
||||
$this->assertEquals(2, count(iterator_to_array($this->previewMapper->getAvailablePreviewsForFile(5))));
|
||||
}
|
||||
|
||||
#[TestDox("Test the migration from the 'new' nested hierarchy to the database format")]
|
||||
|
|
@ -178,11 +184,12 @@ class MovePreviewJobTest extends TestCase {
|
|||
|
||||
$folder = $this->previewAppData->getFolder(self::getInternalFolder((string)5));
|
||||
$this->assertEquals(9, count($folder->getDirectoryListing()));
|
||||
$this->assertEquals(0, count(iterator_to_array($this->previewMapper->getAvailablePreviewForFile(5))));
|
||||
$this->assertEquals(0, count(iterator_to_array($this->previewMapper->getAvailablePreviewsForFile(5))));
|
||||
|
||||
$job = new MovePreviewJob(
|
||||
Server::get(ITimeFactory::class),
|
||||
$this->appConfig,
|
||||
$this->config,
|
||||
$this->previewMapper,
|
||||
$this->storageFactory,
|
||||
Server::get(IDBConnection::class),
|
||||
|
|
@ -193,25 +200,25 @@ class MovePreviewJobTest extends TestCase {
|
|||
Server::get(IAppDataFactory::class)
|
||||
);
|
||||
$this->invokePrivate($job, 'run', [[]]);
|
||||
$this->assertEquals(0, count($this->previewAppData->getDirectoryListing()));
|
||||
$previews = iterator_to_array($this->previewMapper->getAvailablePreviewForFile(5));
|
||||
$previews = iterator_to_array($this->previewMapper->getAvailablePreviewsForFile(5));
|
||||
$this->assertEquals(9, count($previews));
|
||||
$this->assertEquals(0, count($this->previewAppData->getDirectoryListing()));
|
||||
|
||||
$nameVersionMapping = [];
|
||||
foreach ($previews as $preview) {
|
||||
$nameVersionMapping[$preview->getName()] = $preview->getVersion();
|
||||
$nameVersionMapping[$preview->getName($this->mimeTypeLoader)] = $preview->getVersion();
|
||||
}
|
||||
|
||||
$this->assertEquals([
|
||||
'1000-128-128.png' => 1000,
|
||||
'1000-128-128-crop.png' => 1000,
|
||||
'1000-128-128.png' => 1000,
|
||||
'1000-256-256-max.png' => 1000,
|
||||
'1001-128-128.png' => 1001,
|
||||
'1001-128-128-crop.png' => 1001,
|
||||
'1001-128-128.png' => 1001,
|
||||
'1001-256-256-max.png' => 1001,
|
||||
'128-128.png' => -1,
|
||||
'128-128-crop.png' => -1,
|
||||
'256-256-max.png' => -1,
|
||||
'128-128-crop.png' => null,
|
||||
'128-128.png' => null,
|
||||
'256-256-max.png' => null,
|
||||
], $nameVersionMapping);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,9 +12,7 @@ namespace Test\Preview;
|
|||
|
||||
use OC\Preview\Db\Preview;
|
||||
use OC\Preview\Db\PreviewMapper;
|
||||
use OCP\Files\IMimeTypeLoader;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IPreview;
|
||||
use OCP\Server;
|
||||
use Test\TestCase;
|
||||
|
||||
|
|
@ -24,12 +22,10 @@ use Test\TestCase;
|
|||
class PreviewMapperTest extends TestCase {
|
||||
private PreviewMapper $previewMapper;
|
||||
private IDBConnection $connection;
|
||||
private IMimeTypeLoader $mimeTypeLoader;
|
||||
|
||||
public function setUp(): void {
|
||||
$this->previewMapper = Server::get(PreviewMapper::class);
|
||||
$this->connection = Server::get(IDBConnection::class);
|
||||
$this->mimeTypeLoader = Server::get(IMimeTypeLoader::class);
|
||||
}
|
||||
|
||||
public function testGetAvailablePreviews(): void {
|
||||
|
|
@ -71,11 +67,11 @@ class PreviewMapperTest extends TestCase {
|
|||
$preview->setCropped(true);
|
||||
$preview->setMax(true);
|
||||
$preview->setWidth(100);
|
||||
$preview->setSourceMimetype(1);
|
||||
$preview->setSourceMimeType('image/jpeg');
|
||||
$preview->setHeight(100);
|
||||
$preview->setSize(100);
|
||||
$preview->setMtime(time());
|
||||
$preview->setMimetype($this->mimeTypeLoader->getId('image/jpeg'));
|
||||
$preview->setMimetype('image/jpeg');
|
||||
$preview->setEtag('abcdefg');
|
||||
|
||||
if ($locationId !== null) {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ namespace Test\Preview;
|
|||
use OC\Preview\Db\Preview;
|
||||
use OC\Preview\Db\PreviewMapper;
|
||||
use OC\Preview\PreviewService;
|
||||
use OCP\IPreview;
|
||||
use OCP\Server;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
|
@ -24,6 +23,7 @@ use PHPUnit\Framework\TestCase;
|
|||
#[CoversClass(PreviewService::class)]
|
||||
class PreviewServiceTest extends TestCase {
|
||||
private PreviewService $previewService;
|
||||
private PreviewMapper $previewMapper;
|
||||
|
||||
protected function setUp(): void {
|
||||
$this->previewService = Server::get(PreviewService::class);
|
||||
|
|
@ -43,10 +43,10 @@ class PreviewServiceTest extends TestCase {
|
|||
$preview->setWidth($i);
|
||||
$preview->setHeight($i);
|
||||
$preview->setMax(true);
|
||||
$preview->setSourceMimetype(1);
|
||||
$preview->setSourceMimeType('image/jpeg');
|
||||
$preview->setCropped(true);
|
||||
$preview->setEncrypted(false);
|
||||
$preview->setMimetype(42);
|
||||
$preview->setMimetype('image/jpeg');
|
||||
$preview->setEtag('abc');
|
||||
$preview->setMtime((new \DateTime())->getTimestamp());
|
||||
$preview->setSize(0);
|
||||
|
|
|
|||
Loading…
Reference in a new issue