preview.php 23.7 KB
Newer Older
1
2
<?php
/**
3
4
 * Copyright (c) 2013 Frank Karlitschek frank@owncloud.org
 * Copyright (c) 2013 Georg Ehrke georg@ownCloud.com
5
6
7
 * 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\NotFoundException;
Thomas Müller's avatar
Thomas Müller committed
18

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

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

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

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

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

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

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

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

62
	/**
63
	 * check if thumbnail or bigger version of thumbnail of file is cached
64
65
66
67
68
	 * @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
69
	 * @param bool $scalingUp Disable/Enable upscaling of previews
Lukas Reschke's avatar
Lukas Reschke committed
70
	 * @throws \Exception
71
	 * @return mixed (bool / string)
72
73
74
75
	 *                    false if thumbnail does not exist
	 *                    path to thumbnail if thumbnail exists
	 */
	public function __construct($user = '', $root = '/', $file = '', $maxX = 1, $maxY = 1, $scalingUp = true) {
76
		//init fileviews
77
		if ($user === '') {
78
79
80
81
82
			$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
83
		//set config
Georg Ehrke's avatar
Georg Ehrke committed
84
85
		$this->configMaxX = \OC_Config::getValue('preview_max_x', null);
		$this->configMaxY = \OC_Config::getValue('preview_max_y', null);
86
		$this->maxScaleFactor = \OC_Config::getValue('preview_max_scale_factor', 2);
Georg Ehrke's avatar
Georg Ehrke committed
87

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

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

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

101
		if (empty(self::$providers)) {
Georg Ehrke's avatar
Georg Ehrke committed
102
103
			\OC_Log::write('core', 'No preview providers exist', \OC_Log::ERROR);
			throw new \Exception('No preview providers');
Georg Ehrke's avatar
Georg Ehrke committed
104
105
		}
	}
Georg Ehrke's avatar
Georg Ehrke committed
106

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

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

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

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

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

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

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

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

Lukas Reschke's avatar
Lukas Reschke committed
171
172
173
	/**
	 * @return false|Files\FileInfo|\OCP\Files\FileInfo
	 */
174
	protected function getFileInfo() {
175
176
177
178
179
		$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) {
180
181
182
183
184
			$this->info = $this->fileView->getFileInfo($this->file);
		}
		return $this->info;
	}

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

	/**
	 * @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
200
	/**
201
	 * set the path of the file you want a thumbnail from
202
	 * @param string $file
Thomas Müller's avatar
Thomas Müller committed
203
	 * @return \OC\Preview $this
204
	 */
Georg Ehrke's avatar
Georg Ehrke committed
205
206
	public function setFile($file) {
		$this->file = $file;
207
		$this->info = null;
208
		if ($file !== '') {
209
210
			$this->getFileInfo();
			if($this->info !== null && $this->info !== false) {
211
				$this->mimeType = $this->info->getMimetype();
212
			}
213
		}
Georg Ehrke's avatar
Georg Ehrke committed
214
215
216
		return $this;
	}

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

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

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

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

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

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

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

		return true;
	}

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

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

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

	/**
326
	 * deletes all previews of a file
327
	 */
Georg Ehrke's avatar
Georg Ehrke committed
328
	public function deleteAllPreviews() {
Georg Ehrke's avatar
Georg Ehrke committed
329
		$file = $this->getFile();
Georg Ehrke's avatar
Georg Ehrke committed
330

331
		$fileInfo = $this->getFileInfo($file);
332

333
334
335
336
337
338
339
340
341
342
343
344
		$toDelete = $this->getChildren();
		$toDelete[] = $fileInfo;

		foreach ($toDelete as $delete) {
			if ($delete !== null && $delete !== false) {
				/** @var \OCP\Files\FileInfo $delete */
				$fileId = $delete->getId();

				$previewPath = $this->getPreviewPath($fileId);
				$this->userView->deleteAll($previewPath);
				$this->userView->rmdir($previewPath);
			}
345
		}
Georg Ehrke's avatar
Georg Ehrke committed
346
347
348
	}

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

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

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

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

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

375
		if (is_null($fileId)) {
376
377
378
			return false;
		}

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

384
385
		$maxX = $this->getMaxX();

Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
386
387
388
389
390
391
392
393
394
		//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
395
396
		}

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

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

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

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

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

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

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

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

Georg Ehrke's avatar
Georg Ehrke committed
431
		ksort($possibleThumbnails);
Georg Ehrke's avatar
Georg Ehrke committed
432

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Georg Ehrke's avatar
Georg Ehrke committed
541
542
				break;
			}
Georg Ehrke's avatar
Georg Ehrke committed
543
		}
544

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Georg Ehrke's avatar
Georg Ehrke committed
669
			$image = $image->resource();
Georg Ehrke's avatar
Georg Ehrke committed
670

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

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

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

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

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

686
	/**
687
688
	 * register a new preview provider to be used
	 * @param string $class
689
	 * @param array $options
690
	 */
691
	public static function registerProvider($class, $options = array()) {
692
693
694
695
696
697
698
699
700
701
		/**
		 * 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:
702
703
704
705
706
707
		 *  - OC\Preview\MSOfficeDoc
		 *  - OC\Preview\MSOffice2003
		 *  - OC\Preview\MSOffice2007
		 *  - OC\Preview\OpenDocument
		 *  - OC\Preview\StarOffice
 		 *  - OC\Preview\SVG
708
		 *  - OC\Preview\Movie
709
		 *  - OC\Preview\PDF
710
711
712
713
		 *  - OC\Preview\TIFF
		 *  - OC\Preview\Illustrator
		 *  - OC\Preview\Postscript
		 *  - OC\Preview\Photoshop
714
715
716
717
718
719
720
721
722
723
724
725
726
		 */
		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);
		}
727
	}
728

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

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

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

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

753
754
		$keys = array_map('strlen', array_keys(self::$providers));
		array_multisort($keys, SORT_DESC, self::$providers);
755
756
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
	}

	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');
814

815
816
817
818
819
820
821
822
			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');
			}
		}
823
	}
Georg Ehrke's avatar
Georg Ehrke committed
824

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

832
833
834
	/**
	 * @param array $args
	 */
835
836
837
838
	public static function prepare_delete_files($args) {
		self::prepare_delete($args, 'files/');
	}

839
840
841
842
	/**
	 * @param array $args
	 * @param string $prefix
	 */