Passed
Push — master ( 7149ed...962901 )
by John
13:20 queued 11s
created

PreviewManager::createPreview()   A

Complexity

Conditions 3
Paths 5

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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