Skip to content

Commit bae9e45

Browse files
authored
Merge pull request #6146 from WoltLab/61-offload-thumbnail-generation
Add events to delegate the WebP/thumbnail generation
2 parents 747d6fe + 773b922 commit bae9e45

File tree

4 files changed

+198
-42
lines changed

4 files changed

+198
-42
lines changed

wcfsetup/install/files/lib/data/file/File.class.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public function getSourceFilenameWebp(): ?string
8282
);
8383
}
8484

85-
private function getRelativePath(): string
85+
public function getRelativePath(): string
8686
{
8787
$folderA = \substr($this->fileHash, 0, 2);
8888
$folderB = \substr($this->fileHash, 2, 2);
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
namespace wcf\event\file;
4+
5+
use wcf\data\file\File;
6+
use wcf\event\IPsr14Event;
7+
use wcf\system\file\processor\ThumbnailFormat;
8+
9+
/**
10+
* Requests the generation of a thumbnail.
11+
*
12+
* @author Alexander Ebert
13+
* @copyright 2001-2024 WoltLab GmbH
14+
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
15+
* @since 6.1
16+
*/
17+
final class GenerateThumbnail implements IPsr14Event
18+
{
19+
private string $pathname;
20+
private bool $sourceIsDamaged = false;
21+
22+
public function __construct(
23+
public readonly File $file,
24+
public readonly ThumbnailFormat $thumbnailFormat,
25+
) {}
26+
27+
/**
28+
* Returns true if a file has already been set and no further files are
29+
* being accepted.
30+
*/
31+
public function hasFile(): bool
32+
{
33+
return isset($this->pathname);
34+
}
35+
36+
/**
37+
* Sets the pathname of the generated image unless it has already been set
38+
* in which case the call will throw an exception. You must check the result
39+
* of `hasFile()` first.
40+
*/
41+
public function setGeneratedFile(string $pathname): void
42+
{
43+
if (isset($this->pathname)) {
44+
throw new \BadMethodCallException("Cannot set the generated file, a value has already been set.");
45+
}
46+
47+
$this->pathname = $pathname;
48+
}
49+
50+
public function getPathname(): ?string
51+
{
52+
return $this->pathname ?? null;
53+
}
54+
55+
/**
56+
* Flags the source image as damaged which should stop further processing
57+
* of this file.
58+
*/
59+
public function markSourceAsDamaged(): void
60+
{
61+
$this->sourceIsDamaged = true;
62+
}
63+
64+
public function sourceIsMarkedAsDamaged(): bool
65+
{
66+
return $this->sourceIsDamaged;
67+
}
68+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
namespace wcf\event\file;
4+
5+
use BadMethodCallException;
6+
use wcf\data\file\File;
7+
use wcf\event\IPsr14Event;
8+
9+
/**
10+
* Requests the generation of a WebP variant for the provided image.
11+
*
12+
* @author Alexander Ebert
13+
* @copyright 2001-2024 WoltLab GmbH
14+
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
15+
* @since 6.1
16+
*/
17+
final class GenerateWebpVariant implements IPsr14Event
18+
{
19+
private string $pathname;
20+
private bool $sourceIsDamaged = false;
21+
22+
public function __construct(
23+
public readonly File $file
24+
) {}
25+
26+
/**
27+
* Returns true if a file has already been set and no further files are
28+
* being accepted.
29+
*/
30+
public function hasFile(): bool
31+
{
32+
return isset($this->pathname);
33+
}
34+
35+
/**
36+
* Sets the pathname of the generated image unless it has already been set
37+
* in which case the call will throw an exception. You must check the result
38+
* of `hasFile()` first.
39+
*/
40+
public function setGeneratedFile(string $pathname): void
41+
{
42+
if (isset($this->pathname)) {
43+
throw new \BadMethodCallException("Cannot set the generated file, a value has already been set.");
44+
}
45+
46+
$this->pathname = $pathname;
47+
}
48+
49+
public function getPathname(): ?string
50+
{
51+
return $this->pathname ?? null;
52+
}
53+
54+
/**
55+
* Flags the source image as damaged which should stop further processing
56+
* of this file.
57+
*/
58+
public function markSourceAsDamaged(): void
59+
{
60+
$this->sourceIsDamaged = true;
61+
}
62+
63+
public function sourceIsMarkedAsDamaged(): bool
64+
{
65+
return $this->sourceIsDamaged;
66+
}
67+
}

wcfsetup/install/files/lib/system/file/processor/FileProcessor.class.php

Lines changed: 62 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99
use wcf\data\file\thumbnail\FileThumbnailList;
1010
use wcf\data\object\type\ObjectType;
1111
use wcf\data\object\type\ObjectTypeCache;
12+
use wcf\event\file\GenerateThumbnail;
13+
use wcf\event\file\GenerateWebpVariant;
1214
use wcf\system\database\util\PreparedStatementConditionBuilder;
15+
use wcf\system\event\EventHandler;
1316
use wcf\system\exception\SystemException;
1417
use wcf\system\file\processor\exception\DamagedImage;
1518
use wcf\system\image\adapter\exception\ImageNotProcessable;
@@ -159,32 +162,41 @@ public function generateWebpVariant(File $file): void
159162
}
160163
}
161164

162-
$imageAdapter = ImageHandler::getInstance()->getAdapter();
163-
if (!$imageAdapter->checkMemoryLimit($file->width, $file->height, $file->mimeType)) {
164-
return;
165-
}
166-
167-
try {
168-
$imageAdapter->loadSingleFrameFromFile($file->getPathname());
169-
} catch (SystemException | ImageNotReadable) {
165+
$event = new GenerateWebpVariant($file);
166+
EventHandler::getInstance()->fire($event);
167+
if ($event->sourceIsMarkedAsDamaged()) {
170168
throw new DamagedImage($file->fileID);
171-
} catch (ImageNotProcessable $e) {
172-
logThrowable($e);
173-
174-
return;
175169
}
176170

177-
$filename = FileUtil::getTemporaryFilename(extension: 'webp');
171+
$filename = $event->getPathname();
172+
if ($filename === null) {
173+
$imageAdapter = ImageHandler::getInstance()->getAdapter();
174+
if (!$imageAdapter->checkMemoryLimit($file->width, $file->height, $file->mimeType)) {
175+
return;
176+
}
178177

179-
try {
180-
$imageAdapter->saveImageAs($imageAdapter->getImage(), $filename, 'webp', 80);
181-
} catch (\Throwable $e) {
182-
// Ignore any errors trying to save the file unless in debug mode.
183-
if (\ENABLE_DEBUG_MODE) {
184-
throw $e;
178+
try {
179+
$imageAdapter->loadSingleFrameFromFile($file->getPathname());
180+
} catch (SystemException | ImageNotReadable) {
181+
throw new DamagedImage($file->fileID);
182+
} catch (ImageNotProcessable $e) {
183+
logThrowable($e);
184+
185+
return;
185186
}
186187

187-
return;
188+
$filename = FileUtil::getTemporaryFilename(extension: 'webp');
189+
190+
try {
191+
$imageAdapter->saveImageAs($imageAdapter->getImage(), $filename, 'webp', 80);
192+
} catch (\Throwable $e) {
193+
// Ignore any errors trying to save the file unless in debug mode.
194+
if (\ENABLE_DEBUG_MODE) {
195+
throw $e;
196+
}
197+
198+
return;
199+
}
188200
}
189201

190202
(new FileEditor($file))->update([
@@ -251,36 +263,45 @@ public function generateThumbnails(File $file): void
251263
}
252264
}
253265

254-
if ($imageAdapter === null) {
255-
$imageAdapter = ImageHandler::getInstance()->getAdapter();
256-
if (!$imageAdapter->checkMemoryLimit($file->width, $file->height, $file->mimeType)) {
257-
return;
266+
$event = new GenerateThumbnail($file, $format);
267+
EventHandler::getInstance()->fire($event);
268+
if ($event->sourceIsMarkedAsDamaged()) {
269+
throw new DamagedImage($file->fileID);
270+
}
271+
272+
$filename = $event->getPathname();
273+
if ($filename === null) {
274+
if ($imageAdapter === null) {
275+
$imageAdapter = ImageHandler::getInstance()->getAdapter();
276+
if (!$imageAdapter->checkMemoryLimit($file->width, $file->height, $file->mimeType)) {
277+
return;
278+
}
279+
280+
try {
281+
$imageAdapter->loadSingleFrameFromFile($file->getPathname());
282+
} catch (SystemException | ImageNotReadable $e) {
283+
throw new DamagedImage($file->fileID, $e);
284+
} catch (ImageNotProcessable $e) {
285+
logThrowable($e);
286+
287+
return;
288+
}
258289
}
259290

291+
\assert($imageAdapter instanceof ImageAdapter);
292+
260293
try {
261-
$imageAdapter->loadSingleFrameFromFile($file->getPathname());
262-
} catch (SystemException | ImageNotReadable $e) {
263-
throw new DamagedImage($file->fileID, $e);
264-
} catch (ImageNotProcessable $e) {
294+
$image = $imageAdapter->createThumbnail($format->width, $format->height, $format->retainDimensions);
295+
} catch (\Throwable $e) {
265296
logThrowable($e);
266297

267-
return;
298+
continue;
268299
}
269-
}
270300

271-
\assert($imageAdapter instanceof ImageAdapter);
272-
273-
try {
274-
$image = $imageAdapter->createThumbnail($format->width, $format->height, $format->retainDimensions);
275-
} catch (\Throwable $e) {
276-
logThrowable($e);
277-
278-
continue;
301+
$filename = FileUtil::getTemporaryFilename(extension: 'webp');
302+
$imageAdapter->saveImageAs($image, $filename, 'webp', 80);
279303
}
280304

281-
$filename = FileUtil::getTemporaryFilename(extension: 'webp');
282-
$imageAdapter->saveImageAs($image, $filename, 'webp', 80);
283-
284305
$fileThumbnail = FileThumbnailEditor::createFromTemporaryFile($file, $format, $filename);
285306
$processor->adoptThumbnail($fileThumbnail);
286307
}

0 commit comments

Comments
 (0)