preview.php 21.5 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
require_once 'preview/image.php';
20
require_once 'preview/movie.php';
Georg Ehrke's avatar
Georg Ehrke committed
21
22
23
24
require_once 'preview/mp3.php';
require_once 'preview/svg.php';
require_once 'preview/txt.php';
require_once 'preview/office.php';
25
require_once 'preview/bitmap.php';
26

Georg Ehrke's avatar
Georg Ehrke committed
27
class Preview {
Georg Ehrke's avatar
Georg Ehrke committed
28
	//the thumbnail folder
29
	const THUMBNAILS_FOLDER = 'thumbnails';
Georg Ehrke's avatar
Georg Ehrke committed
30

Georg Ehrke's avatar
Georg Ehrke committed
31
	//config
Georg Ehrke's avatar
Georg Ehrke committed
32
33
34
	private $maxScaleFactor;
	private $configMaxX;
	private $configMaxY;
35

36
	//fileview object
Georg Ehrke's avatar
Georg Ehrke committed
37
38
	private $fileView = null;
	private $userView = null;
Georg Ehrke's avatar
Georg Ehrke committed
39
40
41
42
43

	//vars
	private $file;
	private $maxX;
	private $maxY;
44
45
	private $scalingUp;
	private $mimeType;
46
	private $keepAspect = false;
Georg Ehrke's avatar
Georg Ehrke committed
47

48
49
50
	//filemapper used for deleting previews
	// index is path, value is fileinfo
	static public $deleteFileMapper = array();
51
	static public $deleteChildrenMapper = array();
52

53
	/**
Thomas Müller's avatar
Thomas Müller committed
54
55
	 * preview images object
	 *
56
57
	 * @var \OC_Image
	 */
Georg Ehrke's avatar
Georg Ehrke committed
58
	private $preview;
59

60
61
62
	//preview providers
	static private $providers = array();
	static private $registeredProviders = array();
63
	static private $enabledProviders = array();
64

65
66
67
68
69
	/**
	 * @var \OCP\Files\FileInfo
	 */
	protected $info;

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

Georg Ehrke's avatar
Georg Ehrke committed
96
		//save parameters
Georg Ehrke's avatar
Georg Ehrke committed
97
98
99
		$this->setFile($file);
		$this->setMaxX($maxX);
		$this->setMaxY($maxY);
Georg Ehrke's avatar
Georg Ehrke committed
100
		$this->setScalingUp($scalingUp);
Georg Ehrke's avatar
Georg Ehrke committed
101

Georg Ehrke's avatar
Georg Ehrke committed
102
		$this->preview = null;
Georg Ehrke's avatar
Georg Ehrke committed
103

Georg Ehrke's avatar
Georg Ehrke committed
104
		//check if there are preview backends
105
		if (empty(self::$providers)) {
106
			self::initProviders();
Georg Ehrke's avatar
Georg Ehrke committed
107
		}
Georg Ehrke's avatar
Georg Ehrke committed
108

109
		if (empty(self::$providers)) {
Georg Ehrke's avatar
Georg Ehrke committed
110
111
			\OC_Log::write('core', 'No preview providers exist', \OC_Log::ERROR);
			throw new \Exception('No preview providers');
Georg Ehrke's avatar
Georg Ehrke committed
112
113
		}
	}
Georg Ehrke's avatar
Georg Ehrke committed
114

Georg Ehrke's avatar
Georg Ehrke committed
115
	/**
116
	 * returns the path of the file you want a thumbnail from
Georg Ehrke's avatar
Georg Ehrke committed
117
	 * @return string
118
	 */
119
	public function getFile() {
Georg Ehrke's avatar
Georg Ehrke committed
120
121
		return $this->file;
	}
Georg Ehrke's avatar
Georg Ehrke committed
122

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

	/**
132
	 * returns the max height of the preview
Georg Ehrke's avatar
Georg Ehrke committed
133
	 * @return integer
134
	 */
Georg Ehrke's avatar
Georg Ehrke committed
135
	public function getMaxY() {
Georg Ehrke's avatar
Georg Ehrke committed
136
137
		return $this->maxY;
	}
Georg Ehrke's avatar
Georg Ehrke committed
138

Georg Ehrke's avatar
Georg Ehrke committed
139
	/**
140
	 * returns whether or not scalingup is enabled
Georg Ehrke's avatar
Georg Ehrke committed
141
	 * @return bool
142
	 */
Georg Ehrke's avatar
Georg Ehrke committed
143
	public function getScalingUp() {
144
		return $this->scalingUp;
Georg Ehrke's avatar
Georg Ehrke committed
145
	}
Georg Ehrke's avatar
Georg Ehrke committed
146

Georg Ehrke's avatar
Georg Ehrke committed
147
	/**
148
	 * returns the name of the thumbnailfolder
Georg Ehrke's avatar
Georg Ehrke committed
149
	 * @return string
150
	 */
Georg Ehrke's avatar
Georg Ehrke committed
151
	public function getThumbnailsFolder() {
Georg Ehrke's avatar
Georg Ehrke committed
152
153
154
155
		return self::THUMBNAILS_FOLDER;
	}

	/**
156
	 * returns the max scale factor
157
	 * @return string
158
	 */
Georg Ehrke's avatar
Georg Ehrke committed
159
	public function getMaxScaleFactor() {
Georg Ehrke's avatar
Georg Ehrke committed
160
		return $this->maxScaleFactor;
Georg Ehrke's avatar
Georg Ehrke committed
161
162
163
	}

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

	/**
172
	 * returns the max height set in ownCloud's config
173
	 * @return string
174
	 */
Georg Ehrke's avatar
Georg Ehrke committed
175
	public function getConfigMaxY() {
Georg Ehrke's avatar
Georg Ehrke committed
176
		return $this->configMaxY;
Georg Ehrke's avatar
Georg Ehrke committed
177
	}
Georg Ehrke's avatar
Georg Ehrke committed
178

Lukas Reschke's avatar
Lukas Reschke committed
179
180
181
	/**
	 * @return false|Files\FileInfo|\OCP\Files\FileInfo
	 */
182
	protected function getFileInfo() {
183
184
185
186
187
		$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) {
188
189
190
191
192
			$this->info = $this->fileView->getFileInfo($this->file);
		}
		return $this->info;
	}

193
194
195
196
197
198
199
200
201
202
203
204
205
206
207

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

225
	/**
226
	 * set mime type explicitly
227
	 * @param string $mimeType
228
	 */
229
230
	public function setMimetype($mimeType) {
		$this->mimeType = $mimeType;
231
232
	}

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

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

	/**
276
	 * set whether or not scalingup is enabled
Georg Ehrke's avatar
Georg Ehrke committed
277
	 * @param bool $scalingUp
278
	 * @return \OC\Preview $this
279
	 */
Georg Ehrke's avatar
Georg Ehrke committed
280
	public function setScalingup($scalingUp) {
281
		if ($this->getMaxScaleFactor() === 1) {
Georg Ehrke's avatar
Georg Ehrke committed
282
			$scalingUp = false;
Georg Ehrke's avatar
Georg Ehrke committed
283
		}
284
		$this->scalingUp = $scalingUp;
Georg Ehrke's avatar
Georg Ehrke committed
285
286
287
		return $this;
	}

288
289
290
291
	/**
	 * @param bool $keepAspect
	 * @return $this
	 */
292
293
294
295
296
	public function setKeepAspect($keepAspect) {
		$this->keepAspect = $keepAspect;
		return $this;
	}

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

308
		if (!$this->fileView->file_exists($file)) {
Georg Ehrke's avatar
Georg Ehrke committed
309
			\OC_Log::write('core', 'File:"' . $file . '" not found', \OC_Log::DEBUG);
Georg Ehrke's avatar
Georg Ehrke committed
310
311
312
313
314
315
			return false;
		}

		return true;
	}

Georg Ehrke's avatar
Georg Ehrke committed
316
	/**
317
	 * deletes previews of a file with specific x and y
Georg Ehrke's avatar
Georg Ehrke committed
318
	 * @return bool
319
	 */
Georg Ehrke's avatar
Georg Ehrke committed
320
	public function deletePreview() {
Georg Ehrke's avatar
Georg Ehrke committed
321
322
		$file = $this->getFile();

323
		$fileInfo = $this->getFileInfo($file);
324
325
		if($fileInfo !== null && $fileInfo !== false) {
			$fileId = $fileInfo->getId();
326

327
			$previewPath = $this->buildCachePath($fileId);
328
329
330
			return $this->userView->unlink($previewPath);
		}
		return false;
Georg Ehrke's avatar
Georg Ehrke committed
331
332
333
	}

	/**
334
	 * deletes all previews of a file
335
	 */
Georg Ehrke's avatar
Georg Ehrke committed
336
	public function deleteAllPreviews() {
Georg Ehrke's avatar
Georg Ehrke committed
337
		$file = $this->getFile();
Georg Ehrke's avatar
Georg Ehrke committed
338

339
		$fileInfo = $this->getFileInfo($file);
340

341
342
343
344
345
346
347
348
349
350
351
352
		$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);
			}
353
		}
Georg Ehrke's avatar
Georg Ehrke committed
354
355
356
	}

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

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

368
		//does a preview with the wanted height and width already exist?
369
370
		if ($this->userView->file_exists($preview)) {
			return $preview;
371
		}
Georg Ehrke's avatar
Georg Ehrke committed
372

Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
373
		return $this->isCachedBigger($fileId);
374
375
376
	}

	/**
377
	 * check if a bigger version of thumbnail of file is cached
Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
378
	 * @param int $fileId fileId of the original image
379
380
	 * @return string|false path to bigger thumbnail if it exists or false
	*/
Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
381
	private function isCachedBigger($fileId) {
Georg Ehrke's avatar
Georg Ehrke committed
382

383
		if (is_null($fileId)) {
384
385
386
			return false;
		}

387
388
389
390
391
		// in order to not loose quality we better generate aspect preserving previews from the original file
		if ($this->keepAspect) {
			return false;
		}

392
393
		$maxX = $this->getMaxX();

Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
394
395
396
397
398
399
400
401
402
		//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
403
404
		}

Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
405
406
		return false;
	}
Thomas Müller's avatar
Thomas Müller committed
407

Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
408
	/**
409
	 * get possible bigger thumbnails of the given image
Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
410
	 * @param int $fileId fileId of the original image
411
	 * @return array an array of paths to bigger thumbnails
Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
412
413
	*/
	private function getPossibleThumbnails($fileId) {
414
415

		if (is_null($fileId)) {
Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
416
			return array();
417
		}
Georg Ehrke's avatar
Georg Ehrke committed
418

419
		$previewPath = $this->getPreviewPath($fileId);
Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
420
421

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

423
		//array for usable cached thumbnails
Georg Ehrke's avatar
Georg Ehrke committed
424
		$possibleThumbnails = array();
Georg Ehrke's avatar
Georg Ehrke committed
425

Georg Ehrke's avatar
Georg Ehrke committed
426
		$allThumbnails = $this->userView->getDirectoryContent($previewPath);
427
		foreach ($allThumbnails as $thumbnail) {
428
			$name = rtrim($thumbnail['name'], '.png');
Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
429
			list($x, $y, $aspectRatio) = $this->getDimensionsFromFilename($name);
Georg Ehrke's avatar
Georg Ehrke committed
430

431
			if (abs($aspectRatio - $wantedAspectRatio) >= 0.000001
Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
432
433
				|| $this->unscalable($x, $y)
			) {
434
435
				continue;
			}
Georg Ehrke's avatar
Georg Ehrke committed
436
			$possibleThumbnails[$x] = $thumbnail['path'];
437
		}
Georg Ehrke's avatar
Georg Ehrke committed
438

Georg Ehrke's avatar
Georg Ehrke committed
439
		ksort($possibleThumbnails);
Georg Ehrke's avatar
Georg Ehrke committed
440

Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
441
442
		return $possibleThumbnails;
	}
Georg Ehrke's avatar
Georg Ehrke committed
443

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

Lukas Reschke's avatar
Lukas Reschke committed
456
457
458
459
460
	/**
	 * @param int $x
	 * @param int $y
	 * @return bool
	 */
Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
461
	private function unscalable($x, $y) {
462

Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
463
464
465
466
		$maxX = $this->getMaxX();
		$maxY = $this->getMaxY();
		$scalingUp = $this->getScalingUp();
		$maxScaleFactor = $this->getMaxScaleFactor();
467

Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
468
469
470
471
472
473
		if ($x < $maxX || $y < $maxY) {
			if ($scalingUp) {
				$scalefactor = $maxX / $x;
				if ($scalefactor > $maxScaleFactor) {
					return true;
				}
474
			} else {
Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
475
				return true;
476
477
			}
		}
478
		return false;
479
	}
480

481
	/**
482
	 * return a preview of a file
Georg Ehrke's avatar
Georg Ehrke committed
483
	 * @return \OC_Image
484
	 */
Georg Ehrke's avatar
Georg Ehrke committed
485
	public function getPreview() {
486
		if (!is_null($this->preview) && $this->preview->valid()) {
Georg Ehrke's avatar
Georg Ehrke committed
487
488
489
490
491
492
493
			return $this->preview;
		}

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

496
		$fileInfo = $this->getFileInfo($file);
497
498
499
		if($fileInfo === null || $fileInfo === false) {
			return new \OC_Image();
		}
500
		$fileId = $fileInfo->getId();
Georg Ehrke's avatar
Georg Ehrke committed
501

Jörn Friedrich Dreyer's avatar
Jörn Friedrich Dreyer committed
502
		$cached = $this->isCached($fileId);
503
		if ($cached) {
504
			$stream = $this->userView->fopen($cached, 'r');
505
506
507
508
509
			$this->preview = null;
			if ($stream) {
				$image = new \OC_Image();
				$image->loadFromFileHandle($stream);
				$this->preview = $image->valid() ? $image : null;
510

511
512
513
				$this->resizeAndCrop();
				fclose($stream);
			}
Georg Ehrke's avatar
Georg Ehrke committed
514
		}
Georg Ehrke's avatar
Georg Ehrke committed
515

516
		if (is_null($this->preview)) {
517
			$preview = null;
Georg Ehrke's avatar
Georg Ehrke committed
518

519
520
			foreach (self::$providers as $supportedMimeType => $provider) {
				if (!preg_match($supportedMimeType, $this->mimeType)) {
Georg Ehrke's avatar
Georg Ehrke committed
521
522
					continue;
				}
Georg Ehrke's avatar
Georg Ehrke committed
523

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

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

529
				if (!($preview instanceof \OC_Image)) {
Georg Ehrke's avatar
Georg Ehrke committed
530
531
					continue;
				}
Georg Ehrke's avatar
Georg Ehrke committed
532

Georg Ehrke's avatar
Georg Ehrke committed
533
534
535
				$this->preview = $preview;
				$this->resizeAndCrop();

536
				$previewPath = $this->getPreviewPath($fileId);
537
				$cachePath = $this->buildCachePath($fileId);
Georg Ehrke's avatar
Georg Ehrke committed
538

539
				if ($this->userView->is_dir($this->getThumbnailsFolder() . '/') === false) {
Georg Ehrke's avatar
Georg Ehrke committed
540
					$this->userView->mkdir($this->getThumbnailsFolder() . '/');
Georg Ehrke's avatar
Georg Ehrke committed
541
				}
Georg Ehrke's avatar
Georg Ehrke committed
542

543
				if ($this->userView->is_dir($previewPath) === false) {
Georg Ehrke's avatar
Georg Ehrke committed
544
					$this->userView->mkdir($previewPath);
545
				}
546

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

Georg Ehrke's avatar
Georg Ehrke committed
549
550
				break;
			}
Georg Ehrke's avatar
Georg Ehrke committed
551
		}
552

553
		if (is_null($this->preview)) {
Georg Ehrke's avatar
Georg Ehrke committed
554
			$this->preview = new \OC_Image();
Georg Ehrke's avatar
Georg Ehrke committed
555
		}
Georg Ehrke's avatar
Georg Ehrke committed
556

Georg Ehrke's avatar
Georg Ehrke committed
557
		return $this->preview;
558
	}
559
560

	/**
561
562
	 * @param null|string $mimeType
	 * @throws NotFoundException
563
	 */
564
	public function showPreview($mimeType = null) {
565
566
567
568
569
		// Check if file is valid
		if($this->isFileValid() === false) {
			throw new NotFoundException('File not found.');
		}

Georg Ehrke's avatar
Georg Ehrke committed
570
		\OCP\Response::enableCaching(3600 * 24); // 24 hours
571
		if (is_null($this->preview)) {
Georg Ehrke's avatar
Georg Ehrke committed
572
			$this->getPreview();
Georg Ehrke's avatar
Georg Ehrke committed
573
		}
574
575
576
		if ($this->preview instanceof \OC_Image) {
			$this->preview->show($mimeType);
		}
577
578
	}

Georg Ehrke's avatar
Georg Ehrke committed
579
	/**
580
	 * resize, crop and fix orientation
Georg Ehrke's avatar
Georg Ehrke committed
581
	 * @return void
582
	 */
Georg Ehrke's avatar
Georg Ehrke committed
583
	private function resizeAndCrop() {
Georg Ehrke's avatar
Georg Ehrke committed
584
		$image = $this->preview;
Georg Ehrke's avatar
Georg Ehrke committed
585
586
		$x = $this->getMaxX();
		$y = $this->getMaxY();
Georg Ehrke's avatar
Georg Ehrke committed
587
		$scalingUp = $this->getScalingUp();
588
		$maxScaleFactor = $this->getMaxScaleFactor();
Georg Ehrke's avatar
Georg Ehrke committed
589

590
		if (!($image instanceof \OC_Image)) {
Georg Ehrke's avatar
Georg Ehrke committed
591
			\OC_Log::write('core', '$this->preview is not an instance of OC_Image', \OC_Log::DEBUG);
Georg Ehrke's avatar
Georg Ehrke committed
592
593
594
			return;
		}

595
596
		$realX = (int)$image->width();
		$realY = (int)$image->height();
Georg Ehrke's avatar
Georg Ehrke committed
597

598
		// compute $maxY and $maxX using the aspect of the generated preview
599
		if ($this->keepAspect) {
600
601
602
603
604
605
606
			$ratio = $realX / $realY;
			if($x / $ratio < $y) {
				// width restricted
				$y = $x / $ratio;
			} else {
				$x = $y * $ratio;
			}
607
608
		}

609
		if ($x === $realX && $y === $realY) {
Georg Ehrke's avatar
Georg Ehrke committed
610
			$this->preview = $image;
Georg Ehrke's avatar
Georg Ehrke committed
611
			return;
Georg Ehrke's avatar
Georg Ehrke committed
612
613
		}

614
615
		$factorX = $x / $realX;
		$factorY = $y / $realY;
Georg Ehrke's avatar
Georg Ehrke committed
616

617
		if ($factorX >= $factorY) {
Georg Ehrke's avatar
Georg Ehrke committed
618
			$factor = $factorX;
619
		} else {
Georg Ehrke's avatar
Georg Ehrke committed
620
621
			$factor = $factorY;
		}
Georg Ehrke's avatar
Georg Ehrke committed
622

623
624
		if ($scalingUp === false) {
			if ($factor > 1) {
Georg Ehrke's avatar
Georg Ehrke committed
625
626
				$factor = 1;
			}
Georg Ehrke's avatar
Georg Ehrke committed
627
		}
Georg Ehrke's avatar
Georg Ehrke committed
628

629
630
631
632
		if (!is_null($maxScaleFactor)) {
			if ($factor > $maxScaleFactor) {
				\OC_Log::write('core', 'scale factor reduced from ' . $factor . ' to ' . $maxScaleFactor, \OC_Log::DEBUG);
				$factor = $maxScaleFactor;
633
			}
634
		}
Georg Ehrke's avatar
Georg Ehrke committed
635

636
637
		$newXSize = (int)($realX * $factor);
		$newYSize = (int)($realY * $factor);
Georg Ehrke's avatar
Georg Ehrke committed
638

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

641
		if ($newXSize === $x && $newYSize === $y) {
Georg Ehrke's avatar
Georg Ehrke committed
642
643
644
			$this->preview = $image;
			return;
		}
Georg Ehrke's avatar
Georg Ehrke committed
645

646
647
		if ($newXSize >= $x && $newYSize >= $y) {
			$cropX = floor(abs($x - $newXSize) * 0.5);
Georg Ehrke's avatar
Georg Ehrke committed
648
			//don't crop previews on the Y axis, this sucks if it's a document.
Georg Ehrke's avatar
Georg Ehrke committed
649
650
			//$cropY = floor(abs($y - $newYsize) * 0.5);
			$cropY = 0;
Georg Ehrke's avatar
Georg Ehrke committed
651
652

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

Georg Ehrke's avatar
Georg Ehrke committed
654
655
656
			$this->preview = $image;
			return;
		}
Georg Ehrke's avatar
Georg Ehrke committed
657

658
		if (($newXSize < $x || $newYSize < $y) && $scalingUp) {
659
660
661
			if ($newXSize > $x) {
				$cropX = floor(($newXSize - $x) * 0.5);
				$image->crop($cropX, 0, $x, $newYSize);
662
			}
Georg Ehrke's avatar
Georg Ehrke committed
663

664
665
666
			if ($newYSize > $y) {
				$cropY = floor(($newYSize - $y) * 0.5);
				$image->crop(0, $cropY, $newXSize, $y);
667
			}
Georg Ehrke's avatar
Georg Ehrke committed
668

669
670
			$newXSize = (int)$image->width();
			$newYSize = (int)$image->height();
Georg Ehrke's avatar
Georg Ehrke committed
671

Georg Ehrke's avatar
Georg Ehrke committed
672
			//create transparent background layer
673
674
675
			$backgroundLayer = imagecreatetruecolor($x, $y);
			$white = imagecolorallocate($backgroundLayer, 255, 255, 255);
			imagefill($backgroundLayer, 0, 0, $white);
Georg Ehrke's avatar
Georg Ehrke committed
676

Georg Ehrke's avatar
Georg Ehrke committed
677
			$image = $image->resource();
Georg Ehrke's avatar
Georg Ehrke committed
678

679
680
			$mergeX = floor(abs($x - $newXSize) * 0.5);
			$mergeY = floor(abs($y - $newYSize) * 0.5);
Georg Ehrke's avatar
Georg Ehrke committed
681

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

684
685
			//$black = imagecolorallocate(0,0,0);
			//imagecolortransparent($transparentlayer, $black);
Georg Ehrke's avatar
Georg Ehrke committed
686

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

Georg Ehrke's avatar
Georg Ehrke committed
689
690
			$this->preview = $image;
			return;
691
692
		}
	}
693

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

737
	/**
738
	 * create instances of all the registered preview providers
739
740
	 * @return void
	 */
741
	private static function initProviders() {
742
743
		if (!\OC::$server->getConfig()->getSystemValue('enable_previews', true)) {
			self::$providers = array();
744
745
746
			return;
		}

747
		if (count(self::$providers) > 0) {
748
749
			return;
		}
Georg Ehrke's avatar
Georg Ehrke committed
750

751
752
753
		foreach (self::$registeredProviders as $provider) {
			$class = $provider['class'];
			$options = $provider['options'];
Georg Ehrke's avatar
Georg Ehrke committed
754

Thomas Müller's avatar
Thomas Müller committed
755
			/** @var $object Provider */
756
757
			$object = new $class($options);
			self::$providers[$object->getMimeType()] = $object;
758
		}
Georg Ehrke's avatar
Georg Ehrke committed
759

760
761
		$keys = array_map('strlen', array_keys(self::$providers));
		array_multisort($keys, SORT_DESC, self::$providers);
762

763
	}
Georg Ehrke's avatar
Georg Ehrke committed
764

765
766
767
	/**
	 * @param array $args
	 */
Georg Ehrke's avatar
Georg Ehrke committed
768
	public static function post_write($args) {
769
		self::post_delete($args, 'files/');
770
	}
771

772
773
774
	/**
	 * @param array $args
	 */
775
776
777
778
	public static function prepare_delete_files($args) {
		self::prepare_delete($args, 'files/');
	}

779
780
781
782
	/**
	 * @param array $args
	 * @param string $prefix
	 */
783
	public static function prepare_delete($args, $prefix='') {
784
		$path = $args['path'];
785
		if (substr($path, 0, 1) === '/') {
786
787
			$path = substr($path, 1);
		}
788
789
790

		$view = new \OC\Files\View('/' . \OC_User::getUser() . '/' . $prefix);

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
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
		$absPath = Files\Filesystem::normalizePath($view->getAbsolutePath($path));
		self::addPathToDeleteFileMapper($absPath, $view->getFileInfo($path));
		if ($view->is_dir($path)) {
			$children = self::getAllChildren($view, $path);
			self