|
27 | 27 | use OCP\IPreview; |
28 | 28 | use OCP\IStreamImage; |
29 | 29 | use OCP\Preview\BeforePreviewFetchedEvent; |
| 30 | +use OCP\Preview\IProviderV2; |
30 | 31 | use OCP\Preview\IVersionedPreviewFile; |
31 | 32 | use Psr\Log\LoggerInterface; |
32 | 33 |
|
@@ -182,13 +183,10 @@ public function generatePreviews(File $file, array $specifications, ?string $mim |
182 | 183 | } |
183 | 184 |
|
184 | 185 | $this->logger->debug('Cached preview not found for file {path}, generating a new preview.', ['path' => $file->getPath()]); |
185 | | - $sourceImage = $maxPreviewImage === null ? $this->loadSmallerSource($previews, $maxPreview, $width, $height, $previewVersion) : null; |
186 | | - if ($sourceImage !== null) { |
187 | | - $previewFile = $this->generatePreview($file, $sourceImage, $width, $height, $crop, $sourceImage->width(), $sourceImage->height(), $previewVersion, $cacheResult); |
188 | | - if ($sourceImage instanceof Image) { |
189 | | - $sourceImage->destroy(); |
190 | | - } |
191 | | - } else { |
| 186 | + $previewFile = $maxPreviewImage === null |
| 187 | + ? $this->generateFromCheaperSource($file, $previews, $maxPreview, $mimeType, $width, $height, $crop, $previewVersion, $cacheResult) |
| 188 | + : null; |
| 189 | + if ($previewFile === null) { |
192 | 190 | if ($maxPreviewImage === null) { |
193 | 191 | $maxPreviewImage = $this->helper->getImage(new PreviewFile($maxPreview, $this->storageFactory, $this->previewMapper)); |
194 | 192 | } |
@@ -216,23 +214,106 @@ public function generatePreviews(File $file, array $specifications, ?string $mim |
216 | 214 | } |
217 | 215 |
|
218 | 216 | /** |
219 | | - * Load an image smaller than the max preview to resize a preview from: |
220 | | - * the smallest suitable cached preview, or the max preview decoded at a |
221 | | - * reduced size. Both are much cheaper than decoding the full max preview. |
| 217 | + * Generate a preview without decoding the full max preview: from the |
| 218 | + * smallest suitable cached preview, from the original file with a provider |
| 219 | + * that resizes efficiently, or from the max preview decoded at a reduced size. |
222 | 220 | * |
223 | 221 | * @param Preview[] $previews |
| 222 | + * @return ISimpleFile|null null if there is no cheaper source than the max preview |
224 | 223 | */ |
225 | | - private function loadSmallerSource(array $previews, Preview $maxPreview, int $width, int $height, ?string $version): ?IImage { |
| 224 | + private function generateFromCheaperSource(File $file, array $previews, Preview $maxPreview, string $mimeType, int $width, int $height, bool $crop, ?string $version, bool $cacheResult): ?ISimpleFile { |
| 225 | + $sourceImage = null; |
226 | 226 | $source = $this->findResizeSource($previews, $maxPreview, $width, $height, $version); |
227 | 227 | if ($source !== null) { |
228 | | - return $this->helper->getImage(new PreviewFile($source, $this->storageFactory, $this->previewMapper)); |
| 228 | + $sourceImage = $this->helper->getImage(new PreviewFile($source, $this->storageFactory, $this->previewMapper)); |
| 229 | + } else { |
| 230 | + if ($cacheResult) { |
| 231 | + $previewFile = $this->generateWithResizingProvider($file, $maxPreview, $mimeType, $width, $height, $crop, $version); |
| 232 | + if ($previewFile !== null) { |
| 233 | + return $previewFile; |
| 234 | + } |
| 235 | + } |
| 236 | + |
| 237 | + // Decoding at a reduced size only pays off for previews much smaller than the max preview |
| 238 | + if (8 * $width <= $maxPreview->getWidth() && 8 * $height <= $maxPreview->getHeight()) { |
| 239 | + $sourceImage = $this->helper->getScaledImage(new PreviewFile($maxPreview, $this->storageFactory, $this->previewMapper), $width, $height); |
| 240 | + } |
229 | 241 | } |
230 | 242 |
|
231 | | - // Decoding at a reduced size only pays off for previews much smaller than the max preview |
232 | | - if (8 * $width <= $maxPreview->getWidth() && 8 * $height <= $maxPreview->getHeight()) { |
233 | | - return $this->helper->getScaledImage(new PreviewFile($maxPreview, $this->storageFactory, $this->previewMapper), $width, $height); |
| 243 | + if ($sourceImage === null) { |
| 244 | + return null; |
| 245 | + } |
| 246 | + try { |
| 247 | + return $this->generatePreview($file, $sourceImage, $width, $height, $crop, $sourceImage->width(), $sourceImage->height(), $version, $cacheResult); |
| 248 | + } finally { |
| 249 | + if ($sourceImage instanceof Image) { |
| 250 | + $sourceImage->destroy(); |
| 251 | + } |
234 | 252 | } |
235 | | - return null; |
| 253 | + } |
| 254 | + |
| 255 | + /** |
| 256 | + * Let the provider generate the preview from the original file, if it can |
| 257 | + * resize efficiently like Imaginary. Only the provider that generates the |
| 258 | + * max preview is considered, so that all previews of a file look the same. |
| 259 | + */ |
| 260 | + private function generateWithResizingProvider(File $file, Preview $maxPreview, string $mimeType, int $width, int $height, bool $crop, ?string $version): ?ISimpleFile { |
| 261 | + $provider = $this->getAvailableProviders($file, $mimeType)->current(); |
| 262 | + if ($provider === null || !$this->helper->resizesEfficiently($provider)) { |
| 263 | + return null; |
| 264 | + } |
| 265 | + |
| 266 | + $previewConcurrency = $this->getNumConcurrentPreviews('preview_concurrency_new'); |
| 267 | + $sem = self::guardWithSemaphore(self::SEMAPHORE_ID_NEW, $previewConcurrency); |
| 268 | + try { |
| 269 | + $preview = $this->helper->getThumbnail($provider, $file, $width, $height, $crop); |
| 270 | + } finally { |
| 271 | + self::unguardWithSemaphore($sem); |
| 272 | + } |
| 273 | + |
| 274 | + // A different format would not match the lookup of cached previews |
| 275 | + if (!($preview instanceof IImage) || $preview->dataMimeType() !== $maxPreview->getMimetype()) { |
| 276 | + return null; |
| 277 | + } |
| 278 | + |
| 279 | + $previewEntry = $this->createPreviewEntry($file, $width, $height, false, $crop, $preview->dataMimeType(), $version); |
| 280 | + return new PreviewFile($this->savePreview($previewEntry, $preview), $this->storageFactory, $this->previewMapper); |
| 281 | + } |
| 282 | + |
| 283 | + /** |
| 284 | + * @return \Generator<IProviderV2> the available providers for the mime type, by priority |
| 285 | + */ |
| 286 | + private function getAvailableProviders(File $file, string $mimeType): \Generator { |
| 287 | + foreach ($this->previewManager->getProviders() as $supportedMimeType => $providers) { |
| 288 | + if (!preg_match($supportedMimeType, $mimeType)) { |
| 289 | + continue; |
| 290 | + } |
| 291 | + |
| 292 | + foreach ($providers as $providerClosure) { |
| 293 | + $provider = $this->helper->getProvider($providerClosure); |
| 294 | + if ($provider && $provider->isAvailable($file)) { |
| 295 | + yield $provider; |
| 296 | + } |
| 297 | + } |
| 298 | + } |
| 299 | + } |
| 300 | + |
| 301 | + private function createPreviewEntry(File $file, int $width, int $height, bool $max, bool $crop, ?string $mimeType, ?string $version): Preview { |
| 302 | + $previewEntry = new Preview(); |
| 303 | + $previewEntry->generateId(); |
| 304 | + $previewEntry->setFileId($file->getId()); |
| 305 | + $previewEntry->setStorageId($file->getMountPoint()->getNumericStorageId()); |
| 306 | + $previewEntry->setSourceMimeType($file->getMimeType()); |
| 307 | + $previewEntry->setWidth($width); |
| 308 | + $previewEntry->setHeight($height); |
| 309 | + $previewEntry->setVersion($version); |
| 310 | + $previewEntry->setMax($max); |
| 311 | + $previewEntry->setCropped($crop); |
| 312 | + $previewEntry->setEncrypted(false); |
| 313 | + $previewEntry->setMimetype($mimeType); |
| 314 | + $previewEntry->setEtag($file->getEtag()); |
| 315 | + $previewEntry->setMtime((new \DateTime())->getTimestamp()); |
| 316 | + return $previewEntry; |
236 | 317 | } |
237 | 318 |
|
238 | 319 | /** |
@@ -405,60 +486,29 @@ private function getMaxPreview(array $previews, File $file, string $mimeType, ?s |
405 | 486 | * @throws NotFoundException |
406 | 487 | */ |
407 | 488 | private function generateProviderPreview(File $file, int $width, int $height, bool $crop, bool $max, string $mimeType, ?string $version): array { |
408 | | - $previewProviders = $this->previewManager->getProviders(); |
409 | | - foreach ($previewProviders as $supportedMimeType => $providers) { |
410 | | - // Filter out providers that does not support this mime |
411 | | - if (!preg_match($supportedMimeType, $mimeType)) { |
412 | | - continue; |
| 489 | + foreach ($this->getAvailableProviders($file, $mimeType) as $provider) { |
| 490 | + $previewConcurrency = $this->getNumConcurrentPreviews('preview_concurrency_new'); |
| 491 | + $sem = self::guardWithSemaphore(self::SEMAPHORE_ID_NEW, $previewConcurrency); |
| 492 | + try { |
| 493 | + $this->logger->debug('Calling preview provider for {mimeType} with width={width}, height={height}', [ |
| 494 | + 'mimeType' => $mimeType, |
| 495 | + 'width' => $width, |
| 496 | + 'height' => $height, |
| 497 | + ]); |
| 498 | + $preview = $this->helper->getThumbnail($provider, $file, $width, $height); |
| 499 | + } finally { |
| 500 | + self::unguardWithSemaphore($sem); |
413 | 501 | } |
414 | 502 |
|
415 | | - foreach ($providers as $providerClosure) { |
416 | | - |
417 | | - $provider = $this->helper->getProvider($providerClosure); |
418 | | - if (!$provider) { |
419 | | - continue; |
420 | | - } |
421 | | - |
422 | | - if (!$provider->isAvailable($file)) { |
423 | | - continue; |
424 | | - } |
425 | | - |
426 | | - $previewConcurrency = $this->getNumConcurrentPreviews('preview_concurrency_new'); |
427 | | - $sem = self::guardWithSemaphore(self::SEMAPHORE_ID_NEW, $previewConcurrency); |
428 | | - try { |
429 | | - $this->logger->debug('Calling preview provider for {mimeType} with width={width}, height={height}', [ |
430 | | - 'mimeType' => $mimeType, |
431 | | - 'width' => $width, |
432 | | - 'height' => $height, |
433 | | - ]); |
434 | | - $preview = $this->helper->getThumbnail($provider, $file, $width, $height); |
435 | | - } finally { |
436 | | - self::unguardWithSemaphore($sem); |
437 | | - } |
438 | | - |
439 | | - if (!($preview instanceof IImage)) { |
440 | | - continue; |
441 | | - } |
| 503 | + if (!($preview instanceof IImage)) { |
| 504 | + continue; |
| 505 | + } |
442 | 506 |
|
443 | | - try { |
444 | | - $previewEntry = new Preview(); |
445 | | - $previewEntry->generateId(); |
446 | | - $previewEntry->setFileId($file->getId()); |
447 | | - $previewEntry->setStorageId($file->getMountPoint()->getNumericStorageId()); |
448 | | - $previewEntry->setSourceMimeType($file->getMimeType()); |
449 | | - $previewEntry->setWidth($preview->width()); |
450 | | - $previewEntry->setHeight($preview->height()); |
451 | | - $previewEntry->setVersion($version); |
452 | | - $previewEntry->setMax($max); |
453 | | - $previewEntry->setCropped($crop); |
454 | | - $previewEntry->setEncrypted(false); |
455 | | - $previewEntry->setMimetype($preview->dataMimeType()); |
456 | | - $previewEntry->setEtag($file->getEtag()); |
457 | | - $previewEntry->setMtime((new \DateTime())->getTimestamp()); |
458 | | - return [$this->savePreview($previewEntry, $preview), $preview]; |
459 | | - } catch (NotPermittedException) { |
460 | | - throw new NotFoundException(); |
461 | | - } |
| 507 | + try { |
| 508 | + $previewEntry = $this->createPreviewEntry($file, $preview->width(), $preview->height(), $max, $crop, $preview->dataMimeType(), $version); |
| 509 | + return [$this->savePreview($previewEntry, $preview), $preview]; |
| 510 | + } catch (NotPermittedException) { |
| 511 | + throw new NotFoundException(); |
462 | 512 | } |
463 | 513 | } |
464 | 514 |
|
@@ -599,20 +649,7 @@ private function generatePreview( |
599 | 649 | throw new \InvalidArgumentException('Preview generation failed: invalid or null MIME type'); |
600 | 650 | } |
601 | 651 |
|
602 | | - $previewEntry = new Preview(); |
603 | | - $previewEntry->generateId(); |
604 | | - $previewEntry->setFileId($file->getId()); |
605 | | - $previewEntry->setStorageId($file->getMountPoint()->getNumericStorageId()); |
606 | | - $previewEntry->setWidth($width); |
607 | | - $previewEntry->setSourceMimeType($file->getMimeType()); |
608 | | - $previewEntry->setHeight($height); |
609 | | - $previewEntry->setVersion($version); |
610 | | - $previewEntry->setMax(false); |
611 | | - $previewEntry->setCropped($crop); |
612 | | - $previewEntry->setEncrypted(false); |
613 | | - $previewEntry->setMimeType($preview->dataMimeType()); |
614 | | - $previewEntry->setEtag($file->getEtag()); |
615 | | - $previewEntry->setMtime((new \DateTime())->getTimestamp()); |
| 652 | + $previewEntry = $this->createPreviewEntry($file, $width, $height, false, $crop, $preview->dataMimeType(), $version); |
616 | 653 |
|
617 | 654 | if ($cacheResult) { |
618 | 655 | $previewEntry = $this->savePreview($previewEntry, $preview); |
|
0 commit comments