diff options
author | Morris Jobke <hey@morrisjobke.de> | 2015-04-07 18:16:24 +0200 |
---|---|---|
committer | Morris Jobke <hey@morrisjobke.de> | 2015-04-07 18:16:24 +0200 |
commit | 6c327f8331617652ef0b268d51edc6a23624e33c (patch) | |
tree | d42dbffe37fce7b0322b6ca2ad11a1031519ba16 /lib | |
parent | d28d7c470f8716aa925afbd1ac31feb6bcd1722f (diff) | |
parent | 74bf9806b0fd17c82a5d3048d03a8dc735a2ce04 (diff) | |
download | nextcloud-server-6c327f8331617652ef0b268d51edc6a23624e33c.tar.gz nextcloud-server-6c327f8331617652ef0b268d51edc6a23624e33c.zip |
Merge pull request #14879 from oparoz/fix-preview-caching
Introducing the maximum size preview
Diffstat (limited to 'lib')
-rw-r--r-- | lib/private/preview.php | 363 |
1 files changed, 246 insertions, 117 deletions
diff --git a/lib/private/preview.php b/lib/private/preview.php index f8e90cafbc1..eab60e10862 100644 --- a/lib/private/preview.php +++ b/lib/private/preview.php @@ -3,11 +3,11 @@ * @author Björn Schießle <schiessle@owncloud.com> * @author Frank Karlitschek <frank@owncloud.org> * @author Georg Ehrke <georg@owncloud.com> - * @author Georg Ehrke <georg@ownCloud.com> * @author Joas Schilling <nickvergessen@owncloud.com> * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Lukas Reschke <lukas@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> + * @author Olivier Paroz <owncloud@interfasys.ch> * @author Robin Appelman <icewind@owncloud.com> * @author Robin McCorkell <rmccorkell@karoshi.org.uk> * @author Thomas Müller <thomas.mueller@tmit.eu> @@ -95,9 +95,9 @@ class Preview { $this->userView = new \OC\Files\View('/' . $user); //set config - $this->configMaxX = \OC_Config::getValue('preview_max_x', null); - $this->configMaxY = \OC_Config::getValue('preview_max_y', null); - $this->maxScaleFactor = \OC_Config::getValue('preview_max_scale_factor', 2); + $this->configMaxX = \OC::$server->getConfig()->getSystemValue('preview_max_x', 2048); + $this->configMaxY = \OC::$server->getConfig()->getSystemValue('preview_max_y', 2048); + $this->maxScaleFactor = \OC::$server->getConfig()->getSystemValue('preview_max_scale_factor', 2); //save parameters $this->setFile($file); @@ -246,7 +246,7 @@ class Preview { $configMaxX = $this->getConfigMaxX(); if (!is_null($configMaxX)) { if ($maxX > $configMaxX) { - \OC_Log::write('core', 'maxX reduced from ' . $maxX . ' to ' . $configMaxX, \OC_Log::DEBUG); + \OCP\Util::writeLog('core', 'maxX reduced from ' . $maxX . ' to ' . $configMaxX, \OCP\Util::DEBUG); $maxX = $configMaxX; } } @@ -267,7 +267,7 @@ class Preview { $configMaxY = $this->getConfigMaxY(); if (!is_null($configMaxY)) { if ($maxY > $configMaxY) { - \OC_Log::write('core', 'maxX reduced from ' . $maxY . ' to ' . $configMaxY, \OC_Log::DEBUG); + \OCP\Util::writeLog('core', 'maxX reduced from ' . $maxY . ' to ' . $configMaxY, \OCP\Util::DEBUG); $maxY = $configMaxY; } } @@ -304,12 +304,12 @@ class Preview { public function isFileValid() { $file = $this->getFile(); if ($file === '') { - \OC_Log::write('core', 'No filename passed', \OC_Log::DEBUG); + \OCP\Util::writeLog('core', 'No filename passed', \OCP\Util::DEBUG); return false; } if (!$this->fileView->file_exists($file)) { - \OC_Log::write('core', 'File:"' . $file . '" not found', \OC_Log::DEBUG); + \OCP\Util::writeLog('core', 'File:"' . $file . '" not found', \OCP\Util::DEBUG); return false; } @@ -321,9 +321,7 @@ class Preview { * @return bool */ public function deletePreview() { - $file = $this->getFile(); - - $fileInfo = $this->getFileInfo($file); + $fileInfo = $this->getFileInfo(); if($fileInfo !== null && $fileInfo !== false) { $fileId = $fileInfo->getId(); @@ -357,7 +355,8 @@ class Preview { } /** - * check if thumbnail or bigger version of thumbnail of file is cached + * Checks if thumbnail or bigger version of thumbnail of file is already cached + * * @param int $fileId fileId of the original image * @return string|false path to thumbnail if it exists or false */ @@ -366,9 +365,11 @@ class Preview { return false; } + // This gives us a calculated path to a preview of asked dimensions + // thumbnailFolder/fileId/my_image-<maxX>-<maxY>.png $preview = $this->buildCachePath($fileId); - //does a preview with the wanted height and width already exist? + // This checks if a preview exists at that location if ($this->userView->file_exists($preview)) { return $preview; } @@ -377,34 +378,39 @@ class Preview { } /** - * check if a bigger version of thumbnail of file is cached + * Checks if a bigger version of a file preview is cached and if not + * return the preview of max allowed dimensions + * * @param int $fileId fileId of the original image + * * @return string|false path to bigger thumbnail if it exists or false - */ + */ private function isCachedBigger($fileId) { if (is_null($fileId)) { return false; } - // in order to not loose quality we better generate aspect preserving previews from the original file - if ($this->keepAspect) { - return false; - } - $maxX = $this->getMaxX(); //array for usable cached thumbnails + // FIXME: Checking only the width could lead to issues $possibleThumbnails = $this->getPossibleThumbnails($fileId); foreach ($possibleThumbnails as $width => $path) { - if ($width < $maxX) { + if ($width === 'max' || $width < $maxX) { continue; } else { return $path; } } + // At this stage, we didn't find a preview, so if the folder is not empty, + // we return the max preview we generated on the first run + if ($possibleThumbnails) { + return $possibleThumbnails['max']; + } + return false; } @@ -421,7 +427,7 @@ class Preview { $previewPath = $this->getPreviewPath($fileId); - $wantedAspectRatio = (float) ($this->getMaxX() / $this->getMaxY()); + $wantedAspectRatio = (float)($this->getMaxX() / $this->getMaxY()); //array for usable cached thumbnails $possibleThumbnails = array(); @@ -429,6 +435,11 @@ class Preview { $allThumbnails = $this->userView->getDirectoryContent($previewPath); foreach ($allThumbnails as $thumbnail) { $name = rtrim($thumbnail['name'], '.png'); + // Always add the max preview to the array + if (strpos($name, 'max')) { + $possibleThumbnails['max'] = $thumbnail['path']; + continue; + } list($x, $y, $aspectRatio) = $this->getDimensionsFromFilename($name); if (abs($aspectRatio - $wantedAspectRatio) >= 0.000001 @@ -482,7 +493,11 @@ class Preview { } /** - * return a preview of a file + * Returns a preview of a file + * + * The cache is searched first and if nothing usable was found then a preview is + * generated by one of the providers + * * @return \OCP\IImage */ public function getPreview() { @@ -491,76 +506,22 @@ class Preview { } $this->preview = null; - $file = $this->getFile(); - $maxX = $this->getMaxX(); - $maxY = $this->getMaxY(); - $scalingUp = $this->getScalingUp(); - - $fileInfo = $this->getFileInfo($file); - if($fileInfo === null || $fileInfo === false) { + $fileInfo = $this->getFileInfo(); + if ($fileInfo === null || $fileInfo === false) { return new \OC_Image(); } - $fileId = $fileInfo->getId(); + $fileId = $fileInfo->getId(); $cached = $this->isCached($fileId); if ($cached) { - $stream = $this->userView->fopen($cached, 'r'); - $this->preview = null; - if ($stream) { - $image = new \OC_Image(); - $image->loadFromFileHandle($stream); - $this->preview = $image->valid() ? $image : null; - - $this->resizeAndCrop(); - fclose($stream); - } + $this->getCachedPreview($fileId, $cached); } if (is_null($this->preview)) { - $preview = null; - - $previewProviders = \OC::$server->getPreviewManager()->getProviders(); - foreach ($previewProviders as $supportedMimeType => $providers) { - if (!preg_match($supportedMimeType, $this->mimeType)) { - continue; - } - - foreach ($providers as $closure) { - $provider = $closure(); - if (!($provider instanceof \OCP\Preview\IProvider)) { - continue; - } - - \OC_Log::write('core', 'Generating preview for "' . $file . '" with "' . get_class($provider) . '"', \OC_Log::DEBUG); - - /** @var $provider Provider */ - $preview = $provider->getThumbnail($file, $maxX, $maxY, $scalingUp, $this->fileView); - - if (!($preview instanceof \OCP\IImage)) { - continue; - } - - $this->preview = $preview; - $this->resizeAndCrop(); - - $previewPath = $this->getPreviewPath($fileId); - $cachePath = $this->buildCachePath($fileId); - - if ($this->userView->is_dir($this->getThumbnailsFolder() . '/') === false) { - $this->userView->mkdir($this->getThumbnailsFolder() . '/'); - } - - if ($this->userView->is_dir($previewPath) === false) { - $this->userView->mkdir($previewPath); - } - - $this->userView->file_put_contents($cachePath, $preview->data()); - - break 2; - } - } + $this->generatePreview($fileId); } + // We still don't have a preview, so we generate an empty object which can't be displayed if (is_null($this->preview)) { $this->preview = new \OC_Image(); } @@ -588,18 +549,58 @@ class Preview { } /** + * Retrieves the preview from the cache and resizes it if necessary + * + * @param int $fileId fileId of the original image + * @param string $cached the path to the cached preview + */ + private function getCachedPreview($fileId, $cached) { + $stream = $this->userView->fopen($cached, 'r'); + $this->preview = null; + if ($stream) { + $image = new \OC_Image(); + $image->loadFromFileHandle($stream); + + $this->preview = $image->valid() ? $image : null; + + $maxX = (int)$this->getMaxX(); + $maxY = (int)$this->getMaxY(); + $previewX = (int)$this->preview->width(); + $previewY = (int)$this->preview->height(); + + if ($previewX !== $maxX && $previewY !== $maxY) { + $this->resizeAndStore($fileId); + } + + fclose($stream); + } + } + + /** + * Resizes, crops, fixes orientation and stores in the cache + * + * @param int $fileId fileId of the original image + */ + private function resizeAndStore($fileId) { + // Resize and store + $this->resizeAndCrop(); + // We save a copy in the cache to speed up future calls + $cachePath = $this->buildCachePath($fileId); + $this->userView->file_put_contents($cachePath, $this->preview->data()); + } + + /** * resize, crop and fix orientation - * @return void + * + * @param bool $max */ - private function resizeAndCrop() { + private function resizeAndCrop($max = false) { $image = $this->preview; - $x = $this->getMaxX(); - $y = $this->getMaxY(); - $scalingUp = $this->getScalingUp(); - $maxScaleFactor = $this->getMaxScaleFactor(); + + list($x, $y, $scalingUp, $maxScaleFactor) = $this->getResizeData($max); if (!($image instanceof \OCP\IImage)) { - \OC_Log::write('core', '$this->preview is not an instance of \OCP\IImage', \OC_Log::DEBUG); + \OCP\Util::writeLog('core', '$this->preview is not an instance of OC_Image', \OCP\Util::DEBUG); return; } @@ -617,6 +618,7 @@ class Preview { } } + // The preview already has the asked dimensions if ($x === $realX && $y === $realY) { $this->preview = $image; return; @@ -639,7 +641,7 @@ class Preview { if (!is_null($maxScaleFactor)) { if ($factor > $maxScaleFactor) { - \OC_Log::write('core', 'scale factor reduced from ' . $factor . ' to ' . $maxScaleFactor, \OC_Log::DEBUG); + \OCP\Util::writeLog('core', 'scale factor reduced from ' . $factor . ' to ' . $maxScaleFactor, \OCP\Util::DEBUG); $factor = $maxScaleFactor; } } @@ -649,11 +651,13 @@ class Preview { $image->preciseResize($newXSize, $newYSize); + // The preview has been upscaled and now has the asked dimensions if ($newXSize === $x && $newYSize === $y) { $this->preview = $image; return; } + // One dimension of the upscaled preview is too big if ($newXSize >= $x && $newYSize >= $y) { $cropX = floor(abs($x - $newXSize) * 0.5); //don't crop previews on the Y axis, this sucks if it's a document. @@ -666,6 +670,7 @@ class Preview { return; } + // One dimension of the upscaled preview is too small and we're allowed to scale up if (($newXSize < $x || $newYSize < $y) && $scalingUp) { if ($newXSize > $x) { $cropX = floor(($newXSize - $x) * 0.5); @@ -698,11 +703,161 @@ class Preview { $image = new \OC_Image($backgroundLayer); $this->preview = $image; + return; } } /** + * Returns data to be used to resize a preview + * + * @param $max + * + * @return array + */ + private function getResizeData($max) { + if (!$max) { + $x = $this->getMaxX(); + $y = $this->getMaxY(); + $scalingUp = $this->getScalingUp(); + $maxScaleFactor = $this->getMaxScaleFactor(); + } else { + $x = $this->configMaxX; + $y = $this->configMaxY; + $scalingUp = false; + $maxScaleFactor =1; + } + + return [$x, $y, $scalingUp, $maxScaleFactor]; + } + + /** + * Returns the path to a preview based on its dimensions and aspect + * + * @param int $fileId + * + * @return string + */ + private function buildCachePath($fileId) { + $maxX = $this->getMaxX(); + $maxY = $this->getMaxY(); + + $previewPath = $this->getPreviewPath($fileId); + $previewPath = $previewPath . strval($maxX) . '-' . strval($maxY); + if ($this->keepAspect) { + $previewPath .= '-with-aspect'; + } + $previewPath .= '.png'; + + return $previewPath; + } + + /** + * @param int $fileId + * + * @return string + */ + private function getPreviewPath($fileId) { + return $this->getThumbnailsFolder() . '/' . $fileId . '/'; + } + + /** + * Asks the provider to send a preview of the file of maximum dimensions + * and after saving it in the cache, it is then resized to the asked dimensions + * + * This is only called once in order to generate a large PNG of dimensions defined in the + * configuration file. We'll be able to quickly resize it later on. + * We never upscale the original conversion as this will be done later by the resizing operation + * + * @param int $fileId fileId of the original image + */ + private function generatePreview($fileId) { + $file = $this->getFile(); + $preview = null; + + $previewProviders = \OC::$server->getPreviewManager()->getProviders(); + foreach ($previewProviders as $supportedMimeType => $providers) { + if (!preg_match($supportedMimeType, $this->mimeType)) { + continue; + } + + foreach ($providers as $closure) { + $provider = $closure(); + if (!($provider instanceof \OCP\Preview\IProvider)) { + continue; + } + + \OCP\Util::writeLog( + 'core', 'Generating preview for "' . $file . '" with "' . get_class($provider) + . '"', \OCP\Util::DEBUG + ); + + /** @var $provider Provider */ + $preview = $provider->getThumbnail( + $file, $this->configMaxX, $this->configMaxY, $scalingUp = false, $this->fileView + ); + + if (!($preview instanceof \OCP\IImage)) { + continue; + } + + $this->preview = $preview; + $previewPath = $this->getPreviewPath($fileId); + + if ($this->userView->is_dir($this->getThumbnailsFolder() . '/') === false) { + $this->userView->mkdir($this->getThumbnailsFolder() . '/'); + } + + if ($this->userView->is_dir($previewPath) === false) { + $this->userView->mkdir($previewPath); + } + + // This stores our large preview so that it can be used in subsequent resizing requests + $this->storeMaxPreview($previewPath); + + break 2; + } + } + + // The providers have been kind enough to give us a preview + if ($preview) { + $this->resizeAndStore($fileId); + } + } + + /** + * Stores the max preview in the cache + * + * @param string $previewPath path to the preview + */ + private function storeMaxPreview($previewPath) { + $maxPreview = false; + $preview = $this->preview; + + $allThumbnails = $this->userView->getDirectoryContent($previewPath); + // This is so that the cache doesn't need emptying when upgrading + // Can be replaced by an upgrade script... + foreach ($allThumbnails as $thumbnail) { + $name = rtrim($thumbnail['name'], '.png'); + if (strpos($name, 'max')) { + $maxPreview = true; + break; + } + } + // We haven't found the max preview, so we create it + if (!$maxPreview) { + // Most providers don't resize their thumbnails yet + $this->resizeAndCrop(true); + + $maxX = $preview->width(); + $maxY = $preview->height(); + $previewPath = $previewPath . strval($maxX) . '-' . strval($maxY); + $previewPath .= '-max.png'; + $this->userView->file_put_contents($previewPath, $preview->data()); + } + } + + /** * @param array $args */ public static function post_write($args) { @@ -791,30 +946,4 @@ class Preview { $preview->deleteAllPreviews(); } - /** - * @param int $fileId - * @return string - */ - private function buildCachePath($fileId) { - $maxX = $this->getMaxX(); - $maxY = $this->getMaxY(); - - $previewPath = $this->getPreviewPath($fileId); - $preview = $previewPath . strval($maxX) . '-' . strval($maxY); - if ($this->keepAspect) { - $preview .= '-with-aspect'; - } - $preview .= '.png'; - - return $preview; - } - - - /** - * @param int $fileId - * @return string - */ - private function getPreviewPath($fileId) { - return $this->getThumbnailsFolder() . '/' . $fileId . '/'; - } } |