preview.php 23.8 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

19
require_once 'preview/bitmap.php';
20

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

333
		$fileInfo = $this->getFileInfo($file);
334

335
336
337
338
339
340
341
342
343
344
345
346
		$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);
			}
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
	 */
837
838
839
840
	public static function prepare_delete_files($args) {
		self::prepare_delete