Skip to content

Commit 5cda8f0

Browse files
authored
Merge pull request #35129 from Glandos/previewperf2
[Performance] Reuse preview directory listing
2 parents d87cc20 + e542e60 commit 5cda8f0

File tree

2 files changed

+54
-105
lines changed

2 files changed

+54
-105
lines changed

lib/private/Preview/Generator.php

Lines changed: 47 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ public function generatePreviews(File $file, array $specifications, $mimeType =
137137
}
138138

139139
$previewFolder = $this->getPreviewFolder($file);
140+
// List every existing preview first instead of trying to find them one by one
141+
$previewFiles = $previewFolder->getDirectoryListing();
140142

141143
$previewVersion = '';
142144
if ($file instanceof IVersionedPreviewFile) {
@@ -150,7 +152,7 @@ public function generatePreviews(File $file, array $specifications, $mimeType =
150152
&& preg_match(Imaginary::supportedMimeTypes(), $mimeType)
151153
&& $this->config->getSystemValueString('preview_imaginary_url', 'invalid') !== 'invalid') {
152154
$crop = $specifications[0]['crop'] ?? false;
153-
$preview = $this->getSmallImagePreview($previewFolder, $file, $mimeType, $previewVersion, $crop);
155+
$preview = $this->getSmallImagePreview($previewFolder, $previewFiles, $file, $mimeType, $previewVersion, $crop);
154156

155157
if ($preview->getSize() === 0) {
156158
$preview->delete();
@@ -161,7 +163,7 @@ public function generatePreviews(File $file, array $specifications, $mimeType =
161163
}
162164

163165
// Get the max preview and infer the max preview sizes from that
164-
$maxPreview = $this->getMaxPreview($previewFolder, $file, $mimeType, $previewVersion);
166+
$maxPreview = $this->getMaxPreview($previewFolder, $previewFiles, $file, $mimeType, $previewVersion);
165167
$maxPreviewImage = null; // only load the image when we need it
166168
if ($maxPreview->getSize() === 0) {
167169
$maxPreview->delete();
@@ -197,7 +199,7 @@ public function generatePreviews(File $file, array $specifications, $mimeType =
197199
// Try to get a cached preview. Else generate (and store) one
198200
try {
199201
try {
200-
$preview = $this->getCachedPreview($previewFolder, $width, $height, $crop, $maxPreview->getMimeType(), $previewVersion);
202+
$preview = $this->getCachedPreview($previewFiles, $width, $height, $crop, $maxPreview->getMimeType(), $previewVersion);
201203
} catch (NotFoundException $e) {
202204
if (!$this->previewManager->isMimeSupported($mimeType)) {
203205
throw new NotFoundException();
@@ -208,6 +210,8 @@ public function generatePreviews(File $file, array $specifications, $mimeType =
208210
}
209211

210212
$preview = $this->generatePreview($previewFolder, $maxPreviewImage, $width, $height, $crop, $maxWidth, $maxHeight, $previewVersion);
213+
// New file, augment our array
214+
$previewFiles[] = $preview;
211215
}
212216
} catch (\InvalidArgumentException $e) {
213217
throw new NotFoundException("", 0, $e);
@@ -233,75 +237,19 @@ public function generatePreviews(File $file, array $specifications, $mimeType =
233237
* Generate a small image straight away without generating a max preview first
234238
* Preview generated is 256x256
235239
*
240+
* @param ISimpleFile[] $previewFiles
241+
*
236242
* @throws NotFoundException
237243
*/
238-
private function getSmallImagePreview(ISimpleFolder $previewFolder, File $file, string $mimeType, string $prefix, bool $crop): ISimpleFile {
239-
$nodes = $previewFolder->getDirectoryListing();
240-
241-
foreach ($nodes as $node) {
242-
$name = $node->getName();
243-
if (($prefix === '' || str_starts_with($name, $prefix))) {
244-
// Prefix match
245-
if (str_starts_with($name, $prefix . '256-256-crop') && $crop) {
246-
// Cropped image
247-
return $node;
248-
}
249-
250-
if (str_starts_with($name, $prefix . '256-256.') && !$crop) {
251-
// Uncropped image
252-
return $node;
253-
}
254-
}
255-
}
256-
257-
$previewProviders = $this->previewManager->getProviders();
258-
foreach ($previewProviders as $supportedMimeType => $providers) {
259-
// Filter out providers that does not support this mime
260-
if (!preg_match($supportedMimeType, $mimeType)) {
261-
continue;
262-
}
263-
264-
foreach ($providers as $providerClosure) {
265-
$provider = $this->helper->getProvider($providerClosure);
266-
if (!($provider instanceof IProviderV2)) {
267-
continue;
268-
}
269-
270-
if (!$provider->isAvailable($file)) {
271-
continue;
272-
}
273-
274-
$preview = $this->helper->getThumbnail($provider, $file, 256, 256, $crop);
275-
276-
if (!($preview instanceof IImage)) {
277-
continue;
278-
}
279-
280-
// Try to get the extension.
281-
try {
282-
$ext = $this->getExtention($preview->dataMimeType());
283-
} catch (\InvalidArgumentException $e) {
284-
// Just continue to the next iteration if this preview doesn't have a valid mimetype
285-
continue;
286-
}
287-
288-
$path = $this->generatePath(256, 256, $crop, $preview->dataMimeType(), $prefix);
289-
try {
290-
$file = $previewFolder->newFile($path);
291-
if ($preview instanceof IStreamImage) {
292-
$file->putContent($preview->resource());
293-
} else {
294-
$file->putContent($preview->data());
295-
}
296-
} catch (NotPermittedException $e) {
297-
throw new NotFoundException();
298-
}
244+
private function getSmallImagePreview(ISimpleFolder $previewFolder, array $previewFiles, File $file, string $mimeType, string $prefix, bool $crop): ISimpleFile {
245+
$width = 256;
246+
$height = 256;
299247

300-
return $file;
301-
}
248+
try {
249+
return $this->getCachedPreview($previewFiles, $width, $height, $crop, $mimeType, $prefix);
250+
} catch (NotFoundException $e) {
251+
return $this->generateProviderPreview($previewFolder, $file, $width, $height, $crop, false, $mimeType, $prefix);
302252
}
303-
304-
throw new NotFoundException('No provider successfully handled the preview generation');
305253
}
306254

307255
/**
@@ -398,22 +346,30 @@ public function getNumConcurrentPreviews(string $type): int {
398346

399347
/**
400348
* @param ISimpleFolder $previewFolder
349+
* @param ISimpleFile[] $previewFiles
401350
* @param File $file
402351
* @param string $mimeType
403352
* @param string $prefix
404353
* @return ISimpleFile
405354
* @throws NotFoundException
406355
*/
407-
private function getMaxPreview(ISimpleFolder $previewFolder, File $file, $mimeType, $prefix) {
408-
$nodes = $previewFolder->getDirectoryListing();
409-
410-
foreach ($nodes as $node) {
356+
private function getMaxPreview(ISimpleFolder $previewFolder, array $previewFiles, File $file, $mimeType, $prefix) {
357+
// We don't know the max preview size, so we can't use getCachedPreview.
358+
// It might have been generated with a higher resolution than the current value.
359+
foreach ($previewFiles as $node) {
411360
$name = $node->getName();
412361
if (($prefix === '' || strpos($name, $prefix) === 0) && strpos($name, 'max')) {
413362
return $node;
414363
}
415364
}
416365

366+
$maxWidth = $this->config->getSystemValueInt('preview_max_x', 4096);
367+
$maxHeight = $this->config->getSystemValueInt('preview_max_y', 4096);
368+
369+
return $this->generateProviderPreview($previewFolder, $file, $maxWidth, $maxHeight, false, true, $mimeType, $prefix);
370+
}
371+
372+
private function generateProviderPreview(ISimpleFolder $previewFolder, File $file, int $width, int $height, bool $crop, bool $max, string $mimeType, string $prefix) {
417373
$previewProviders = $this->previewManager->getProviders();
418374
foreach ($previewProviders as $supportedMimeType => $providers) {
419375
// Filter out providers that does not support this mime
@@ -431,13 +387,10 @@ private function getMaxPreview(ISimpleFolder $previewFolder, File $file, $mimeTy
431387
continue;
432388
}
433389

434-
$maxWidth = $this->config->getSystemValueInt('preview_max_x', 4096);
435-
$maxHeight = $this->config->getSystemValueInt('preview_max_y', 4096);
436-
437390
$previewConcurrency = $this->getNumConcurrentPreviews('preview_concurrency_new');
438391
$sem = self::guardWithSemaphore(self::SEMAPHORE_ID_NEW, $previewConcurrency);
439392
try {
440-
$preview = $this->helper->getThumbnail($provider, $file, $maxWidth, $maxHeight);
393+
$preview = $this->helper->getThumbnail($provider, $file, $width, $height);
441394
} finally {
442395
self::unguardWithSemaphore($sem);
443396
}
@@ -446,15 +399,7 @@ private function getMaxPreview(ISimpleFolder $previewFolder, File $file, $mimeTy
446399
continue;
447400
}
448401

449-
// Try to get the extention.
450-
try {
451-
$ext = $this->getExtention($preview->dataMimeType());
452-
} catch (\InvalidArgumentException $e) {
453-
// Just continue to the next iteration if this preview doesn't have a valid mimetype
454-
continue;
455-
}
456-
457-
$path = $prefix . (string)$preview->width() . '-' . (string)$preview->height() . '-max.' . $ext;
402+
$path = $this->generatePath($preview->width(), $preview->height(), $crop, $max, $preview->dataMimeType(), $prefix);
458403
try {
459404
$file = $previewFolder->newFile($path);
460405
if ($preview instanceof IStreamImage) {
@@ -470,7 +415,7 @@ private function getMaxPreview(ISimpleFolder $previewFolder, File $file, $mimeTy
470415
}
471416
}
472417

473-
throw new NotFoundException();
418+
throw new NotFoundException('No provider successfully handled the preview generation');
474419
}
475420

476421
/**
@@ -487,15 +432,19 @@ private function getPreviewSize(ISimpleFile $file, string $prefix = '') {
487432
* @param int $width
488433
* @param int $height
489434
* @param bool $crop
435+
* @param bool $max
490436
* @param string $mimeType
491437
* @param string $prefix
492438
* @return string
493439
*/
494-
private function generatePath($width, $height, $crop, $mimeType, $prefix) {
440+
private function generatePath($width, $height, $crop, $max, $mimeType, $prefix) {
495441
$path = $prefix . (string)$width . '-' . (string)$height;
496442
if ($crop) {
497443
$path .= '-crop';
498444
}
445+
if ($max) {
446+
$path .= '-max';
447+
}
499448

500449
$ext = $this->getExtention($mimeType);
501450
$path .= '.' . $ext;
@@ -637,7 +586,8 @@ private function generatePreview(ISimpleFolder $previewFolder, IImage $maxPrevie
637586
self::unguardWithSemaphore($sem);
638587
}
639588

640-
$path = $this->generatePath($width, $height, $crop, $preview->dataMimeType(), $prefix);
589+
590+
$path = $this->generatePath($width, $height, $crop, false, $preview->dataMimeType(), $prefix);
641591
try {
642592
$file = $previewFolder->newFile($path);
643593
$file->putContent($preview->data());
@@ -649,7 +599,7 @@ private function generatePreview(ISimpleFolder $previewFolder, IImage $maxPrevie
649599
}
650600

651601
/**
652-
* @param ISimpleFolder $previewFolder
602+
* @param ISimpleFile[] $files Array of FileInfo, as the result of getDirectoryListing()
653603
* @param int $width
654604
* @param int $height
655605
* @param bool $crop
@@ -659,10 +609,14 @@ private function generatePreview(ISimpleFolder $previewFolder, IImage $maxPrevie
659609
*
660610
* @throws NotFoundException
661611
*/
662-
private function getCachedPreview(ISimpleFolder $previewFolder, $width, $height, $crop, $mimeType, $prefix) {
663-
$path = $this->generatePath($width, $height, $crop, $mimeType, $prefix);
664-
665-
return $previewFolder->getFile($path);
612+
private function getCachedPreview($files, $width, $height, $crop, $mimeType, $prefix) {
613+
$path = $this->generatePath($width, $height, $crop, false, $mimeType, $prefix);
614+
foreach ($files as $file) {
615+
if ($file->getName() === $path) {
616+
return $file;
617+
}
618+
}
619+
throw new NotFoundException();
666620
}
667621

668622
/**

tests/lib/Preview/GeneratorTest.php

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -105,15 +105,12 @@ public function testGetCachedPreview() {
105105
$maxPreview->method('getMimeType')
106106
->willReturn('image/png');
107107

108-
$previewFolder->method('getDirectoryListing')
109-
->willReturn([$maxPreview]);
110-
111108
$previewFile = $this->createMock(ISimpleFile::class);
112109
$previewFile->method('getSize')->willReturn(1000);
110+
$previewFile->method('getName')->willReturn('256-256.png');
113111

114-
$previewFolder->method('getFile')
115-
->with($this->equalTo('256-256.png'))
116-
->willReturn($previewFile);
112+
$previewFolder->method('getDirectoryListing')
113+
->willReturn([$maxPreview, $previewFile]);
117114

118115
$this->legacyEventDispatcher->expects($this->once())
119116
->method('dispatch')
@@ -344,14 +341,12 @@ public function testReturnCachedPreviewsWithoutCheckingSupportedMimetype() {
344341
$maxPreview->method('getMimeType')
345342
->willReturn('image/png');
346343

347-
$previewFolder->method('getDirectoryListing')
348-
->willReturn([$maxPreview]);
349-
350344
$preview = $this->createMock(ISimpleFile::class);
351345
$preview->method('getSize')->willReturn(1000);
352-
$previewFolder->method('getFile')
353-
->with($this->equalTo('1024-512-crop.png'))
354-
->willReturn($preview);
346+
$preview->method('getName')->willReturn('1024-512-crop.png');
347+
348+
$previewFolder->method('getDirectoryListing')
349+
->willReturn([$maxPreview, $preview]);
355350

356351
$this->previewManager->expects($this->never())
357352
->method('isMimeSupported');

0 commit comments

Comments
 (0)