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