Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
refactor(preview): Use same mimetype ids as filecache
Signed-off-by: Carl Schwan <[email protected]>
  • Loading branch information
CarlSchwan committed Oct 6, 2025
commit 66f50bd585f428862849db0db2847287e59453c9
13 changes: 12 additions & 1 deletion core/BackgroundJobs/MovePreviewJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@
use OCP\DB\Exception;
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\IDBConnection;
use Override;
use Psr\Log\LoggerInterface;

class MovePreviewJob extends TimedJob {
private IAppData $appData;
Expand All @@ -36,6 +39,9 @@ public function __construct(
private readonly StorageFactory $storageFactory,
private readonly IDBConnection $connection,
private readonly IRootFolder $rootFolder,
private readonly IMimeTypeDetector $mimeTypeDetector,
private readonly IMimeTypeLoader $mimeTypeLoader,
private readonly LoggerInterface $logger,
IAppDataFactory $appDataFactory,
) {
parent::__construct($time);
Expand Down Expand Up @@ -125,8 +131,13 @@ private function processPreviews(int|string $fileId, bool $simplePaths): void {
$previewFiles = [];

foreach ($folder->getDirectoryListing() as $previewFile) {
$path = $fileId . '/' . $previewFile->getName();
/** @var SimpleFile $previewFile */
$preview = Preview::fromPath($fileId . '/' . $previewFile->getName());
$preview = Preview::fromPath($path, $this->mimeTypeDetector, $this->mimeTypeLoader);
if (!$preview) {
$this->logger->error('Unable to import old preview at path.');
continue;
}
$preview->setSize($previewFile->getSize());
$preview->setMtime($previewFile->getMtime());
$preview->setOldFileId($previewFile->getId());
Expand Down
2 changes: 1 addition & 1 deletion core/Command/Preview/ResetRenderedTexts.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ private function deletePreviews(OutputInterface $output, bool $dryMode): void {
$previewsToDeleteCount = 0;

foreach ($this->getPreviewsToDelete() as $preview) {
$output->writeln('Deleting preview ' . $preview->getName() . ' for fileId ' . $preview->getFileId(), OutputInterface::VERBOSITY_VERBOSE);
$output->writeln('Deleting preview ' . $preview->getName($this->mimeTypeLoader) . ' for fileId ' . $preview->getFileId(), OutputInterface::VERBOSITY_VERBOSE);

$previewsToDeleteCount++;

Expand Down
14 changes: 14 additions & 0 deletions lib/private/Files/Cache/LocalRootScanner.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,18 @@
*/
namespace OC\Files\Cache;

use OCP\IConfig;
use OCP\Server;

class LocalRootScanner extends Scanner {
private string $previewFolder;

public function __construct(\OC\Files\Storage\Storage $storage) {
parent::__construct($storage);
$config = Server::get(IConfig::class);
$this->previewFolder = 'appdata_' . $config->getSystemValueString('instanceid', '') . '/preview';
}

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);
Expand All @@ -27,6 +38,9 @@ public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $loc

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');
}
}
22 changes: 7 additions & 15 deletions lib/private/Files/Cache/Scanner.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@
use OC\Files\Storage\Wrapper\Encryption;
use OC\Files\Storage\Wrapper\Jail;
use OC\Hooks\BasicEmitter;
use OC\SystemConfig;
use OCP\Files\Cache\IScanner;
use OCP\Files\ForbiddenException;
use OCP\Files\NotFoundException;
use OCP\Files\Storage\ILockingStorage;
use OCP\Files\Storage\IReliableEtagStorage;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\Lock\ILockingProvider;
use OCP\Server;
use Psr\Log\LoggerInterface;

/**
Expand Down Expand Up @@ -65,19 +66,15 @@ class Scanner extends BasicEmitter implements IScanner {

protected IDBConnection $connection;

private string $previewFolder;

public function __construct(\OC\Files\Storage\Storage $storage) {
$this->storage = $storage;
$this->storageId = $this->storage->getId();
$this->cache = $storage->getCache();
/** @var SystemConfig $config */
$config = \OC::$server->get(SystemConfig::class);
$this->cacheActive = !$config->getValue('filesystem_cache_readonly', false);
$this->useTransactions = !$config->getValue('filescanner_no_transactions', false);
$this->lockingProvider = \OC::$server->get(ILockingProvider::class);
$this->connection = \OC::$server->get(IDBConnection::class);
$this->previewFolder = 'appdata_' . $config->getValue('instanceid', '') . '/preview';
$config = Server::get(IConfig::class);
$this->cacheActive = !$config->getSystemValueBool('filesystem_cache_readonly', false);
$this->useTransactions = !$config->getSystemValueBool('filescanner_no_transactions', false);
$this->lockingProvider = Server::get(ILockingProvider::class);
$this->connection = Server::get(IDBConnection::class);
}

/**
Expand Down Expand Up @@ -415,11 +412,6 @@ protected function scanChildren(string $path, $recursive, int $reuse, int $folde
$size = 0;
$childQueue = $this->handleChildren($path, $recursive, $reuse, $folderId, $lock, $size, $etagChanged);

if (str_starts_with($path, $this->previewFolder)) {
// Preview scanning is handled in LocalPreviewStorage
return 0;
}

foreach ($childQueue as $child => [$childId, $childSize]) {
// "etag changed" propagates up, but not down, so we pass `false` to the children even if we already know that the etag of the current folder changed
$childEtagChanged = false;
Expand Down
73 changes: 34 additions & 39 deletions lib/private/Preview/Db/Preview.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@

use OCP\AppFramework\Db\Entity;
use OCP\DB\Types;
use OCP\IPreview;
use OCP\Files\IMimeTypeDetector;
use OCP\Files\IMimeTypeLoader;
use OCP\Server;

/**
* Preview entity mapped to the oc_previews and oc_preview_locations table.
Expand Down Expand Up @@ -91,42 +93,39 @@ public function __construct() {
$this->addType('version', Types::BIGINT);
}

public static function fromPath(string $path): Preview {
public static function fromPath(string $path, IMimeTypeDetector $mimeTypeDetector, IMimeTypeLoader $mimeTypeLoader): 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);

[0 => $baseName, 1 => $extension] = explode('.', $fileName);
$preview->setMimetype(match ($extension) {
'jpg' | 'jpeg' => IPreview::MIMETYPE_JPEG,
'png' => IPreview::MIMETYPE_PNG,
'gif' => IPreview::MIMETYPE_GIF,
'webp' => IPreview::MIMETYPE_WEBP,
default => IPreview::MIMETYPE_JPEG,
});
$nameSplit = explode('-', $baseName);

$offset = 0;
$preview->setVersion(null);
if (count($nameSplit) === 4 || (count($nameSplit) === 3 && is_numeric($nameSplit[2]))) {
$offset = 1;
$preview->setVersion((int)$nameSplit[0]);
if ($ok !== 1) {
return false;
}

$preview->setWidth((int)$nameSplit[$offset + 0]);
$preview->setHeight((int)$nameSplit[$offset + 1]);
[
2 => $version,
3 => $width,
4 => $height,
6 => $crop,
8 => $max,
] = $matches;

$preview->setCropped(false);
$preview->setMax(false);
if (isset($nameSplit[$offset + 2])) {
$preview->setCropped($nameSplit[$offset + 2] === 'crop');
$preview->setMax($nameSplit[$offset + 2] === 'max');
$preview->setMimetype($mimeTypeLoader->getId($mimeTypeDetector->detectPath($fileName)));

$preview->setWidth((int)$width);
$preview->setHeight((int)$height);
$preview->setCropped($crop === 'crop');
$preview->setMax($max === 'max');

if (!empty($version)) {
$preview->setVersion((int)$version);
}
return $preview;
}

public function getName(): string {
public function getName(IMimeTypeLoader $mimeTypeLoader): string {
$path = ($this->getVersion() > -1 ? $this->getVersion() . '-' : '') . $this->getWidth() . '-' . $this->getHeight();
if ($this->isCropped()) {
$path .= '-crop';
Expand All @@ -135,27 +134,23 @@ public function getName(): string {
$path .= '-max';
}

$ext = $this->getExtension();
$ext = $this->getExtension($mimeTypeLoader);
$path .= '.' . $ext;
return $path;
}

public function getMimetypeValue(): string {
return match ($this->mimetype) {
IPreview::MIMETYPE_JPEG => 'image/jpeg',
IPreview::MIMETYPE_PNG => 'image/png',
IPreview::MIMETYPE_WEBP => 'image/webp',
IPreview::MIMETYPE_GIF => 'image/gif',
public function getExtension(IMimeTypeLoader $mimeTypeLoader): string {
return match ($this->getMimetypeValue($mimeTypeLoader)) {
'image/png' => 'png',
'image/gif' => 'gif',
'image/jpeg' => 'jpg',
'image/webp' => 'webp',
default => 'png',
};
}

public function getExtension(): string {
return match ($this->mimetype) {
IPreview::MIMETYPE_JPEG => 'jpg',
IPreview::MIMETYPE_PNG => 'png',
IPreview::MIMETYPE_WEBP => 'webp',
IPreview::MIMETYPE_GIF => 'gif',
};
public function getMimetypeValue(IMimeTypeLoader $mimeTypeLoader): string {
return $mimeTypeLoader->getMimetypeById($this->mimetype) ?? 'image/jpeg';
}

public function setBucketName(string $bucketName): void {
Expand Down
22 changes: 4 additions & 18 deletions lib/private/Preview/Db/PreviewMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\Exception;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Files\IMimeTypeLoader;
use OCP\IDBConnection;
use OCP\IPreview;

Expand All @@ -24,7 +25,9 @@ class PreviewMapper extends QBMapper {
private const TABLE_NAME = 'previews';
private const LOCATION_TABLE_NAME = 'preview_locations';

public function __construct(IDBConnection $db) {
public function __construct(
IDBConnection $db,
) {
parent::__construct($db, self::TABLE_NAME, Preview::class);
}

Expand Down Expand Up @@ -57,23 +60,6 @@ public function getAvailablePreviews(array $fileIds): array {
return $previews;
}

public function getPreview(int $fileId, int $width, int $height, string $mode, int $mimetype = IPreview::MIMETYPE_JPEG): ?Preview {
$selectQb = $this->db->getQueryBuilder();
$this->joinLocation($selectQb)
->where(
$selectQb->expr()->eq('file_id', $selectQb->createNamedParameter($fileId)),
$selectQb->expr()->eq('width', $selectQb->createNamedParameter($width)),
$selectQb->expr()->eq('height', $selectQb->createNamedParameter($height)),
$selectQb->expr()->eq('mode', $selectQb->createNamedParameter($mode)),
$selectQb->expr()->eq('mimetype', $selectQb->createNamedParameter($mimetype)),
);
try {
return $this->findEntity($selectQb);
} catch (DoesNotExistException) {
return null;
}
}

/**
* @return \Generator<Preview>
*/
Expand Down
Loading