Completed
Pull Request — stable8.2 (#24656)
by Joas
12:22
created

Preview::unscalable()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 5.009
Metric Value
dl 0
loc 20
ccs 13
cts 14
cp 0.9286
rs 8.8571
cc 5
eloc 13
nc 4
nop 2
crap 5.009
1
<?php
2
/**
3
 * @author Björn Schießle <[email protected]>
4
 * @author Frank Karlitschek <[email protected]>
5
 * @author Georg Ehrke <[email protected]>
6
 * @author Joas Schilling <[email protected]>
7
 * @author Jörn Friedrich Dreyer <[email protected]>
8
 * @author Lukas Reschke <[email protected]>
9
 * @author Morris Jobke <[email protected]>
10
 * @author Olivier Paroz <[email protected]>
11
 * @author Robin Appelman <[email protected]>
12
 * @author Thomas Müller <[email protected]>
13
 * @author Tobias Kaminsky <[email protected]>
14
 *
15
 * @copyright Copyright (c) 2015, ownCloud, Inc.
16
 * @license AGPL-3.0
17
 *
18
 * This code is free software: you can redistribute it and/or modify
19
 * it under the terms of the GNU Affero General Public License, version 3,
20
 * as published by the Free Software Foundation.
21
 *
22
 * This program is distributed in the hope that it will be useful,
23
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25
 * GNU Affero General Public License for more details.
26
 *
27
 * You should have received a copy of the GNU Affero General Public License, version 3,
28
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
29
 *
30
 */
31
namespace OC;
32
33
use OC\Preview\Provider;
34
use OCP\Files\FileInfo;
35
use OCP\Files\NotFoundException;
36
37
class Preview {
38
	//the thumbnail folder
39
	const THUMBNAILS_FOLDER = 'thumbnails';
40
41
	const MODE_FILL = 'fill';
42
	const MODE_COVER = 'cover';
43
44
	//config
45
	private $maxScaleFactor;
46
	/** @var int maximum width allowed for a preview */
47
	private $configMaxWidth;
48
	/** @var int maximum height allowed for a preview */
49
	private $configMaxHeight;
50
51
	//fileview object
52
	private $fileView = null;
53
	private $userView = null;
54
55
	//vars
56
	private $file;
57
	private $maxX;
58
	private $maxY;
59
	private $scalingUp;
60
	private $mimeType;
61
	private $keepAspect = false;
62
	private $mode = self::MODE_FILL;
63
64
	//used to calculate the size of the preview to generate
65
	/** @var int $maxPreviewWidth max width a preview can have */
66
	private $maxPreviewWidth;
67
	/** @var int $maxPreviewHeight max height a preview can have */
68
	private $maxPreviewHeight;
69
	/** @var int $previewWidth calculated width of the preview we're looking for */
70
	private $previewWidth;
71
	/** @var int $previewHeight calculated height of the preview we're looking for */
72
	private $previewHeight;
73
74
	//filemapper used for deleting previews
75
	// index is path, value is fileinfo
76
	static public $deleteFileMapper = array();
77
	static public $deleteChildrenMapper = array();
78
79
	/**
80
	 * preview images object
81
	 *
82
	 * @var \OCP\IImage
83
	 */
84
	private $preview;
85
86
	/**
87
	 * @var \OCP\Files\FileInfo
88
	 */
89
	protected $info;
90
91
	/**
92
	 * check if thumbnail or bigger version of thumbnail of file is cached
93
	 *
94
	 * @param string $user userid - if no user is given, OC_User::getUser will be used
95
	 * @param string $root path of root
96
	 * @param string $file The path to the file where you want a thumbnail from
97
	 * @param int $maxX The maximum X size of the thumbnail. It can be smaller depending on the
98
	 *     shape of the image
99
	 * @param int $maxY The maximum Y size of the thumbnail. It can be smaller depending on the
100
	 *     shape of the image
101
	 * @param bool $scalingUp Disable/Enable upscaling of previews
102
	 *
103
	 * @throws \Exception
104
	 * @return mixed (bool / string)
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
105
	 *                    false if thumbnail does not exist
106
	 *                    path to thumbnail if thumbnail exists
107
	 */
108 169
	public function __construct(
109
		$user = '',
110
		$root = '/',
111
		$file = '', $maxX = 1,
112
		$maxY = 1,
113
		$scalingUp = true
114
	) {
115
		//init fileviews
116 169
		if ($user === '') {
117 1
			$user = \OC_User::getUser();
118 1
		}
119 169
		$this->fileView = new \OC\Files\View('/' . $user . '/' . $root);
120 169
		$this->userView = new \OC\Files\View('/' . $user);
121
122
		//set config
123 169
		$sysConfig = \OC::$server->getConfig();
124 169
		$this->configMaxWidth = $sysConfig->getSystemValue('preview_max_x', 2048);
125 169
		$this->configMaxHeight = $sysConfig->getSystemValue('preview_max_y', 2048);
126 169
		$this->maxScaleFactor = $sysConfig->getSystemValue('preview_max_scale_factor', 2);
127
128
		//save parameters
129 169
		$this->setFile($file);
130 169
		$this->setMaxX((int)$maxX);
131 169
		$this->setMaxY((int)$maxY);
132 169
		$this->setScalingUp($scalingUp);
133
134 169
		$this->preview = null;
135
136
		//check if there are preview backends
137 169
		if (!\OC::$server->getPreviewManager()
138 169
						 ->hasProviders()
139 169
			&& \OC::$server->getConfig()
140
						   ->getSystemValue('enable_previews', true)
141 169
		) {
142
			\OCP\Util::writeLog('core', 'No preview providers exist', \OCP\Util::ERROR);
143
			throw new \Exception('No preview providers');
144
		}
145 169
	}
146
147
	/**
148
	 * returns the path of the file you want a thumbnail from
149
	 *
150
	 * @return string
151
	 */
152 168
	public function getFile() {
153 168
		return $this->file;
154
	}
155
156
	/**
157
	 * returns the max width of the preview
158
	 *
159
	 * @return integer
160
	 */
161 167
	public function getMaxX() {
162 167
		return $this->maxX;
163
	}
164
165
	/**
166
	 * returns the max height of the preview
167
	 *
168
	 * @return integer
169
	 */
170 167
	public function getMaxY() {
171 167
		return $this->maxY;
172
	}
173
174
	/**
175
	 * returns whether or not scalingup is enabled
176
	 *
177
	 * @return bool
178
	 */
179 167
	public function getScalingUp() {
180 167
		return $this->scalingUp;
181
	}
182
183
	/**
184
	 * returns the name of the thumbnailfolder
185
	 *
186
	 * @return string
187
	 */
188 168
	public function getThumbnailsFolder() {
189 168
		return self::THUMBNAILS_FOLDER;
190
	}
191
192
	/**
193
	 * returns the max scale factor
194
	 *
195
	 * @return string
196
	 */
197 169
	public function getMaxScaleFactor() {
198 169
		return $this->maxScaleFactor;
199
	}
200
201
	/**
202
	 * returns the max width set in ownCloud's config
203
	 *
204
	 * @return string
205
	 */
206 169
	public function getConfigMaxX() {
207 169
		return $this->configMaxWidth;
208
	}
209
210
	/**
211
	 * returns the max height set in ownCloud's config
212
	 *
213
	 * @return string
214
	 */
215 169
	public function getConfigMaxY() {
216 169
		return $this->configMaxHeight;
217
	}
218
219
	/**
220
	 * Returns the FileInfo object associated with the file to preview
221
	 *
222
	 * @return false|Files\FileInfo|\OCP\Files\FileInfo
223
	 */
224 169
	protected function getFileInfo() {
225 169
		$absPath = $this->fileView->getAbsolutePath($this->file);
226 169
		$absPath = Files\Filesystem::normalizePath($absPath);
227 169
		if (array_key_exists($absPath, self::$deleteFileMapper)) {
228
			$this->info = self::$deleteFileMapper[$absPath];
229 169
		} else if (!$this->info) {
230 167
			$this->info = $this->fileView->getFileInfo($this->file);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->fileView->getFileInfo($this->file) can also be of type array or false. However, the property $info is declared as type object<OCP\Files\FileInfo>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
231 167
		}
232
233 169
		return $this->info;
234
	}
235
236
237
	/**
238
	 * @return array|null
239
	 */
240 98
	private function getChildren() {
241 98
		$absPath = $this->fileView->getAbsolutePath($this->file);
242 98
		$absPath = Files\Filesystem::normalizePath($absPath);
243
244 98
		if (array_key_exists($absPath, self::$deleteChildrenMapper)) {
245
			return self::$deleteChildrenMapper[$absPath];
246
		}
247
248 98
		return null;
249
	}
250
251
	/**
252
	 * Sets the path of the file you want a preview of
253
	 *
254
	 * @param string $file
255
	 * @param \OCP\Files\FileInfo|null $info
256
	 *
257
	 * @return \OC\Preview
258
	 */
259 169
	public function setFile($file, $info = null) {
260 169
		$this->file = $file;
261 169
		$this->info = $info;
262
263 169
		if ($file !== '') {
264 169
			$this->getFileInfo();
265 169
			if ($this->info instanceof \OCP\Files\FileInfo) {
266 169
				$this->mimeType = $this->info->getMimetype();
267 169
			}
268 169
		}
269
270 169
		return $this;
271
	}
272
273
	/**
274
	 * Forces the use of a specific media type
275
	 *
276
	 * @param string $mimeType
277
	 */
278
	public function setMimetype($mimeType) {
279
		$this->mimeType = $mimeType;
280
	}
281
282
	/**
283
	 * Sets the max width of the preview. It's capped by the maximum allowed size set in the
284
	 * configuration
285
	 *
286
	 * @param int $maxX
287
	 *
288
	 * @throws \Exception
289
	 * @return \OC\Preview
290
	 */
291 169
	public function setMaxX($maxX = 1) {
292 169
		if ($maxX <= 0) {
293
			throw new \Exception('Cannot set width of 0 or smaller!');
294
		}
295 169
		$configMaxX = $this->getConfigMaxX();
296 169
		$maxX = $this->limitMaxDim($maxX, $configMaxX, 'maxX');
297 169
		$this->maxX = $maxX;
298
299 169
		return $this;
300
	}
301
302
	/**
303
	 * Sets the max height of the preview. It's capped by the maximum allowed size set in the
304
	 * configuration
305
	 *
306
	 * @param int $maxY
307
	 *
308
	 * @throws \Exception
309
	 * @return \OC\Preview
310
	 */
311 169
	public function setMaxY($maxY = 1) {
312 169
		if ($maxY <= 0) {
313
			throw new \Exception('Cannot set height of 0 or smaller!');
314
		}
315 169
		$configMaxY = $this->getConfigMaxY();
316 169
		$maxY = $this->limitMaxDim($maxY, $configMaxY, 'maxY');
317 169
		$this->maxY = $maxY;
318
319 169
		return $this;
320
	}
321
322
	/**
323
	 * Sets whether we're allowed to scale up when generating a preview. It's capped by the maximum
324
	 * allowed scale factor set in the configuration
325
	 *
326
	 * @param bool $scalingUp
327
	 *
328
	 * @return \OC\Preview
329
	 */
330 169
	public function setScalingup($scalingUp) {
331 169
		if ($this->getMaxScaleFactor() === 1) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $this->getMaxScaleFactor() (string) and 1 (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
332
			$scalingUp = false;
333
		}
334 169
		$this->scalingUp = $scalingUp;
335
336 169
		return $this;
337
	}
338
339
	/**
340
	 * Set whether to cover or fill the specified dimensions
341
	 *
342
	 * @param string $mode
343
	 *
344
	 * @return \OC\Preview
345
	 */
346 1
	public function setMode($mode) {
347 1
		$this->mode = $mode;
348
349 1
		return $this;
350
	}
351
352
	/**
353
	 * Sets whether we need to generate a preview which keeps the aspect ratio of the original file
354
	 *
355
	 * @param bool $keepAspect
356
	 *
357
	 * @return \OC\Preview
358
	 */
359 163
	public function setKeepAspect($keepAspect) {
360 163
		$this->keepAspect = $keepAspect;
361
362 163
		return $this;
363
	}
364
365
	/**
366
	 * Makes sure we were given a file to preview and that it exists in the filesystem
367
	 *
368
	 * @return bool
369
	 */
370 161
	public function isFileValid() {
371 161
		$file = $this->getFile();
372 161
		if ($file === '') {
373
			\OCP\Util::writeLog('core', 'No filename passed', \OCP\Util::DEBUG);
374
375
			return false;
376
		}
377
378 161
		if (!$this->getFileInfo() instanceof FileInfo) {
379
			\OCP\Util::writeLog('core', 'File:"' . $file . '" not found', \OCP\Util::DEBUG);
380
381
			return false;
382
		}
383
384 161
		return true;
385
	}
386
387
	/**
388
	 * Deletes the preview of a file with specific width and height
389
	 *
390
	 * This should never delete the max preview, use deleteAllPreviews() instead
391
	 *
392
	 * @return bool
393
	 */
394 2
	public function deletePreview() {
395 2
		$fileInfo = $this->getFileInfo();
396 2
		if ($fileInfo !== null && $fileInfo !== false) {
397 2
			$fileId = $fileInfo->getId();
398
399 2
			$previewPath = $this->buildCachePath($fileId);
400 2
			if (!strpos($previewPath, 'max')) {
401 1
				return $this->userView->unlink($previewPath);
402
			}
403 1
		}
404
405 1
		return false;
406
	}
407
408
	/**
409
	 * Deletes all previews of a file
410
	 */
411 98
	public function deleteAllPreviews() {
412 98
		$toDelete = $this->getChildren();
413 98
		$toDelete[] = $this->getFileInfo();
414
415 98
		foreach ($toDelete as $delete) {
416 98
			if ($delete instanceof FileInfo) {
417
				/** @var \OCP\Files\FileInfo $delete */
418 98
				$fileId = $delete->getId();
419
420
				// getId() might return null, e.g. when the file is a
421
				// .ocTransferId*.part file from chunked file upload.
422 98
				if (!empty($fileId)) {
423 98
					$previewPath = $this->getPreviewPath($fileId);
424 98
					$this->userView->deleteAll($previewPath);
425 98
					$this->userView->rmdir($previewPath);
426 98
				}
427 98
			}
428 98
		}
429 98
	}
430
431
	/**
432
	 * Checks if a preview matching the asked dimensions or a bigger version is already cached
433
	 *
434
	 *    * We first retrieve the size of the max preview since this is what we be used to create
435
	 * all our preview. If it doesn't exist we return false, so that it can be generated
436
	 *    * Using the dimensions of the max preview, we calculate what the size of the new
437
	 * thumbnail should be
438
	 *    * And finally, we look for a suitable candidate in the cache
439
	 *
440
	 * @param int $fileId fileId of the original file we need a preview of
441
	 *
442
	 * @return string|false path to the cached preview if it exists or false
443
	 */
444 168
	public function isCached($fileId) {
445 168
		if (is_null($fileId)) {
446
			return false;
447
		}
448
449
		/**
450
		 * Phase 1: Looking for the max preview
451
		 */
452 168
		$previewPath = $this->getPreviewPath($fileId);
453
		// We currently can't look for a single file due to bugs related to #16478
454 168
		$allThumbnails = $this->userView->getDirectoryContent($previewPath);
455 168
		list($maxPreviewWidth, $maxPreviewHeight) = $this->getMaxPreviewSize($allThumbnails);
456
457
		// Only use the cache if we have a max preview
458 168
		if (!is_null($maxPreviewWidth) && !is_null($maxPreviewHeight)) {
459
460
			/**
461
			 * Phase 2: Calculating the size of the preview we need to send back
462
			 */
463 98
			$this->maxPreviewWidth = $maxPreviewWidth;
464 98
			$this->maxPreviewHeight = $maxPreviewHeight;
465
466 98
			list($previewWidth, $previewHeight) = $this->simulatePreviewDimensions();
467 98
			if (empty($previewWidth) || empty($previewHeight)) {
468
				return false;
469
			}
470
471 98
			$this->previewWidth = $previewWidth;
472 98
			$this->previewHeight = $previewHeight;
473
474
			/**
475
			 * Phase 3: We look for a preview of the exact size
476
			 */
477
			// This gives us a calculated path to a preview of asked dimensions
478
			// thumbnailFolder/fileId/<maxX>-<maxY>(-max|-with-aspect).png
479 98
			$preview = $this->buildCachePath($fileId, $previewWidth, $previewHeight);
480
481
			// This checks if we have a preview of those exact dimensions in the cache
482 98
			if ($this->thumbnailSizeExists($allThumbnails, basename($preview))) {
483 45
				return $preview;
484
			}
485
486
			/**
487
			 * Phase 4: We look for a larger preview, matching the aspect ratio
488
			 */
489 69
			if (($this->getMaxX() >= $maxPreviewWidth)
490 69
				&& ($this->getMaxY() >= $maxPreviewHeight)
491 69
			) {
492
				// The preview we-re looking for is the exact size or larger than the max preview,
493
				// so return that
494 34
				return $this->buildCachePath($fileId, $maxPreviewWidth, $maxPreviewHeight);
495
			} else {
496
				// The last resort is to look for something bigger than what we've calculated,
497
				// but still smaller than the max preview
498 38
				return $this->isCachedBigger($fileId, $allThumbnails);
499
			}
500
		}
501
502 168
		return false;
503
	}
504
505
	/**
506
	 * Returns the dimensions of the max preview
507
	 *
508
	 * @param FileInfo[] $allThumbnails the list of all our cached thumbnails
509
	 *
510
	 * @return int[]
511
	 */
512 168
	private function getMaxPreviewSize($allThumbnails) {
513 168
		$maxPreviewX = null;
514 168
		$maxPreviewY = null;
515
516 168
		foreach ($allThumbnails as $thumbnail) {
517 98
			$name = $thumbnail['name'];
518 98
			if (strpos($name, 'max')) {
519 98
				list($maxPreviewX, $maxPreviewY) = $this->getDimensionsFromFilename($name);
520 98
				break;
521
			}
522 168
		}
523
524 168
		return [$maxPreviewX, $maxPreviewY];
525
	}
526
527
	/**
528
	 * Check if a specific thumbnail size is cached
529
	 *
530
	 * @param FileInfo[] $allThumbnails the list of all our cached thumbnails
531
	 * @param string $name
532
	 * @return bool
533
	 */
534 98
	private function thumbnailSizeExists(array $allThumbnails, $name) {
535
536 98
		foreach ($allThumbnails as $thumbnail) {
537 98
			if ($name === $thumbnail->getName()) {
538 45
				return true;
539
			}
540 70
		}
541
542 69
		return false;
543
	}
544
545
	/**
546
	 * Determines the size of the preview we should be looking for in the cache
547
	 *
548
	 * @return int[]
549
	 */
550 98
	private function simulatePreviewDimensions() {
551 98
		$askedWidth = $this->getMaxX();
552 98
		$askedHeight = $this->getMaxY();
553
554 98
		if ($this->keepAspect) {
555
			list($newPreviewWidth, $newPreviewHeight) =
556 57
				$this->applyAspectRatio($askedWidth, $askedHeight);
557 57
		} else {
558 57
			list($newPreviewWidth, $newPreviewHeight) = $this->fixSize($askedWidth, $askedHeight);
559
		}
560
561 98
		return [(int)$newPreviewWidth, (int)$newPreviewHeight];
562
	}
563
564
	/**
565
	 * Resizes the boundaries to match the aspect ratio
566
	 *
567
	 * @param int $askedWidth
568
	 * @param int $askedHeight
569
	 *
570
	 * @param int $originalWidth
571
	 * @param int $originalHeight
572
	 * @return \int[]
573
	 */
574 131
	private function applyAspectRatio($askedWidth, $askedHeight, $originalWidth = 0, $originalHeight = 0) {
575 131
		if(!$originalWidth){
576 57
			$originalWidth= $this->maxPreviewWidth;
577 57
		}
578 131
		if (!$originalHeight) {
579 57
			$originalHeight = $this->maxPreviewHeight;
580 57
		}
581 131
		$originalRatio = $originalWidth / $originalHeight;
582
		// Defines the box in which the preview has to fit
583 131
		$scaleFactor = $this->scalingUp ? $this->maxScaleFactor : 1;
584 131
		$askedWidth = min($askedWidth, $originalWidth * $scaleFactor);
585 131
		$askedHeight = min($askedHeight, $originalHeight * $scaleFactor);
586
587 131 View Code Duplication
		if ($askedWidth / $originalRatio < $askedHeight) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
588
			// width restricted
589 43
			$askedHeight = round($askedWidth / $originalRatio);
590 43
		} else {
591 104
			$askedWidth = round($askedHeight * $originalRatio);
592
		}
593
594 131
		return [(int)$askedWidth, (int)$askedHeight];
595
	}
596
597
	/**
598
	 * Resizes the boundaries to cover the area
599
	 *
600
	 * @param int $askedWidth
601
	 * @param int $askedHeight
602
	 * @param int $previewWidth
603
	 * @param int $previewHeight
604
	 * @return \int[]
605
	 */
606 1
	private function applyCover($askedWidth, $askedHeight, $previewWidth, $previewHeight) {
607 1
		$originalRatio = $previewWidth / $previewHeight;
608
		// Defines the box in which the preview has to fit
609 1
		$scaleFactor = $this->scalingUp ? $this->maxScaleFactor : 1;
610 1
		$askedWidth = min($askedWidth, $previewWidth * $scaleFactor);
611 1
		$askedHeight = min($askedHeight, $previewHeight * $scaleFactor);
612
613 1 View Code Duplication
		if ($askedWidth / $originalRatio > $askedHeight) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
614
			// height restricted
615
			$askedHeight = round($askedWidth / $originalRatio);
616
		} else {
617 1
			$askedWidth = round($askedHeight * $originalRatio);
618
		}
619
620 1
		return [(int)$askedWidth, (int)$askedHeight];
621
	}
622
623
	/**
624
	 * Makes sure an upscaled preview doesn't end up larger than the max dimensions defined in the
625
	 * config
626
	 *
627
	 * @param int $askedWidth
628
	 * @param int $askedHeight
629
	 *
630
	 * @return \int[]
631
	 */
632 57 View Code Duplication
	private function fixSize($askedWidth, $askedHeight) {
633 57
		if ($this->scalingUp) {
634 29
			$askedWidth = min($this->configMaxWidth, $askedWidth);
635 29
			$askedHeight = min($this->configMaxHeight, $askedHeight);
636 29
		}
637
638 57
		return [(int)$askedWidth, (int)$askedHeight];
639
	}
640
641
	/**
642
	 * Checks if a bigger version of a file preview is cached and if not
643
	 * return the preview of max allowed dimensions
644
	 *
645
	 * @param int $fileId fileId of the original image
646
	 * @param FileInfo[] $allThumbnails the list of all our cached thumbnails
647
	 *
648
	 * @return string path to bigger thumbnail
649
	 */
650 38
	private function isCachedBigger($fileId, $allThumbnails) {
651
		// This is used to eliminate any thumbnail narrower than what we need
652 38
		$maxX = $this->getMaxX();
653
654
		//array for usable cached thumbnails
655 38
		$possibleThumbnails = $this->getPossibleThumbnails($allThumbnails);
656
657 38
		foreach ($possibleThumbnails as $width => $path) {
658 25
			if ($width < $maxX) {
659 1
				continue;
660
			} else {
661 24
				return $path;
662
			}
663 22
		}
664
665
		// At this stage, we didn't find a preview, so we return the max preview
666 22
		return $this->buildCachePath($fileId, $this->maxPreviewWidth, $this->maxPreviewHeight);
667
	}
668
669
	/**
670
	 * Get possible bigger thumbnails of the given image with the proper aspect ratio
671
	 *
672
	 * @param FileInfo[] $allThumbnails the list of all our cached thumbnails
673
	 *
674
	 * @return string[] an array of paths to bigger thumbnails
675
	 */
676 38
	private function getPossibleThumbnails($allThumbnails) {
677 38
		if ($this->keepAspect) {
678 17
			$wantedAspectRatio = (float)($this->maxPreviewWidth / $this->maxPreviewHeight);
679 17
		} else {
680 27
			$wantedAspectRatio = (float)($this->getMaxX() / $this->getMaxY());
681
		}
682
683
		//array for usable cached thumbnails
684 38
		$possibleThumbnails = array();
685 38
		foreach ($allThumbnails as $thumbnail) {
686 38
			$name = rtrim($thumbnail['name'], '.png');
687 38
			list($x, $y, $aspectRatio) = $this->getDimensionsFromFilename($name);
688 38
			if (abs($aspectRatio - $wantedAspectRatio) >= 0.000001
689 38
				|| $this->unscalable($x, $y)
690 38
			) {
691 25
				continue;
692
			}
693 25
			$possibleThumbnails[$x] = $thumbnail['path'];
694 38
		}
695
696 38
		ksort($possibleThumbnails);
697
698 38
		return $possibleThumbnails;
699
	}
700
701
	/**
702
	 * Looks at the preview filename from the cache and extracts the size of the preview
703
	 *
704
	 * @param string $name
705
	 *
706
	 * @return array<int,int,float>
0 ignored issues
show
Documentation introduced by
The doc-type array<int,int,float> could not be parsed: Expected ">" at position 5, but found ",". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
707
	 */
708 98
	private function getDimensionsFromFilename($name) {
709 98
		$size = explode('-', $name);
710 98
		$x = (int)$size[0];
711 98
		$y = (int)$size[1];
712 98
		$aspectRatio = (float)($x / $y);
713
714 98
		return array($x, $y, $aspectRatio);
715
	}
716
717
	/**
718
	 * @param int $x
719
	 * @param int $y
720
	 *
721
	 * @return bool
722
	 */
723 27
	private function unscalable($x, $y) {
724
725 27
		$maxX = $this->getMaxX();
726 27
		$maxY = $this->getMaxY();
727 27
		$scalingUp = $this->getScalingUp();
728 27
		$maxScaleFactor = $this->getMaxScaleFactor();
729
730 27
		if ($x < $maxX || $y < $maxY) {
731 8
			if ($scalingUp) {
732 4
				$scaleFactor = $maxX / $x;
733 4
				if ($scaleFactor > $maxScaleFactor) {
734
					return true;
735
				}
736 4
			} else {
737 4
				return true;
738
			}
739 4
		}
740
741 25
		return false;
742
	}
743
744
	/**
745
	 * Returns a preview of a file
746
	 *
747
	 * The cache is searched first and if nothing usable was found then a preview is
748
	 * generated by one of the providers
749
	 *
750
	 * @return \OCP\IImage
751
	 */
752 168
	public function getPreview() {
753 168
		if (!is_null($this->preview) && $this->preview->valid()) {
754
			return $this->preview;
755
		}
756
757 168
		$this->preview = null;
758 168
		$fileInfo = $this->getFileInfo();
759 168
		if ($fileInfo === null || $fileInfo === false) {
760
			return new \OC_Image();
761
		}
762
763 168
		$fileId = $fileInfo->getId();
764 168
		$cached = $this->isCached($fileId);
765 168
		if ($cached) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $cached of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
766 32
			$this->getCachedPreview($fileId, $cached);
767 32
		}
768
769 168
		if (is_null($this->preview)) {
770 168
			$this->generatePreview($fileId);
771 168
		}
772
773
		// We still don't have a preview, so we send back an empty object
774 168
		if (is_null($this->preview)) {
775 1
			$this->preview = new \OC_Image();
776 1
		}
777
778 168
		return $this->preview;
779
	}
780
781
	/**
782
	 * Sends the preview, including the headers to client which requested it
783
	 *
784
	 * @param null|string $mimeTypeForHeaders the media type to use when sending back the reply
785
	 *
786
	 * @throws NotFoundException
787
	 */
788
	public function showPreview($mimeTypeForHeaders = null) {
789
		// Check if file is valid
790
		if ($this->isFileValid() === false) {
791
			throw new NotFoundException('File not found.');
792
		}
793
794
		if (is_null($this->preview)) {
795
			$this->getPreview();
796
		}
797
		if ($this->preview instanceof \OCP\IImage) {
798
			if ($this->preview->valid()) {
799
				\OCP\Response::enableCaching(3600 * 24); // 24 hours
800
			} else {
801
				$this->getMimeIcon();
802
			}
803
			$this->preview->show($mimeTypeForHeaders);
804
		}
805
	}
806
807
	/**
808
	 * Retrieves the preview from the cache and resizes it if necessary
809
	 *
810
	 * @param int $fileId fileId of the original image
811
	 * @param string $cached the path to the cached preview
812
	 */
813 32
	private function getCachedPreview($fileId, $cached) {
814 32
		$stream = $this->userView->fopen($cached, 'r');
815 32
		$this->preview = null;
816 32
		if ($stream) {
817 32
			$image = new \OC_Image();
818 32
			$image->loadFromFileHandle($stream);
819
820 32
			$this->preview = $image->valid() ? $image : null;
821
822 32
			if (!is_null($this->preview)) {
823
				// Size of the preview we calculated
824 32
				$maxX = $this->previewWidth;
825 32
				$maxY = $this->previewHeight;
826
				// Size of the preview we retrieved from the cache
827 32
				$previewX = (int)$this->preview->width();
828 32
				$previewY = (int)$this->preview->height();
829
830
				// We don't have an exact match
831 32
				if ($previewX !== $maxX || $previewY !== $maxY) {
832 25
					$this->resizeAndStore($fileId);
833 25
				}
834 32
			}
835
836 32
			fclose($stream);
837 32
		}
838 32
	}
839
840
	/**
841
	 * Resizes, crops, fixes orientation and stores in the cache
842
	 *
843
	 * @param int $fileId fileId of the original image
844
	 */
845 167
	private function resizeAndStore($fileId) {
846 167
		$image = $this->preview;
847 167
		if (!($image instanceof \OCP\IImage)) {
848
			\OCP\Util::writeLog(
849
				'core', '$this->preview is not an instance of \OCP\IImage', \OCP\Util::DEBUG
850
			);
851
852
			return;
853
		}
854 167
		$previewWidth = (int)$image->width();
855 167
		$previewHeight = (int)$image->height();
856 167
		$askedWidth = $this->getMaxX();
857 167
		$askedHeight = $this->getMaxY();
858
859 167
		if ($this->mode === self::MODE_COVER) {
860
			list($askedWidth, $askedHeight) =
861 1
				$this->applyCover($askedWidth, $askedHeight, $previewWidth, $previewHeight);
862 1
		}
863
864
		/**
865
		 * Phase 1: If required, adjust boundaries to keep aspect ratio
866
		 */
867 167
		if ($this->keepAspect) {
868
			list($askedWidth, $askedHeight) =
869 130
				$this->applyAspectRatio($askedWidth, $askedHeight, $previewWidth, $previewHeight);
870 130
		}
871
872
		/**
873
		 * Phase 2: Resizes preview to try and match requirements.
874
		 * Takes the scaling ratio into consideration
875
		 */
876 167
		list($newPreviewWidth, $newPreviewHeight) = $this->scale(
877 167
			$image, $askedWidth, $askedHeight, $previewWidth, $previewHeight
878 167
		);
879
880
		// The preview has been resized and should now have the asked dimensions
881 167
		if ($newPreviewWidth === $askedWidth && $newPreviewHeight === $askedHeight) {
882
			$this->storePreview($fileId, $newPreviewWidth, $newPreviewHeight);
883
884
			return;
885
		}
886
887
		/**
888
		 * Phase 3: We're still not there yet, so we're clipping and filling
889
		 * to match the asked dimensions
890
		 */
891
		// It turns out the scaled preview is now too big, so we crop the image
892 167
		if ($newPreviewWidth >= $askedWidth && $newPreviewHeight >= $askedHeight) {
893 152
			$this->crop($image, $askedWidth, $askedHeight, $newPreviewWidth, $newPreviewHeight);
894 152
			$this->storePreview($fileId, $askedWidth, $askedHeight);
895
896 152
			return;
897
		}
898
899
		// At least one dimension of the scaled preview is too small,
900
		// so we fill the space with a transparent background
901 28
		if (($newPreviewWidth < $askedWidth || $newPreviewHeight < $askedHeight)) {
902 28
			$this->cropAndFill(
903 28
				$image, $askedWidth, $askedHeight, $newPreviewWidth, $newPreviewHeight
904 28
			);
905 28
			$this->storePreview($fileId, $askedWidth, $askedHeight);
906
907 28
			return;
908
		}
909
910
		// The preview is smaller, but we can't touch it
911
		$this->storePreview($fileId, $newPreviewWidth, $newPreviewHeight);
912
	}
913
914
	/**
915
	 * Calculates the new dimensions of the preview
916
	 *
917
	 * The new dimensions can be larger or smaller than the ones of the preview we have to resize
918
	 *
919
	 * @param \OCP\IImage $image
920
	 * @param int $askedWidth
921
	 * @param int $askedHeight
922
	 * @param int $previewWidth
923
	 * @param null $previewHeight
924
	 *
925
	 * @return int[]
926
	 */
927 167
	private function scale($image, $askedWidth, $askedHeight, $previewWidth, $previewHeight) {
928 167
		$scalingUp = $this->getScalingUp();
929 167
		$maxScaleFactor = $this->getMaxScaleFactor();
930
931 167
		$factorX = $askedWidth / $previewWidth;
932 167
		$factorY = $askedHeight / $previewHeight;
933
934 167
		if ($factorX >= $factorY) {
935 140
			$factor = $factorX;
936 140
		} else {
937 45
			$factor = $factorY;
938
		}
939
940 167
		if ($scalingUp === false) {
941 121
			if ($factor > 1) {
942 22
				$factor = 1;
943 22
			}
944 121
		}
945
946
		// We cap when upscaling
947 167
		if (!is_null($maxScaleFactor)) {
948 167
			if ($factor > $maxScaleFactor) {
949 6
				\OCP\Util::writeLog(
950 6
					'core', 'scale factor reduced from ' . $factor . ' to ' . $maxScaleFactor,
951
					\OCP\Util::DEBUG
952 6
				);
953 6
				$factor = $maxScaleFactor;
954 6
			}
955 167
		}
956
957 167
		$newPreviewWidth = round($previewWidth * $factor);
958 167
		$newPreviewHeight = round($previewHeight * $factor);
959
960 167
		$image->preciseResize($newPreviewWidth, $newPreviewHeight);
961 167
		$this->preview = $image;
962
963 167
		return [$newPreviewWidth, $newPreviewHeight];
964
	}
965
966
	/**
967
	 * Crops a preview which is larger than the dimensions we've received
968
	 *
969
	 * @param \OCP\IImage $image
970
	 * @param int $askedWidth
971
	 * @param int $askedHeight
972
	 * @param int $previewWidth
973
	 * @param null $previewHeight
974
	 */
975 152
	private function crop($image, $askedWidth, $askedHeight, $previewWidth, $previewHeight = null) {
0 ignored issues
show
Unused Code introduced by
The parameter $previewHeight is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
976 152
		$cropX = floor(abs($askedWidth - $previewWidth) * 0.5);
977
		//don't crop previews on the Y axis, this sucks if it's a document.
978
		//$cropY = floor(abs($y - $newPreviewHeight) * 0.5);
979 152
		$cropY = 0;
980 152
		$image->crop($cropX, $cropY, $askedWidth, $askedHeight);
981 152
		$this->preview = $image;
982 152
	}
983
984
	/**
985
	 * Crops an image if it's larger than the dimensions we've received and fills the empty space
986
	 * with a transparent background
987
	 *
988
	 * @param \OCP\IImage $image
989
	 * @param int $askedWidth
990
	 * @param int $askedHeight
991
	 * @param int $previewWidth
992
	 * @param null $previewHeight
993
	 */
994 28
	private function cropAndFill($image, $askedWidth, $askedHeight, $previewWidth, $previewHeight) {
995 28 View Code Duplication
		if ($previewWidth > $askedWidth) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
996 7
			$cropX = floor(($previewWidth - $askedWidth) * 0.5);
997 7
			$image->crop($cropX, 0, $askedWidth, $previewHeight);
998 7
			$previewWidth = $askedWidth;
999 7
		}
1000
1001 28 View Code Duplication
		if ($previewHeight > $askedHeight) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1002 1
			$cropY = floor(($previewHeight - $askedHeight) * 0.5);
1003 1
			$image->crop(0, $cropY, $previewWidth, $askedHeight);
1004 1
			$previewHeight = $askedHeight;
1005 1
		}
1006
1007
		// Creates a transparent background
1008 28
		$backgroundLayer = imagecreatetruecolor($askedWidth, $askedHeight);
1009 28
		imagealphablending($backgroundLayer, false);
1010 28
		$transparency = imagecolorallocatealpha($backgroundLayer, 0, 0, 0, 127);
1011 28
		imagefill($backgroundLayer, 0, 0, $transparency);
1012 28
		imagesavealpha($backgroundLayer, true);
1013
1014 28
		$image = $image->resource();
1015
1016 28
		$mergeX = floor(abs($askedWidth - $previewWidth) * 0.5);
1017 28
		$mergeY = floor(abs($askedHeight - $previewHeight) * 0.5);
1018
1019
		// Pastes the preview on top of the background
1020 28
		imagecopy(
1021 28
			$backgroundLayer, $image, $mergeX, $mergeY, 0, 0, $previewWidth,
1022
			$previewHeight
1023 28
		);
1024
1025 28
		$image = new \OC_Image($backgroundLayer);
1026
1027 28
		$this->preview = $image;
1028 28
	}
1029
1030
	/**
1031
	 * Saves a preview in the cache to speed up future calls
1032
	 *
1033
	 * Do not nullify the preview as it might send the whole process in a loop
1034
	 *
1035
	 * @param int $fileId fileId of the original image
1036
	 * @param int $previewWidth
1037
	 * @param int $previewHeight
1038
	 */
1039 167
	private function storePreview($fileId, $previewWidth, $previewHeight) {
1040 167
		if (empty($previewWidth) || empty($previewHeight)) {
1041
			\OCP\Util::writeLog(
1042
				'core', 'Cannot save preview of dimension ' . $previewWidth . 'x' . $previewHeight,
1043
				\OCP\Util::DEBUG
1044
			);
1045
1046
		} else {
1047 167
			$cachePath = $this->buildCachePath($fileId, $previewWidth, $previewHeight);
1048 167
			$this->userView->file_put_contents($cachePath, $this->preview->data());
1049
		}
1050 167
	}
1051
1052
	/**
1053
	 * Returns the path to a preview based on its dimensions and aspect
1054
	 *
1055
	 * @param int $fileId
1056
	 * @param int|null $maxX
1057
	 * @param int|null $maxY
1058
	 *
1059
	 * @return string
1060
	 */
1061 167
	private function buildCachePath($fileId, $maxX = null, $maxY = null) {
1062 167
		if (is_null($maxX)) {
1063 2
			$maxX = $this->getMaxX();
1064 2
		}
1065 167
		if (is_null($maxY)) {
1066 2
			$maxY = $this->getMaxY();
1067 2
		}
1068
1069 167
		$previewPath = $this->getPreviewPath($fileId);
1070 167
		$previewPath = $previewPath . strval($maxX) . '-' . strval($maxY);
1071
		$isMaxPreview =
1072 167
			($maxX === $this->maxPreviewWidth && $maxY === $this->maxPreviewHeight) ? true : false;
1073 167
		if ($isMaxPreview) {
1074 113
			$previewPath .= '-max';
1075 113
		}
1076 167
		if ($this->keepAspect && !$isMaxPreview) {
1077 41
			$previewPath .= '-with-aspect';
1078 41
		}
1079 167
		if ($this->mode === self::MODE_COVER) {
1080 1
			$previewPath .= '-cover';
1081 1
		}
1082 167
		$previewPath .= '.png';
1083
1084 167
		return $previewPath;
1085
	}
1086
1087
	/**
1088
	 * Returns the path to the folder where the previews are stored, identified by the fileId
1089
	 *
1090
	 * @param int $fileId
1091
	 *
1092
	 * @return string
1093
	 */
1094 168
	private function getPreviewPath($fileId) {
1095 168
		return $this->getThumbnailsFolder() . '/' . $fileId . '/';
1096
	}
1097
1098
	/**
1099
	 * Asks the provider to send a preview of the file which respects the maximum dimensions
1100
	 * defined in the configuration and after saving it in the cache, it is then resized to the
1101
	 * asked dimensions
1102
	 *
1103
	 * This is only called once in order to generate a large PNG of dimensions defined in the
1104
	 * configuration file. We'll be able to quickly resize it later on.
1105
	 * We never upscale the original conversion as this will be done later by the resizing
1106
	 * operation
1107
	 *
1108
	 * @param int $fileId fileId of the original image
1109
	 */
1110 168
	private function generatePreview($fileId) {
1111 168
		$file = $this->getFile();
1112 168
		$preview = null;
1113
1114 168
		$previewProviders = \OC::$server->getPreviewManager()
1115 168
										->getProviders();
1116 168
		foreach ($previewProviders as $supportedMimeType => $providers) {
1117 168
			if (!preg_match($supportedMimeType, $this->mimeType)) {
1118 128
				continue;
1119
			}
1120
1121 167
			foreach ($providers as $closure) {
1122 167
				$provider = $closure();
1123 167
				if (!($provider instanceof \OCP\Preview\IProvider)) {
1124
					continue;
1125
				}
1126
1127 167
				\OCP\Util::writeLog(
1128 167
					'core', 'Generating preview for "' . $file . '" with "' . get_class($provider)
1129 167
							. '"', \OCP\Util::DEBUG
1130 167
				);
1131
1132
				/** @var $provider Provider */
1133 167
				$preview = $provider->getThumbnail(
1134 167
					$file, $this->configMaxWidth, $this->configMaxHeight, $scalingUp = false,
1135 167
					$this->fileView
1136 167
				);
1137
1138 167
				if (!($preview instanceof \OCP\IImage)) {
1139
					continue;
1140
				}
1141
1142 167
				$this->preview = $preview;
1143 167
				$previewPath = $this->getPreviewPath($fileId);
1144
1145 167
				if ($this->userView->is_dir($this->getThumbnailsFolder() . '/') === false) {
1146 167
					$this->userView->mkdir($this->getThumbnailsFolder() . '/');
1147 167
				}
1148
1149 167
				if ($this->userView->is_dir($previewPath) === false) {
1150 167
					$this->userView->mkdir($previewPath);
1151 167
				}
1152
1153
				// This stores our large preview so that it can be used in subsequent resizing requests
1154 167
				$this->storeMaxPreview($previewPath);
1155
1156 167
				break 2;
1157
			}
1158 168
		}
1159
1160
		// The providers have been kind enough to give us a preview
1161 168
		if ($preview) {
1162 167
			$this->resizeAndStore($fileId);
1163 167
		}
1164 168
	}
1165
1166
	/**
1167
	 * Defines the media icon, for the media type of the original file, as the preview
1168
	 */
1169
	private function getMimeIcon() {
1170
		$image = new \OC_Image();
1171
		$mimeIconWebPath = \OC_Helper::mimetypeIcon($this->mimeType);
1172
		if (empty(\OC::$WEBROOT)) {
1173
			$mimeIconServerPath = \OC::$SERVERROOT . $mimeIconWebPath;
1174
		} else {
1175
			$mimeIconServerPath = str_replace(\OC::$WEBROOT, \OC::$SERVERROOT, $mimeIconWebPath);
1176
		}
1177
		$image->loadFromFile($mimeIconServerPath);
1178
1179
		$this->preview = $image;
1180
	}
1181
1182
	/**
1183
	 * Stores the max preview in the cache
1184
	 *
1185
	 * @param string $previewPath path to the preview
1186
	 */
1187 167
	private function storeMaxPreview($previewPath) {
1188 167
		$maxPreviewExists = false;
1189 167
		$preview = $this->preview;
1190
1191 167
		$allThumbnails = $this->userView->getDirectoryContent($previewPath);
1192
		// This is so that the cache doesn't need emptying when upgrading
1193
		// Can be replaced by an upgrade script...
1194 167
		foreach ($allThumbnails as $thumbnail) {
1195
			$name = rtrim($thumbnail['name'], '.png');
1196
			if (strpos($name, 'max')) {
1197
				$maxPreviewExists = true;
1198
				break;
1199
			}
1200 167
		}
1201
		// We haven't found the max preview, so we create it
1202 167
		if (!$maxPreviewExists) {
1203 167
			$previewWidth = $preview->width();
1204 167
			$previewHeight = $preview->height();
1205 167
			$previewPath = $previewPath . strval($previewWidth) . '-' . strval($previewHeight);
1206 167
			$previewPath .= '-max.png';
1207 167
			$this->userView->file_put_contents($previewPath, $preview->data());
1208 167
			$this->maxPreviewWidth = $previewWidth;
1209 167
			$this->maxPreviewHeight = $previewHeight;
1210 167
		}
1211 167
	}
1212
1213
	/**
1214
	 * Limits a dimension to the maximum dimension provided as argument
1215
	 *
1216
	 * @param int $dim
1217
	 * @param int $maxDim
1218
	 * @param string $dimName
1219
	 *
1220
	 * @return mixed
1221
	 */
1222 169
	private function limitMaxDim($dim, $maxDim, $dimName) {
1223 169
		if (!is_null($maxDim)) {
1224 169
			if ($dim > $maxDim) {
1225 128
				\OCP\Util::writeLog(
1226 128
					'core', $dimName . ' reduced from ' . $dim . ' to ' . $maxDim, \OCP\Util::DEBUG
1227 128
				);
1228 128
				$dim = $maxDim;
1229 128
			}
1230 169
		}
1231
1232 169
		return $dim;
1233
	}
1234
1235
	/**
1236
	 * @param array $args
1237
	 */
1238
	public static function post_write($args) {
1239
		self::post_delete($args, 'files/');
1240
	}
1241
1242
	/**
1243
	 * @param array $args
1244
	 */
1245
	public static function prepare_delete_files($args) {
1246
		self::prepare_delete($args, 'files/');
1247
	}
1248
1249
	/**
1250
	 * @param array $args
1251
	 * @param string $prefix
1252
	 */
1253
	public static function prepare_delete($args, $prefix = '') {
1254
		$path = $args['path'];
1255
		if (substr($path, 0, 1) === '/') {
1256
			$path = substr($path, 1);
1257
		}
1258
1259
		$view = new \OC\Files\View('/' . \OC_User::getUser() . '/' . $prefix);
1260
1261
		$absPath = Files\Filesystem::normalizePath($view->getAbsolutePath($path));
1262
		self::addPathToDeleteFileMapper($absPath, $view->getFileInfo($path));
1263
		if ($view->is_dir($path)) {
1264
			$children = self::getAllChildren($view, $path);
1265
			self::$deleteChildrenMapper[$absPath] = $children;
1266
		}
1267
	}
1268
1269
	/**
1270
	 * @param string $absolutePath
1271
	 * @param \OCP\Files\FileInfo $info
1272
	 */
1273
	private static function addPathToDeleteFileMapper($absolutePath, $info) {
1274
		self::$deleteFileMapper[$absolutePath] = $info;
1275
	}
1276
1277
	/**
1278
	 * @param \OC\Files\View $view
1279
	 * @param string $path
1280
	 *
1281
	 * @return array
1282
	 */
1283
	private static function getAllChildren($view, $path) {
1284
		$children = $view->getDirectoryContent($path);
1285
		$childrensFiles = array();
1286
1287
		$fakeRootLength = strlen($view->getRoot());
1288
1289
		for ($i = 0; $i < count($children); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
1290
			$child = $children[$i];
1291
1292
			$childsPath = substr($child->getPath(), $fakeRootLength);
1293
1294
			if ($view->is_dir($childsPath)) {
1295
				$children = array_merge(
1296
					$children,
1297
					$view->getDirectoryContent($childsPath)
1298
				);
1299
			} else {
1300
				$childrensFiles[] = $child;
1301
			}
1302
		}
1303
1304
		return $childrensFiles;
1305
	}
1306
1307
	/**
1308
	 * @param array $args
1309
	 */
1310
	public static function post_delete_files($args) {
1311
		self::post_delete($args, 'files/');
1312
	}
1313
1314
	/**
1315
	 * @param array $args
1316
	 */
1317
	public static function post_delete_versions($args) {
1318
		self::post_delete($args, 'files/');
1319
	}
1320
1321
	/**
1322
	 * @param array $args
1323
	 * @param string $prefix
1324
	 */
1325
	public static function post_delete($args, $prefix = '') {
1326
		$path = Files\Filesystem::normalizePath($args['path']);
1327
1328
		$preview = new Preview(\OC_User::getUser(), $prefix, $path);
1329
		$preview->deleteAllPreviews();
1330
	}
1331
1332
}
1333