Merge pull request #55083 from nextcloud/carl/preview-interface-cleanup

refactor(preview): Cleanup a bit the public interface
This commit is contained in:
Carl Schwan 2025-10-10 11:30:02 +02:00 committed by GitHub
commit c89ca89f2f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 64 additions and 259 deletions

View file

@ -384,6 +384,7 @@ class ShareControllerTest extends \Test\TestCase {
$file->method('isReadable')->willReturn(true);
$file->method('isShareable')->willReturn(true);
$file->method('getId')->willReturn(1234);
$file->method('getMimetype')->willReturn('text/plain');
$file->method('getName')->willReturn($filename);
$accountName = $this->createMock(IAccountProperty::class);

View file

@ -4119,14 +4119,6 @@
<code><![CDATA[\OCA\Notifications\App]]></code>
</UndefinedClass>
</file>
<file src="lib/private/Preview/ProviderV1Adapter.php">
<InvalidReturnStatement>
<code><![CDATA[$thumbnail === false ? null: $thumbnail]]></code>
</InvalidReturnStatement>
<InvalidReturnType>
<code><![CDATA[?IImage]]></code>
</InvalidReturnType>
</file>
<file src="lib/private/Profile/Actions/FediverseAction.php">
<NoValue>
<code><![CDATA[$instance]]></code>

View file

@ -720,7 +720,6 @@ return array(
'OCP\\PreConditionNotMetException' => $baseDir . '/lib/public/PreConditionNotMetException.php',
'OCP\\Preview\\BeforePreviewFetchedEvent' => $baseDir . '/lib/public/Preview/BeforePreviewFetchedEvent.php',
'OCP\\Preview\\IMimeIconProvider' => $baseDir . '/lib/public/Preview/IMimeIconProvider.php',
'OCP\\Preview\\IProvider' => $baseDir . '/lib/public/Preview/IProvider.php',
'OCP\\Preview\\IProviderV2' => $baseDir . '/lib/public/Preview/IProviderV2.php',
'OCP\\Preview\\IVersionedPreviewFile' => $baseDir . '/lib/public/Preview/IVersionedPreviewFile.php',
'OCP\\Profile\\BeforeTemplateRenderedEvent' => $baseDir . '/lib/public/Profile/BeforeTemplateRenderedEvent.php',
@ -1907,8 +1906,6 @@ return array(
'OC\\Preview\\Photoshop' => $baseDir . '/lib/private/Preview/Photoshop.php',
'OC\\Preview\\Postscript' => $baseDir . '/lib/private/Preview/Postscript.php',
'OC\\Preview\\PreviewService' => $baseDir . '/lib/private/Preview/PreviewService.php',
'OC\\Preview\\Provider' => $baseDir . '/lib/private/Preview/Provider.php',
'OC\\Preview\\ProviderV1Adapter' => $baseDir . '/lib/private/Preview/ProviderV1Adapter.php',
'OC\\Preview\\ProviderV2' => $baseDir . '/lib/private/Preview/ProviderV2.php',
'OC\\Preview\\SGI' => $baseDir . '/lib/private/Preview/SGI.php',
'OC\\Preview\\SVG' => $baseDir . '/lib/private/Preview/SVG.php',

View file

@ -11,32 +11,32 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
);
public static $prefixLengthsPsr4 = array (
'O' =>
'O' =>
array (
'OC\\Core\\' => 8,
'OC\\' => 3,
'OCP\\' => 4,
),
'N' =>
'N' =>
array (
'NCU\\' => 4,
),
);
public static $prefixDirsPsr4 = array (
'OC\\Core\\' =>
'OC\\Core\\' =>
array (
0 => __DIR__ . '/../../..' . '/core',
),
'OC\\' =>
'OC\\' =>
array (
0 => __DIR__ . '/../../..' . '/lib/private',
),
'OCP\\' =>
'OCP\\' =>
array (
0 => __DIR__ . '/../../..' . '/lib/public',
),
'NCU\\' =>
'NCU\\' =>
array (
0 => __DIR__ . '/../../..' . '/lib/unstable',
),
@ -761,7 +761,6 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\PreConditionNotMetException' => __DIR__ . '/../../..' . '/lib/public/PreConditionNotMetException.php',
'OCP\\Preview\\BeforePreviewFetchedEvent' => __DIR__ . '/../../..' . '/lib/public/Preview/BeforePreviewFetchedEvent.php',
'OCP\\Preview\\IMimeIconProvider' => __DIR__ . '/../../..' . '/lib/public/Preview/IMimeIconProvider.php',
'OCP\\Preview\\IProvider' => __DIR__ . '/../../..' . '/lib/public/Preview/IProvider.php',
'OCP\\Preview\\IProviderV2' => __DIR__ . '/../../..' . '/lib/public/Preview/IProviderV2.php',
'OCP\\Preview\\IVersionedPreviewFile' => __DIR__ . '/../../..' . '/lib/public/Preview/IVersionedPreviewFile.php',
'OCP\\Profile\\BeforeTemplateRenderedEvent' => __DIR__ . '/../../..' . '/lib/public/Profile/BeforeTemplateRenderedEvent.php',
@ -1948,8 +1947,6 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Preview\\Photoshop' => __DIR__ . '/../../..' . '/lib/private/Preview/Photoshop.php',
'OC\\Preview\\Postscript' => __DIR__ . '/../../..' . '/lib/private/Preview/Postscript.php',
'OC\\Preview\\PreviewService' => __DIR__ . '/../../..' . '/lib/private/Preview/PreviewService.php',
'OC\\Preview\\Provider' => __DIR__ . '/../../..' . '/lib/private/Preview/Provider.php',
'OC\\Preview\\ProviderV1Adapter' => __DIR__ . '/../../..' . '/lib/private/Preview/ProviderV1Adapter.php',
'OC\\Preview\\ProviderV2' => __DIR__ . '/../../..' . '/lib/private/Preview/ProviderV2.php',
'OC\\Preview\\SGI' => __DIR__ . '/../../..' . '/lib/private/Preview/SGI.php',
'OC\\Preview\\SVG' => __DIR__ . '/../../..' . '/lib/private/Preview/SVG.php',

View file

@ -22,7 +22,6 @@ use OCP\IImage;
use OCP\IPreview;
use OCP\IStreamImage;
use OCP\Preview\BeforePreviewFetchedEvent;
use OCP\Preview\IProviderV2;
use OCP\Preview\IVersionedPreviewFile;
use Psr\Log\LoggerInterface;
@ -322,7 +321,7 @@ class Generator {
foreach ($providers as $providerClosure) {
$provider = $this->helper->getProvider($providerClosure);
if (!($provider instanceof IProviderV2)) {
if (!$provider) {
continue;
}

View file

@ -7,38 +7,18 @@
namespace OC\Preview;
use OCP\Files\File;
use OCP\Files\IRootFolder;
use OCP\Files\SimpleFS\ISimpleFile;
use OCP\IConfig;
use OCP\IImage;
use OCP\Image as OCPImage;
use OCP\Preview\IProvider;
use OCP\IPreview;
use OCP\Preview\IProviderV2;
/**
* Very small wrapper class to make the generator fully unit testable
* @psalm-import-type ProviderClosure from IPreview
*/
class GeneratorHelper {
/** @var IRootFolder */
private $rootFolder;
/** @var IConfig */
private $config;
public function __construct(IRootFolder $rootFolder, IConfig $config) {
$this->rootFolder = $rootFolder;
$this->config = $config;
}
/**
* @param IProviderV2 $provider
* @param File $file
* @param int $maxWidth
* @param int $maxHeight
*
* @return bool|IImage
*/
public function getThumbnail(IProviderV2 $provider, File $file, $maxWidth, $maxHeight, bool $crop = false) {
public function getThumbnail(IProviderV2 $provider, File $file, int $maxWidth, int $maxHeight, bool $crop = false): IImage|false {
if ($provider instanceof Imaginary) {
return $provider->getCroppedThumbnail($file, $maxWidth, $maxHeight, $crop) ?? false;
}
@ -52,14 +32,9 @@ class GeneratorHelper {
}
/**
* @param callable $providerClosure
* @return IProviderV2
* @param \Closure|string $providerClosure (string is only authorized in unit tests)
*/
public function getProvider($providerClosure) {
$provider = $providerClosure();
if ($provider instanceof IProvider) {
$provider = new ProviderV1Adapter($provider);
}
return $provider;
public function getProvider(\Closure|string $providerClosure): IProviderV2|false {
return $providerClosure();
}
}

View file

@ -1,50 +0,0 @@
<?php
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Preview;
use OCP\Preview\IProvider;
abstract class Provider implements IProvider {
private $options;
/**
* Constructor
*
* @param array $options
*/
public function __construct(array $options = []) {
$this->options = $options;
}
/**
* @return string Regex with the mimetypes that are supported by this provider
*/
abstract public function getMimeType();
/**
* Check if a preview can be generated for $path
*
* @param \OCP\Files\FileInfo $file
* @return bool
*/
public function isAvailable(\OCP\Files\FileInfo $file) {
return true;
}
/**
* Generates thumbnail which fits in $maxX and $maxY and keeps the aspect ratio, for file at path $path
*
* @param string $path Path of file
* @param int $maxX The maximum X size of the thumbnail. It can be smaller depending on the shape of the image
* @param int $maxY The maximum Y size of the thumbnail. It can be smaller depending on the shape of the image
* @param bool $scalingup Disable/Enable upscaling of previews
* @param \OC\Files\View $fileview fileview object of user folder
* @return bool|\OCP\IImage false if no preview was generated
*/
abstract public function getThumbnail($path, $maxX, $maxY, $scalingup, $fileview);
}

View file

@ -1,45 +0,0 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Preview;
use OC\Files\View;
use OCP\Files\File;
use OCP\Files\FileInfo;
use OCP\IImage;
use OCP\Preview\IProvider;
use OCP\Preview\IProviderV2;
class ProviderV1Adapter implements IProviderV2 {
private $providerV1;
public function __construct(IProvider $providerV1) {
$this->providerV1 = $providerV1;
}
public function getMimeType(): string {
return (string)$this->providerV1->getMimeType();
}
public function isAvailable(FileInfo $file): bool {
return (bool)$this->providerV1->isAvailable($file);
}
public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage {
[$view, $path] = $this->getViewAndPath($file);
$thumbnail = $this->providerV1->getThumbnail($path, $maxX, $maxY, false, $view);
return $thumbnail === false ? null: $thumbnail;
}
private function getViewAndPath(File $file) {
$view = new View(dirname($file->getPath()));
$path = $file->getName();
return [$view, $path];
}
}

View file

@ -7,13 +7,13 @@
*/
namespace OC;
use Closure;
use OC\AppFramework\Bootstrap\Coordinator;
use OC\Preview\Db\PreviewMapper;
use OC\Preview\Generator;
use OC\Preview\GeneratorHelper;
use OC\Preview\IMagickSupport;
use OC\Preview\Storage\StorageFactory;
use OCP\AppFramework\QueryException;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\File;
use OCP\Files\IRootFolder;
@ -24,10 +24,14 @@ use OCP\IConfig;
use OCP\IPreview;
use OCP\Preview\IProviderV2;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use Psr\Log\LoggerInterface;
use function array_key_exists;
/**
* @psalm-import-type ProviderClosure from IPreview
*/
class PreviewManager implements IPreview {
protected IConfig $config;
protected IRootFolder $rootFolder;
@ -36,10 +40,14 @@ class PreviewManager implements IPreview {
private GeneratorHelper $helper;
protected bool $providerListDirty = false;
protected bool $registeredCoreProviders = false;
/**
* @var array<string, list<ProviderClosure>> $providers
*/
protected array $providers = [];
/** @var array mime type => support status */
protected array $mimeTypeSupportMap = [];
/** @var ?list<class-string<IProviderV2>> $defaultProviders */
protected ?array $defaultProviders = null;
protected ?string $userId;
private Coordinator $bootstrapCoordinator;
@ -81,13 +89,10 @@ class PreviewManager implements IPreview {
* In order to improve lazy loading a closure can be registered which will be
* called in case preview providers are actually requested
*
* $callable has to return an instance of \OCP\Preview\IProvider or \OCP\Preview\IProviderV2
*
* @param string $mimeTypeRegex Regex with the mime types that are supported by this provider
* @param \Closure $callable
* @return void
* @param ProviderClosure $callable
*/
public function registerProvider($mimeTypeRegex, \Closure $callable): void {
public function registerProvider(string $mimeTypeRegex, Closure $callable): void {
if (!$this->enablePreviews) {
return;
}
@ -131,10 +136,7 @@ class PreviewManager implements IPreview {
$this->generator = new Generator(
$this->config,
$this,
new GeneratorHelper(
$this->rootFolder,
$this->config
),
new GeneratorHelper(),
$this->eventDispatcher,
$this->container->get(LoggerInterface::class),
$this->container->get(PreviewMapper::class),
@ -146,11 +148,11 @@ class PreviewManager implements IPreview {
public function getPreview(
File $file,
$width = -1,
$height = -1,
$crop = false,
$mode = IPreview::MODE_FILL,
$mimeType = null,
int $width = -1,
int $height = -1,
bool $crop = false,
string $mode = IPreview::MODE_FILL,
?string $mimeType = null,
bool $cacheResult = true,
): ISimpleFile {
$this->throwIfPreviewsDisabled($file, $mimeType);
@ -168,26 +170,18 @@ class PreviewManager implements IPreview {
/**
* Generates previews of a file
*
* @param File $file
* @param array $specifications
* @param string $mimeType
* @return ISimpleFile the last preview that was generated
* @throws NotFoundException
* @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
* @since 19.0.0
*/
public function generatePreviews(File $file, array $specifications, $mimeType = null) {
public function generatePreviews(File $file, array $specifications, ?string $mimeType = null): ISimpleFile {
$this->throwIfPreviewsDisabled($file, $mimeType);
return $this->getGenerator()->generatePreviews($file, $specifications, $mimeType);
}
/**
* returns true if the passed mime type is supported
*
* @param string $mimeType
* @return boolean
*/
public function isMimeSupported($mimeType = '*') {
public function isMimeSupported(string $mimeType = '*'): bool {
if (!$this->enablePreviews) {
return false;
}
@ -209,9 +203,6 @@ class PreviewManager implements IPreview {
return false;
}
/**
* Check if a preview can be generated for a file
*/
public function isAvailable(\OCP\Files\FileInfo $file, ?string $mimeType = null): bool {
if (!$this->enablePreviews) {
return false;
@ -233,10 +224,9 @@ class PreviewManager implements IPreview {
if (preg_match($supportedMimeType, $fileMimeType)) {
foreach ($providers as $providerClosure) {
$provider = $this->helper->getProvider($providerClosure);
if (!($provider instanceof IProviderV2)) {
if (!$provider) {
continue;
}
if ($provider->isAvailable($file)) {
return true;
}
@ -275,9 +265,9 @@ class PreviewManager implements IPreview {
* - OC\Preview\SVG
* - OC\Preview\TIFF
*
* @return array
* @return list<class-string<IProviderV2>>
*/
protected function getEnabledDefaultProvider() {
protected function getEnabledDefaultProvider(): array {
if ($this->defaultProviders !== null) {
return $this->defaultProviders;
}
@ -302,17 +292,16 @@ class PreviewManager implements IPreview {
if (in_array(Preview\Image::class, $this->defaultProviders)) {
$this->defaultProviders = array_merge($this->defaultProviders, $imageProviders);
}
$this->defaultProviders = array_unique($this->defaultProviders);
return $this->defaultProviders;
$this->defaultProviders = array_values(array_unique($this->defaultProviders));
/** @var list<class-string<IProviderV2>> $providers */
$providers = $this->defaultProviders;
return $providers;
}
/**
* Register the default providers (if enabled)
*
* @param string $class
* @param string $mimeType
*/
protected function registerCoreProvider($class, $mimeType, $options = []) {
protected function registerCoreProvider(string $class, string $mimeType, array $options = []): void {
if (in_array(trim($class, '\\'), $this->getEnabledDefaultProvider())) {
$this->registerProvider($mimeType, function () use ($class, $options) {
return new $class($options);
@ -323,7 +312,7 @@ class PreviewManager implements IPreview {
/**
* Register the default providers (if enabled)
*/
protected function registerCoreProviders() {
protected function registerCoreProviders(): void {
if ($this->registeredCoreProviders) {
return;
}
@ -440,11 +429,11 @@ class PreviewManager implements IPreview {
}
$this->loadedBootstrapProviders[$key] = null;
$this->registerProvider($provider->getMimeTypeRegex(), function () use ($provider) {
$this->registerProvider($provider->getMimeTypeRegex(), function () use ($provider): IProviderV2|false {
try {
return $this->container->get($provider->getService());
} catch (QueryException $e) {
return null;
} catch (NotFoundExceptionInterface) {
return false;
}
});
}

View file

@ -10,14 +10,20 @@
namespace OCP;
use Closure;
use OCP\AppFramework\Attribute\Consumable;
use OCP\Files\File;
use OCP\Files\FileInfo;
use OCP\Files\NotFoundException;
use OCP\Files\SimpleFS\ISimpleFile;
use OCP\Preview\IProviderV2;
/**
* This class provides functions to render and show thumbnails and previews of files
* @since 6.0.0
* @psalm-type ProviderClosure = Closure():(IProviderV2|false)
*/
#[Consumable(since: '6.0.0')]
interface IPreview {
/**
* @since 11.0.0
@ -33,31 +39,27 @@ interface IPreview {
* In order to improve lazy loading a closure can be registered which will be
* called in case preview providers are actually requested
*
* $callable has to return an instance of \OCP\Preview\IProvider
*
* @param string $mimeTypeRegex Regex with the mime types that are supported by this provider
* @param \Closure $callable
* @return void
* @param ProviderClosure $callable
* @since 8.1.0
* @see \OCP\AppFramework\Bootstrap\IRegistrationContext::registerPreviewProvider
*
* @deprecated 23.0.0 Register your provider via the IRegistrationContext when booting the app
*/
public function registerProvider($mimeTypeRegex, \Closure $callable);
public function registerProvider(string $mimeTypeRegex, Closure $callable): void;
/**
* Get all providers
* @return array
* @return array<string, list<ProviderClosure>>
* @since 8.1.0
*/
public function getProviders();
public function getProviders(): array;
/**
* Does the manager have any providers
* @return bool
* @since 8.1.0
*/
public function hasProviders();
public function hasProviders(): bool;
/**
* Returns a preview of a file
@ -65,50 +67,42 @@ interface IPreview {
* The cache is searched first and if nothing usable was found then a preview is
* generated by one of the providers
*
* @param File $file
* @param int $width
* @param int $height
* @param bool $crop
* @param string $mode
* @param IPreview::MODE_* $mode
* @param string $mimeType To force a given mimetype for the file (files_versions needs this)
* @param bool $cacheResult Whether or not to cache the preview on the filesystem. Default to true. Can be useful to set to false to limit the amount of stored previews.
* @param bool $cacheResult Whether to cache the preview on the filesystem. Default to true. Can be useful to set to false to limit the amount of stored previews.
* @return ISimpleFile
* @throws NotFoundException
* @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
* @since 11.0.0 - \InvalidArgumentException was added in 12.0.0
* @since 32.0.0 - getPreview($cacheResult) added the $cacheResult argument to the signature
*/
public function getPreview(File $file, $width = -1, $height = -1, $crop = false, $mode = IPreview::MODE_FILL, $mimeType = null, bool $cacheResult = true);
public function getPreview(File $file, int $width = -1, int $height = -1, bool $crop = false, string $mode = IPreview::MODE_FILL, ?string $mimeType = null, bool $cacheResult = true): ISimpleFile;
/**
* Returns true if the passed mime type is supported
* @param string $mimeType
* @return boolean
* @param string $mimeType A glob
* @since 6.0.0
*/
public function isMimeSupported($mimeType = '*');
public function isMimeSupported(string $mimeType = '*'): bool;
/**
* Check if a preview can be generated for a file
*
* @param \OCP\Files\FileInfo $file
* @param FileInfo $file
* @param string|null $mimeType To force a given mimetype for the file
* @return bool
* @since 8.0.0
* @since 32.0.0 - isAvailable($mimeType) added the $mimeType argument to the signature
*/
public function isAvailable(\OCP\Files\FileInfo $file, ?string $mimeType = null);
public function isAvailable(FileInfo $file, ?string $mimeType = null): bool;
/**
* Generates previews of a file
*
* @param File $file
* @param array $specifications
* @param string $mimeType
* @return ISimpleFile the last preview that was generated
* @throws NotFoundException
* @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
* @since 19.0.0
*/
public function generatePreviews(File $file, array $specifications, $mimeType = null);
public function generatePreviews(File $file, array $specifications, ?string $mimeType = null): ISimpleFile;
}

View file

@ -1,44 +0,0 @@
<?php
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OCP\Preview;
/**
* Interface IProvider
*
* @since 8.1.0
* @deprecated 17.0.0 use IProviderV2 instead
*/
interface IProvider {
/**
* @return string Regex with the mimetypes that are supported by this provider
* @since 8.1.0
*/
public function getMimeType();
/**
* Check if a preview can be generated for $path
*
* @param \OCP\Files\FileInfo $file
* @return bool
* @since 8.1.0
*/
public function isAvailable(\OCP\Files\FileInfo $file);
/**
* get thumbnail for file at path $path
*
* @param string $path Path of file
* @param int $maxX The maximum X size of the thumbnail. It can be smaller depending on the shape of the image
* @param int $maxY The maximum Y size of the thumbnail. It can be smaller depending on the shape of the image
* @param bool $scalingup Disable/Enable upscaling of previews
* @param \OC\Files\View $fileview fileview object of user folder
* @return bool|\OCP\IImage false if no preview was generated
* @since 8.1.0
*/
public function getThumbnail($path, $maxX, $maxY, $scalingup, $fileview);
}