Passed
Push — master ( edf8ce...eba372 )
by Roeland
13:54 queued 15s
created

PreviewManager   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 384
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 151
dl 0
loc 384
rs 8.64
c 2
b 0
f 0
wmc 47

12 Methods

Rating   Name   Duplication   Size   Complexity  
A registerCoreProvider() 0 4 2
A registerProvider() 0 10 3
A getProviders() 0 13 3
A hasProviders() 0 3 1
A __construct() 0 12 1
A getEnabledDefaultProvider() 0 27 3
A generatePreviews() 0 2 1
C registerCoreProviders() 0 79 15
A getPreview() 0 2 1
B isAvailable() 0 31 10
A getGenerator() 0 14 2
A isMimeSupported() 0 19 5

How to fix   Complexity   

Complex Class

Complex classes like PreviewManager often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use PreviewManager, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Joas Schilling <[email protected]>
6
 * @author John Molakvoæ (skjnldsv) <[email protected]>
7
 * @author Morris Jobke <[email protected]>
8
 * @author Olivier Paroz <[email protected]>
9
 * @author Robin Appelman <[email protected]>
10
 * @author Roeland Jago Douma <[email protected]>
11
 * @author Sebastian Steinmetz <[email protected]>
12
 * @author Thomas Müller <[email protected]>
13
 *
14
 * @license AGPL-3.0
15
 *
16
 * This code is free software: you can redistribute it and/or modify
17
 * it under the terms of the GNU Affero General Public License, version 3,
18
 * as published by the Free Software Foundation.
19
 *
20
 * This program is distributed in the hope that it will be useful,
21
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23
 * GNU Affero General Public License for more details.
24
 *
25
 * You should have received a copy of the GNU Affero General Public License, version 3,
26
 * along with this program. If not, see <http://www.gnu.org/licenses/>
27
 *
28
 */
29
30
namespace OC;
31
32
use OC\Preview\Generator;
33
use OC\Preview\GeneratorHelper;
34
use OCP\Files\File;
35
use OCP\Files\IAppData;
36
use OCP\Files\IRootFolder;
37
use OCP\Files\NotFoundException;
38
use OCP\Files\SimpleFS\ISimpleFile;
39
use OCP\IConfig;
40
use OCP\IPreview;
41
use OCP\Preview\IProviderV2;
42
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
43
44
class PreviewManager implements IPreview {
45
	/** @var IConfig */
46
	protected $config;
47
48
	/** @var IRootFolder */
49
	protected $rootFolder;
50
51
	/** @var IAppData */
52
	protected $appData;
53
54
	/** @var EventDispatcherInterface */
55
	protected $eventDispatcher;
56
57
	/** @var Generator */
58
	private $generator;
59
	
60
	/** @var GeneratorHelper */
61
	private $helper;
62
63
	/** @var bool */
64
	protected $providerListDirty = false;
65
66
	/** @var bool */
67
	protected $registeredCoreProviders = false;
68
69
	/** @var array */
70
	protected $providers = [];
71
72
	/** @var array mime type => support status */
73
	protected $mimeTypeSupportMap = [];
74
75
	/** @var array */
76
	protected $defaultProviders;
77
78
	/** @var string */
79
	protected $userId;
80
81
	/**
82
	 * PreviewManager constructor.
83
	 *
84
	 * @param IConfig $config
85
	 * @param IRootFolder $rootFolder
86
	 * @param IAppData $appData
87
	 * @param EventDispatcherInterface $eventDispatcher
88
	 * @param string $userId
89
	 */
90
	public function __construct(IConfig $config,
91
								IRootFolder $rootFolder,
92
								IAppData $appData,
93
								EventDispatcherInterface $eventDispatcher,
94
								GeneratorHelper $helper,
95
								$userId) {
96
		$this->config = $config;
97
		$this->rootFolder = $rootFolder;
98
		$this->appData = $appData;
99
		$this->eventDispatcher = $eventDispatcher;
100
		$this->helper = $helper;
101
		$this->userId = $userId;
102
	}
103
104
	/**
105
	 * In order to improve lazy loading a closure can be registered which will be
106
	 * called in case preview providers are actually requested
107
	 *
108
	 * $callable has to return an instance of \OCP\Preview\IProvider or \OCP\Preview\IProviderV2
109
	 *
110
	 * @param string $mimeTypeRegex Regex with the mime types that are supported by this provider
111
	 * @param \Closure $callable
112
	 * @return void
113
	 */
114
	public function registerProvider($mimeTypeRegex, \Closure $callable) {
115
		if (!$this->config->getSystemValue('enable_previews', true)) {
116
			return;
117
		}
118
119
		if (!isset($this->providers[$mimeTypeRegex])) {
120
			$this->providers[$mimeTypeRegex] = [];
121
		}
122
		$this->providers[$mimeTypeRegex][] = $callable;
123
		$this->providerListDirty = true;
124
	}
125
126
	/**
127
	 * Get all providers
128
	 * @return array
129
	 */
130
	public function getProviders() {
131
		if (!$this->config->getSystemValue('enable_previews', true)) {
132
			return [];
133
		}
134
135
		$this->registerCoreProviders();
136
		if ($this->providerListDirty) {
137
			$keys = array_map('strlen', array_keys($this->providers));
138
			array_multisort($keys, SORT_DESC, $this->providers);
139
			$this->providerListDirty = false;
140
		}
141
142
		return $this->providers;
143
	}
144
145
	/**
146
	 * Does the manager have any providers
147
	 * @return bool
148
	 */
149
	public function hasProviders() {
150
		$this->registerCoreProviders();
151
		return !empty($this->providers);
152
	}
153
154
	private function getGenerator(): Generator {
155
		if ($this->generator === null) {
156
			$this->generator = new Generator(
157
				$this->config,
158
				$this,
159
				$this->appData,
160
				new GeneratorHelper(
161
					$this->rootFolder,
162
					$this->config
163
				),
164
				$this->eventDispatcher
165
			);
166
		}
167
		return $this->generator;
168
	}
169
170
	/**
171
	 * Returns a preview of a file
172
	 *
173
	 * The cache is searched first and if nothing usable was found then a preview is
174
	 * generated by one of the providers
175
	 *
176
	 * @param File $file
177
	 * @param int $width
178
	 * @param int $height
179
	 * @param bool $crop
180
	 * @param string $mode
181
	 * @param string $mimeType
182
	 * @return ISimpleFile
183
	 * @throws NotFoundException
184
	 * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
185
	 * @since 11.0.0 - \InvalidArgumentException was added in 12.0.0
186
	 */
187
	public function getPreview(File $file, $width = -1, $height = -1, $crop = false, $mode = IPreview::MODE_FILL, $mimeType = null) {
188
		return $this->getGenerator()->getPreview($file, $width, $height, $crop, $mode, $mimeType);
189
	}
190
191
	/**
192
	 * Generates previews of a file
193
	 *
194
	 * @param File $file
195
	 * @param array $specifications
196
	 * @param string $mimeType
197
	 * @return ISimpleFile the last preview that was generated
198
	 * @throws NotFoundException
199
	 * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
200
	 * @since 19.0.0
201
	 */
202
	public function generatePreviews(File $file, array $specifications, $mimeType = null) {
203
		return $this->getGenerator()->generatePreviews($file, $specifications, $mimeType);
204
	}
205
206
	/**
207
	 * returns true if the passed mime type is supported
208
	 *
209
	 * @param string $mimeType
210
	 * @return boolean
211
	 */
212
	public function isMimeSupported($mimeType = '*') {
213
		if (!$this->config->getSystemValue('enable_previews', true)) {
214
			return false;
215
		}
216
217
		if (isset($this->mimeTypeSupportMap[$mimeType])) {
218
			return $this->mimeTypeSupportMap[$mimeType];
219
		}
220
221
		$this->registerCoreProviders();
222
		$providerMimeTypes = array_keys($this->providers);
223
		foreach ($providerMimeTypes as $supportedMimeType) {
224
			if (preg_match($supportedMimeType, $mimeType)) {
225
				$this->mimeTypeSupportMap[$mimeType] = true;
226
				return true;
227
			}
228
		}
229
		$this->mimeTypeSupportMap[$mimeType] = false;
230
		return false;
231
	}
232
233
	/**
234
	 * Check if a preview can be generated for a file
235
	 *
236
	 * @param \OCP\Files\FileInfo $file
237
	 * @return bool
238
	 */
239
	public function isAvailable(\OCP\Files\FileInfo $file) {
240
		if (!$this->config->getSystemValue('enable_previews', true)) {
241
			return false;
242
		}
243
244
		$this->registerCoreProviders();
245
		if (!$this->isMimeSupported($file->getMimetype())) {
246
			return false;
247
		}
248
249
		$mount = $file->getMountPoint();
250
		if ($mount and !$mount->getOption('previews', true)){
251
			return false;
252
		}
253
254
		foreach ($this->providers as $supportedMimeType => $providers) {
255
			if (preg_match($supportedMimeType, $file->getMimetype())) {
256
				foreach ($providers as $providerClosure) {
257
					$provider = $this->helper->getProvider($providerClosure);
258
					if (!($provider instanceof IProviderV2)) {
259
						continue;
260
					}
261
262
					/** @var $provider IProvider */
263
					if ($provider->isAvailable($file)) {
264
						return true;
265
					}
266
				}
267
			}
268
		}
269
		return false;
270
	}
271
272
	/**
273
	 * List of enabled default providers
274
	 *
275
	 * The following providers are enabled by default:
276
	 *  - OC\Preview\PNG
277
	 *  - OC\Preview\JPEG
278
	 *  - OC\Preview\GIF
279
	 *  - OC\Preview\BMP
280
	 *  - OC\Preview\HEIC
281
	 *  - OC\Preview\XBitmap
282
	 *  - OC\Preview\MarkDown
283
	 *  - OC\Preview\MP3
284
	 *  - OC\Preview\TXT
285
	 *
286
	 * The following providers are disabled by default due to performance or privacy concerns:
287
	 *  - OC\Preview\Font
288
	 *  - OC\Preview\Illustrator
289
	 *  - OC\Preview\Movie
290
	 *  - OC\Preview\MSOfficeDoc
291
	 *  - OC\Preview\MSOffice2003
292
	 *  - OC\Preview\MSOffice2007
293
	 *  - OC\Preview\OpenDocument
294
	 *  - OC\Preview\PDF
295
	 *  - OC\Preview\Photoshop
296
	 *  - OC\Preview\Postscript
297
	 *  - OC\Preview\StarOffice
298
	 *  - OC\Preview\SVG
299
	 *  - OC\Preview\TIFF
300
	 *
301
	 * @return array
302
	 */
303
	protected function getEnabledDefaultProvider() {
304
		if ($this->defaultProviders !== null) {
305
			return $this->defaultProviders;
306
		}
307
308
		$imageProviders = [
309
			Preview\PNG::class,
310
			Preview\JPEG::class,
311
			Preview\GIF::class,
312
			Preview\BMP::class,
313
			Preview\HEIC::class,
314
			Preview\XBitmap::class,
315
			Preview\Krita::class,
316
		];
317
318
		$this->defaultProviders = $this->config->getSystemValue('enabledPreviewProviders', array_merge([
319
			Preview\MarkDown::class,
320
			Preview\MP3::class,
321
			Preview\TXT::class,
322
			Preview\OpenDocument::class,
323
		], $imageProviders));
324
325
		if (in_array(Preview\Image::class, $this->defaultProviders)) {
326
			$this->defaultProviders = array_merge($this->defaultProviders, $imageProviders);
327
		}
328
		$this->defaultProviders = array_unique($this->defaultProviders);
329
		return $this->defaultProviders;
330
	}
331
332
	/**
333
	 * Register the default providers (if enabled)
334
	 *
335
	 * @param string $class
336
	 * @param string $mimeType
337
	 */
338
	protected function registerCoreProvider($class, $mimeType, $options = []) {
339
		if (in_array(trim($class, '\\'), $this->getEnabledDefaultProvider())) {
340
			$this->registerProvider($mimeType, function () use ($class, $options) {
341
				return new $class($options);
342
			});
343
		}
344
	}
345
346
	/**
347
	 * Register the default providers (if enabled)
348
	 */
349
	protected function registerCoreProviders() {
350
		if ($this->registeredCoreProviders) {
351
			return;
352
		}
353
		$this->registeredCoreProviders = true;
354
355
		$this->registerCoreProvider(Preview\TXT::class, '/text\/plain/');
356
		$this->registerCoreProvider(Preview\MarkDown::class, '/text\/(x-)?markdown/');
357
		$this->registerCoreProvider(Preview\PNG::class, '/image\/png/');
358
		$this->registerCoreProvider(Preview\JPEG::class, '/image\/jpeg/');
359
		$this->registerCoreProvider(Preview\GIF::class, '/image\/gif/');
360
		$this->registerCoreProvider(Preview\BMP::class, '/image\/bmp/');
361
		$this->registerCoreProvider(Preview\XBitmap::class, '/image\/x-xbitmap/');
362
		$this->registerCoreProvider(Preview\Krita::class, '/application\/x-krita/');
363
		$this->registerCoreProvider(Preview\MP3::class, '/audio\/mpeg/');
364
		$this->registerCoreProvider(Preview\OpenDocument::class, '/application\/vnd.oasis.opendocument.*/');
365
366
		// SVG, Office and Bitmap require imagick
367
		if (extension_loaded('imagick')) {
368
			$checkImagick = new \Imagick();
369
370
			$imagickProviders = [
371
				'SVG'	=> ['mimetype' => '/image\/svg\+xml/', 'class' => Preview\SVG::class],
372
				'TIFF'	=> ['mimetype' => '/image\/tiff/', 'class' => Preview\TIFF::class],
373
				'PDF'	=> ['mimetype' => '/application\/pdf/', 'class' => Preview\PDF::class],
374
				'AI'	=> ['mimetype' => '/application\/illustrator/', 'class' => Preview\Illustrator::class],
375
				'PSD'	=> ['mimetype' => '/application\/x-photoshop/', 'class' => Preview\Photoshop::class],
376
				'EPS'	=> ['mimetype' => '/application\/postscript/', 'class' => Preview\Postscript::class],
377
				'TTF'	=> ['mimetype' => '/application\/(?:font-sfnt|x-font$)/', 'class' => Preview\Font::class],
378
				'HEIC'  => ['mimetype' => '/image\/hei(f|c)/', 'class' => Preview\HEIC::class],
379
			];
380
381
			foreach ($imagickProviders as $queryFormat => $provider) {
382
				$class = $provider['class'];
383
				if (!in_array(trim($class, '\\'), $this->getEnabledDefaultProvider())) {
384
					continue;
385
				}
386
387
				if (count($checkImagick->queryFormats($queryFormat)) === 1) {
388
					$this->registerCoreProvider($class, $provider['mimetype']);
389
				}
390
			}
391
392
			if (count($checkImagick->queryFormats('PDF')) === 1) {
393
				if (\OC_Helper::is_function_enabled('shell_exec')) {
394
					$officeFound = is_string($this->config->getSystemValue('preview_libreoffice_path', null));
395
396
					if (!$officeFound) {
397
						//let's see if there is libreoffice or openoffice on this machine
398
						$whichLibreOffice = shell_exec('command -v libreoffice');
399
						$officeFound = !empty($whichLibreOffice);
400
						if (!$officeFound) {
401
							$whichOpenOffice = shell_exec('command -v openoffice');
402
							$officeFound = !empty($whichOpenOffice);
403
						}
404
					}
405
406
					if ($officeFound) {
407
						$this->registerCoreProvider(Preview\MSOfficeDoc::class, '/application\/msword/');
408
						$this->registerCoreProvider(Preview\MSOffice2003::class, '/application\/vnd.ms-.*/');
409
						$this->registerCoreProvider(Preview\MSOffice2007::class, '/application\/vnd.openxmlformats-officedocument.*/');
410
						$this->registerCoreProvider(Preview\OpenDocument::class, '/application\/vnd.oasis.opendocument.*/');
411
						$this->registerCoreProvider(Preview\StarOffice::class, '/application\/vnd.sun.xml.*/');
412
					}
413
				}
414
			}
415
		}
416
417
		// Video requires avconv or ffmpeg
418
		if (in_array(Preview\Movie::class, $this->getEnabledDefaultProvider())) {
419
			$avconvBinary = \OC_Helper::findBinaryPath('avconv');
420
			$ffmpegBinary = $avconvBinary ? null : \OC_Helper::findBinaryPath('ffmpeg');
421
422
			if ($avconvBinary || $ffmpegBinary) {
423
				// FIXME // a bit hacky but didn't want to use subclasses
424
				\OC\Preview\Movie::$avconvBinary = $avconvBinary;
425
				\OC\Preview\Movie::$ffmpegBinary = $ffmpegBinary;
426
427
				$this->registerCoreProvider(Preview\Movie::class, '/video\/.*/');
428
			}
429
		}
430
	}
431
}
432