Completed
Push — master ( 9f6b2b...25bc18 )
by
unknown
17:55 queued 16s
created
lib/private/PreviewManager.php 1 patch
Indentation   +441 added lines, -441 removed lines patch added patch discarded remove patch
@@ -28,445 +28,445 @@
 block discarded – undo
28 28
 use function array_key_exists;
29 29
 
30 30
 class PreviewManager implements IPreview {
31
-	protected IConfig $config;
32
-	protected IRootFolder $rootFolder;
33
-	protected IAppData $appData;
34
-	protected IEventDispatcher $eventDispatcher;
35
-	private ?Generator $generator = null;
36
-	private GeneratorHelper $helper;
37
-	protected bool $providerListDirty = false;
38
-	protected bool $registeredCoreProviders = false;
39
-	protected array $providers = [];
40
-
41
-	/** @var array mime type => support status */
42
-	protected array $mimeTypeSupportMap = [];
43
-	protected ?array $defaultProviders = null;
44
-	protected ?string $userId;
45
-	private Coordinator $bootstrapCoordinator;
46
-
47
-	/**
48
-	 * Hash map (without value) of loaded bootstrap providers
49
-	 * @psalm-var array<string, null>
50
-	 */
51
-	private array $loadedBootstrapProviders = [];
52
-	private ContainerInterface $container;
53
-	private IBinaryFinder $binaryFinder;
54
-	private IMagickSupport $imagickSupport;
55
-	private bool $enablePreviews;
56
-
57
-	public function __construct(
58
-		IConfig $config,
59
-		IRootFolder $rootFolder,
60
-		IAppData $appData,
61
-		IEventDispatcher $eventDispatcher,
62
-		GeneratorHelper $helper,
63
-		?string $userId,
64
-		Coordinator $bootstrapCoordinator,
65
-		ContainerInterface $container,
66
-		IBinaryFinder $binaryFinder,
67
-		IMagickSupport $imagickSupport,
68
-	) {
69
-		$this->config = $config;
70
-		$this->rootFolder = $rootFolder;
71
-		$this->appData = $appData;
72
-		$this->eventDispatcher = $eventDispatcher;
73
-		$this->helper = $helper;
74
-		$this->userId = $userId;
75
-		$this->bootstrapCoordinator = $bootstrapCoordinator;
76
-		$this->container = $container;
77
-		$this->binaryFinder = $binaryFinder;
78
-		$this->imagickSupport = $imagickSupport;
79
-		$this->enablePreviews = $config->getSystemValueBool('enable_previews', true);
80
-	}
81
-
82
-	/**
83
-	 * In order to improve lazy loading a closure can be registered which will be
84
-	 * called in case preview providers are actually requested
85
-	 *
86
-	 * $callable has to return an instance of \OCP\Preview\IProvider or \OCP\Preview\IProviderV2
87
-	 *
88
-	 * @param string $mimeTypeRegex Regex with the mime types that are supported by this provider
89
-	 * @param \Closure $callable
90
-	 * @return void
91
-	 */
92
-	public function registerProvider($mimeTypeRegex, \Closure $callable): void {
93
-		if (!$this->enablePreviews) {
94
-			return;
95
-		}
96
-
97
-		if (!isset($this->providers[$mimeTypeRegex])) {
98
-			$this->providers[$mimeTypeRegex] = [];
99
-		}
100
-		$this->providers[$mimeTypeRegex][] = $callable;
101
-		$this->providerListDirty = true;
102
-	}
103
-
104
-	/**
105
-	 * Get all providers
106
-	 */
107
-	public function getProviders(): array {
108
-		if (!$this->enablePreviews) {
109
-			return [];
110
-		}
111
-
112
-		$this->registerCoreProviders();
113
-		$this->registerBootstrapProviders();
114
-		if ($this->providerListDirty) {
115
-			$keys = array_map('strlen', array_keys($this->providers));
116
-			array_multisort($keys, SORT_DESC, $this->providers);
117
-			$this->providerListDirty = false;
118
-		}
119
-
120
-		return $this->providers;
121
-	}
122
-
123
-	/**
124
-	 * Does the manager have any providers
125
-	 */
126
-	public function hasProviders(): bool {
127
-		$this->registerCoreProviders();
128
-		return !empty($this->providers);
129
-	}
130
-
131
-	private function getGenerator(): Generator {
132
-		if ($this->generator === null) {
133
-			$this->generator = new Generator(
134
-				$this->config,
135
-				$this,
136
-				$this->appData,
137
-				new GeneratorHelper(
138
-					$this->rootFolder,
139
-					$this->config
140
-				),
141
-				$this->eventDispatcher,
142
-				$this->container->get(LoggerInterface::class),
143
-			);
144
-		}
145
-		return $this->generator;
146
-	}
147
-
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) {
166
-		$this->throwIfPreviewsDisabled();
167
-		$previewConcurrency = $this->getGenerator()->getNumConcurrentPreviews('preview_concurrency_all');
168
-		$sem = Generator::guardWithSemaphore(Generator::SEMAPHORE_ID_ALL, $previewConcurrency);
169
-		try {
170
-			$preview = $this->getGenerator()->getPreview($file, $width, $height, $crop, $mode, $mimeType);
171
-		} finally {
172
-			Generator::unguardWithSemaphore($sem);
173
-		}
174
-
175
-		return $preview;
176
-	}
177
-
178
-	/**
179
-	 * Generates previews of a file
180
-	 *
181
-	 * @param File $file
182
-	 * @param array $specifications
183
-	 * @param string $mimeType
184
-	 * @return ISimpleFile the last preview that was generated
185
-	 * @throws NotFoundException
186
-	 * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
187
-	 * @since 19.0.0
188
-	 */
189
-	public function generatePreviews(File $file, array $specifications, $mimeType = null) {
190
-		$this->throwIfPreviewsDisabled();
191
-		return $this->getGenerator()->generatePreviews($file, $specifications, $mimeType);
192
-	}
193
-
194
-	/**
195
-	 * returns true if the passed mime type is supported
196
-	 *
197
-	 * @param string $mimeType
198
-	 * @return boolean
199
-	 */
200
-	public function isMimeSupported($mimeType = '*') {
201
-		if (!$this->enablePreviews) {
202
-			return false;
203
-		}
204
-
205
-		if (isset($this->mimeTypeSupportMap[$mimeType])) {
206
-			return $this->mimeTypeSupportMap[$mimeType];
207
-		}
208
-
209
-		$this->registerCoreProviders();
210
-		$this->registerBootstrapProviders();
211
-		$providerMimeTypes = array_keys($this->providers);
212
-		foreach ($providerMimeTypes as $supportedMimeType) {
213
-			if (preg_match($supportedMimeType, $mimeType)) {
214
-				$this->mimeTypeSupportMap[$mimeType] = true;
215
-				return true;
216
-			}
217
-		}
218
-		$this->mimeTypeSupportMap[$mimeType] = false;
219
-		return false;
220
-	}
221
-
222
-	/**
223
-	 * Check if a preview can be generated for a file
224
-	 */
225
-	public function isAvailable(\OCP\Files\FileInfo $file): bool {
226
-		if (!$this->enablePreviews) {
227
-			return false;
228
-		}
229
-
230
-		$this->registerCoreProviders();
231
-		if (!$this->isMimeSupported($file->getMimetype())) {
232
-			return false;
233
-		}
234
-
235
-		$mount = $file->getMountPoint();
236
-		if ($mount and !$mount->getOption('previews', true)) {
237
-			return false;
238
-		}
239
-
240
-		foreach ($this->providers as $supportedMimeType => $providers) {
241
-			if (preg_match($supportedMimeType, $file->getMimetype())) {
242
-				foreach ($providers as $providerClosure) {
243
-					$provider = $this->helper->getProvider($providerClosure);
244
-					if (!($provider instanceof IProviderV2)) {
245
-						continue;
246
-					}
247
-
248
-					if ($provider->isAvailable($file)) {
249
-						return true;
250
-					}
251
-				}
252
-			}
253
-		}
254
-		return false;
255
-	}
256
-
257
-	/**
258
-	 * List of enabled default providers
259
-	 *
260
-	 * The following providers are enabled by default:
261
-	 *  - OC\Preview\PNG
262
-	 *  - OC\Preview\JPEG
263
-	 *  - OC\Preview\GIF
264
-	 *  - OC\Preview\BMP
265
-	 *  - OC\Preview\XBitmap
266
-	 *  - OC\Preview\MarkDown
267
-	 *  - OC\Preview\MP3
268
-	 *  - OC\Preview\TXT
269
-	 *
270
-	 * The following providers are disabled by default due to performance or privacy concerns:
271
-	 *  - OC\Preview\Font
272
-	 *  - OC\Preview\HEIC
273
-	 *  - OC\Preview\Illustrator
274
-	 *  - OC\Preview\Movie
275
-	 *  - OC\Preview\MSOfficeDoc
276
-	 *  - OC\Preview\MSOffice2003
277
-	 *  - OC\Preview\MSOffice2007
278
-	 *  - OC\Preview\OpenDocument
279
-	 *  - OC\Preview\PDF
280
-	 *  - OC\Preview\Photoshop
281
-	 *  - OC\Preview\Postscript
282
-	 *  - OC\Preview\StarOffice
283
-	 *  - OC\Preview\SVG
284
-	 *  - OC\Preview\TIFF
285
-	 *
286
-	 * @return array
287
-	 */
288
-	protected function getEnabledDefaultProvider() {
289
-		if ($this->defaultProviders !== null) {
290
-			return $this->defaultProviders;
291
-		}
292
-
293
-		$imageProviders = [
294
-			Preview\PNG::class,
295
-			Preview\JPEG::class,
296
-			Preview\GIF::class,
297
-			Preview\BMP::class,
298
-			Preview\XBitmap::class,
299
-			Preview\Krita::class,
300
-			Preview\WebP::class,
301
-		];
302
-
303
-		$this->defaultProviders = $this->config->getSystemValue('enabledPreviewProviders', array_merge([
304
-			Preview\MarkDown::class,
305
-			Preview\MP3::class,
306
-			Preview\TXT::class,
307
-			Preview\OpenDocument::class,
308
-		], $imageProviders));
309
-
310
-		if (in_array(Preview\Image::class, $this->defaultProviders)) {
311
-			$this->defaultProviders = array_merge($this->defaultProviders, $imageProviders);
312
-		}
313
-		$this->defaultProviders = array_unique($this->defaultProviders);
314
-		return $this->defaultProviders;
315
-	}
316
-
317
-	/**
318
-	 * Register the default providers (if enabled)
319
-	 *
320
-	 * @param string $class
321
-	 * @param string $mimeType
322
-	 */
323
-	protected function registerCoreProvider($class, $mimeType, $options = []) {
324
-		if (in_array(trim($class, '\\'), $this->getEnabledDefaultProvider())) {
325
-			$this->registerProvider($mimeType, function () use ($class, $options) {
326
-				return new $class($options);
327
-			});
328
-		}
329
-	}
330
-
331
-	/**
332
-	 * Register the default providers (if enabled)
333
-	 */
334
-	protected function registerCoreProviders() {
335
-		if ($this->registeredCoreProviders) {
336
-			return;
337
-		}
338
-		$this->registeredCoreProviders = true;
339
-
340
-		$this->registerCoreProvider(Preview\TXT::class, '/text\/plain/');
341
-		$this->registerCoreProvider(Preview\MarkDown::class, '/text\/(x-)?markdown/');
342
-		$this->registerCoreProvider(Preview\PNG::class, '/image\/png/');
343
-		$this->registerCoreProvider(Preview\JPEG::class, '/image\/jpeg/');
344
-		$this->registerCoreProvider(Preview\GIF::class, '/image\/gif/');
345
-		$this->registerCoreProvider(Preview\BMP::class, '/image\/bmp/');
346
-		$this->registerCoreProvider(Preview\XBitmap::class, '/image\/x-xbitmap/');
347
-		$this->registerCoreProvider(Preview\WebP::class, '/image\/webp/');
348
-		$this->registerCoreProvider(Preview\Krita::class, '/application\/x-krita/');
349
-		$this->registerCoreProvider(Preview\MP3::class, '/audio\/mpeg$/');
350
-		$this->registerCoreProvider(Preview\OpenDocument::class, '/application\/vnd.oasis.opendocument.*/');
351
-		$this->registerCoreProvider(Preview\Imaginary::class, Preview\Imaginary::supportedMimeTypes());
352
-		$this->registerCoreProvider(Preview\ImaginaryPDF::class, Preview\ImaginaryPDF::supportedMimeTypes());
353
-
354
-		// SVG and Bitmap require imagick
355
-		if ($this->imagickSupport->hasExtension()) {
356
-			$imagickProviders = [
357
-				'SVG' => ['mimetype' => '/image\/svg\+xml/', 'class' => Preview\SVG::class],
358
-				'TIFF' => ['mimetype' => '/image\/tiff/', 'class' => Preview\TIFF::class],
359
-				'PDF' => ['mimetype' => '/application\/pdf/', 'class' => Preview\PDF::class],
360
-				'AI' => ['mimetype' => '/application\/illustrator/', 'class' => Preview\Illustrator::class],
361
-				'PSD' => ['mimetype' => '/application\/x-photoshop/', 'class' => Preview\Photoshop::class],
362
-				'EPS' => ['mimetype' => '/application\/postscript/', 'class' => Preview\Postscript::class],
363
-				'TTF' => ['mimetype' => '/application\/(?:font-sfnt|x-font$)/', 'class' => Preview\Font::class],
364
-				'HEIC' => ['mimetype' => '/image\/(x-)?hei(f|c)/', 'class' => Preview\HEIC::class],
365
-				'TGA' => ['mimetype' => '/image\/(x-)?t(ar)?ga/', 'class' => Preview\TGA::class],
366
-				'SGI' => ['mimetype' => '/image\/(x-)?sgi/', 'class' => Preview\SGI::class],
367
-			];
368
-
369
-			foreach ($imagickProviders as $queryFormat => $provider) {
370
-				$class = $provider['class'];
371
-				if (!in_array(trim($class, '\\'), $this->getEnabledDefaultProvider())) {
372
-					continue;
373
-				}
374
-
375
-				if ($this->imagickSupport->supportsFormat($queryFormat)) {
376
-					$this->registerCoreProvider($class, $provider['mimetype']);
377
-				}
378
-			}
379
-		}
380
-
381
-		$this->registerCoreProvidersOffice();
382
-
383
-		// Video requires avconv or ffmpeg
384
-		if (in_array(Preview\Movie::class, $this->getEnabledDefaultProvider())) {
385
-			$movieBinary = $this->config->getSystemValue('preview_ffmpeg_path', null);
386
-			if (!is_string($movieBinary)) {
387
-				$movieBinary = $this->binaryFinder->findBinaryPath('avconv');
388
-				if (!is_string($movieBinary)) {
389
-					$movieBinary = $this->binaryFinder->findBinaryPath('ffmpeg');
390
-				}
391
-			}
392
-
393
-
394
-			if (is_string($movieBinary)) {
395
-				$this->registerCoreProvider(Preview\Movie::class, '/video\/.*/', ['movieBinary' => $movieBinary]);
396
-			}
397
-		}
398
-	}
399
-
400
-	private function registerCoreProvidersOffice(): void {
401
-		$officeProviders = [
402
-			['mimetype' => '/application\/msword/', 'class' => Preview\MSOfficeDoc::class],
403
-			['mimetype' => '/application\/vnd.ms-.*/', 'class' => Preview\MSOffice2003::class],
404
-			['mimetype' => '/application\/vnd.openxmlformats-officedocument.*/', 'class' => Preview\MSOffice2007::class],
405
-			['mimetype' => '/application\/vnd.oasis.opendocument.*/', 'class' => Preview\OpenDocument::class],
406
-			['mimetype' => '/application\/vnd.sun.xml.*/', 'class' => Preview\StarOffice::class],
407
-			['mimetype' => '/image\/emf/', 'class' => Preview\EMF::class],
408
-		];
409
-
410
-		$findBinary = true;
411
-		$officeBinary = false;
412
-
413
-		foreach ($officeProviders as $provider) {
414
-			$class = $provider['class'];
415
-			if (!in_array(trim($class, '\\'), $this->getEnabledDefaultProvider())) {
416
-				continue;
417
-			}
418
-
419
-			if ($findBinary) {
420
-				// Office requires openoffice or libreoffice
421
-				$officeBinary = $this->config->getSystemValue('preview_libreoffice_path', false);
422
-				if ($officeBinary === false) {
423
-					$officeBinary = $this->binaryFinder->findBinaryPath('libreoffice');
424
-				}
425
-				if ($officeBinary === false) {
426
-					$officeBinary = $this->binaryFinder->findBinaryPath('openoffice');
427
-				}
428
-				$findBinary = false;
429
-			}
430
-
431
-			if ($officeBinary) {
432
-				$this->registerCoreProvider($class, $provider['mimetype'], ['officeBinary' => $officeBinary]);
433
-			}
434
-		}
435
-	}
436
-
437
-	private function registerBootstrapProviders(): void {
438
-		$context = $this->bootstrapCoordinator->getRegistrationContext();
439
-
440
-		if ($context === null) {
441
-			// Just ignore for now
442
-			return;
443
-		}
444
-
445
-		$providers = $context->getPreviewProviders();
446
-		foreach ($providers as $provider) {
447
-			$key = $provider->getMimeTypeRegex() . '-' . $provider->getService();
448
-			if (array_key_exists($key, $this->loadedBootstrapProviders)) {
449
-				// Do not load the provider more than once
450
-				continue;
451
-			}
452
-			$this->loadedBootstrapProviders[$key] = null;
453
-
454
-			$this->registerProvider($provider->getMimeTypeRegex(), function () use ($provider) {
455
-				try {
456
-					return $this->container->get($provider->getService());
457
-				} catch (QueryException $e) {
458
-					return null;
459
-				}
460
-			});
461
-		}
462
-	}
463
-
464
-	/**
465
-	 * @throws NotFoundException if preview generation is disabled
466
-	 */
467
-	private function throwIfPreviewsDisabled(): void {
468
-		if (!$this->enablePreviews) {
469
-			throw new NotFoundException('Previews disabled');
470
-		}
471
-	}
31
+    protected IConfig $config;
32
+    protected IRootFolder $rootFolder;
33
+    protected IAppData $appData;
34
+    protected IEventDispatcher $eventDispatcher;
35
+    private ?Generator $generator = null;
36
+    private GeneratorHelper $helper;
37
+    protected bool $providerListDirty = false;
38
+    protected bool $registeredCoreProviders = false;
39
+    protected array $providers = [];
40
+
41
+    /** @var array mime type => support status */
42
+    protected array $mimeTypeSupportMap = [];
43
+    protected ?array $defaultProviders = null;
44
+    protected ?string $userId;
45
+    private Coordinator $bootstrapCoordinator;
46
+
47
+    /**
48
+     * Hash map (without value) of loaded bootstrap providers
49
+     * @psalm-var array<string, null>
50
+     */
51
+    private array $loadedBootstrapProviders = [];
52
+    private ContainerInterface $container;
53
+    private IBinaryFinder $binaryFinder;
54
+    private IMagickSupport $imagickSupport;
55
+    private bool $enablePreviews;
56
+
57
+    public function __construct(
58
+        IConfig $config,
59
+        IRootFolder $rootFolder,
60
+        IAppData $appData,
61
+        IEventDispatcher $eventDispatcher,
62
+        GeneratorHelper $helper,
63
+        ?string $userId,
64
+        Coordinator $bootstrapCoordinator,
65
+        ContainerInterface $container,
66
+        IBinaryFinder $binaryFinder,
67
+        IMagickSupport $imagickSupport,
68
+    ) {
69
+        $this->config = $config;
70
+        $this->rootFolder = $rootFolder;
71
+        $this->appData = $appData;
72
+        $this->eventDispatcher = $eventDispatcher;
73
+        $this->helper = $helper;
74
+        $this->userId = $userId;
75
+        $this->bootstrapCoordinator = $bootstrapCoordinator;
76
+        $this->container = $container;
77
+        $this->binaryFinder = $binaryFinder;
78
+        $this->imagickSupport = $imagickSupport;
79
+        $this->enablePreviews = $config->getSystemValueBool('enable_previews', true);
80
+    }
81
+
82
+    /**
83
+     * In order to improve lazy loading a closure can be registered which will be
84
+     * called in case preview providers are actually requested
85
+     *
86
+     * $callable has to return an instance of \OCP\Preview\IProvider or \OCP\Preview\IProviderV2
87
+     *
88
+     * @param string $mimeTypeRegex Regex with the mime types that are supported by this provider
89
+     * @param \Closure $callable
90
+     * @return void
91
+     */
92
+    public function registerProvider($mimeTypeRegex, \Closure $callable): void {
93
+        if (!$this->enablePreviews) {
94
+            return;
95
+        }
96
+
97
+        if (!isset($this->providers[$mimeTypeRegex])) {
98
+            $this->providers[$mimeTypeRegex] = [];
99
+        }
100
+        $this->providers[$mimeTypeRegex][] = $callable;
101
+        $this->providerListDirty = true;
102
+    }
103
+
104
+    /**
105
+     * Get all providers
106
+     */
107
+    public function getProviders(): array {
108
+        if (!$this->enablePreviews) {
109
+            return [];
110
+        }
111
+
112
+        $this->registerCoreProviders();
113
+        $this->registerBootstrapProviders();
114
+        if ($this->providerListDirty) {
115
+            $keys = array_map('strlen', array_keys($this->providers));
116
+            array_multisort($keys, SORT_DESC, $this->providers);
117
+            $this->providerListDirty = false;
118
+        }
119
+
120
+        return $this->providers;
121
+    }
122
+
123
+    /**
124
+     * Does the manager have any providers
125
+     */
126
+    public function hasProviders(): bool {
127
+        $this->registerCoreProviders();
128
+        return !empty($this->providers);
129
+    }
130
+
131
+    private function getGenerator(): Generator {
132
+        if ($this->generator === null) {
133
+            $this->generator = new Generator(
134
+                $this->config,
135
+                $this,
136
+                $this->appData,
137
+                new GeneratorHelper(
138
+                    $this->rootFolder,
139
+                    $this->config
140
+                ),
141
+                $this->eventDispatcher,
142
+                $this->container->get(LoggerInterface::class),
143
+            );
144
+        }
145
+        return $this->generator;
146
+    }
147
+
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) {
166
+        $this->throwIfPreviewsDisabled();
167
+        $previewConcurrency = $this->getGenerator()->getNumConcurrentPreviews('preview_concurrency_all');
168
+        $sem = Generator::guardWithSemaphore(Generator::SEMAPHORE_ID_ALL, $previewConcurrency);
169
+        try {
170
+            $preview = $this->getGenerator()->getPreview($file, $width, $height, $crop, $mode, $mimeType);
171
+        } finally {
172
+            Generator::unguardWithSemaphore($sem);
173
+        }
174
+
175
+        return $preview;
176
+    }
177
+
178
+    /**
179
+     * Generates previews of a file
180
+     *
181
+     * @param File $file
182
+     * @param array $specifications
183
+     * @param string $mimeType
184
+     * @return ISimpleFile the last preview that was generated
185
+     * @throws NotFoundException
186
+     * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
187
+     * @since 19.0.0
188
+     */
189
+    public function generatePreviews(File $file, array $specifications, $mimeType = null) {
190
+        $this->throwIfPreviewsDisabled();
191
+        return $this->getGenerator()->generatePreviews($file, $specifications, $mimeType);
192
+    }
193
+
194
+    /**
195
+     * returns true if the passed mime type is supported
196
+     *
197
+     * @param string $mimeType
198
+     * @return boolean
199
+     */
200
+    public function isMimeSupported($mimeType = '*') {
201
+        if (!$this->enablePreviews) {
202
+            return false;
203
+        }
204
+
205
+        if (isset($this->mimeTypeSupportMap[$mimeType])) {
206
+            return $this->mimeTypeSupportMap[$mimeType];
207
+        }
208
+
209
+        $this->registerCoreProviders();
210
+        $this->registerBootstrapProviders();
211
+        $providerMimeTypes = array_keys($this->providers);
212
+        foreach ($providerMimeTypes as $supportedMimeType) {
213
+            if (preg_match($supportedMimeType, $mimeType)) {
214
+                $this->mimeTypeSupportMap[$mimeType] = true;
215
+                return true;
216
+            }
217
+        }
218
+        $this->mimeTypeSupportMap[$mimeType] = false;
219
+        return false;
220
+    }
221
+
222
+    /**
223
+     * Check if a preview can be generated for a file
224
+     */
225
+    public function isAvailable(\OCP\Files\FileInfo $file): bool {
226
+        if (!$this->enablePreviews) {
227
+            return false;
228
+        }
229
+
230
+        $this->registerCoreProviders();
231
+        if (!$this->isMimeSupported($file->getMimetype())) {
232
+            return false;
233
+        }
234
+
235
+        $mount = $file->getMountPoint();
236
+        if ($mount and !$mount->getOption('previews', true)) {
237
+            return false;
238
+        }
239
+
240
+        foreach ($this->providers as $supportedMimeType => $providers) {
241
+            if (preg_match($supportedMimeType, $file->getMimetype())) {
242
+                foreach ($providers as $providerClosure) {
243
+                    $provider = $this->helper->getProvider($providerClosure);
244
+                    if (!($provider instanceof IProviderV2)) {
245
+                        continue;
246
+                    }
247
+
248
+                    if ($provider->isAvailable($file)) {
249
+                        return true;
250
+                    }
251
+                }
252
+            }
253
+        }
254
+        return false;
255
+    }
256
+
257
+    /**
258
+     * List of enabled default providers
259
+     *
260
+     * The following providers are enabled by default:
261
+     *  - OC\Preview\PNG
262
+     *  - OC\Preview\JPEG
263
+     *  - OC\Preview\GIF
264
+     *  - OC\Preview\BMP
265
+     *  - OC\Preview\XBitmap
266
+     *  - OC\Preview\MarkDown
267
+     *  - OC\Preview\MP3
268
+     *  - OC\Preview\TXT
269
+     *
270
+     * The following providers are disabled by default due to performance or privacy concerns:
271
+     *  - OC\Preview\Font
272
+     *  - OC\Preview\HEIC
273
+     *  - OC\Preview\Illustrator
274
+     *  - OC\Preview\Movie
275
+     *  - OC\Preview\MSOfficeDoc
276
+     *  - OC\Preview\MSOffice2003
277
+     *  - OC\Preview\MSOffice2007
278
+     *  - OC\Preview\OpenDocument
279
+     *  - OC\Preview\PDF
280
+     *  - OC\Preview\Photoshop
281
+     *  - OC\Preview\Postscript
282
+     *  - OC\Preview\StarOffice
283
+     *  - OC\Preview\SVG
284
+     *  - OC\Preview\TIFF
285
+     *
286
+     * @return array
287
+     */
288
+    protected function getEnabledDefaultProvider() {
289
+        if ($this->defaultProviders !== null) {
290
+            return $this->defaultProviders;
291
+        }
292
+
293
+        $imageProviders = [
294
+            Preview\PNG::class,
295
+            Preview\JPEG::class,
296
+            Preview\GIF::class,
297
+            Preview\BMP::class,
298
+            Preview\XBitmap::class,
299
+            Preview\Krita::class,
300
+            Preview\WebP::class,
301
+        ];
302
+
303
+        $this->defaultProviders = $this->config->getSystemValue('enabledPreviewProviders', array_merge([
304
+            Preview\MarkDown::class,
305
+            Preview\MP3::class,
306
+            Preview\TXT::class,
307
+            Preview\OpenDocument::class,
308
+        ], $imageProviders));
309
+
310
+        if (in_array(Preview\Image::class, $this->defaultProviders)) {
311
+            $this->defaultProviders = array_merge($this->defaultProviders, $imageProviders);
312
+        }
313
+        $this->defaultProviders = array_unique($this->defaultProviders);
314
+        return $this->defaultProviders;
315
+    }
316
+
317
+    /**
318
+     * Register the default providers (if enabled)
319
+     *
320
+     * @param string $class
321
+     * @param string $mimeType
322
+     */
323
+    protected function registerCoreProvider($class, $mimeType, $options = []) {
324
+        if (in_array(trim($class, '\\'), $this->getEnabledDefaultProvider())) {
325
+            $this->registerProvider($mimeType, function () use ($class, $options) {
326
+                return new $class($options);
327
+            });
328
+        }
329
+    }
330
+
331
+    /**
332
+     * Register the default providers (if enabled)
333
+     */
334
+    protected function registerCoreProviders() {
335
+        if ($this->registeredCoreProviders) {
336
+            return;
337
+        }
338
+        $this->registeredCoreProviders = true;
339
+
340
+        $this->registerCoreProvider(Preview\TXT::class, '/text\/plain/');
341
+        $this->registerCoreProvider(Preview\MarkDown::class, '/text\/(x-)?markdown/');
342
+        $this->registerCoreProvider(Preview\PNG::class, '/image\/png/');
343
+        $this->registerCoreProvider(Preview\JPEG::class, '/image\/jpeg/');
344
+        $this->registerCoreProvider(Preview\GIF::class, '/image\/gif/');
345
+        $this->registerCoreProvider(Preview\BMP::class, '/image\/bmp/');
346
+        $this->registerCoreProvider(Preview\XBitmap::class, '/image\/x-xbitmap/');
347
+        $this->registerCoreProvider(Preview\WebP::class, '/image\/webp/');
348
+        $this->registerCoreProvider(Preview\Krita::class, '/application\/x-krita/');
349
+        $this->registerCoreProvider(Preview\MP3::class, '/audio\/mpeg$/');
350
+        $this->registerCoreProvider(Preview\OpenDocument::class, '/application\/vnd.oasis.opendocument.*/');
351
+        $this->registerCoreProvider(Preview\Imaginary::class, Preview\Imaginary::supportedMimeTypes());
352
+        $this->registerCoreProvider(Preview\ImaginaryPDF::class, Preview\ImaginaryPDF::supportedMimeTypes());
353
+
354
+        // SVG and Bitmap require imagick
355
+        if ($this->imagickSupport->hasExtension()) {
356
+            $imagickProviders = [
357
+                'SVG' => ['mimetype' => '/image\/svg\+xml/', 'class' => Preview\SVG::class],
358
+                'TIFF' => ['mimetype' => '/image\/tiff/', 'class' => Preview\TIFF::class],
359
+                'PDF' => ['mimetype' => '/application\/pdf/', 'class' => Preview\PDF::class],
360
+                'AI' => ['mimetype' => '/application\/illustrator/', 'class' => Preview\Illustrator::class],
361
+                'PSD' => ['mimetype' => '/application\/x-photoshop/', 'class' => Preview\Photoshop::class],
362
+                'EPS' => ['mimetype' => '/application\/postscript/', 'class' => Preview\Postscript::class],
363
+                'TTF' => ['mimetype' => '/application\/(?:font-sfnt|x-font$)/', 'class' => Preview\Font::class],
364
+                'HEIC' => ['mimetype' => '/image\/(x-)?hei(f|c)/', 'class' => Preview\HEIC::class],
365
+                'TGA' => ['mimetype' => '/image\/(x-)?t(ar)?ga/', 'class' => Preview\TGA::class],
366
+                'SGI' => ['mimetype' => '/image\/(x-)?sgi/', 'class' => Preview\SGI::class],
367
+            ];
368
+
369
+            foreach ($imagickProviders as $queryFormat => $provider) {
370
+                $class = $provider['class'];
371
+                if (!in_array(trim($class, '\\'), $this->getEnabledDefaultProvider())) {
372
+                    continue;
373
+                }
374
+
375
+                if ($this->imagickSupport->supportsFormat($queryFormat)) {
376
+                    $this->registerCoreProvider($class, $provider['mimetype']);
377
+                }
378
+            }
379
+        }
380
+
381
+        $this->registerCoreProvidersOffice();
382
+
383
+        // Video requires avconv or ffmpeg
384
+        if (in_array(Preview\Movie::class, $this->getEnabledDefaultProvider())) {
385
+            $movieBinary = $this->config->getSystemValue('preview_ffmpeg_path', null);
386
+            if (!is_string($movieBinary)) {
387
+                $movieBinary = $this->binaryFinder->findBinaryPath('avconv');
388
+                if (!is_string($movieBinary)) {
389
+                    $movieBinary = $this->binaryFinder->findBinaryPath('ffmpeg');
390
+                }
391
+            }
392
+
393
+
394
+            if (is_string($movieBinary)) {
395
+                $this->registerCoreProvider(Preview\Movie::class, '/video\/.*/', ['movieBinary' => $movieBinary]);
396
+            }
397
+        }
398
+    }
399
+
400
+    private function registerCoreProvidersOffice(): void {
401
+        $officeProviders = [
402
+            ['mimetype' => '/application\/msword/', 'class' => Preview\MSOfficeDoc::class],
403
+            ['mimetype' => '/application\/vnd.ms-.*/', 'class' => Preview\MSOffice2003::class],
404
+            ['mimetype' => '/application\/vnd.openxmlformats-officedocument.*/', 'class' => Preview\MSOffice2007::class],
405
+            ['mimetype' => '/application\/vnd.oasis.opendocument.*/', 'class' => Preview\OpenDocument::class],
406
+            ['mimetype' => '/application\/vnd.sun.xml.*/', 'class' => Preview\StarOffice::class],
407
+            ['mimetype' => '/image\/emf/', 'class' => Preview\EMF::class],
408
+        ];
409
+
410
+        $findBinary = true;
411
+        $officeBinary = false;
412
+
413
+        foreach ($officeProviders as $provider) {
414
+            $class = $provider['class'];
415
+            if (!in_array(trim($class, '\\'), $this->getEnabledDefaultProvider())) {
416
+                continue;
417
+            }
418
+
419
+            if ($findBinary) {
420
+                // Office requires openoffice or libreoffice
421
+                $officeBinary = $this->config->getSystemValue('preview_libreoffice_path', false);
422
+                if ($officeBinary === false) {
423
+                    $officeBinary = $this->binaryFinder->findBinaryPath('libreoffice');
424
+                }
425
+                if ($officeBinary === false) {
426
+                    $officeBinary = $this->binaryFinder->findBinaryPath('openoffice');
427
+                }
428
+                $findBinary = false;
429
+            }
430
+
431
+            if ($officeBinary) {
432
+                $this->registerCoreProvider($class, $provider['mimetype'], ['officeBinary' => $officeBinary]);
433
+            }
434
+        }
435
+    }
436
+
437
+    private function registerBootstrapProviders(): void {
438
+        $context = $this->bootstrapCoordinator->getRegistrationContext();
439
+
440
+        if ($context === null) {
441
+            // Just ignore for now
442
+            return;
443
+        }
444
+
445
+        $providers = $context->getPreviewProviders();
446
+        foreach ($providers as $provider) {
447
+            $key = $provider->getMimeTypeRegex() . '-' . $provider->getService();
448
+            if (array_key_exists($key, $this->loadedBootstrapProviders)) {
449
+                // Do not load the provider more than once
450
+                continue;
451
+            }
452
+            $this->loadedBootstrapProviders[$key] = null;
453
+
454
+            $this->registerProvider($provider->getMimeTypeRegex(), function () use ($provider) {
455
+                try {
456
+                    return $this->container->get($provider->getService());
457
+                } catch (QueryException $e) {
458
+                    return null;
459
+                }
460
+            });
461
+        }
462
+    }
463
+
464
+    /**
465
+     * @throws NotFoundException if preview generation is disabled
466
+     */
467
+    private function throwIfPreviewsDisabled(): void {
468
+        if (!$this->enablePreviews) {
469
+            throw new NotFoundException('Previews disabled');
470
+        }
471
+    }
472 472
 }
Please login to merge, or discard this patch.
lib/private/Preview/Generator.php 1 patch
Indentation   +572 added lines, -572 removed lines patch added patch discarded remove patch
@@ -24,592 +24,592 @@
 block discarded – undo
24 24
 use Psr\Log\LoggerInterface;
25 25
 
26 26
 class Generator {
27
-	public const SEMAPHORE_ID_ALL = 0x0a11;
28
-	public const SEMAPHORE_ID_NEW = 0x07ea;
29
-
30
-	public function __construct(
31
-		private IConfig $config,
32
-		private IPreview $previewManager,
33
-		private IAppData $appData,
34
-		private GeneratorHelper $helper,
35
-		private IEventDispatcher $eventDispatcher,
36
-		private LoggerInterface $logger,
37
-	) {
38
-	}
39
-
40
-	/**
41
-	 * Returns a preview of a file
42
-	 *
43
-	 * The cache is searched first and if nothing usable was found then a preview is
44
-	 * generated by one of the providers
45
-	 *
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
52
-	 * @return ISimpleFile
53
-	 * @throws NotFoundException
54
-	 * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
55
-	 */
56
-	public function getPreview(File $file, $width = -1, $height = -1, $crop = false, $mode = IPreview::MODE_FILL, $mimeType = null) {
57
-		$specification = [
58
-			'width' => $width,
59
-			'height' => $height,
60
-			'crop' => $crop,
61
-			'mode' => $mode,
62
-		];
63
-
64
-		$this->eventDispatcher->dispatchTyped(new BeforePreviewFetchedEvent(
65
-			$file,
66
-			$width,
67
-			$height,
68
-			$crop,
69
-			$mode,
70
-			$mimeType,
71
-		));
72
-
73
-		$this->logger->debug('Requesting preview for {path} with width={width}, height={height}, crop={crop}, mode={mode}, mimeType={mimeType}', [
74
-			'path' => $file->getPath(),
75
-			'width' => $width,
76
-			'height' => $height,
77
-			'crop' => $crop,
78
-			'mode' => $mode,
79
-			'mimeType' => $mimeType,
80
-		]);
27
+    public const SEMAPHORE_ID_ALL = 0x0a11;
28
+    public const SEMAPHORE_ID_NEW = 0x07ea;
29
+
30
+    public function __construct(
31
+        private IConfig $config,
32
+        private IPreview $previewManager,
33
+        private IAppData $appData,
34
+        private GeneratorHelper $helper,
35
+        private IEventDispatcher $eventDispatcher,
36
+        private LoggerInterface $logger,
37
+    ) {
38
+    }
39
+
40
+    /**
41
+     * Returns a preview of a file
42
+     *
43
+     * The cache is searched first and if nothing usable was found then a preview is
44
+     * generated by one of the providers
45
+     *
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
52
+     * @return ISimpleFile
53
+     * @throws NotFoundException
54
+     * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
55
+     */
56
+    public function getPreview(File $file, $width = -1, $height = -1, $crop = false, $mode = IPreview::MODE_FILL, $mimeType = null) {
57
+        $specification = [
58
+            'width' => $width,
59
+            'height' => $height,
60
+            'crop' => $crop,
61
+            'mode' => $mode,
62
+        ];
63
+
64
+        $this->eventDispatcher->dispatchTyped(new BeforePreviewFetchedEvent(
65
+            $file,
66
+            $width,
67
+            $height,
68
+            $crop,
69
+            $mode,
70
+            $mimeType,
71
+        ));
72
+
73
+        $this->logger->debug('Requesting preview for {path} with width={width}, height={height}, crop={crop}, mode={mode}, mimeType={mimeType}', [
74
+            'path' => $file->getPath(),
75
+            'width' => $width,
76
+            'height' => $height,
77
+            'crop' => $crop,
78
+            'mode' => $mode,
79
+            'mimeType' => $mimeType,
80
+        ]);
81 81
 		
82 82
 
83
-		// 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);
85
-	}
86
-
87
-	/**
88
-	 * Generates previews of a file
89
-	 *
90
-	 * @param File $file
91
-	 * @param non-empty-array $specifications
92
-	 * @param string $mimeType
93
-	 * @return ISimpleFile the last preview that was generated
94
-	 * @throws NotFoundException
95
-	 * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
96
-	 */
97
-	public function generatePreviews(File $file, array $specifications, $mimeType = null) {
98
-		//Make sure that we can read the file
99
-		if (!$file->isReadable()) {
100
-			$this->logger->warning('Cannot read file: {path}, skipping preview generation.', ['path' => $file->getPath()]);
101
-			throw new NotFoundException('Cannot read file');
102
-		}
103
-
104
-		if ($mimeType === null) {
105
-			$mimeType = $file->getMimeType();
106
-		}
107
-
108
-		$previewFolder = $this->getPreviewFolder($file);
109
-		// List every existing preview first instead of trying to find them one by one
110
-		$previewFiles = $previewFolder->getDirectoryListing();
111
-
112
-		$previewVersion = '';
113
-		if ($file instanceof IVersionedPreviewFile) {
114
-			$previewVersion = $file->getPreviewVersion() . '-';
115
-		}
116
-
117
-		// Get the max preview and infer the max preview sizes from that
118
-		$maxPreview = $this->getMaxPreview($previewFolder, $previewFiles, $file, $mimeType, $previewVersion);
119
-		$maxPreviewImage = null; // only load the image when we need it
120
-		if ($maxPreview->getSize() === 0) {
121
-			$maxPreview->delete();
122
-			$this->logger->error("Max preview generated for file {$file->getPath()} has size 0, deleting and throwing exception.");
123
-			throw new NotFoundException('Max preview size 0, invalid!');
124
-		}
125
-
126
-		[$maxWidth, $maxHeight] = $this->getPreviewSize($maxPreview, $previewVersion);
127
-
128
-		if ($maxWidth <= 0 || $maxHeight <= 0) {
129
-			throw new NotFoundException('The maximum preview sizes are zero or less pixels');
130
-		}
131
-
132
-		$preview = null;
133
-
134
-		foreach ($specifications as $specification) {
135
-			$width = $specification['width'] ?? -1;
136
-			$height = $specification['height'] ?? -1;
137
-			$crop = $specification['crop'] ?? false;
138
-			$mode = $specification['mode'] ?? IPreview::MODE_FILL;
139
-
140
-			// If both width and height are -1 we just want the max preview
141
-			if ($width === -1 && $height === -1) {
142
-				$width = $maxWidth;
143
-				$height = $maxHeight;
144
-			}
145
-
146
-			// Calculate the preview size
147
-			[$width, $height] = $this->calculateSize($width, $height, $crop, $mode, $maxWidth, $maxHeight);
148
-
149
-			// No need to generate a preview that is just the max preview
150
-			if ($width === $maxWidth && $height === $maxHeight) {
151
-				// ensure correct return value if this was the last one
152
-				$preview = $maxPreview;
153
-				continue;
154
-			}
155
-
156
-			// Try to get a cached preview. Else generate (and store) one
157
-			try {
158
-				try {
159
-					$preview = $this->getCachedPreview($previewFiles, $width, $height, $crop, $maxPreview->getMimeType(), $previewVersion);
160
-				} catch (NotFoundException $e) {
161
-					if (!$this->previewManager->isMimeSupported($mimeType)) {
162
-						throw new NotFoundException();
163
-					}
164
-
165
-					if ($maxPreviewImage === null) {
166
-						$maxPreviewImage = $this->helper->getImage($maxPreview);
167
-					}
168
-
169
-					$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);
171
-					// New file, augment our array
172
-					$previewFiles[] = $preview;
173
-				}
174
-			} catch (\InvalidArgumentException $e) {
175
-				throw new NotFoundException('', 0, $e);
176
-			}
177
-
178
-			if ($preview->getSize() === 0) {
179
-				$preview->delete();
180
-				throw new NotFoundException('Cached preview size 0, invalid!');
181
-			}
182
-		}
183
-		assert($preview !== null);
184
-
185
-		// Free memory being used by the embedded image resource.  Without this the image is kept in memory indefinitely.
186
-		// Garbage Collection does NOT free this memory.  We have to do it ourselves.
187
-		if ($maxPreviewImage instanceof \OCP\Image) {
188
-			$maxPreviewImage->destroy();
189
-		}
190
-
191
-		return $preview;
192
-	}
193
-
194
-	/**
195
-	 * Acquire a semaphore of the specified id and concurrency, blocking if necessary.
196
-	 * Return an identifier of the semaphore on success, which can be used to release it via
197
-	 * {@see Generator::unguardWithSemaphore()}.
198
-	 *
199
-	 * @param int $semId
200
-	 * @param int $concurrency
201
-	 * @return false|\SysvSemaphore the semaphore on success or false on failure
202
-	 */
203
-	public static function guardWithSemaphore(int $semId, int $concurrency) {
204
-		if (!extension_loaded('sysvsem')) {
205
-			return false;
206
-		}
207
-		$sem = sem_get($semId, $concurrency);
208
-		if ($sem === false) {
209
-			return false;
210
-		}
211
-		if (!sem_acquire($sem)) {
212
-			return false;
213
-		}
214
-		return $sem;
215
-	}
216
-
217
-	/**
218
-	 * Releases the semaphore acquired from {@see Generator::guardWithSemaphore()}.
219
-	 *
220
-	 * @param false|\SysvSemaphore $semId the semaphore identifier returned by guardWithSemaphore
221
-	 * @return bool
222
-	 */
223
-	public static function unguardWithSemaphore(false|\SysvSemaphore $semId): bool {
224
-		if ($semId === false || !($semId instanceof \SysvSemaphore)) {
225
-			return false;
226
-		}
227
-		return sem_release($semId);
228
-	}
229
-
230
-	/**
231
-	 * Get the number of concurrent threads supported by the host.
232
-	 *
233
-	 * @return int number of concurrent threads, or 0 if it cannot be determined
234
-	 */
235
-	public static function getHardwareConcurrency(): int {
236
-		static $width;
237
-
238
-		if (!isset($width)) {
239
-			if (function_exists('ini_get')) {
240
-				$openBasedir = ini_get('open_basedir');
241
-				if (empty($openBasedir) || strpos($openBasedir, '/proc/cpuinfo') !== false) {
242
-					$width = is_readable('/proc/cpuinfo') ? substr_count(file_get_contents('/proc/cpuinfo'), 'processor') : 0;
243
-				} else {
244
-					$width = 0;
245
-				}
246
-			} else {
247
-				$width = 0;
248
-			}
249
-		}
250
-		return $width;
251
-	}
252
-
253
-	/**
254
-	 * Get number of concurrent preview generations from system config
255
-	 *
256
-	 * Two config entries, `preview_concurrency_new` and `preview_concurrency_all`,
257
-	 * are available. If not set, the default values are determined with the hardware concurrency
258
-	 * of the host. In case the hardware concurrency cannot be determined, or the user sets an
259
-	 * invalid value, fallback values are:
260
-	 * For new images whose previews do not exist and need to be generated, 4;
261
-	 * For all preview generation requests, 8.
262
-	 * Value of `preview_concurrency_all` should be greater than or equal to that of
263
-	 * `preview_concurrency_new`, otherwise, the latter is returned.
264
-	 *
265
-	 * @param string $type either `preview_concurrency_new` or `preview_concurrency_all`
266
-	 * @return int number of concurrent preview generations, or -1 if $type is invalid
267
-	 */
268
-	public function getNumConcurrentPreviews(string $type): int {
269
-		static $cached = [];
270
-		if (array_key_exists($type, $cached)) {
271
-			return $cached[$type];
272
-		}
273
-
274
-		$hardwareConcurrency = self::getHardwareConcurrency();
275
-		switch ($type) {
276
-			case 'preview_concurrency_all':
277
-				$fallback = $hardwareConcurrency > 0 ? $hardwareConcurrency * 2 : 8;
278
-				$concurrency_all = $this->config->getSystemValueInt($type, $fallback);
279
-				$concurrency_new = $this->getNumConcurrentPreviews('preview_concurrency_new');
280
-				$cached[$type] = max($concurrency_all, $concurrency_new);
281
-				break;
282
-			case 'preview_concurrency_new':
283
-				$fallback = $hardwareConcurrency > 0 ? $hardwareConcurrency : 4;
284
-				$cached[$type] = $this->config->getSystemValueInt($type, $fallback);
285
-				break;
286
-			default:
287
-				return -1;
288
-		}
289
-		return $cached[$type];
290
-	}
291
-
292
-	/**
293
-	 * @param ISimpleFolder $previewFolder
294
-	 * @param ISimpleFile[] $previewFiles
295
-	 * @param File $file
296
-	 * @param string $mimeType
297
-	 * @param string $prefix
298
-	 * @return ISimpleFile
299
-	 * @throws NotFoundException
300
-	 */
301
-	private function getMaxPreview(ISimpleFolder $previewFolder, array $previewFiles, File $file, $mimeType, $prefix) {
302
-		// We don't know the max preview size, so we can't use getCachedPreview.
303
-		// It might have been generated with a higher resolution than the current value.
304
-		foreach ($previewFiles as $node) {
305
-			$name = $node->getName();
306
-			if (($prefix === '' || str_starts_with($name, $prefix)) && strpos($name, 'max')) {
307
-				return $node;
308
-			}
309
-		}
310
-
311
-		$maxWidth = $this->config->getSystemValueInt('preview_max_x', 4096);
312
-		$maxHeight = $this->config->getSystemValueInt('preview_max_y', 4096);
313
-
314
-		return $this->generateProviderPreview($previewFolder, $file, $maxWidth, $maxHeight, false, true, $mimeType, $prefix);
315
-	}
316
-
317
-	private function generateProviderPreview(ISimpleFolder $previewFolder, File $file, int $width, int $height, bool $crop, bool $max, string $mimeType, string $prefix) {
318
-		$previewProviders = $this->previewManager->getProviders();
319
-		foreach ($previewProviders as $supportedMimeType => $providers) {
320
-			// Filter out providers that does not support this mime
321
-			if (!preg_match($supportedMimeType, $mimeType)) {
322
-				continue;
323
-			}
324
-
325
-			foreach ($providers as $providerClosure) {
326
-				$provider = $this->helper->getProvider($providerClosure);
327
-				if (!($provider instanceof IProviderV2)) {
328
-					continue;
329
-				}
330
-
331
-				if (!$provider->isAvailable($file)) {
332
-					continue;
333
-				}
334
-
335
-				$previewConcurrency = $this->getNumConcurrentPreviews('preview_concurrency_new');
336
-				$sem = self::guardWithSemaphore(self::SEMAPHORE_ID_NEW, $previewConcurrency);
337
-				try {
338
-					$this->logger->debug('Calling preview provider for {mimeType} with width={width}, height={height}', [
339
-						'mimeType' => $mimeType,
340
-						'width' => $width,
341
-						'height' => $height,
342
-					]);
343
-					$preview = $this->helper->getThumbnail($provider, $file, $width, $height);
344
-				} finally {
345
-					self::unguardWithSemaphore($sem);
346
-				}
347
-
348
-				if (!($preview instanceof IImage)) {
349
-					continue;
350
-				}
351
-
352
-				$path = $this->generatePath($preview->width(), $preview->height(), $crop, $max, $preview->dataMimeType(), $prefix);
353
-				try {
354
-					$file = $previewFolder->newFile($path);
355
-					if ($preview instanceof IStreamImage) {
356
-						$file->putContent($preview->resource());
357
-					} else {
358
-						$file->putContent($preview->data());
359
-					}
360
-				} catch (NotPermittedException $e) {
361
-					throw new NotFoundException();
362
-				}
363
-
364
-				return $file;
365
-			}
366
-		}
367
-
368
-		throw new NotFoundException('No provider successfully handled the preview generation');
369
-	}
370
-
371
-	/**
372
-	 * @param ISimpleFile $file
373
-	 * @param string $prefix
374
-	 * @return int[]
375
-	 */
376
-	private function getPreviewSize(ISimpleFile $file, string $prefix = '') {
377
-		$size = explode('-', substr($file->getName(), strlen($prefix)));
378
-		return [(int)$size[0], (int)$size[1]];
379
-	}
380
-
381
-	/**
382
-	 * @param int $width
383
-	 * @param int $height
384
-	 * @param bool $crop
385
-	 * @param bool $max
386
-	 * @param string $mimeType
387
-	 * @param string $prefix
388
-	 * @return string
389
-	 */
390
-	private function generatePath($width, $height, $crop, $max, $mimeType, $prefix) {
391
-		$path = $prefix . (string)$width . '-' . (string)$height;
392
-		if ($crop) {
393
-			$path .= '-crop';
394
-		}
395
-		if ($max) {
396
-			$path .= '-max';
397
-		}
398
-
399
-		$ext = $this->getExtension($mimeType);
400
-		$path .= '.' . $ext;
401
-		return $path;
402
-	}
403
-
404
-
405
-	/**
406
-	 * @param int $width
407
-	 * @param int $height
408
-	 * @param bool $crop
409
-	 * @param string $mode
410
-	 * @param int $maxWidth
411
-	 * @param int $maxHeight
412
-	 * @return int[]
413
-	 */
414
-	private function calculateSize($width, $height, $crop, $mode, $maxWidth, $maxHeight) {
415
-		/*
83
+        // 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);
85
+    }
86
+
87
+    /**
88
+     * Generates previews of a file
89
+     *
90
+     * @param File $file
91
+     * @param non-empty-array $specifications
92
+     * @param string $mimeType
93
+     * @return ISimpleFile the last preview that was generated
94
+     * @throws NotFoundException
95
+     * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
96
+     */
97
+    public function generatePreviews(File $file, array $specifications, $mimeType = null) {
98
+        //Make sure that we can read the file
99
+        if (!$file->isReadable()) {
100
+            $this->logger->warning('Cannot read file: {path}, skipping preview generation.', ['path' => $file->getPath()]);
101
+            throw new NotFoundException('Cannot read file');
102
+        }
103
+
104
+        if ($mimeType === null) {
105
+            $mimeType = $file->getMimeType();
106
+        }
107
+
108
+        $previewFolder = $this->getPreviewFolder($file);
109
+        // List every existing preview first instead of trying to find them one by one
110
+        $previewFiles = $previewFolder->getDirectoryListing();
111
+
112
+        $previewVersion = '';
113
+        if ($file instanceof IVersionedPreviewFile) {
114
+            $previewVersion = $file->getPreviewVersion() . '-';
115
+        }
116
+
117
+        // Get the max preview and infer the max preview sizes from that
118
+        $maxPreview = $this->getMaxPreview($previewFolder, $previewFiles, $file, $mimeType, $previewVersion);
119
+        $maxPreviewImage = null; // only load the image when we need it
120
+        if ($maxPreview->getSize() === 0) {
121
+            $maxPreview->delete();
122
+            $this->logger->error("Max preview generated for file {$file->getPath()} has size 0, deleting and throwing exception.");
123
+            throw new NotFoundException('Max preview size 0, invalid!');
124
+        }
125
+
126
+        [$maxWidth, $maxHeight] = $this->getPreviewSize($maxPreview, $previewVersion);
127
+
128
+        if ($maxWidth <= 0 || $maxHeight <= 0) {
129
+            throw new NotFoundException('The maximum preview sizes are zero or less pixels');
130
+        }
131
+
132
+        $preview = null;
133
+
134
+        foreach ($specifications as $specification) {
135
+            $width = $specification['width'] ?? -1;
136
+            $height = $specification['height'] ?? -1;
137
+            $crop = $specification['crop'] ?? false;
138
+            $mode = $specification['mode'] ?? IPreview::MODE_FILL;
139
+
140
+            // If both width and height are -1 we just want the max preview
141
+            if ($width === -1 && $height === -1) {
142
+                $width = $maxWidth;
143
+                $height = $maxHeight;
144
+            }
145
+
146
+            // Calculate the preview size
147
+            [$width, $height] = $this->calculateSize($width, $height, $crop, $mode, $maxWidth, $maxHeight);
148
+
149
+            // No need to generate a preview that is just the max preview
150
+            if ($width === $maxWidth && $height === $maxHeight) {
151
+                // ensure correct return value if this was the last one
152
+                $preview = $maxPreview;
153
+                continue;
154
+            }
155
+
156
+            // Try to get a cached preview. Else generate (and store) one
157
+            try {
158
+                try {
159
+                    $preview = $this->getCachedPreview($previewFiles, $width, $height, $crop, $maxPreview->getMimeType(), $previewVersion);
160
+                } catch (NotFoundException $e) {
161
+                    if (!$this->previewManager->isMimeSupported($mimeType)) {
162
+                        throw new NotFoundException();
163
+                    }
164
+
165
+                    if ($maxPreviewImage === null) {
166
+                        $maxPreviewImage = $this->helper->getImage($maxPreview);
167
+                    }
168
+
169
+                    $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);
171
+                    // New file, augment our array
172
+                    $previewFiles[] = $preview;
173
+                }
174
+            } catch (\InvalidArgumentException $e) {
175
+                throw new NotFoundException('', 0, $e);
176
+            }
177
+
178
+            if ($preview->getSize() === 0) {
179
+                $preview->delete();
180
+                throw new NotFoundException('Cached preview size 0, invalid!');
181
+            }
182
+        }
183
+        assert($preview !== null);
184
+
185
+        // Free memory being used by the embedded image resource.  Without this the image is kept in memory indefinitely.
186
+        // Garbage Collection does NOT free this memory.  We have to do it ourselves.
187
+        if ($maxPreviewImage instanceof \OCP\Image) {
188
+            $maxPreviewImage->destroy();
189
+        }
190
+
191
+        return $preview;
192
+    }
193
+
194
+    /**
195
+     * Acquire a semaphore of the specified id and concurrency, blocking if necessary.
196
+     * Return an identifier of the semaphore on success, which can be used to release it via
197
+     * {@see Generator::unguardWithSemaphore()}.
198
+     *
199
+     * @param int $semId
200
+     * @param int $concurrency
201
+     * @return false|\SysvSemaphore the semaphore on success or false on failure
202
+     */
203
+    public static function guardWithSemaphore(int $semId, int $concurrency) {
204
+        if (!extension_loaded('sysvsem')) {
205
+            return false;
206
+        }
207
+        $sem = sem_get($semId, $concurrency);
208
+        if ($sem === false) {
209
+            return false;
210
+        }
211
+        if (!sem_acquire($sem)) {
212
+            return false;
213
+        }
214
+        return $sem;
215
+    }
216
+
217
+    /**
218
+     * Releases the semaphore acquired from {@see Generator::guardWithSemaphore()}.
219
+     *
220
+     * @param false|\SysvSemaphore $semId the semaphore identifier returned by guardWithSemaphore
221
+     * @return bool
222
+     */
223
+    public static function unguardWithSemaphore(false|\SysvSemaphore $semId): bool {
224
+        if ($semId === false || !($semId instanceof \SysvSemaphore)) {
225
+            return false;
226
+        }
227
+        return sem_release($semId);
228
+    }
229
+
230
+    /**
231
+     * Get the number of concurrent threads supported by the host.
232
+     *
233
+     * @return int number of concurrent threads, or 0 if it cannot be determined
234
+     */
235
+    public static function getHardwareConcurrency(): int {
236
+        static $width;
237
+
238
+        if (!isset($width)) {
239
+            if (function_exists('ini_get')) {
240
+                $openBasedir = ini_get('open_basedir');
241
+                if (empty($openBasedir) || strpos($openBasedir, '/proc/cpuinfo') !== false) {
242
+                    $width = is_readable('/proc/cpuinfo') ? substr_count(file_get_contents('/proc/cpuinfo'), 'processor') : 0;
243
+                } else {
244
+                    $width = 0;
245
+                }
246
+            } else {
247
+                $width = 0;
248
+            }
249
+        }
250
+        return $width;
251
+    }
252
+
253
+    /**
254
+     * Get number of concurrent preview generations from system config
255
+     *
256
+     * Two config entries, `preview_concurrency_new` and `preview_concurrency_all`,
257
+     * are available. If not set, the default values are determined with the hardware concurrency
258
+     * of the host. In case the hardware concurrency cannot be determined, or the user sets an
259
+     * invalid value, fallback values are:
260
+     * For new images whose previews do not exist and need to be generated, 4;
261
+     * For all preview generation requests, 8.
262
+     * Value of `preview_concurrency_all` should be greater than or equal to that of
263
+     * `preview_concurrency_new`, otherwise, the latter is returned.
264
+     *
265
+     * @param string $type either `preview_concurrency_new` or `preview_concurrency_all`
266
+     * @return int number of concurrent preview generations, or -1 if $type is invalid
267
+     */
268
+    public function getNumConcurrentPreviews(string $type): int {
269
+        static $cached = [];
270
+        if (array_key_exists($type, $cached)) {
271
+            return $cached[$type];
272
+        }
273
+
274
+        $hardwareConcurrency = self::getHardwareConcurrency();
275
+        switch ($type) {
276
+            case 'preview_concurrency_all':
277
+                $fallback = $hardwareConcurrency > 0 ? $hardwareConcurrency * 2 : 8;
278
+                $concurrency_all = $this->config->getSystemValueInt($type, $fallback);
279
+                $concurrency_new = $this->getNumConcurrentPreviews('preview_concurrency_new');
280
+                $cached[$type] = max($concurrency_all, $concurrency_new);
281
+                break;
282
+            case 'preview_concurrency_new':
283
+                $fallback = $hardwareConcurrency > 0 ? $hardwareConcurrency : 4;
284
+                $cached[$type] = $this->config->getSystemValueInt($type, $fallback);
285
+                break;
286
+            default:
287
+                return -1;
288
+        }
289
+        return $cached[$type];
290
+    }
291
+
292
+    /**
293
+     * @param ISimpleFolder $previewFolder
294
+     * @param ISimpleFile[] $previewFiles
295
+     * @param File $file
296
+     * @param string $mimeType
297
+     * @param string $prefix
298
+     * @return ISimpleFile
299
+     * @throws NotFoundException
300
+     */
301
+    private function getMaxPreview(ISimpleFolder $previewFolder, array $previewFiles, File $file, $mimeType, $prefix) {
302
+        // We don't know the max preview size, so we can't use getCachedPreview.
303
+        // It might have been generated with a higher resolution than the current value.
304
+        foreach ($previewFiles as $node) {
305
+            $name = $node->getName();
306
+            if (($prefix === '' || str_starts_with($name, $prefix)) && strpos($name, 'max')) {
307
+                return $node;
308
+            }
309
+        }
310
+
311
+        $maxWidth = $this->config->getSystemValueInt('preview_max_x', 4096);
312
+        $maxHeight = $this->config->getSystemValueInt('preview_max_y', 4096);
313
+
314
+        return $this->generateProviderPreview($previewFolder, $file, $maxWidth, $maxHeight, false, true, $mimeType, $prefix);
315
+    }
316
+
317
+    private function generateProviderPreview(ISimpleFolder $previewFolder, File $file, int $width, int $height, bool $crop, bool $max, string $mimeType, string $prefix) {
318
+        $previewProviders = $this->previewManager->getProviders();
319
+        foreach ($previewProviders as $supportedMimeType => $providers) {
320
+            // Filter out providers that does not support this mime
321
+            if (!preg_match($supportedMimeType, $mimeType)) {
322
+                continue;
323
+            }
324
+
325
+            foreach ($providers as $providerClosure) {
326
+                $provider = $this->helper->getProvider($providerClosure);
327
+                if (!($provider instanceof IProviderV2)) {
328
+                    continue;
329
+                }
330
+
331
+                if (!$provider->isAvailable($file)) {
332
+                    continue;
333
+                }
334
+
335
+                $previewConcurrency = $this->getNumConcurrentPreviews('preview_concurrency_new');
336
+                $sem = self::guardWithSemaphore(self::SEMAPHORE_ID_NEW, $previewConcurrency);
337
+                try {
338
+                    $this->logger->debug('Calling preview provider for {mimeType} with width={width}, height={height}', [
339
+                        'mimeType' => $mimeType,
340
+                        'width' => $width,
341
+                        'height' => $height,
342
+                    ]);
343
+                    $preview = $this->helper->getThumbnail($provider, $file, $width, $height);
344
+                } finally {
345
+                    self::unguardWithSemaphore($sem);
346
+                }
347
+
348
+                if (!($preview instanceof IImage)) {
349
+                    continue;
350
+                }
351
+
352
+                $path = $this->generatePath($preview->width(), $preview->height(), $crop, $max, $preview->dataMimeType(), $prefix);
353
+                try {
354
+                    $file = $previewFolder->newFile($path);
355
+                    if ($preview instanceof IStreamImage) {
356
+                        $file->putContent($preview->resource());
357
+                    } else {
358
+                        $file->putContent($preview->data());
359
+                    }
360
+                } catch (NotPermittedException $e) {
361
+                    throw new NotFoundException();
362
+                }
363
+
364
+                return $file;
365
+            }
366
+        }
367
+
368
+        throw new NotFoundException('No provider successfully handled the preview generation');
369
+    }
370
+
371
+    /**
372
+     * @param ISimpleFile $file
373
+     * @param string $prefix
374
+     * @return int[]
375
+     */
376
+    private function getPreviewSize(ISimpleFile $file, string $prefix = '') {
377
+        $size = explode('-', substr($file->getName(), strlen($prefix)));
378
+        return [(int)$size[0], (int)$size[1]];
379
+    }
380
+
381
+    /**
382
+     * @param int $width
383
+     * @param int $height
384
+     * @param bool $crop
385
+     * @param bool $max
386
+     * @param string $mimeType
387
+     * @param string $prefix
388
+     * @return string
389
+     */
390
+    private function generatePath($width, $height, $crop, $max, $mimeType, $prefix) {
391
+        $path = $prefix . (string)$width . '-' . (string)$height;
392
+        if ($crop) {
393
+            $path .= '-crop';
394
+        }
395
+        if ($max) {
396
+            $path .= '-max';
397
+        }
398
+
399
+        $ext = $this->getExtension($mimeType);
400
+        $path .= '.' . $ext;
401
+        return $path;
402
+    }
403
+
404
+
405
+    /**
406
+     * @param int $width
407
+     * @param int $height
408
+     * @param bool $crop
409
+     * @param string $mode
410
+     * @param int $maxWidth
411
+     * @param int $maxHeight
412
+     * @return int[]
413
+     */
414
+    private function calculateSize($width, $height, $crop, $mode, $maxWidth, $maxHeight) {
415
+        /*
416 416
 		 * If we are not cropping we have to make sure the requested image
417 417
 		 * respects the aspect ratio of the original.
418 418
 		 */
419
-		if (!$crop) {
420
-			$ratio = $maxHeight / $maxWidth;
419
+        if (!$crop) {
420
+            $ratio = $maxHeight / $maxWidth;
421 421
 
422
-			if ($width === -1) {
423
-				$width = $height / $ratio;
424
-			}
425
-			if ($height === -1) {
426
-				$height = $width * $ratio;
427
-			}
422
+            if ($width === -1) {
423
+                $width = $height / $ratio;
424
+            }
425
+            if ($height === -1) {
426
+                $height = $width * $ratio;
427
+            }
428 428
 
429
-			$ratioH = $height / $maxHeight;
430
-			$ratioW = $width / $maxWidth;
429
+            $ratioH = $height / $maxHeight;
430
+            $ratioW = $width / $maxWidth;
431 431
 
432
-			/*
432
+            /*
433 433
 			 * Fill means that the $height and $width are the max
434 434
 			 * Cover means min.
435 435
 			 */
436
-			if ($mode === IPreview::MODE_FILL) {
437
-				if ($ratioH > $ratioW) {
438
-					$height = $width * $ratio;
439
-				} else {
440
-					$width = $height / $ratio;
441
-				}
442
-			} elseif ($mode === IPreview::MODE_COVER) {
443
-				if ($ratioH > $ratioW) {
444
-					$width = $height / $ratio;
445
-				} else {
446
-					$height = $width * $ratio;
447
-				}
448
-			}
449
-		}
450
-
451
-		if ($height !== $maxHeight && $width !== $maxWidth) {
452
-			/*
436
+            if ($mode === IPreview::MODE_FILL) {
437
+                if ($ratioH > $ratioW) {
438
+                    $height = $width * $ratio;
439
+                } else {
440
+                    $width = $height / $ratio;
441
+                }
442
+            } elseif ($mode === IPreview::MODE_COVER) {
443
+                if ($ratioH > $ratioW) {
444
+                    $width = $height / $ratio;
445
+                } else {
446
+                    $height = $width * $ratio;
447
+                }
448
+            }
449
+        }
450
+
451
+        if ($height !== $maxHeight && $width !== $maxWidth) {
452
+            /*
453 453
 			 * Scale to the nearest power of four
454 454
 			 */
455
-			$pow4height = 4 ** ceil(log($height) / log(4));
456
-			$pow4width = 4 ** ceil(log($width) / log(4));
457
-
458
-			// Minimum size is 64
459
-			$pow4height = max($pow4height, 64);
460
-			$pow4width = max($pow4width, 64);
461
-
462
-			$ratioH = $height / $pow4height;
463
-			$ratioW = $width / $pow4width;
464
-
465
-			if ($ratioH < $ratioW) {
466
-				$width = $pow4width;
467
-				$height /= $ratioW;
468
-			} else {
469
-				$height = $pow4height;
470
-				$width /= $ratioH;
471
-			}
472
-		}
473
-
474
-		/*
455
+            $pow4height = 4 ** ceil(log($height) / log(4));
456
+            $pow4width = 4 ** ceil(log($width) / log(4));
457
+
458
+            // Minimum size is 64
459
+            $pow4height = max($pow4height, 64);
460
+            $pow4width = max($pow4width, 64);
461
+
462
+            $ratioH = $height / $pow4height;
463
+            $ratioW = $width / $pow4width;
464
+
465
+            if ($ratioH < $ratioW) {
466
+                $width = $pow4width;
467
+                $height /= $ratioW;
468
+            } else {
469
+                $height = $pow4height;
470
+                $width /= $ratioH;
471
+            }
472
+        }
473
+
474
+        /*
475 475
 		 * Make sure the requested height and width fall within the max
476 476
 		 * of the preview.
477 477
 		 */
478
-		if ($height > $maxHeight) {
479
-			$ratio = $height / $maxHeight;
480
-			$height = $maxHeight;
481
-			$width /= $ratio;
482
-		}
483
-		if ($width > $maxWidth) {
484
-			$ratio = $width / $maxWidth;
485
-			$width = $maxWidth;
486
-			$height /= $ratio;
487
-		}
488
-
489
-		return [(int)round($width), (int)round($height)];
490
-	}
491
-
492
-	/**
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
502
-	 * @throws NotFoundException
503
-	 * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
504
-	 */
505
-	private function generatePreview(ISimpleFolder $previewFolder, IImage $maxPreview, $width, $height, $crop, $maxWidth, $maxHeight, $prefix) {
506
-		$preview = $maxPreview;
507
-		if (!$preview->valid()) {
508
-			throw new \InvalidArgumentException('Failed to generate preview, failed to load image');
509
-		}
510
-
511
-		$previewConcurrency = $this->getNumConcurrentPreviews('preview_concurrency_new');
512
-		$sem = self::guardWithSemaphore(self::SEMAPHORE_ID_NEW, $previewConcurrency);
513
-		try {
514
-			if ($crop) {
515
-				if ($height !== $preview->height() && $width !== $preview->width()) {
516
-					//Resize
517
-					$widthR = $preview->width() / $width;
518
-					$heightR = $preview->height() / $height;
519
-
520
-					if ($widthR > $heightR) {
521
-						$scaleH = $height;
522
-						$scaleW = $maxWidth / $heightR;
523
-					} else {
524
-						$scaleH = $maxHeight / $widthR;
525
-						$scaleW = $width;
526
-					}
527
-					$preview = $preview->preciseResizeCopy((int)round($scaleW), (int)round($scaleH));
528
-				}
529
-				$cropX = (int)floor(abs($width - $preview->width()) * 0.5);
530
-				$cropY = (int)floor(abs($height - $preview->height()) * 0.5);
531
-				$preview = $preview->cropCopy($cropX, $cropY, $width, $height);
532
-			} else {
533
-				$preview = $maxPreview->resizeCopy(max($width, $height));
534
-			}
535
-		} finally {
536
-			self::unguardWithSemaphore($sem);
537
-		}
538
-
539
-
540
-		$path = $this->generatePath($width, $height, $crop, false, $preview->dataMimeType(), $prefix);
541
-		try {
542
-			$file = $previewFolder->newFile($path);
543
-			$file->putContent($preview->data());
544
-		} catch (NotPermittedException $e) {
545
-			throw new NotFoundException();
546
-		}
547
-
548
-		return $file;
549
-	}
550
-
551
-	/**
552
-	 * @param ISimpleFile[] $files Array of FileInfo, as the result of getDirectoryListing()
553
-	 * @param int $width
554
-	 * @param int $height
555
-	 * @param bool $crop
556
-	 * @param string $mimeType
557
-	 * @param string $prefix
558
-	 * @return ISimpleFile
559
-	 *
560
-	 * @throws NotFoundException
561
-	 */
562
-	private function getCachedPreview($files, $width, $height, $crop, $mimeType, $prefix) {
563
-		$path = $this->generatePath($width, $height, $crop, false, $mimeType, $prefix);
564
-		foreach ($files as $file) {
565
-			if ($file->getName() === $path) {
566
-				$this->logger->debug('Found cached preview: {path}', ['path' => $path]);
567
-				return $file;
568
-			}
569
-		}
570
-		throw new NotFoundException();
571
-	}
572
-
573
-	/**
574
-	 * Get the specific preview folder for this file
575
-	 *
576
-	 * @param File $file
577
-	 * @return ISimpleFolder
578
-	 *
579
-	 * @throws InvalidPathException
580
-	 * @throws NotFoundException
581
-	 * @throws NotPermittedException
582
-	 */
583
-	private function getPreviewFolder(File $file) {
584
-		// Obtain file id outside of try catch block to prevent the creation of an existing folder
585
-		$fileId = (string)$file->getId();
586
-
587
-		try {
588
-			$folder = $this->appData->getFolder($fileId);
589
-		} catch (NotFoundException $e) {
590
-			$folder = $this->appData->newFolder($fileId);
591
-		}
592
-
593
-		return $folder;
594
-	}
595
-
596
-	/**
597
-	 * @param string $mimeType
598
-	 * @return null|string
599
-	 * @throws \InvalidArgumentException
600
-	 */
601
-	private function getExtension($mimeType) {
602
-		switch ($mimeType) {
603
-			case 'image/png':
604
-				return 'png';
605
-			case 'image/jpeg':
606
-				return 'jpg';
607
-			case 'image/webp':
608
-				return 'webp';
609
-			case 'image/gif':
610
-				return 'gif';
611
-			default:
612
-				throw new \InvalidArgumentException('Not a valid mimetype: "' . $mimeType . '"');
613
-		}
614
-	}
478
+        if ($height > $maxHeight) {
479
+            $ratio = $height / $maxHeight;
480
+            $height = $maxHeight;
481
+            $width /= $ratio;
482
+        }
483
+        if ($width > $maxWidth) {
484
+            $ratio = $width / $maxWidth;
485
+            $width = $maxWidth;
486
+            $height /= $ratio;
487
+        }
488
+
489
+        return [(int)round($width), (int)round($height)];
490
+    }
491
+
492
+    /**
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
502
+     * @throws NotFoundException
503
+     * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
504
+     */
505
+    private function generatePreview(ISimpleFolder $previewFolder, IImage $maxPreview, $width, $height, $crop, $maxWidth, $maxHeight, $prefix) {
506
+        $preview = $maxPreview;
507
+        if (!$preview->valid()) {
508
+            throw new \InvalidArgumentException('Failed to generate preview, failed to load image');
509
+        }
510
+
511
+        $previewConcurrency = $this->getNumConcurrentPreviews('preview_concurrency_new');
512
+        $sem = self::guardWithSemaphore(self::SEMAPHORE_ID_NEW, $previewConcurrency);
513
+        try {
514
+            if ($crop) {
515
+                if ($height !== $preview->height() && $width !== $preview->width()) {
516
+                    //Resize
517
+                    $widthR = $preview->width() / $width;
518
+                    $heightR = $preview->height() / $height;
519
+
520
+                    if ($widthR > $heightR) {
521
+                        $scaleH = $height;
522
+                        $scaleW = $maxWidth / $heightR;
523
+                    } else {
524
+                        $scaleH = $maxHeight / $widthR;
525
+                        $scaleW = $width;
526
+                    }
527
+                    $preview = $preview->preciseResizeCopy((int)round($scaleW), (int)round($scaleH));
528
+                }
529
+                $cropX = (int)floor(abs($width - $preview->width()) * 0.5);
530
+                $cropY = (int)floor(abs($height - $preview->height()) * 0.5);
531
+                $preview = $preview->cropCopy($cropX, $cropY, $width, $height);
532
+            } else {
533
+                $preview = $maxPreview->resizeCopy(max($width, $height));
534
+            }
535
+        } finally {
536
+            self::unguardWithSemaphore($sem);
537
+        }
538
+
539
+
540
+        $path = $this->generatePath($width, $height, $crop, false, $preview->dataMimeType(), $prefix);
541
+        try {
542
+            $file = $previewFolder->newFile($path);
543
+            $file->putContent($preview->data());
544
+        } catch (NotPermittedException $e) {
545
+            throw new NotFoundException();
546
+        }
547
+
548
+        return $file;
549
+    }
550
+
551
+    /**
552
+     * @param ISimpleFile[] $files Array of FileInfo, as the result of getDirectoryListing()
553
+     * @param int $width
554
+     * @param int $height
555
+     * @param bool $crop
556
+     * @param string $mimeType
557
+     * @param string $prefix
558
+     * @return ISimpleFile
559
+     *
560
+     * @throws NotFoundException
561
+     */
562
+    private function getCachedPreview($files, $width, $height, $crop, $mimeType, $prefix) {
563
+        $path = $this->generatePath($width, $height, $crop, false, $mimeType, $prefix);
564
+        foreach ($files as $file) {
565
+            if ($file->getName() === $path) {
566
+                $this->logger->debug('Found cached preview: {path}', ['path' => $path]);
567
+                return $file;
568
+            }
569
+        }
570
+        throw new NotFoundException();
571
+    }
572
+
573
+    /**
574
+     * Get the specific preview folder for this file
575
+     *
576
+     * @param File $file
577
+     * @return ISimpleFolder
578
+     *
579
+     * @throws InvalidPathException
580
+     * @throws NotFoundException
581
+     * @throws NotPermittedException
582
+     */
583
+    private function getPreviewFolder(File $file) {
584
+        // Obtain file id outside of try catch block to prevent the creation of an existing folder
585
+        $fileId = (string)$file->getId();
586
+
587
+        try {
588
+            $folder = $this->appData->getFolder($fileId);
589
+        } catch (NotFoundException $e) {
590
+            $folder = $this->appData->newFolder($fileId);
591
+        }
592
+
593
+        return $folder;
594
+    }
595
+
596
+    /**
597
+     * @param string $mimeType
598
+     * @return null|string
599
+     * @throws \InvalidArgumentException
600
+     */
601
+    private function getExtension($mimeType) {
602
+        switch ($mimeType) {
603
+            case 'image/png':
604
+                return 'png';
605
+            case 'image/jpeg':
606
+                return 'jpg';
607
+            case 'image/webp':
608
+                return 'webp';
609
+            case 'image/gif':
610
+                return 'gif';
611
+            default:
612
+                throw new \InvalidArgumentException('Not a valid mimetype: "' . $mimeType . '"');
613
+        }
614
+    }
615 615
 }
Please login to merge, or discard this patch.
tests/lib/Preview/GeneratorTest.php 2 patches
Indentation   +450 added lines, -450 removed lines patch added patch discarded remove patch
@@ -22,454 +22,454 @@
 block discarded – undo
22 22
 use Psr\Log\LoggerInterface;
23 23
 
24 24
 class GeneratorTest extends \Test\TestCase {
25
-	/** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */
26
-	private $config;
27
-
28
-	/** @var IPreview|\PHPUnit\Framework\MockObject\MockObject */
29
-	private $previewManager;
30
-
31
-	/** @var IAppData|\PHPUnit\Framework\MockObject\MockObject */
32
-	private $appData;
33
-
34
-	/** @var GeneratorHelper|\PHPUnit\Framework\MockObject\MockObject */
35
-	private $helper;
36
-
37
-	/** @var IEventDispatcher|\PHPUnit\Framework\MockObject\MockObject */
38
-	private $eventDispatcher;
39
-
40
-	/** @var Generator */
41
-	private $generator;
42
-
43
-	private LoggerInterface|\PHPUnit\Framework\MockObject\MockObject $logger;
44
-
45
-	protected function setUp(): void {
46
-		parent::setUp();
47
-
48
-		$this->config = $this->createMock(IConfig::class);
49
-		$this->previewManager = $this->createMock(IPreview::class);
50
-		$this->appData = $this->createMock(IAppData::class);
51
-		$this->helper = $this->createMock(GeneratorHelper::class);
52
-		$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
53
-		$this->logger = $this->createMock(LoggerInterface::class);
54
-
55
-		$this->generator = new Generator(
56
-			$this->config,
57
-			$this->previewManager,
58
-			$this->appData,
59
-			$this->helper,
60
-			$this->eventDispatcher,
61
-			$this->logger,
62
-		);
63
-	}
64
-
65
-	public function testGetCachedPreview(): void {
66
-		$file = $this->createMock(File::class);
67
-		$file->method('isReadable')
68
-			->willReturn(true);
69
-		$file->method('getMimeType')
70
-			->willReturn('myMimeType');
71
-		$file->method('getId')
72
-			->willReturn(42);
73
-
74
-		$this->previewManager->method('isMimeSupported')
75
-			->with($this->equalTo('myMimeType'))
76
-			->willReturn(true);
77
-
78
-		$previewFolder = $this->createMock(ISimpleFolder::class);
79
-		$this->appData->method('getFolder')
80
-			->with($this->equalTo(42))
81
-			->willReturn($previewFolder);
82
-
83
-		$maxPreview = $this->createMock(ISimpleFile::class);
84
-		$maxPreview->method('getName')
85
-			->willReturn('1000-1000-max.png');
86
-		$maxPreview->method('getSize')->willReturn(1000);
87
-		$maxPreview->method('getMimeType')
88
-			->willReturn('image/png');
89
-
90
-		$previewFile = $this->createMock(ISimpleFile::class);
91
-		$previewFile->method('getSize')->willReturn(1000);
92
-		$previewFile->method('getName')->willReturn('256-256.png');
93
-
94
-		$previewFolder->method('getDirectoryListing')
95
-			->willReturn([$maxPreview, $previewFile]);
96
-
97
-		$this->eventDispatcher->expects($this->once())
98
-			->method('dispatchTyped')
99
-			->with(new BeforePreviewFetchedEvent($file, 100, 100, false, IPreview::MODE_FILL, null));
100
-
101
-		$result = $this->generator->getPreview($file, 100, 100);
102
-		$this->assertSame($previewFile, $result);
103
-	}
104
-
105
-	public function testGetNewPreview(): void {
106
-		$file = $this->createMock(File::class);
107
-		$file->method('isReadable')
108
-			->willReturn(true);
109
-		$file->method('getMimeType')
110
-			->willReturn('myMimeType');
111
-		$file->method('getId')
112
-			->willReturn(42);
113
-
114
-		$this->previewManager->method('isMimeSupported')
115
-			->with($this->equalTo('myMimeType'))
116
-			->willReturn(true);
117
-
118
-		$previewFolder = $this->createMock(ISimpleFolder::class);
119
-		$this->appData->method('getFolder')
120
-			->with($this->equalTo(42))
121
-			->willThrowException(new NotFoundException());
122
-
123
-		$this->appData->method('newFolder')
124
-			->with($this->equalTo(42))
125
-			->willReturn($previewFolder);
126
-
127
-		$this->config->method('getSystemValue')
128
-			->willReturnCallback(function ($key, $default) {
129
-				return $default;
130
-			});
131
-
132
-		$this->config->method('getSystemValueInt')
133
-			->willReturnCallback(function ($key, $default) {
134
-				return $default;
135
-			});
136
-
137
-		$invalidProvider = $this->createMock(IProviderV2::class);
138
-		$invalidProvider->method('isAvailable')
139
-			->willReturn(true);
140
-		$unavailableProvider = $this->createMock(IProviderV2::class);
141
-		$unavailableProvider->method('isAvailable')
142
-			->willReturn(false);
143
-		$validProvider = $this->createMock(IProviderV2::class);
144
-		$validProvider->method('isAvailable')
145
-			->with($file)
146
-			->willReturn(true);
147
-
148
-		$this->previewManager->method('getProviders')
149
-			->willReturn([
150
-				'/image\/png/' => ['wrongProvider'],
151
-				'/myMimeType/' => ['brokenProvider', 'invalidProvider', 'unavailableProvider', 'validProvider'],
152
-			]);
153
-
154
-		$this->helper->method('getProvider')
155
-			->willReturnCallback(function ($provider) use ($invalidProvider, $validProvider, $unavailableProvider) {
156
-				if ($provider === 'wrongProvider') {
157
-					$this->fail('Wrongprovider should not be constructed!');
158
-				} elseif ($provider === 'brokenProvider') {
159
-					return false;
160
-				} elseif ($provider === 'invalidProvider') {
161
-					return $invalidProvider;
162
-				} elseif ($provider === 'validProvider') {
163
-					return $validProvider;
164
-				} elseif ($provider === 'unavailableProvider') {
165
-					return $unavailableProvider;
166
-				}
167
-				$this->fail('Unexpected provider requested');
168
-			});
169
-
170
-		$image = $this->createMock(IImage::class);
171
-		$image->method('width')->willReturn(2048);
172
-		$image->method('height')->willReturn(2048);
173
-		$image->method('valid')->willReturn(true);
174
-		$image->method('dataMimeType')->willReturn('image/png');
175
-
176
-		$this->helper->method('getThumbnail')
177
-			->willReturnCallback(function ($provider, $file, $x, $y) use ($invalidProvider, $validProvider, $image) {
178
-				if ($provider === $validProvider) {
179
-					return $image;
180
-				} else {
181
-					return false;
182
-				}
183
-			});
184
-
185
-		$image->method('data')
186
-			->willReturn('my data');
187
-
188
-		$maxPreview = $this->createMock(ISimpleFile::class);
189
-		$maxPreview->method('getName')->willReturn('2048-2048-max.png');
190
-		$maxPreview->method('getMimeType')->willReturn('image/png');
191
-		$maxPreview->method('getSize')->willReturn(1000);
192
-
193
-		$previewFile = $this->createMock(ISimpleFile::class);
194
-		$previewFile->method('getSize')->willReturn(1000);
195
-
196
-		$previewFolder->method('getDirectoryListing')
197
-			->willReturn([]);
198
-		$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'));
211
-
212
-		$previewFolder->method('getFile')
213
-			->with($this->equalTo('256-256.png'))
214
-			->willThrowException(new NotFoundException());
215
-
216
-		$image = $this->getMockImage(2048, 2048, 'my resized data');
217
-		$this->helper->method('getImage')
218
-			->with($this->equalTo($maxPreview))
219
-			->willReturn($image);
220
-
221
-		$previewFile->expects($this->once())
222
-			->method('putContent')
223
-			->with('my resized data');
224
-
225
-		$this->eventDispatcher->expects($this->once())
226
-			->method('dispatchTyped')
227
-			->with(new BeforePreviewFetchedEvent($file, 100, 100, false, IPreview::MODE_FILL, null));
228
-
229
-		$result = $this->generator->getPreview($file, 100, 100);
230
-		$this->assertSame($previewFile, $result);
231
-	}
232
-
233
-	public function testInvalidMimeType(): void {
234
-		$this->expectException(NotFoundException::class);
235
-
236
-		$file = $this->createMock(File::class);
237
-		$file->method('isReadable')
238
-			->willReturn(true);
239
-		$file->method('getId')
240
-			->willReturn(42);
241
-
242
-		$this->previewManager->method('isMimeSupported')
243
-			->with('invalidType')
244
-			->willReturn(false);
245
-
246
-		$previewFolder = $this->createMock(ISimpleFolder::class);
247
-		$this->appData->method('getFolder')
248
-			->with($this->equalTo(42))
249
-			->willReturn($previewFolder);
250
-
251
-		$maxPreview = $this->createMock(ISimpleFile::class);
252
-		$maxPreview->method('getName')
253
-			->willReturn('2048-2048-max.png');
254
-		$maxPreview->method('getMimeType')
255
-			->willReturn('image/png');
256
-
257
-		$previewFolder->method('getDirectoryListing')
258
-			->willReturn([$maxPreview]);
259
-
260
-		$previewFolder->method('getFile')
261
-			->with($this->equalTo('1024-512-crop.png'))
262
-			->willThrowException(new NotFoundException());
263
-
264
-		$this->eventDispatcher->expects($this->once())
265
-			->method('dispatchTyped')
266
-			->with(new BeforePreviewFetchedEvent($file, 1024, 512, true, IPreview::MODE_COVER, 'invalidType'));
267
-
268
-		$this->generator->getPreview($file, 1024, 512, true, IPreview::MODE_COVER, 'invalidType');
269
-	}
270
-
271
-	public function testReturnCachedPreviewsWithoutCheckingSupportedMimetype(): void {
272
-		$file = $this->createMock(File::class);
273
-		$file->method('isReadable')
274
-			->willReturn(true);
275
-		$file->method('getId')
276
-			->willReturn(42);
277
-
278
-
279
-		$previewFolder = $this->createMock(ISimpleFolder::class);
280
-		$this->appData->method('getFolder')
281
-			->with($this->equalTo(42))
282
-			->willReturn($previewFolder);
283
-
284
-		$maxPreview = $this->createMock(ISimpleFile::class);
285
-		$maxPreview->method('getName')
286
-			->willReturn('2048-2048-max.png');
287
-		$maxPreview->method('getSize')->willReturn(1000);
288
-		$maxPreview->method('getMimeType')
289
-			->willReturn('image/png');
290
-
291
-		$preview = $this->createMock(ISimpleFile::class);
292
-		$preview->method('getSize')->willReturn(1000);
293
-		$preview->method('getName')->willReturn('1024-512-crop.png');
294
-
295
-		$previewFolder->method('getDirectoryListing')
296
-			->willReturn([$maxPreview, $preview]);
297
-
298
-		$this->previewManager->expects($this->never())
299
-			->method('isMimeSupported');
300
-
301
-		$this->eventDispatcher->expects($this->once())
302
-			->method('dispatchTyped')
303
-			->with(new BeforePreviewFetchedEvent($file, 1024, 512, true, IPreview::MODE_COVER, 'invalidType'));
304
-
305
-		$result = $this->generator->getPreview($file, 1024, 512, true, IPreview::MODE_COVER, 'invalidType');
306
-		$this->assertSame($preview, $result);
307
-	}
308
-
309
-	public function testNoProvider(): void {
310
-		$file = $this->createMock(File::class);
311
-		$file->method('isReadable')
312
-			->willReturn(true);
313
-		$file->method('getMimeType')
314
-			->willReturn('myMimeType');
315
-		$file->method('getId')
316
-			->willReturn(42);
317
-
318
-		$previewFolder = $this->createMock(ISimpleFolder::class);
319
-		$this->appData->method('getFolder')
320
-			->with($this->equalTo(42))
321
-			->willReturn($previewFolder);
322
-
323
-		$previewFolder->method('getDirectoryListing')
324
-			->willReturn([]);
325
-
326
-		$this->previewManager->method('getProviders')
327
-			->willReturn([]);
328
-
329
-		$this->eventDispatcher->expects($this->once())
330
-			->method('dispatchTyped')
331
-			->with(new BeforePreviewFetchedEvent($file, 100, 100, false, IPreview::MODE_FILL, null));
332
-
333
-		$this->expectException(NotFoundException::class);
334
-		$this->generator->getPreview($file, 100, 100);
335
-	}
336
-
337
-	private function getMockImage($width, $height, $data = null) {
338
-		$image = $this->createMock(IImage::class);
339
-		$image->method('height')->willReturn($width);
340
-		$image->method('width')->willReturn($height);
341
-		$image->method('valid')->willReturn(true);
342
-		$image->method('dataMimeType')->willReturn('image/png');
343
-		$image->method('data')->willReturn($data);
344
-
345
-		$image->method('resizeCopy')->willReturnCallback(function ($size) use ($data) {
346
-			return $this->getMockImage($size, $size, $data);
347
-		});
348
-		$image->method('preciseResizeCopy')->willReturnCallback(function ($width, $height) use ($data) {
349
-			return $this->getMockImage($width, $height, $data);
350
-		});
351
-		$image->method('cropCopy')->willReturnCallback(function ($x, $y, $width, $height) use ($data) {
352
-			return $this->getMockImage($width, $height, $data);
353
-		});
354
-
355
-		return $image;
356
-	}
357
-
358
-	public function dataSize() {
359
-		return [
360
-			[1024, 2048, 512, 512, false, IPreview::MODE_FILL, 256, 512],
361
-			[1024, 2048, 512, 512, false, IPreview::MODE_COVER, 512, 1024],
362
-			[1024, 2048, 512, 512, true, IPreview::MODE_FILL, 1024, 1024],
363
-			[1024, 2048, 512, 512, true, IPreview::MODE_COVER, 1024, 1024],
364
-
365
-			[1024, 2048, -1, 512, false, IPreview::MODE_COVER, 256, 512],
366
-			[1024, 2048, 512, -1, false, IPreview::MODE_FILL, 512, 1024],
367
-
368
-			[1024, 2048, 250, 1100, true, IPreview::MODE_COVER, 256, 1126],
369
-			[1024, 1100, 250, 1100, true, IPreview::MODE_COVER, 250, 1100],
370
-
371
-			[1024, 2048, 4096, 2048, false, IPreview::MODE_FILL, 1024, 2048],
372
-			[1024, 2048, 4096, 2048, false, IPreview::MODE_COVER, 1024, 2048],
373
-
374
-
375
-			[2048, 1024, 512, 512, false, IPreview::MODE_FILL, 512, 256],
376
-			[2048, 1024, 512, 512, false, IPreview::MODE_COVER, 1024, 512],
377
-			[2048, 1024, 512, 512, true, IPreview::MODE_FILL, 1024, 1024],
378
-			[2048, 1024, 512, 512, true, IPreview::MODE_COVER, 1024, 1024],
379
-
380
-			[2048, 1024, -1, 512, false, IPreview::MODE_FILL, 1024, 512],
381
-			[2048, 1024, 512, -1, false, IPreview::MODE_COVER, 512, 256],
382
-
383
-			[2048, 1024, 4096, 1024, true, IPreview::MODE_FILL, 2048, 512],
384
-			[2048, 1024, 4096, 1024, true, IPreview::MODE_COVER, 2048, 512],
385
-
386
-			//Test minimum size
387
-			[2048, 1024, 32, 32, false, IPreview::MODE_FILL, 64, 32],
388
-			[2048, 1024, 32, 32, false, IPreview::MODE_COVER, 64, 32],
389
-			[2048, 1024, 32, 32, true, IPreview::MODE_FILL, 64, 64],
390
-			[2048, 1024, 32, 32, true, IPreview::MODE_COVER, 64, 64],
391
-		];
392
-	}
393
-
394
-	/**
395
-	 * @dataProvider dataSize
396
-	 *
397
-	 * @param int $maxX
398
-	 * @param int $maxY
399
-	 * @param int $reqX
400
-	 * @param int $reqY
401
-	 * @param bool $crop
402
-	 * @param string $mode
403
-	 * @param int $expectedX
404
-	 * @param int $expectedY
405
-	 */
406
-	public function testCorrectSize($maxX, $maxY, $reqX, $reqY, $crop, $mode, $expectedX, $expectedY): void {
407
-		$file = $this->createMock(File::class);
408
-		$file->method('isReadable')
409
-			->willReturn(true);
410
-		$file->method('getMimeType')
411
-			->willReturn('myMimeType');
412
-		$file->method('getId')
413
-			->willReturn(42);
414
-
415
-		$this->previewManager->method('isMimeSupported')
416
-			->with($this->equalTo('myMimeType'))
417
-			->willReturn(true);
418
-
419
-		$previewFolder = $this->createMock(ISimpleFolder::class);
420
-		$this->appData->method('getFolder')
421
-			->with($this->equalTo(42))
422
-			->willReturn($previewFolder);
423
-
424
-		$maxPreview = $this->createMock(ISimpleFile::class);
425
-		$maxPreview->method('getName')
426
-			->willReturn($maxX . '-' . $maxY . '-max.png');
427
-		$maxPreview->method('getMimeType')
428
-			->willReturn('image/png');
429
-		$maxPreview->method('getSize')->willReturn(1000);
430
-
431
-		$previewFolder->method('getDirectoryListing')
432
-			->willReturn([$maxPreview]);
433
-
434
-		$filename = $expectedX . '-' . $expectedY;
435
-		if ($crop) {
436
-			$filename .= '-crop';
437
-		}
438
-		$filename .= '.png';
439
-		$previewFolder->method('getFile')
440
-			->with($this->equalTo($filename))
441
-			->willThrowException(new NotFoundException());
442
-
443
-		$image = $this->getMockImage($maxX, $maxY);
444
-		$this->helper->method('getImage')
445
-			->with($this->equalTo($maxPreview))
446
-			->willReturn($image);
447
-
448
-		$preview = $this->createMock(ISimpleFile::class);
449
-		$preview->method('getSize')->willReturn(1000);
450
-		$previewFolder->method('newFile')
451
-			->with($this->equalTo($filename))
452
-			->willReturn($preview);
453
-
454
-		$this->eventDispatcher->expects($this->once())
455
-			->method('dispatchTyped')
456
-			->with(new BeforePreviewFetchedEvent($file, $reqX, $reqY, $crop, $mode, null));
457
-
458
-		$result = $this->generator->getPreview($file, $reqX, $reqY, $crop, $mode);
459
-		if ($expectedX === $maxX && $expectedY === $maxY) {
460
-			$this->assertSame($maxPreview, $result);
461
-		} else {
462
-			$this->assertSame($preview, $result);
463
-		}
464
-	}
465
-
466
-	public function testUnreadbleFile(): void {
467
-		$file = $this->createMock(File::class);
468
-		$file->method('isReadable')
469
-			->willReturn(false);
470
-
471
-		$this->expectException(NotFoundException::class);
472
-
473
-		$this->generator->getPreview($file, 100, 100, false);
474
-	}
25
+    /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */
26
+    private $config;
27
+
28
+    /** @var IPreview|\PHPUnit\Framework\MockObject\MockObject */
29
+    private $previewManager;
30
+
31
+    /** @var IAppData|\PHPUnit\Framework\MockObject\MockObject */
32
+    private $appData;
33
+
34
+    /** @var GeneratorHelper|\PHPUnit\Framework\MockObject\MockObject */
35
+    private $helper;
36
+
37
+    /** @var IEventDispatcher|\PHPUnit\Framework\MockObject\MockObject */
38
+    private $eventDispatcher;
39
+
40
+    /** @var Generator */
41
+    private $generator;
42
+
43
+    private LoggerInterface|\PHPUnit\Framework\MockObject\MockObject $logger;
44
+
45
+    protected function setUp(): void {
46
+        parent::setUp();
47
+
48
+        $this->config = $this->createMock(IConfig::class);
49
+        $this->previewManager = $this->createMock(IPreview::class);
50
+        $this->appData = $this->createMock(IAppData::class);
51
+        $this->helper = $this->createMock(GeneratorHelper::class);
52
+        $this->eventDispatcher = $this->createMock(IEventDispatcher::class);
53
+        $this->logger = $this->createMock(LoggerInterface::class);
54
+
55
+        $this->generator = new Generator(
56
+            $this->config,
57
+            $this->previewManager,
58
+            $this->appData,
59
+            $this->helper,
60
+            $this->eventDispatcher,
61
+            $this->logger,
62
+        );
63
+    }
64
+
65
+    public function testGetCachedPreview(): void {
66
+        $file = $this->createMock(File::class);
67
+        $file->method('isReadable')
68
+            ->willReturn(true);
69
+        $file->method('getMimeType')
70
+            ->willReturn('myMimeType');
71
+        $file->method('getId')
72
+            ->willReturn(42);
73
+
74
+        $this->previewManager->method('isMimeSupported')
75
+            ->with($this->equalTo('myMimeType'))
76
+            ->willReturn(true);
77
+
78
+        $previewFolder = $this->createMock(ISimpleFolder::class);
79
+        $this->appData->method('getFolder')
80
+            ->with($this->equalTo(42))
81
+            ->willReturn($previewFolder);
82
+
83
+        $maxPreview = $this->createMock(ISimpleFile::class);
84
+        $maxPreview->method('getName')
85
+            ->willReturn('1000-1000-max.png');
86
+        $maxPreview->method('getSize')->willReturn(1000);
87
+        $maxPreview->method('getMimeType')
88
+            ->willReturn('image/png');
89
+
90
+        $previewFile = $this->createMock(ISimpleFile::class);
91
+        $previewFile->method('getSize')->willReturn(1000);
92
+        $previewFile->method('getName')->willReturn('256-256.png');
93
+
94
+        $previewFolder->method('getDirectoryListing')
95
+            ->willReturn([$maxPreview, $previewFile]);
96
+
97
+        $this->eventDispatcher->expects($this->once())
98
+            ->method('dispatchTyped')
99
+            ->with(new BeforePreviewFetchedEvent($file, 100, 100, false, IPreview::MODE_FILL, null));
100
+
101
+        $result = $this->generator->getPreview($file, 100, 100);
102
+        $this->assertSame($previewFile, $result);
103
+    }
104
+
105
+    public function testGetNewPreview(): void {
106
+        $file = $this->createMock(File::class);
107
+        $file->method('isReadable')
108
+            ->willReturn(true);
109
+        $file->method('getMimeType')
110
+            ->willReturn('myMimeType');
111
+        $file->method('getId')
112
+            ->willReturn(42);
113
+
114
+        $this->previewManager->method('isMimeSupported')
115
+            ->with($this->equalTo('myMimeType'))
116
+            ->willReturn(true);
117
+
118
+        $previewFolder = $this->createMock(ISimpleFolder::class);
119
+        $this->appData->method('getFolder')
120
+            ->with($this->equalTo(42))
121
+            ->willThrowException(new NotFoundException());
122
+
123
+        $this->appData->method('newFolder')
124
+            ->with($this->equalTo(42))
125
+            ->willReturn($previewFolder);
126
+
127
+        $this->config->method('getSystemValue')
128
+            ->willReturnCallback(function ($key, $default) {
129
+                return $default;
130
+            });
131
+
132
+        $this->config->method('getSystemValueInt')
133
+            ->willReturnCallback(function ($key, $default) {
134
+                return $default;
135
+            });
136
+
137
+        $invalidProvider = $this->createMock(IProviderV2::class);
138
+        $invalidProvider->method('isAvailable')
139
+            ->willReturn(true);
140
+        $unavailableProvider = $this->createMock(IProviderV2::class);
141
+        $unavailableProvider->method('isAvailable')
142
+            ->willReturn(false);
143
+        $validProvider = $this->createMock(IProviderV2::class);
144
+        $validProvider->method('isAvailable')
145
+            ->with($file)
146
+            ->willReturn(true);
147
+
148
+        $this->previewManager->method('getProviders')
149
+            ->willReturn([
150
+                '/image\/png/' => ['wrongProvider'],
151
+                '/myMimeType/' => ['brokenProvider', 'invalidProvider', 'unavailableProvider', 'validProvider'],
152
+            ]);
153
+
154
+        $this->helper->method('getProvider')
155
+            ->willReturnCallback(function ($provider) use ($invalidProvider, $validProvider, $unavailableProvider) {
156
+                if ($provider === 'wrongProvider') {
157
+                    $this->fail('Wrongprovider should not be constructed!');
158
+                } elseif ($provider === 'brokenProvider') {
159
+                    return false;
160
+                } elseif ($provider === 'invalidProvider') {
161
+                    return $invalidProvider;
162
+                } elseif ($provider === 'validProvider') {
163
+                    return $validProvider;
164
+                } elseif ($provider === 'unavailableProvider') {
165
+                    return $unavailableProvider;
166
+                }
167
+                $this->fail('Unexpected provider requested');
168
+            });
169
+
170
+        $image = $this->createMock(IImage::class);
171
+        $image->method('width')->willReturn(2048);
172
+        $image->method('height')->willReturn(2048);
173
+        $image->method('valid')->willReturn(true);
174
+        $image->method('dataMimeType')->willReturn('image/png');
175
+
176
+        $this->helper->method('getThumbnail')
177
+            ->willReturnCallback(function ($provider, $file, $x, $y) use ($invalidProvider, $validProvider, $image) {
178
+                if ($provider === $validProvider) {
179
+                    return $image;
180
+                } else {
181
+                    return false;
182
+                }
183
+            });
184
+
185
+        $image->method('data')
186
+            ->willReturn('my data');
187
+
188
+        $maxPreview = $this->createMock(ISimpleFile::class);
189
+        $maxPreview->method('getName')->willReturn('2048-2048-max.png');
190
+        $maxPreview->method('getMimeType')->willReturn('image/png');
191
+        $maxPreview->method('getSize')->willReturn(1000);
192
+
193
+        $previewFile = $this->createMock(ISimpleFile::class);
194
+        $previewFile->method('getSize')->willReturn(1000);
195
+
196
+        $previewFolder->method('getDirectoryListing')
197
+            ->willReturn([]);
198
+        $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'));
211
+
212
+        $previewFolder->method('getFile')
213
+            ->with($this->equalTo('256-256.png'))
214
+            ->willThrowException(new NotFoundException());
215
+
216
+        $image = $this->getMockImage(2048, 2048, 'my resized data');
217
+        $this->helper->method('getImage')
218
+            ->with($this->equalTo($maxPreview))
219
+            ->willReturn($image);
220
+
221
+        $previewFile->expects($this->once())
222
+            ->method('putContent')
223
+            ->with('my resized data');
224
+
225
+        $this->eventDispatcher->expects($this->once())
226
+            ->method('dispatchTyped')
227
+            ->with(new BeforePreviewFetchedEvent($file, 100, 100, false, IPreview::MODE_FILL, null));
228
+
229
+        $result = $this->generator->getPreview($file, 100, 100);
230
+        $this->assertSame($previewFile, $result);
231
+    }
232
+
233
+    public function testInvalidMimeType(): void {
234
+        $this->expectException(NotFoundException::class);
235
+
236
+        $file = $this->createMock(File::class);
237
+        $file->method('isReadable')
238
+            ->willReturn(true);
239
+        $file->method('getId')
240
+            ->willReturn(42);
241
+
242
+        $this->previewManager->method('isMimeSupported')
243
+            ->with('invalidType')
244
+            ->willReturn(false);
245
+
246
+        $previewFolder = $this->createMock(ISimpleFolder::class);
247
+        $this->appData->method('getFolder')
248
+            ->with($this->equalTo(42))
249
+            ->willReturn($previewFolder);
250
+
251
+        $maxPreview = $this->createMock(ISimpleFile::class);
252
+        $maxPreview->method('getName')
253
+            ->willReturn('2048-2048-max.png');
254
+        $maxPreview->method('getMimeType')
255
+            ->willReturn('image/png');
256
+
257
+        $previewFolder->method('getDirectoryListing')
258
+            ->willReturn([$maxPreview]);
259
+
260
+        $previewFolder->method('getFile')
261
+            ->with($this->equalTo('1024-512-crop.png'))
262
+            ->willThrowException(new NotFoundException());
263
+
264
+        $this->eventDispatcher->expects($this->once())
265
+            ->method('dispatchTyped')
266
+            ->with(new BeforePreviewFetchedEvent($file, 1024, 512, true, IPreview::MODE_COVER, 'invalidType'));
267
+
268
+        $this->generator->getPreview($file, 1024, 512, true, IPreview::MODE_COVER, 'invalidType');
269
+    }
270
+
271
+    public function testReturnCachedPreviewsWithoutCheckingSupportedMimetype(): void {
272
+        $file = $this->createMock(File::class);
273
+        $file->method('isReadable')
274
+            ->willReturn(true);
275
+        $file->method('getId')
276
+            ->willReturn(42);
277
+
278
+
279
+        $previewFolder = $this->createMock(ISimpleFolder::class);
280
+        $this->appData->method('getFolder')
281
+            ->with($this->equalTo(42))
282
+            ->willReturn($previewFolder);
283
+
284
+        $maxPreview = $this->createMock(ISimpleFile::class);
285
+        $maxPreview->method('getName')
286
+            ->willReturn('2048-2048-max.png');
287
+        $maxPreview->method('getSize')->willReturn(1000);
288
+        $maxPreview->method('getMimeType')
289
+            ->willReturn('image/png');
290
+
291
+        $preview = $this->createMock(ISimpleFile::class);
292
+        $preview->method('getSize')->willReturn(1000);
293
+        $preview->method('getName')->willReturn('1024-512-crop.png');
294
+
295
+        $previewFolder->method('getDirectoryListing')
296
+            ->willReturn([$maxPreview, $preview]);
297
+
298
+        $this->previewManager->expects($this->never())
299
+            ->method('isMimeSupported');
300
+
301
+        $this->eventDispatcher->expects($this->once())
302
+            ->method('dispatchTyped')
303
+            ->with(new BeforePreviewFetchedEvent($file, 1024, 512, true, IPreview::MODE_COVER, 'invalidType'));
304
+
305
+        $result = $this->generator->getPreview($file, 1024, 512, true, IPreview::MODE_COVER, 'invalidType');
306
+        $this->assertSame($preview, $result);
307
+    }
308
+
309
+    public function testNoProvider(): void {
310
+        $file = $this->createMock(File::class);
311
+        $file->method('isReadable')
312
+            ->willReturn(true);
313
+        $file->method('getMimeType')
314
+            ->willReturn('myMimeType');
315
+        $file->method('getId')
316
+            ->willReturn(42);
317
+
318
+        $previewFolder = $this->createMock(ISimpleFolder::class);
319
+        $this->appData->method('getFolder')
320
+            ->with($this->equalTo(42))
321
+            ->willReturn($previewFolder);
322
+
323
+        $previewFolder->method('getDirectoryListing')
324
+            ->willReturn([]);
325
+
326
+        $this->previewManager->method('getProviders')
327
+            ->willReturn([]);
328
+
329
+        $this->eventDispatcher->expects($this->once())
330
+            ->method('dispatchTyped')
331
+            ->with(new BeforePreviewFetchedEvent($file, 100, 100, false, IPreview::MODE_FILL, null));
332
+
333
+        $this->expectException(NotFoundException::class);
334
+        $this->generator->getPreview($file, 100, 100);
335
+    }
336
+
337
+    private function getMockImage($width, $height, $data = null) {
338
+        $image = $this->createMock(IImage::class);
339
+        $image->method('height')->willReturn($width);
340
+        $image->method('width')->willReturn($height);
341
+        $image->method('valid')->willReturn(true);
342
+        $image->method('dataMimeType')->willReturn('image/png');
343
+        $image->method('data')->willReturn($data);
344
+
345
+        $image->method('resizeCopy')->willReturnCallback(function ($size) use ($data) {
346
+            return $this->getMockImage($size, $size, $data);
347
+        });
348
+        $image->method('preciseResizeCopy')->willReturnCallback(function ($width, $height) use ($data) {
349
+            return $this->getMockImage($width, $height, $data);
350
+        });
351
+        $image->method('cropCopy')->willReturnCallback(function ($x, $y, $width, $height) use ($data) {
352
+            return $this->getMockImage($width, $height, $data);
353
+        });
354
+
355
+        return $image;
356
+    }
357
+
358
+    public function dataSize() {
359
+        return [
360
+            [1024, 2048, 512, 512, false, IPreview::MODE_FILL, 256, 512],
361
+            [1024, 2048, 512, 512, false, IPreview::MODE_COVER, 512, 1024],
362
+            [1024, 2048, 512, 512, true, IPreview::MODE_FILL, 1024, 1024],
363
+            [1024, 2048, 512, 512, true, IPreview::MODE_COVER, 1024, 1024],
364
+
365
+            [1024, 2048, -1, 512, false, IPreview::MODE_COVER, 256, 512],
366
+            [1024, 2048, 512, -1, false, IPreview::MODE_FILL, 512, 1024],
367
+
368
+            [1024, 2048, 250, 1100, true, IPreview::MODE_COVER, 256, 1126],
369
+            [1024, 1100, 250, 1100, true, IPreview::MODE_COVER, 250, 1100],
370
+
371
+            [1024, 2048, 4096, 2048, false, IPreview::MODE_FILL, 1024, 2048],
372
+            [1024, 2048, 4096, 2048, false, IPreview::MODE_COVER, 1024, 2048],
373
+
374
+
375
+            [2048, 1024, 512, 512, false, IPreview::MODE_FILL, 512, 256],
376
+            [2048, 1024, 512, 512, false, IPreview::MODE_COVER, 1024, 512],
377
+            [2048, 1024, 512, 512, true, IPreview::MODE_FILL, 1024, 1024],
378
+            [2048, 1024, 512, 512, true, IPreview::MODE_COVER, 1024, 1024],
379
+
380
+            [2048, 1024, -1, 512, false, IPreview::MODE_FILL, 1024, 512],
381
+            [2048, 1024, 512, -1, false, IPreview::MODE_COVER, 512, 256],
382
+
383
+            [2048, 1024, 4096, 1024, true, IPreview::MODE_FILL, 2048, 512],
384
+            [2048, 1024, 4096, 1024, true, IPreview::MODE_COVER, 2048, 512],
385
+
386
+            //Test minimum size
387
+            [2048, 1024, 32, 32, false, IPreview::MODE_FILL, 64, 32],
388
+            [2048, 1024, 32, 32, false, IPreview::MODE_COVER, 64, 32],
389
+            [2048, 1024, 32, 32, true, IPreview::MODE_FILL, 64, 64],
390
+            [2048, 1024, 32, 32, true, IPreview::MODE_COVER, 64, 64],
391
+        ];
392
+    }
393
+
394
+    /**
395
+     * @dataProvider dataSize
396
+     *
397
+     * @param int $maxX
398
+     * @param int $maxY
399
+     * @param int $reqX
400
+     * @param int $reqY
401
+     * @param bool $crop
402
+     * @param string $mode
403
+     * @param int $expectedX
404
+     * @param int $expectedY
405
+     */
406
+    public function testCorrectSize($maxX, $maxY, $reqX, $reqY, $crop, $mode, $expectedX, $expectedY): void {
407
+        $file = $this->createMock(File::class);
408
+        $file->method('isReadable')
409
+            ->willReturn(true);
410
+        $file->method('getMimeType')
411
+            ->willReturn('myMimeType');
412
+        $file->method('getId')
413
+            ->willReturn(42);
414
+
415
+        $this->previewManager->method('isMimeSupported')
416
+            ->with($this->equalTo('myMimeType'))
417
+            ->willReturn(true);
418
+
419
+        $previewFolder = $this->createMock(ISimpleFolder::class);
420
+        $this->appData->method('getFolder')
421
+            ->with($this->equalTo(42))
422
+            ->willReturn($previewFolder);
423
+
424
+        $maxPreview = $this->createMock(ISimpleFile::class);
425
+        $maxPreview->method('getName')
426
+            ->willReturn($maxX . '-' . $maxY . '-max.png');
427
+        $maxPreview->method('getMimeType')
428
+            ->willReturn('image/png');
429
+        $maxPreview->method('getSize')->willReturn(1000);
430
+
431
+        $previewFolder->method('getDirectoryListing')
432
+            ->willReturn([$maxPreview]);
433
+
434
+        $filename = $expectedX . '-' . $expectedY;
435
+        if ($crop) {
436
+            $filename .= '-crop';
437
+        }
438
+        $filename .= '.png';
439
+        $previewFolder->method('getFile')
440
+            ->with($this->equalTo($filename))
441
+            ->willThrowException(new NotFoundException());
442
+
443
+        $image = $this->getMockImage($maxX, $maxY);
444
+        $this->helper->method('getImage')
445
+            ->with($this->equalTo($maxPreview))
446
+            ->willReturn($image);
447
+
448
+        $preview = $this->createMock(ISimpleFile::class);
449
+        $preview->method('getSize')->willReturn(1000);
450
+        $previewFolder->method('newFile')
451
+            ->with($this->equalTo($filename))
452
+            ->willReturn($preview);
453
+
454
+        $this->eventDispatcher->expects($this->once())
455
+            ->method('dispatchTyped')
456
+            ->with(new BeforePreviewFetchedEvent($file, $reqX, $reqY, $crop, $mode, null));
457
+
458
+        $result = $this->generator->getPreview($file, $reqX, $reqY, $crop, $mode);
459
+        if ($expectedX === $maxX && $expectedY === $maxY) {
460
+            $this->assertSame($maxPreview, $result);
461
+        } else {
462
+            $this->assertSame($preview, $result);
463
+        }
464
+    }
465
+
466
+    public function testUnreadbleFile(): void {
467
+        $file = $this->createMock(File::class);
468
+        $file->method('isReadable')
469
+            ->willReturn(false);
470
+
471
+        $this->expectException(NotFoundException::class);
472
+
473
+        $this->generator->getPreview($file, 100, 100, false);
474
+    }
475 475
 }
Please login to merge, or discard this patch.
Spacing   +11 added lines, -11 removed lines patch added patch discarded remove patch
@@ -40,7 +40,7 @@  discard block
 block discarded – undo
40 40
 	/** @var Generator */
41 41
 	private $generator;
42 42
 
43
-	private LoggerInterface|\PHPUnit\Framework\MockObject\MockObject $logger;
43
+	private LoggerInterface | \PHPUnit\Framework\MockObject\MockObject $logger;
44 44
 
45 45
 	protected function setUp(): void {
46 46
 		parent::setUp();
@@ -125,12 +125,12 @@  discard block
 block discarded – undo
125 125
 			->willReturn($previewFolder);
126 126
 
127 127
 		$this->config->method('getSystemValue')
128
-			->willReturnCallback(function ($key, $default) {
128
+			->willReturnCallback(function($key, $default) {
129 129
 				return $default;
130 130
 			});
131 131
 
132 132
 		$this->config->method('getSystemValueInt')
133
-			->willReturnCallback(function ($key, $default) {
133
+			->willReturnCallback(function($key, $default) {
134 134
 				return $default;
135 135
 			});
136 136
 
@@ -152,7 +152,7 @@  discard block
 block discarded – undo
152 152
 			]);
153 153
 
154 154
 		$this->helper->method('getProvider')
155
-			->willReturnCallback(function ($provider) use ($invalidProvider, $validProvider, $unavailableProvider) {
155
+			->willReturnCallback(function($provider) use ($invalidProvider, $validProvider, $unavailableProvider) {
156 156
 				if ($provider === 'wrongProvider') {
157 157
 					$this->fail('Wrongprovider should not be constructed!');
158 158
 				} elseif ($provider === 'brokenProvider') {
@@ -174,7 +174,7 @@  discard block
 block discarded – undo
174 174
 		$image->method('dataMimeType')->willReturn('image/png');
175 175
 
176 176
 		$this->helper->method('getThumbnail')
177
-			->willReturnCallback(function ($provider, $file, $x, $y) use ($invalidProvider, $validProvider, $image) {
177
+			->willReturnCallback(function($provider, $file, $x, $y) use ($invalidProvider, $validProvider, $image) {
178 178
 				if ($provider === $validProvider) {
179 179
 					return $image;
180 180
 				} else {
@@ -196,7 +196,7 @@  discard block
 block discarded – undo
196 196
 		$previewFolder->method('getDirectoryListing')
197 197
 			->willReturn([]);
198 198
 		$previewFolder->method('newFile')
199
-			->willReturnCallback(function ($filename) use ($maxPreview, $previewFile) {
199
+			->willReturnCallback(function($filename) use ($maxPreview, $previewFile) {
200 200
 				if ($filename === '2048-2048-max.png') {
201 201
 					return $maxPreview;
202 202
 				} elseif ($filename === '256-256.png') {
@@ -342,13 +342,13 @@  discard block
 block discarded – undo
342 342
 		$image->method('dataMimeType')->willReturn('image/png');
343 343
 		$image->method('data')->willReturn($data);
344 344
 
345
-		$image->method('resizeCopy')->willReturnCallback(function ($size) use ($data) {
345
+		$image->method('resizeCopy')->willReturnCallback(function($size) use ($data) {
346 346
 			return $this->getMockImage($size, $size, $data);
347 347
 		});
348
-		$image->method('preciseResizeCopy')->willReturnCallback(function ($width, $height) use ($data) {
348
+		$image->method('preciseResizeCopy')->willReturnCallback(function($width, $height) use ($data) {
349 349
 			return $this->getMockImage($width, $height, $data);
350 350
 		});
351
-		$image->method('cropCopy')->willReturnCallback(function ($x, $y, $width, $height) use ($data) {
351
+		$image->method('cropCopy')->willReturnCallback(function($x, $y, $width, $height) use ($data) {
352 352
 			return $this->getMockImage($width, $height, $data);
353 353
 		});
354 354
 
@@ -423,7 +423,7 @@  discard block
 block discarded – undo
423 423
 
424 424
 		$maxPreview = $this->createMock(ISimpleFile::class);
425 425
 		$maxPreview->method('getName')
426
-			->willReturn($maxX . '-' . $maxY . '-max.png');
426
+			->willReturn($maxX.'-'.$maxY.'-max.png');
427 427
 		$maxPreview->method('getMimeType')
428 428
 			->willReturn('image/png');
429 429
 		$maxPreview->method('getSize')->willReturn(1000);
@@ -431,7 +431,7 @@  discard block
 block discarded – undo
431 431
 		$previewFolder->method('getDirectoryListing')
432 432
 			->willReturn([$maxPreview]);
433 433
 
434
-		$filename = $expectedX . '-' . $expectedY;
434
+		$filename = $expectedX.'-'.$expectedY;
435 435
 		if ($crop) {
436 436
 			$filename .= '-crop';
437 437
 		}
Please login to merge, or discard this patch.