preview.php 24 KB
Newer Older
1
2
<?php
/**
3
4
5
6
7
 * Copyright (c) 2013 Frank Karlitschek frank@owncloud.org
 * Copyright (c) 2013 Georg Ehrke georg@ownCloud.com
 * This file is licensed under the Affero General Public License version 3 or
 * later.
 * See the COPYING-README file.
Georg Ehrke's avatar
Georg Ehrke committed
8
 *
9
10
11
 * Thumbnails:
 * structure of filename:
 * /data/user/thumbnails/pathhash/x-y.png
12
 *
13
 */
Georg Ehrke's avatar
Georg Ehrke committed
14
15
namespace OC;

Thomas Müller's avatar
Thomas Müller committed
16
use OC\Preview\Provider;
17
use OCP\Files\FileInfo;
18
use OCP\Files\NotFoundException;
Thomas Müller's avatar
Thomas Müller committed
19

Georg Ehrke's avatar
Georg Ehrke committed
20
class Preview {
Georg Ehrke's avatar
Georg Ehrke committed
21
	//the thumbnail folder
22
	const THUMBNAILS_FOLDER = 'thumbnails';
Georg Ehrke's avatar
Georg Ehrke committed
23

Georg Ehrke's avatar
Georg Ehrke committed
24
	//config
Georg Ehrke's avatar
Georg Ehrke committed
25
26
27
	private $maxScaleFactor;
	private $configMaxX;
	private $configMaxY;
28

29
	//fileview object
Georg Ehrke's avatar
Georg Ehrke committed
30
31
	private $fileView = null;
	private $userView = null;
Georg Ehrke's avatar
Georg Ehrke committed
32
33
34
35
36

	//vars
	private $file;
	private $maxX;
	private $maxY;
37
38
	private $scalingUp;
	private $mimeType;
39
	private $keepAspect = false;
Georg Ehrke's avatar
Georg Ehrke committed
40

41
42
43
	//filemapper used for deleting previews
	// index is path, value is fileinfo
	static public $deleteFileMapper = array();
44
	static public $deleteChildrenMapper = array();
45

46
	/**
Thomas Müller's avatar
Thomas Müller committed
47
48
	 * preview images object
	 *
49
50
	 * @var \OC_Image
	 */
Georg Ehrke's avatar
Georg Ehrke committed
51
	private $preview;
52

53
54
55
	//preview providers
	static private $providers = array();
	static private $registeredProviders = array();
56
	static private $enabledProviders = array();
57

58
59
60
61
62
	/**
	 * @var \OCP\Files\FileInfo
	 */
	protected $info;

63
	/**
64
	 * check if thumbnail or bigger version of thumbnail of file is cached
65
66
67
68
69
	 * @param string $user userid - if no user is given, OC_User::getUser will be used
	 * @param string $root path of root
	 * @param string $file The path to the file where you want a thumbnail from
	 * @param int $maxX The maximum X size of the thumbnail. It can be smaller depending on the shape of the image
	 * @param int $maxY The maximum Y size of the thumbnail. It can be smaller depending on the shape of the image
Georg Ehrke's avatar
Georg Ehrke committed
70
	 * @param bool $scalingUp Disable/Enable upscaling of previews
Lukas Reschke's avatar
Lukas Reschke committed
71
	 * @throws \Exception
72
	 * @return mixed (bool / string)
73
74
75
76
	 *                    false if thumbnail does not exist
	 *                    path to thumbnail if thumbnail exists
	 */
	public function __construct($user = '', $root = '/', $file = '', $maxX = 1, $maxY = 1, $scalingUp = true) {
77
		//init fileviews
78
		if ($user === '') {
79
80
81
82
83
			$user = \OC_User::getUser();
		}
		$this->fileView = new \OC\Files\View('/' . $user . '/' . $root);
		$this->userView = new \OC\Files\View('/' . $user);

Georg Ehrke's avatar
Georg Ehrke committed
84
		//set config
Georg Ehrke's avatar
Georg Ehrke committed
85
86
		$this->configMaxX = \OC_Config::getValue('preview_max_x', null);
		$this->configMaxY = \OC_Config::getValue('preview_max_y', null);
87
		$this->maxScaleFactor = \OC_Config::getValue('preview_max_scale_factor', 2);
Georg Ehrke's avatar
Georg Ehrke committed
88

Georg Ehrke's avatar
Georg Ehrke committed
89
		//save parameters
Georg Ehrke's avatar
Georg Ehrke committed
90
91
92
		$this->setFile($file);
		$this->setMaxX($maxX);
		$this->setMaxY($maxY);
Georg Ehrke's avatar
Georg Ehrke committed
93
		$this->setScalingUp($scalingUp);
Georg Ehrke's avatar
Georg Ehrke committed
94

Georg Ehrke's avatar
Georg Ehrke committed
95
		$this->preview = null;
Georg Ehrke's avatar
Georg Ehrke committed
96

Georg Ehrke's avatar
Georg Ehrke committed
97
		//check if there are preview backends
98
		if (empty(self::$providers)) {
99
			self::initProviders();
Georg Ehrke's avatar
Georg Ehrke committed
100
		}
Georg Ehrke's avatar
Georg Ehrke committed
101

102
		if (empty(self::$providers) && \OC::$server->getConfig()->getSystemValue('enable_previews', true)) {
Georg Ehrke's avatar
Georg Ehrke committed
103
104
			\OC_Log::write('core', 'No preview providers exist', \OC_Log::ERROR);
			throw new \Exception('No preview providers');
Georg Ehrke's avatar
Georg Ehrke committed
105
106
		}
	}
Georg Ehrke's avatar
Georg Ehrke committed
107

Georg Ehrke's avatar
Georg Ehrke committed
108
	/**
109
	 * returns the path of the file you want a thumbnail from
Georg Ehrke's avatar
Georg Ehrke committed
110
	 * @return string
111
	 */
112
	public function getFile() {
Georg Ehrke's avatar
Georg Ehrke committed
113
114
		return $this->file;
	}
Georg Ehrke's avatar
Georg Ehrke committed
115

Georg Ehrke's avatar
Georg Ehrke committed
116
	/**
117
	 * returns the max width of the preview
Georg Ehrke's avatar
Georg Ehrke committed
118
	 * @return integer
119
	 */
Georg Ehrke's avatar
Georg Ehrke committed
120
	public function getMaxX() {
Georg Ehrke's avatar
Georg Ehrke committed
121
122
123
124
		return $this->maxX;
	}

	/**
125
	 * returns the max height of the preview
Georg Ehrke's avatar
Georg Ehrke committed
126
	 * @return integer
127
	 */
Georg Ehrke's avatar
Georg Ehrke committed
128
	public function getMaxY() {
Georg Ehrke's avatar
Georg Ehrke committed
129
130
		return $this->maxY;
	}
Georg Ehrke's avatar
Georg Ehrke committed
131

Georg Ehrke's avatar
Georg Ehrke committed
132
	/**
133
	 * returns whether or not scalingup is enabled
Georg Ehrke's avatar
Georg Ehrke committed
134
	 * @return bool
135
	 */
Georg Ehrke's avatar
Georg Ehrke committed
136
	public function getScalingUp() {
137
		return $this->scalingUp;
Georg Ehrke's avatar
Georg Ehrke committed
138
	}
Georg Ehrke's avatar
Georg Ehrke committed
139

Georg Ehrke's avatar
Georg Ehrke committed
140
	/**
141
	 * returns the name of the thumbnailfolder
Georg Ehrke's avatar
Georg Ehrke committed
142
	 * @return string
143
	 */
Georg Ehrke's avatar
Georg Ehrke committed
144
	public function getThumbnailsFolder() {
Georg Ehrke's avatar
Georg Ehrke committed
145
146
147
148
		return self::THUMBNAILS_FOLDER;
	}

	/**
149
	 * returns the max scale factor
150
	 * @return string
151
	 */
Georg Ehrke's avatar
Georg Ehrke committed
152
	public function getMaxScaleFactor() {
Georg Ehrke's avatar
Georg Ehrke committed
153
		return $this->maxScaleFactor;
Georg Ehrke's avatar
Georg Ehrke committed
154
155
156
	}

	/**
157
	 * returns the max width set in ownCloud's config
158
	 * @return string
159
	 */
Georg Ehrke's avatar
Georg Ehrke committed
160
	public function getConfigMaxX() {
Georg Ehrke's avatar
Georg Ehrke committed
161
		return $this->configMaxX;
Georg Ehrke's avatar
Georg Ehrke committed
162
163
164
	}

	/**
165
	 * returns the max height set in ownCloud's config
166
	 * @return string
167
	 */
Georg Ehrke's avatar
Georg Ehrke committed
168
	public function getConfigMaxY() {
Georg Ehrke's avatar
Georg Ehrke committed
169
		return $this->configMaxY;
Georg Ehrke's avatar
Georg Ehrke committed
170
	}
Georg Ehrke's avatar
Georg Ehrke committed
171

Lukas Reschke's avatar
Lukas Reschke committed
172
173
174
	/**
	 * @return false|Files\FileInfo|\OCP\Files\FileInfo
	 */
175
	protected function getFileInfo() {
176
177
178
179
180
		$absPath = $this->fileView->getAbsolutePath($this->file);
		$absPath = Files\Filesystem::normalizePath($absPath);
		if(array_key_exists($absPath, self::$deleteFileMapper)) {
			$this->info = self::$deleteFileMapper[$absPath];
		} else if (!$this->info) {
181
182
183
184
185
			$this->info = $this->fileView->getFileInfo($this->file);
		}
		return $this->info;
	}

186
187
188
189
190
191
192
193
194
195
196
197
198
199
200

	/**
	 * @return array|null
	 */
	private function getChildren() {
		$absPath = $this->fileView->getAbsolutePath($this->file);
		$absPath = Files\Filesystem::normalizePath($absPath);

		if (array_key_exists($absPath, self::$deleteChildrenMapper)) {
			return self::$deleteChildrenMapper[$absPath];
		}

		return null;
	}

Georg Ehrke's avatar
Georg Ehrke committed
201
	/**
202
	 * set the path of the file you want a thumbnail from
203
	 * @param string $file
Lukas Reschke's avatar
Lukas Reschke committed
204
	 * @return $this
205
	 */
Georg Ehrke's avatar
Georg Ehrke committed
206
207
	public function setFile($file) {
		$this->file = $file;
208
		$this->info = null;
Lukas Reschke's avatar
Lukas Reschke committed
209

210
		if ($file !== '') {
211
			$this->getFileInfo();
Lukas Reschke's avatar
Lukas Reschke committed
212
			if($this->info instanceof \OCP\Files\FileInfo) {
213
				$this->mimeType = $this->info->getMimetype();
214
			}
215
		}
Georg Ehrke's avatar
Georg Ehrke committed
216
217
218
		return $this;
	}

219
	/**
220
	 * set mime type explicitly
221
	 * @param string $mimeType
222
	 */
223
224
	public function setMimetype($mimeType) {
		$this->mimeType = $mimeType;
225
226
	}

Georg Ehrke's avatar
Georg Ehrke committed
227
	/**
228
	 * set the the max width of the preview
229
	 * @param int $maxX
Lukas Reschke's avatar
Lukas Reschke committed
230
	 * @throws \Exception
231
	 * @return \OC\Preview $this
232
233
234
	 */
	public function setMaxX($maxX = 1) {
		if ($maxX <= 0) {
Georg Ehrke's avatar
Georg Ehrke committed
235
			throw new \Exception('Cannot set width of 0 or smaller!');
Georg Ehrke's avatar
Georg Ehrke committed
236
237
		}
		$configMaxX = $this->getConfigMaxX();
238
239
		if (!is_null($configMaxX)) {
			if ($maxX > $configMaxX) {
Georg Ehrke's avatar
Georg Ehrke committed
240
241
242
243
244
245
246
247
248
				\OC_Log::write('core', 'maxX reduced from ' . $maxX . ' to ' . $configMaxX, \OC_Log::DEBUG);
				$maxX = $configMaxX;
			}
		}
		$this->maxX = $maxX;
		return $this;
	}

	/**
249
	 * set the the max height of the preview
250
	 * @param int $maxY
Lukas Reschke's avatar
Lukas Reschke committed
251
	 * @throws \Exception
252
	 * @return \OC\Preview $this
253
254
255
	 */
	public function setMaxY($maxY = 1) {
		if ($maxY <= 0) {
Georg Ehrke's avatar
Georg Ehrke committed
256
			throw new \Exception('Cannot set height of 0 or smaller!');
Georg Ehrke's avatar
Georg Ehrke committed
257
258
		}
		$configMaxY = $this->getConfigMaxY();
259
260
		if (!is_null($configMaxY)) {
			if ($maxY > $configMaxY) {
Georg Ehrke's avatar
Georg Ehrke committed
261
262
263
264
265
266
267
268
269
				\OC_Log::write('core', 'maxX reduced from ' . $maxY . ' to ' . $configMaxY, \OC_Log::DEBUG);
				$maxY = $configMaxY;
			}
		}
		$this->maxY = $maxY;
		return $this;
	}

	/**
270
	 * set whether or not scalingup is enabled
Georg Ehrke's avatar
Georg Ehrke committed
271
	 * @param bool $scalingUp
272
	 * @return \OC\Preview $this
273
	 */
Georg Ehrke's avatar
Georg Ehrke committed
274
	public function setScalingup($scalingUp) {
275
		if ($this->getMaxScaleFactor() === 1) {
Georg Ehrke's avatar
Georg Ehrke committed
276
			$scalingUp = false;
Georg Ehrke's avatar
Georg Ehrke committed
277
		}
278
		$this->scalingUp = $scalingUp;
Georg Ehrke's avatar
Georg Ehrke committed
279
280
281
		return $this;
	}

282
283
284
285
	/**
	 * @param bool $keepAspect
	 * @return $this
	 */
286
287
288
289
290
	public function setKeepAspect($keepAspect) {
		$this->keepAspect = $keepAspect;
		return $this;
	}

Georg Ehrke's avatar
Georg Ehrke committed
291
	/**
292
	 * check if all parameters are valid
293
	 * @return bool
294
	 */
Georg Ehrke's avatar
Georg Ehrke committed
295
296
	public function isFileValid() {
		$file = $this->getFile();
297
		if ($file === '') {
Georg Ehrke's avatar
Georg Ehrke committed
298
			\OC_Log::write('core', 'No filename passed', \OC_Log::DEBUG);
Georg Ehrke's avatar
Georg Ehrke committed
299
300
301
			return false;
		}

302
		if (!$this->fileView->file_exists($file)) {
Georg Ehrke's avatar
Georg Ehrke committed
303
			\OC_Log::write('core', 'File:"' . $file . '" not found', \OC_Log::DEBUG);
Georg Ehrke's avatar
Georg Ehrke committed
304
305
306
307
308
309
			return false;
		}

		return true;
	}

Georg Ehrke's avatar
Georg Ehrke committed
310
	/**
311
	 * deletes previews of a file with specific x and y
Georg Ehrke's avatar
Georg Ehrke committed
312
	 * @return bool
313
	 */
Georg Ehrke's avatar
Georg Ehrke committed
314
	public function deletePreview() {
Georg Ehrke's avatar
Georg Ehrke committed
315
316
		$file = $this->getFile();

317
		$fileInfo = $this->getFileInfo($file);
318
319
		if($fileInfo !== null && $fileInfo !== false) {
			$fileId = $fileInfo->getId();
320

321
			$previewPath = $this->buildCachePath($fileId);
322
323
324
			return $this->userView->unlink($previewPath);
		}
		return false;
Georg Ehrke's avatar
Georg Ehrke committed
325
326
327
	}

	/**
328
	 * deletes all previews of a file
329
	 */
Georg Ehrke's avatar
Georg Ehrke committed
330
	public function deleteAllPreviews() {
331
		$toDelete = $this->getChildren();
332
		$toDelete[] = $this->getFileInfo();
333
334

		foreach ($toDelete as $delete) {
335
			if ($delete instanceof FileInfo) {
336
337
338
				/** @var \OCP\Files\FileInfo $delete */
				$fileId = $delete->getId();

339
340
341
342
343
344
345
				// getId() might return null, e.g. when the file is a
				// .ocTransferId*.part file from chunked file upload.
				if (!empty($fileId)) {
					$previewPath = $this->getPreviewPath($fileId);
					$this->userView->deleteAll($previewPath);
					$this->userView->rmdir($previewPath);
				}
346
			}
347
		}
Georg Ehrke's avatar
Georg Ehrke committed
348
349
350
	}

	/**
351
	 * check if thumbnail or bigger version of thumbnail of file is cached
Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
352
	 * @param int $fileId fileId of the original image
353
	 * @return string|false path to thumbnail if it exists or false
354
	 */
355
	public function isCached($fileId) {
356
		if (is_null($fileId)) {
357
358
			return false;
		}
Georg Ehrke's avatar
Georg Ehrke committed
359

360
		$preview = $this->buildCachePath($fileId);
Georg Ehrke's avatar
Georg Ehrke committed
361

362
		//does a preview with the wanted height and width already exist?
363
364
		if ($this->userView->file_exists($preview)) {
			return $preview;
365
		}
Georg Ehrke's avatar
Georg Ehrke committed
366

Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
367
		return $this->isCachedBigger($fileId);
368
369
370
	}

	/**
371
	 * check if a bigger version of thumbnail of file is cached
Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
372
	 * @param int $fileId fileId of the original image
373
374
	 * @return string|false path to bigger thumbnail if it exists or false
	*/
Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
375
	private function isCachedBigger($fileId) {
Georg Ehrke's avatar
Georg Ehrke committed
376

377
		if (is_null($fileId)) {
378
379
380
			return false;
		}

381
382
383
384
385
		// in order to not loose quality we better generate aspect preserving previews from the original file
		if ($this->keepAspect) {
			return false;
		}

386
387
		$maxX = $this->getMaxX();

Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
388
389
390
391
392
393
394
395
396
		//array for usable cached thumbnails
		$possibleThumbnails = $this->getPossibleThumbnails($fileId);

		foreach ($possibleThumbnails as $width => $path) {
			if ($width < $maxX) {
				continue;
			} else {
				return $path;
			}
Georg Ehrke's avatar
Georg Ehrke committed
397
398
		}

Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
399
400
		return false;
	}
Thomas Müller's avatar
Thomas Müller committed
401

Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
402
	/**
403
	 * get possible bigger thumbnails of the given image
Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
404
	 * @param int $fileId fileId of the original image
405
	 * @return array an array of paths to bigger thumbnails
Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
406
407
	*/
	private function getPossibleThumbnails($fileId) {
408
409

		if (is_null($fileId)) {
Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
410
			return array();
411
		}
Georg Ehrke's avatar
Georg Ehrke committed
412

413
		$previewPath = $this->getPreviewPath($fileId);
Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
414
415

		$wantedAspectRatio = (float) ($this->getMaxX() / $this->getMaxY());
Georg Ehrke's avatar
Georg Ehrke committed
416

417
		//array for usable cached thumbnails
Georg Ehrke's avatar
Georg Ehrke committed
418
		$possibleThumbnails = array();
Georg Ehrke's avatar
Georg Ehrke committed
419

Georg Ehrke's avatar
Georg Ehrke committed
420
		$allThumbnails = $this->userView->getDirectoryContent($previewPath);
421
		foreach ($allThumbnails as $thumbnail) {
422
			$name = rtrim($thumbnail['name'], '.png');
Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
423
			list($x, $y, $aspectRatio) = $this->getDimensionsFromFilename($name);
Georg Ehrke's avatar
Georg Ehrke committed
424

425
			if (abs($aspectRatio - $wantedAspectRatio) >= 0.000001
Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
426
427
				|| $this->unscalable($x, $y)
			) {
428
429
				continue;
			}
Georg Ehrke's avatar
Georg Ehrke committed
430
			$possibleThumbnails[$x] = $thumbnail['path'];
431
		}
Georg Ehrke's avatar
Georg Ehrke committed
432

Georg Ehrke's avatar
Georg Ehrke committed
433
		ksort($possibleThumbnails);
Georg Ehrke's avatar
Georg Ehrke committed
434

Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
435
436
		return $possibleThumbnails;
	}
Georg Ehrke's avatar
Georg Ehrke committed
437

Lukas Reschke's avatar
Lukas Reschke committed
438
439
440
441
	/**
	 * @param string $name
	 * @return array
	 */
Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
442
443
444
445
446
	private function getDimensionsFromFilename($name) {
			$size = explode('-', $name);
			$x = (int) $size[0];
			$y = (int) $size[1];
			$aspectRatio = (float) ($x / $y);
447
			return array($x, $y, $aspectRatio);
Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
448
	}
Georg Ehrke's avatar
Georg Ehrke committed
449

Lukas Reschke's avatar
Lukas Reschke committed
450
451
452
453
454
	/**
	 * @param int $x
	 * @param int $y
	 * @return bool
	 */
Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
455
	private function unscalable($x, $y) {
456

Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
457
458
459
460
		$maxX = $this->getMaxX();
		$maxY = $this->getMaxY();
		$scalingUp = $this->getScalingUp();
		$maxScaleFactor = $this->getMaxScaleFactor();
461

Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
462
463
464
465
466
467
		if ($x < $maxX || $y < $maxY) {
			if ($scalingUp) {
				$scalefactor = $maxX / $x;
				if ($scalefactor > $maxScaleFactor) {
					return true;
				}
468
			} else {
Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
469
				return true;
470
471
			}
		}
472
		return false;
473
	}
474

475
	/**
476
	 * return a preview of a file
Georg Ehrke's avatar
Georg Ehrke committed
477
	 * @return \OC_Image
478
	 */
Georg Ehrke's avatar
Georg Ehrke committed
479
	public function getPreview() {
480
		if (!is_null($this->preview) && $this->preview->valid()) {
Georg Ehrke's avatar
Georg Ehrke committed
481
482
483
484
485
486
487
			return $this->preview;
		}

		$this->preview = null;
		$file = $this->getFile();
		$maxX = $this->getMaxX();
		$maxY = $this->getMaxY();
Georg Ehrke's avatar
Georg Ehrke committed
488
		$scalingUp = $this->getScalingUp();
489

490
		$fileInfo = $this->getFileInfo($file);
491
492
493
		if($fileInfo === null || $fileInfo === false) {
			return new \OC_Image();
		}
494
		$fileId = $fileInfo->getId();
Georg Ehrke's avatar
Georg Ehrke committed
495

Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
496
		$cached = $this->isCached($fileId);
497
		if ($cached) {
498
			$stream = $this->userView->fopen($cached, 'r');
499
500
501
502
503
			$this->preview = null;
			if ($stream) {
				$image = new \OC_Image();
				$image->loadFromFileHandle($stream);
				$this->preview = $image->valid() ? $image : null;
504

505
506
507
				$this->resizeAndCrop();
				fclose($stream);
			}
Georg Ehrke's avatar
Georg Ehrke committed
508
		}
Georg Ehrke's avatar
Georg Ehrke committed
509

510
		if (is_null($this->preview)) {
511
			$preview = null;
Georg Ehrke's avatar
Georg Ehrke committed
512

513
514
			foreach (self::$providers as $supportedMimeType => $provider) {
				if (!preg_match($supportedMimeType, $this->mimeType)) {
Georg Ehrke's avatar
Georg Ehrke committed
515
516
					continue;
				}
Georg Ehrke's avatar
Georg Ehrke committed
517

518
519
				\OC_Log::write('core', 'Generating preview for "' . $file . '" with "' . get_class($provider) . '"', \OC_Log::DEBUG);

Thomas Müller's avatar
Thomas Müller committed
520
				/** @var $provider Provider */
Georg Ehrke's avatar
Georg Ehrke committed
521
				$preview = $provider->getThumbnail($file, $maxX, $maxY, $scalingUp, $this->fileView);
522

523
				if (!($preview instanceof \OC_Image)) {
Georg Ehrke's avatar
Georg Ehrke committed
524
525
					continue;
				}
Georg Ehrke's avatar
Georg Ehrke committed
526

Georg Ehrke's avatar
Georg Ehrke committed
527
528
529
				$this->preview = $preview;
				$this->resizeAndCrop();

530
				$previewPath = $this->getPreviewPath($fileId);
531
				$cachePath = $this->buildCachePath($fileId);
Georg Ehrke's avatar
Georg Ehrke committed
532

533
				if ($this->userView->is_dir($this->getThumbnailsFolder() . '/') === false) {
Georg Ehrke's avatar
Georg Ehrke committed
534
					$this->userView->mkdir($this->getThumbnailsFolder() . '/');
Georg Ehrke's avatar
Georg Ehrke committed
535
				}
Georg Ehrke's avatar
Georg Ehrke committed
536

537
				if ($this->userView->is_dir($previewPath) === false) {
Georg Ehrke's avatar
Georg Ehrke committed
538
					$this->userView->mkdir($previewPath);
539
				}
540

Georg Ehrke's avatar
Georg Ehrke committed
541
				$this->userView->file_put_contents($cachePath, $preview->data());
Georg Ehrke's avatar
Georg Ehrke committed
542

Georg Ehrke's avatar
Georg Ehrke committed
543
544
				break;
			}
Georg Ehrke's avatar
Georg Ehrke committed
545
		}
546

547
		if (is_null($this->preview)) {
Georg Ehrke's avatar
Georg Ehrke committed
548
			$this->preview = new \OC_Image();
Georg Ehrke's avatar
Georg Ehrke committed
549
		}
Georg Ehrke's avatar
Georg Ehrke committed
550

Georg Ehrke's avatar
Georg Ehrke committed
551
		return $this->preview;
552
	}
553
554

	/**
555
556
	 * @param null|string $mimeType
	 * @throws NotFoundException
557
	 */
558
	public function showPreview($mimeType = null) {
559
560
561
562
563
		// Check if file is valid
		if($this->isFileValid() === false) {
			throw new NotFoundException('File not found.');
		}

Georg Ehrke's avatar
Georg Ehrke committed
564
		\OCP\Response::enableCaching(3600 * 24); // 24 hours
565
		if (is_null($this->preview)) {
Georg Ehrke's avatar
Georg Ehrke committed
566
			$this->getPreview();
Georg Ehrke's avatar
Georg Ehrke committed
567
		}
568
569
570
		if ($this->preview instanceof \OC_Image) {
			$this->preview->show($mimeType);
		}
571
572
	}

Georg Ehrke's avatar
Georg Ehrke committed
573
	/**
574
	 * resize, crop and fix orientation
Georg Ehrke's avatar
Georg Ehrke committed
575
	 * @return void
576
	 */
Georg Ehrke's avatar
Georg Ehrke committed
577
	private function resizeAndCrop() {
Georg Ehrke's avatar
Georg Ehrke committed
578
		$image = $this->preview;
Georg Ehrke's avatar
Georg Ehrke committed
579
580
		$x = $this->getMaxX();
		$y = $this->getMaxY();
Georg Ehrke's avatar
Georg Ehrke committed
581
		$scalingUp = $this->getScalingUp();
582
		$maxScaleFactor = $this->getMaxScaleFactor();
Georg Ehrke's avatar
Georg Ehrke committed
583

584
		if (!($image instanceof \OC_Image)) {
Georg Ehrke's avatar
Georg Ehrke committed
585
			\OC_Log::write('core', '$this->preview is not an instance of OC_Image', \OC_Log::DEBUG);
Georg Ehrke's avatar
Georg Ehrke committed
586
587
588
			return;
		}

589
590
		$realX = (int)$image->width();
		$realY = (int)$image->height();
Georg Ehrke's avatar
Georg Ehrke committed
591

592
		// compute $maxY and $maxX using the aspect of the generated preview
593
		if ($this->keepAspect) {
594
595
596
597
598
599
600
			$ratio = $realX / $realY;
			if($x / $ratio < $y) {
				// width restricted
				$y = $x / $ratio;
			} else {
				$x = $y * $ratio;
			}
601
602
		}

603
		if ($x === $realX && $y === $realY) {
Georg Ehrke's avatar
Georg Ehrke committed
604
			$this->preview = $image;
Georg Ehrke's avatar
Georg Ehrke committed
605
			return;
Georg Ehrke's avatar
Georg Ehrke committed
606
607
		}

608
609
		$factorX = $x / $realX;
		$factorY = $y / $realY;
Georg Ehrke's avatar
Georg Ehrke committed
610

611
		if ($factorX >= $factorY) {
Georg Ehrke's avatar
Georg Ehrke committed
612
			$factor = $factorX;
613
		} else {
Georg Ehrke's avatar
Georg Ehrke committed
614
615
			$factor = $factorY;
		}
Georg Ehrke's avatar
Georg Ehrke committed
616

617
618
		if ($scalingUp === false) {
			if ($factor > 1) {
Georg Ehrke's avatar
Georg Ehrke committed
619
620
				$factor = 1;
			}
Georg Ehrke's avatar
Georg Ehrke committed
621
		}
Georg Ehrke's avatar
Georg Ehrke committed
622

623
624
625
626
		if (!is_null($maxScaleFactor)) {
			if ($factor > $maxScaleFactor) {
				\OC_Log::write('core', 'scale factor reduced from ' . $factor . ' to ' . $maxScaleFactor, \OC_Log::DEBUG);
				$factor = $maxScaleFactor;
627
			}
628
		}
Georg Ehrke's avatar
Georg Ehrke committed
629

630
631
		$newXSize = (int)($realX * $factor);
		$newYSize = (int)($realY * $factor);
Georg Ehrke's avatar
Georg Ehrke committed
632

633
		$image->preciseResize($newXSize, $newYSize);
Georg Ehrke's avatar
Georg Ehrke committed
634

635
		if ($newXSize === $x && $newYSize === $y) {
Georg Ehrke's avatar
Georg Ehrke committed
636
637
638
			$this->preview = $image;
			return;
		}
Georg Ehrke's avatar
Georg Ehrke committed
639

640
641
		if ($newXSize >= $x && $newYSize >= $y) {
			$cropX = floor(abs($x - $newXSize) * 0.5);
Georg Ehrke's avatar
Georg Ehrke committed
642
			//don't crop previews on the Y axis, this sucks if it's a document.
Georg Ehrke's avatar
Georg Ehrke committed
643
644
			//$cropY = floor(abs($y - $newYsize) * 0.5);
			$cropY = 0;
Georg Ehrke's avatar
Georg Ehrke committed
645
646

			$image->crop($cropX, $cropY, $x, $y);
647

Georg Ehrke's avatar
Georg Ehrke committed
648
649
650
			$this->preview = $image;
			return;
		}
Georg Ehrke's avatar
Georg Ehrke committed
651

652
		if (($newXSize < $x || $newYSize < $y) && $scalingUp) {
653
654
655
			if ($newXSize > $x) {
				$cropX = floor(($newXSize - $x) * 0.5);
				$image->crop($cropX, 0, $x, $newYSize);
656
			}
Georg Ehrke's avatar
Georg Ehrke committed
657

658
659
660
			if ($newYSize > $y) {
				$cropY = floor(($newYSize - $y) * 0.5);
				$image->crop(0, $cropY, $newXSize, $y);
661
			}
Georg Ehrke's avatar
Georg Ehrke committed
662

663
664
			$newXSize = (int)$image->width();
			$newYSize = (int)$image->height();
Georg Ehrke's avatar
Georg Ehrke committed
665

Georg Ehrke's avatar
Georg Ehrke committed
666
			//create transparent background layer
667
668
669
			$backgroundLayer = imagecreatetruecolor($x, $y);
			$white = imagecolorallocate($backgroundLayer, 255, 255, 255);
			imagefill($backgroundLayer, 0, 0, $white);
Georg Ehrke's avatar
Georg Ehrke committed
670

Georg Ehrke's avatar
Georg Ehrke committed
671
			$image = $image->resource();
Georg Ehrke's avatar
Georg Ehrke committed
672

673
674
			$mergeX = floor(abs($x - $newXSize) * 0.5);
			$mergeY = floor(abs($y - $newYSize) * 0.5);
Georg Ehrke's avatar
Georg Ehrke committed
675

676
			imagecopy($backgroundLayer, $image, $mergeX, $mergeY, 0, 0, $newXSize, $newYSize);
Georg Ehrke's avatar
Georg Ehrke committed
677

678
679
			//$black = imagecolorallocate(0,0,0);
			//imagecolortransparent($transparentlayer, $black);
Georg Ehrke's avatar
Georg Ehrke committed
680

681
			$image = new \OC_Image($backgroundLayer);
Georg Ehrke's avatar
Georg Ehrke committed
682

Georg Ehrke's avatar
Georg Ehrke committed
683
684
			$this->preview = $image;
			return;
685
686
		}
	}
687

688
	/**
689
690
	 * register a new preview provider to be used
	 * @param string $class
691
	 * @param array $options
692
	 */
693
	public static function registerProvider($class, $options = array()) {
694
695
696
697
698
699
700
701
702
703
		/**
		 * Only register providers that have been explicitly enabled
		 *
		 * The following providers are enabled by default:
		 *  - OC\Preview\Image
		 *  - OC\Preview\MP3
		 *  - OC\Preview\TXT
		 *  - OC\Preview\MarkDown
		 *
		 * The following providers are disabled by default due to performance or privacy concerns:
704
705
706
707
708
709
		 *  - OC\Preview\MSOfficeDoc
		 *  - OC\Preview\MSOffice2003
		 *  - OC\Preview\MSOffice2007
		 *  - OC\Preview\OpenDocument
		 *  - OC\Preview\StarOffice
 		 *  - OC\Preview\SVG
710
		 *  - OC\Preview\Movie
711
		 *  - OC\Preview\PDF
712
713
714
715
		 *  - OC\Preview\TIFF
		 *  - OC\Preview\Illustrator
		 *  - OC\Preview\Postscript
		 *  - OC\Preview\Photoshop
716
717
718
719
720
721
722
723
724
725
726
727
728
		 */
		if(empty(self::$enabledProviders)) {
			self::$enabledProviders = \OC::$server->getConfig()->getSystemValue('enabledPreviewProviders', array(
				'OC\Preview\Image',
				'OC\Preview\MP3',
				'OC\Preview\TXT',
				'OC\Preview\MarkDown',
			));
		}

		if(in_array($class, self::$enabledProviders)) {
			self::$registeredProviders[] = array('class' => $class, 'options' => $options);
		}
729
	}
730

731
	/**
732
	 * create instances of all the registered preview providers
733
734
	 * @return void
	 */
735
	private static function initProviders() {
736
737
		if (!\OC::$server->getConfig()->getSystemValue('enable_previews', true)) {
			self::$providers = array();
738
739
740
			return;
		}

741
		if (!empty(self::$providers)) {
742
743
			return;
		}
Georg Ehrke's avatar
Georg Ehrke committed
744

745
		self::registerCoreProviders();
746
747
748
		foreach (self::$registeredProviders as $provider) {
			$class = $provider['class'];
			$options = $provider['options'];
Georg Ehrke's avatar
Georg Ehrke committed
749

Thomas Müller's avatar
Thomas Müller committed
750
			/** @var $object Provider */
751
752
			$object = new $class($options);
			self::$providers[$object->getMimeType()] = $object;
753
		}
Georg Ehrke's avatar
Georg Ehrke committed
754

755
756
		$keys = array_map('strlen', array_keys(self::$providers));
		array_multisort($keys, SORT_DESC, self::$providers);
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
	}

	protected static function registerCoreProviders() {
		self::registerProvider('OC\Preview\TXT');
		self::registerProvider('OC\Preview\MarkDown');
		self::registerProvider('OC\Preview\Image');
		self::registerProvider('OC\Preview\MP3');

		// SVG, Office and Bitmap require imagick
		if (extension_loaded('imagick')) {
			$checkImagick = new \Imagick();

			$imagickProviders = array(
				'SVG'	=> 'OC\Preview\SVG',
				'TIFF'	=> 'OC\Preview\TIFF',
				'PDF'	=> 'OC\Preview\PDF',
				'AI'	=> 'OC\Preview\Illustrator',
				'PSD'	=> 'OC\Preview\Photoshop',
				// Requires adding 'eps' => array('application/postscript', null), to lib/private/mimetypes.list.php
				'EPS'	=> 'OC\Preview\Postscript',
			);

			foreach ($imagickProviders as $queryFormat => $provider) {
				if (count($checkImagick->queryFormats($queryFormat)) === 1) {
					self::registerProvider($provider);
				}
			}

			if (count($checkImagick->queryFormats('PDF')) === 1) {
				// Office previews are currently not supported on Windows
				if (!\OC_Util::runningOnWindows() && \OC_Helper::is_function_enabled('shell_exec')) {
					$officeFound = is_string(\OC::$server->getConfig()->getSystemValue('preview_libreoffice_path', null));

					if (!$officeFound) {
						//let's see if there is libreoffice or openoffice on this machine
						$whichLibreOffice = shell_exec('command -v libreoffice');
						$officeFound = !empty($whichLibreOffice);
						if (!$officeFound) {
							$whichOpenOffice = shell_exec('command -v openoffice');
							$officeFound = !empty($whichOpenOffice);
						}
					}

					if ($officeFound) {
						self::registerProvider('OC\Preview\MSOfficeDoc');
						self::registerProvider('OC\Preview\MSOffice2003');
						self::registerProvider('OC\Preview\MSOffice2007');
						self::registerProvider('OC\Preview\OpenDocument');
						self::registerProvider('OC\Preview\StarOffice');
					}
				}
			}
		}

		// Video requires avconv or ffmpeg and is therefor
		// currently not supported on Windows.
		if (!\OC_Util::runningOnWindows()) {
			$avconvBinary = \OC_Helper::findBinaryPath('avconv');
			$ffmpegBinary = ($avconvBinary) ? null : \OC_Helper::findBinaryPath('ffmpeg');
816

817
818
819
820
821
822
823
824
			if ($avconvBinary || $ffmpegBinary) {
				// FIXME // a bit hacky but didn't want to use subclasses
				\OC\Preview\Movie::$avconvBinary = $avconvBinary;
				\OC\Preview\Movie::$ffmpegBinary = $ffmpegBinary;

				self::registerProvider('OC\Preview\Movie');
			}
		}
825
	}
Georg Ehrke's avatar
Georg Ehrke committed
826

827
828
829
	/**
	 * @param array $args
	 */
Georg Ehrke's avatar
Georg Ehrke committed
830
	public static function post_write($args) {
831
		self::post_delete($args, 'files/');
832
	}
833

834
835
836
	/**
	 * @param array $args
	 */