Skip to content

Commit 1c518a2

Browse files
authored
Merge pull request #52360 from nextcloud/artonge/fix/use_preview_api_for_blurhash_generation
2 parents 31899d9 + 867be35 commit 1c518a2

File tree

6 files changed

+64
-120
lines changed

6 files changed

+64
-120
lines changed

build/psalm-baseline.xml

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2230,24 +2230,9 @@
22302230
</InvalidReturnStatement>
22312231
</file>
22322232
<file src="lib/private/Preview/Generator.php">
2233-
<InvalidArgument>
2234-
<code><![CDATA[$maxPreviewImage]]></code>
2235-
</InvalidArgument>
22362233
<LessSpecificReturnType>
22372234
<code><![CDATA[null|string]]></code>
22382235
</LessSpecificReturnType>
2239-
<MismatchingDocblockParamType>
2240-
<code><![CDATA[ISimpleFile]]></code>
2241-
</MismatchingDocblockParamType>
2242-
<UndefinedInterfaceMethod>
2243-
<code><![CDATA[height]]></code>
2244-
<code><![CDATA[height]]></code>
2245-
<code><![CDATA[preciseResizeCopy]]></code>
2246-
<code><![CDATA[resizeCopy]]></code>
2247-
<code><![CDATA[valid]]></code>
2248-
<code><![CDATA[width]]></code>
2249-
<code><![CDATA[width]]></code>
2250-
</UndefinedInterfaceMethod>
22512236
</file>
22522237
<file src="lib/private/Preview/ProviderV1Adapter.php">
22532238
<InvalidReturnStatement>

lib/private/Blurhash/Listener/GenerateBlurhashMetadata.php

Lines changed: 9 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use OCP\FilesMetadata\AMetadataEvent;
2020
use OCP\FilesMetadata\Event\MetadataBackgroundEvent;
2121
use OCP\FilesMetadata\Event\MetadataLiveEvent;
22+
use OCP\IPreview;
2223
use OCP\Lock\LockedException;
2324

2425
/**
@@ -27,11 +28,14 @@
2728
* @template-implements IEventListener<AMetadataEvent>
2829
*/
2930
class GenerateBlurhashMetadata implements IEventListener {
30-
private const RESIZE_BOXSIZE = 30;
31-
3231
private const COMPONENTS_X = 4;
3332
private const COMPONENTS_Y = 3;
3433

34+
public function __construct(
35+
private IPreview $preview,
36+
) {
37+
}
38+
3539
/**
3640
* @throws NotPermittedException
3741
* @throws GenericFileException
@@ -64,7 +68,9 @@ public function handle(Event $event): void {
6468
return;
6569
}
6670

67-
$image = $this->resizedImageFromFile($file);
71+
$preview = $this->preview->getPreview($file, 64, 64, cacheResult: false);
72+
$image = @imagecreatefromstring($preview->getContent());
73+
6874
if (!$image) {
6975
return;
7076
}
@@ -73,35 +79,6 @@ public function handle(Event $event): void {
7379
->setEtag('blurhash', $currentEtag);
7480
}
7581

76-
/**
77-
* @param File $file
78-
*
79-
* @return GdImage|false
80-
* @throws GenericFileException
81-
* @throws NotPermittedException
82-
* @throws LockedException
83-
*/
84-
private function resizedImageFromFile(File $file): GdImage|false {
85-
$image = @imagecreatefromstring($file->getContent());
86-
if ($image === false) {
87-
return false;
88-
}
89-
90-
$currX = imagesx($image);
91-
$currY = imagesy($image);
92-
93-
if ($currX > $currY) {
94-
$newX = self::RESIZE_BOXSIZE;
95-
$newY = intval($currY * $newX / $currX);
96-
} else {
97-
$newY = self::RESIZE_BOXSIZE;
98-
$newX = intval($currX * $newY / $currY);
99-
}
100-
101-
$newImage = @imagescale($image, $newX, $newY);
102-
return ($newImage !== false) ? $newImage : $image;
103-
}
104-
10582
/**
10683
* @param GdImage $image
10784
*

lib/private/Preview/Generator.php

Lines changed: 32 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use OCP\Files\InvalidPathException;
1313
use OCP\Files\NotFoundException;
1414
use OCP\Files\NotPermittedException;
15+
use OCP\Files\SimpleFS\InMemoryFile;
1516
use OCP\Files\SimpleFS\ISimpleFile;
1617
use OCP\Files\SimpleFS\ISimpleFolder;
1718
use OCP\IConfig;
@@ -43,17 +44,19 @@ public function __construct(
4344
* The cache is searched first and if nothing usable was found then a preview is
4445
* generated by one of the providers
4546
*
46-
* @param File $file
47-
* @param int $width
48-
* @param int $height
49-
* @param bool $crop
50-
* @param string $mode
51-
* @param string|null $mimeType
5247
* @return ISimpleFile
5348
* @throws NotFoundException
5449
* @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
5550
*/
56-
public function getPreview(File $file, $width = -1, $height = -1, $crop = false, $mode = IPreview::MODE_FILL, $mimeType = null) {
51+
public function getPreview(
52+
File $file,
53+
int $width = -1,
54+
int $height = -1,
55+
bool $crop = false,
56+
string $mode = IPreview::MODE_FILL,
57+
?string $mimeType = null,
58+
bool $cacheResult = true,
59+
): ISimpleFile {
5760
$specification = [
5861
'width' => $width,
5962
'height' => $height,
@@ -78,23 +81,19 @@ public function getPreview(File $file, $width = -1, $height = -1, $crop = false,
7881
'mode' => $mode,
7982
'mimeType' => $mimeType,
8083
]);
81-
84+
8285

8386
// since we only ask for one preview, and the generate method return the last one it created, it returns the one we want
84-
return $this->generatePreviews($file, [$specification], $mimeType);
87+
return $this->generatePreviews($file, [$specification], $mimeType, $cacheResult);
8588
}
8689

8790
/**
8891
* Generates previews of a file
8992
*
90-
* @param File $file
91-
* @param non-empty-array $specifications
92-
* @param string $mimeType
93-
* @return ISimpleFile the last preview that was generated
9493
* @throws NotFoundException
9594
* @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
9695
*/
97-
public function generatePreviews(File $file, array $specifications, $mimeType = null) {
96+
public function generatePreviews(File $file, array $specifications, ?string $mimeType = null, bool $cacheResult = true): ISimpleFile {
9897
//Make sure that we can read the file
9998
if (!$file->isReadable()) {
10099
$this->logger->warning('Cannot read file: {path}, skipping preview generation.', ['path' => $file->getPath()]);
@@ -167,7 +166,7 @@ public function generatePreviews(File $file, array $specifications, $mimeType =
167166
}
168167

169168
$this->logger->warning('Cached preview not found for file {path}, generating a new preview.', ['path' => $file->getPath()]);
170-
$preview = $this->generatePreview($previewFolder, $maxPreviewImage, $width, $height, $crop, $maxWidth, $maxHeight, $previewVersion);
169+
$preview = $this->generatePreview($previewFolder, $maxPreviewImage, $width, $height, $crop, $maxWidth, $maxHeight, $previewVersion, $cacheResult);
171170
// New file, augment our array
172171
$previewFiles[] = $preview;
173172
}
@@ -351,11 +350,10 @@ private function generateProviderPreview(ISimpleFolder $previewFolder, File $fil
351350

352351
$path = $this->generatePath($preview->width(), $preview->height(), $crop, $max, $preview->dataMimeType(), $prefix);
353352
try {
354-
$file = $previewFolder->newFile($path);
355353
if ($preview instanceof IStreamImage) {
356-
$file->putContent($preview->resource());
354+
return $previewFolder->newFile($path, $preview->resource());
357355
} else {
358-
$file->putContent($preview->data());
356+
return $previewFolder->newFile($path, $preview->data());
359357
}
360358
} catch (NotPermittedException $e) {
361359
throw new NotFoundException();
@@ -490,19 +488,20 @@ private function calculateSize($width, $height, $crop, $mode, $maxWidth, $maxHei
490488
}
491489

492490
/**
493-
* @param ISimpleFolder $previewFolder
494-
* @param ISimpleFile $maxPreview
495-
* @param int $width
496-
* @param int $height
497-
* @param bool $crop
498-
* @param int $maxWidth
499-
* @param int $maxHeight
500-
* @param string $prefix
501-
* @return ISimpleFile
502491
* @throws NotFoundException
503492
* @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
504493
*/
505-
private function generatePreview(ISimpleFolder $previewFolder, IImage $maxPreview, $width, $height, $crop, $maxWidth, $maxHeight, $prefix) {
494+
private function generatePreview(
495+
ISimpleFolder $previewFolder,
496+
IImage $maxPreview,
497+
int $width,
498+
int $height,
499+
bool $crop,
500+
int $maxWidth,
501+
int $maxHeight,
502+
string $prefix,
503+
bool $cacheResult,
504+
): ISimpleFile {
506505
$preview = $maxPreview;
507506
if (!$preview->valid()) {
508507
throw new \InvalidArgumentException('Failed to generate preview, failed to load image');
@@ -539,12 +538,14 @@ private function generatePreview(ISimpleFolder $previewFolder, IImage $maxPrevie
539538

540539
$path = $this->generatePath($width, $height, $crop, false, $preview->dataMimeType(), $prefix);
541540
try {
542-
$file = $previewFolder->newFile($path);
543-
$file->putContent($preview->data());
541+
if ($cacheResult) {
542+
return $previewFolder->newFile($path, $preview->data());
543+
} else {
544+
return new InMemoryFile($path, $preview->data());
545+
}
544546
} catch (NotPermittedException $e) {
545547
throw new NotFoundException();
546548
}
547-
548549
return $file;
549550
}
550551

lib/private/PreviewManager.php

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -145,29 +145,20 @@ private function getGenerator(): Generator {
145145
return $this->generator;
146146
}
147147

148-
/**
149-
* Returns a preview of a file
150-
*
151-
* The cache is searched first and if nothing usable was found then a preview is
152-
* generated by one of the providers
153-
*
154-
* @param File $file
155-
* @param int $width
156-
* @param int $height
157-
* @param bool $crop
158-
* @param string $mode
159-
* @param string $mimeType
160-
* @return ISimpleFile
161-
* @throws NotFoundException
162-
* @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
163-
* @since 11.0.0 - \InvalidArgumentException was added in 12.0.0
164-
*/
165-
public function getPreview(File $file, $width = -1, $height = -1, $crop = false, $mode = IPreview::MODE_FILL, $mimeType = null) {
148+
public function getPreview(
149+
File $file,
150+
$width = -1,
151+
$height = -1,
152+
$crop = false,
153+
$mode = IPreview::MODE_FILL,
154+
$mimeType = null,
155+
bool $cacheResult = true,
156+
): ISimpleFile {
166157
$this->throwIfPreviewsDisabled();
167158
$previewConcurrency = $this->getGenerator()->getNumConcurrentPreviews('preview_concurrency_all');
168159
$sem = Generator::guardWithSemaphore(Generator::SEMAPHORE_ID_ALL, $previewConcurrency);
169160
try {
170-
$preview = $this->getGenerator()->getPreview($file, $width, $height, $crop, $mode, $mimeType);
161+
$preview = $this->getGenerator()->getPreview($file, $width, $height, $crop, $mode, $mimeType, $cacheResult);
171162
} finally {
172163
Generator::unguardWithSemaphore($sem);
173164
}

lib/public/IPreview.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,14 @@ public function hasProviders();
7171
* @param bool $crop
7272
* @param string $mode
7373
* @param string $mimeType To force a given mimetype for the file (files_versions needs this)
74+
* @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.
7475
* @return ISimpleFile
7576
* @throws NotFoundException
7677
* @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
7778
* @since 11.0.0 - \InvalidArgumentException was added in 12.0.0
79+
* @since 32.0.0 - getPreview($cacheResult) added the $cacheResult argument to the signature
7880
*/
79-
public function getPreview(File $file, $width = -1, $height = -1, $crop = false, $mode = IPreview::MODE_FILL, $mimeType = null);
81+
public function getPreview(File $file, $width = -1, $height = -1, $crop = false, $mode = IPreview::MODE_FILL, $mimeType = null, bool $cacheResult = true);
8082

8183
/**
8284
* Returns true if the passed mime type is supported

tests/lib/Preview/GeneratorTest.php

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,25 @@
2222
use Psr\Log\LoggerInterface;
2323

2424
class GeneratorTest extends \Test\TestCase {
25-
/** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */
25+
/** @var IConfig&\PHPUnit\Framework\MockObject\MockObject */
2626
private $config;
2727

28-
/** @var IPreview|\PHPUnit\Framework\MockObject\MockObject */
28+
/** @var IPreview&\PHPUnit\Framework\MockObject\MockObject */
2929
private $previewManager;
3030

31-
/** @var IAppData|\PHPUnit\Framework\MockObject\MockObject */
31+
/** @var IAppData&\PHPUnit\Framework\MockObject\MockObject */
3232
private $appData;
3333

34-
/** @var GeneratorHelper|\PHPUnit\Framework\MockObject\MockObject */
34+
/** @var GeneratorHelper&\PHPUnit\Framework\MockObject\MockObject */
3535
private $helper;
3636

37-
/** @var IEventDispatcher|\PHPUnit\Framework\MockObject\MockObject */
37+
/** @var IEventDispatcher&\PHPUnit\Framework\MockObject\MockObject */
3838
private $eventDispatcher;
3939

4040
/** @var Generator */
4141
private $generator;
4242

43-
private LoggerInterface|\PHPUnit\Framework\MockObject\MockObject $logger;
43+
private LoggerInterface&\PHPUnit\Framework\MockObject\MockObject $logger;
4444

4545
protected function setUp(): void {
4646
parent::setUp();
@@ -196,18 +196,10 @@ public function testGetNewPreview(): void {
196196
$previewFolder->method('getDirectoryListing')
197197
->willReturn([]);
198198
$previewFolder->method('newFile')
199-
->willReturnCallback(function ($filename) use ($maxPreview, $previewFile) {
200-
if ($filename === '2048-2048-max.png') {
201-
return $maxPreview;
202-
} elseif ($filename === '256-256.png') {
203-
return $previewFile;
204-
}
205-
$this->fail('Unexpected file');
206-
});
207-
208-
$maxPreview->expects($this->once())
209-
->method('putContent')
210-
->with($this->equalTo('my data'));
199+
->willReturnMap([
200+
['2048-2048-max.png', 'my data', $maxPreview],
201+
['256-256.png', 'my resized data', $previewFile],
202+
]);
211203

212204
$previewFolder->method('getFile')
213205
->with($this->equalTo('256-256.png'))
@@ -218,10 +210,6 @@ public function testGetNewPreview(): void {
218210
->with($this->equalTo($maxPreview))
219211
->willReturn($image);
220212

221-
$previewFile->expects($this->once())
222-
->method('putContent')
223-
->with('my resized data');
224-
225213
$this->eventDispatcher->expects($this->once())
226214
->method('dispatchTyped')
227215
->with(new BeforePreviewFetchedEvent($file, 100, 100, false, IPreview::MODE_FILL, null));

0 commit comments

Comments
 (0)