From e723363053e466a5b530a73c48ef6a40813c27ee Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Wed, 4 Jan 2017 20:14:16 +0100 Subject: [PATCH] Remove old preview code * \OC\Preview is no more * \Test\PreviewTests is no more * PreviewHooks in base.php are gone Signed-off-by: Roeland Jago Douma --- lib/base.php | 17 - lib/private/Preview.php | 1349 ------------------------------------- tests/lib/PreviewTest.php | 963 -------------------------- 3 files changed, 2329 deletions(-) delete mode 100644 lib/private/Preview.php delete mode 100644 tests/lib/PreviewTest.php diff --git a/lib/base.php b/lib/base.php index 3ab41f37599..f848a4fff54 100644 --- a/lib/base.php +++ b/lib/base.php @@ -749,9 +749,6 @@ class OC { self::registerCacheHooks(); self::registerFilesystemHooks(); - if ($systemConfig->getValue('enable_previews', true)) { - self::registerPreviewHooks(); - } self::registerShareHooks(); self::registerLogRotate(); self::registerEncryptionWrapper(); @@ -893,20 +890,6 @@ class OC { OC_Hook::connect('OC_Filesystem', 'rename', 'OC\Files\Filesystem', 'isBlacklisted'); } - /** - * register hooks for previews - */ - public static function registerPreviewHooks() { - OC_Hook::connect('OC_Filesystem', 'post_write', 'OC\Preview', 'post_write'); - OC_Hook::connect('OC_Filesystem', 'delete', 'OC\Preview', 'prepare_delete_files'); - OC_Hook::connect('\OCP\Versions', 'preDelete', 'OC\Preview', 'prepare_delete'); - OC_Hook::connect('\OCP\Trashbin', 'preDelete', 'OC\Preview', 'prepare_delete'); - OC_Hook::connect('OC_Filesystem', 'post_delete', 'OC\Preview', 'post_delete_files'); - OC_Hook::connect('\OCP\Versions', 'delete', 'OC\Preview', 'post_delete_versions'); - OC_Hook::connect('\OCP\Trashbin', 'delete', 'OC\Preview', 'post_delete'); - OC_Hook::connect('\OCP\Versions', 'rollback', 'OC\Preview', 'post_delete_versions'); - } - /** * register hooks for sharing */ diff --git a/lib/private/Preview.php b/lib/private/Preview.php deleted file mode 100644 index caa1e89bacc..00000000000 --- a/lib/private/Preview.php +++ /dev/null @@ -1,1349 +0,0 @@ - - * @author Frank Karlitschek - * @author Georg Ehrke - * @author Joas Schilling - * @author Jörn Friedrich Dreyer - * @author Lukas Reschke - * @author Morris Jobke - * @author Olivier Paroz - * @author Robin Appelman - * @author Roeland Jago Douma - * @author Thomas Müller - * @author Tobias Kaminsky - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ -namespace OC; - -use OC\Preview\Provider; -use OCP\Files\FileInfo; -use OCP\Files\NotFoundException; - -class Preview { - //the thumbnail folder - const THUMBNAILS_FOLDER = 'thumbnails'; - - const MODE_FILL = 'fill'; - const MODE_COVER = 'cover'; - - //config - private $maxScaleFactor; - /** @var int maximum width allowed for a preview */ - private $configMaxWidth; - /** @var int maximum height allowed for a preview */ - private $configMaxHeight; - - //fileview object - private $fileView = null; - private $userView = null; - - //vars - private $file; - private $maxX; - private $maxY; - private $scalingUp; - private $mimeType; - private $keepAspect = false; - private $mode = self::MODE_FILL; - - //used to calculate the size of the preview to generate - /** @var int $maxPreviewWidth max width a preview can have */ - private $maxPreviewWidth; - /** @var int $maxPreviewHeight max height a preview can have */ - private $maxPreviewHeight; - /** @var int $previewWidth calculated width of the preview we're looking for */ - private $previewWidth; - /** @var int $previewHeight calculated height of the preview we're looking for */ - private $previewHeight; - - //filemapper used for deleting previews - // index is path, value is fileinfo - static public $deleteFileMapper = array(); - static public $deleteChildrenMapper = array(); - - /** - * preview images object - * - * @var \OCP\IImage - */ - private $preview; - - /** - * @var \OCP\Files\FileInfo - */ - protected $info; - - /** - * check if thumbnail or bigger version of thumbnail of file is cached - * - * @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 - * @param bool $scalingUp Disable/Enable upscaling of previews - * - * @throws \Exception - * @return mixed (bool / string) - * false if thumbnail does not exist - * path to thumbnail if thumbnail exists - */ - public function __construct( - $user = '', - $root = '/', - $file = '', $maxX = 1, - $maxY = 1, - $scalingUp = true - ) { - //init fileviews - if ($user === '') { - $user = \OC_User::getUser(); - } - $this->fileView = new \OC\Files\View('/' . $user . '/' . $root); - $this->userView = new \OC\Files\View('/' . $user); - - //set config - $sysConfig = \OC::$server->getConfig(); - $this->configMaxWidth = $sysConfig->getSystemValue('preview_max_x', 2048); - $this->configMaxHeight = $sysConfig->getSystemValue('preview_max_y', 2048); - $this->maxScaleFactor = $sysConfig->getSystemValue('preview_max_scale_factor', 1); - - //save parameters - $this->setFile($file); - $this->setMaxX((int)$maxX); - $this->setMaxY((int)$maxY); - $this->setScalingup($scalingUp); - - $this->preview = null; - - //check if there are preview backends - if (!\OC::$server->getPreviewManager() - ->hasProviders() - && \OC::$server->getConfig() - ->getSystemValue('enable_previews', true) - ) { - \OCP\Util::writeLog('core', 'No preview providers exist', \OCP\Util::ERROR); - throw new \Exception('No preview providers'); - } - } - - /** - * returns the path of the file you want a thumbnail from - * - * @return string - */ - public function getFile() { - return $this->file; - } - - /** - * returns the max width of the preview - * - * @return integer - */ - public function getMaxX() { - return $this->maxX; - } - - /** - * returns the max height of the preview - * - * @return integer - */ - public function getMaxY() { - return $this->maxY; - } - - /** - * returns whether or not scalingup is enabled - * - * @return bool - */ - public function getScalingUp() { - return $this->scalingUp; - } - - /** - * returns the name of the thumbnailfolder - * - * @return string - */ - public function getThumbnailsFolder() { - return self::THUMBNAILS_FOLDER; - } - - /** - * returns the max scale factor - * - * @return string - */ - public function getMaxScaleFactor() { - return $this->maxScaleFactor; - } - - /** - * returns the max width set in ownCloud's config - * - * @return integer - */ - public function getConfigMaxX() { - return $this->configMaxWidth; - } - - /** - * returns the max height set in ownCloud's config - * - * @return integer - */ - public function getConfigMaxY() { - return $this->configMaxHeight; - } - - /** - * Returns the FileInfo object associated with the file to preview - * - * @return false|Files\FileInfo|\OCP\Files\FileInfo - */ - protected function getFileInfo() { - $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) { - $this->info = $this->fileView->getFileInfo($this->file); - } - - return $this->info; - } - - - /** - * @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; - } - - /** - * Sets the path of the file you want a preview of - * - * @param string $file - * @param \OCP\Files\FileInfo|null $info - * - * @return \OC\Preview - */ - public function setFile($file, $info = null) { - $this->file = $file; - $this->info = $info; - - if ($file !== '') { - $this->getFileInfo(); - if ($this->info instanceof \OCP\Files\FileInfo) { - $this->mimeType = $this->info->getMimetype(); - } - } - - return $this; - } - - /** - * Forces the use of a specific media type - * - * @param string $mimeType - */ - public function setMimetype($mimeType) { - $this->mimeType = $mimeType; - } - - /** - * Sets the max width of the preview. It's capped by the maximum allowed size set in the - * configuration - * - * @param int $maxX - * - * @throws \Exception - * @return \OC\Preview - */ - public function setMaxX($maxX = 1) { - if ($maxX <= 0) { - throw new \Exception('Cannot set width of 0 or smaller!'); - } - $configMaxX = $this->getConfigMaxX(); - $maxX = $this->limitMaxDim($maxX, $configMaxX, 'maxX'); - $this->maxX = $maxX; - - return $this; - } - - /** - * Sets the max height of the preview. It's capped by the maximum allowed size set in the - * configuration - * - * @param int $maxY - * - * @throws \Exception - * @return \OC\Preview - */ - public function setMaxY($maxY = 1) { - if ($maxY <= 0) { - throw new \Exception('Cannot set height of 0 or smaller!'); - } - $configMaxY = $this->getConfigMaxY(); - $maxY = $this->limitMaxDim($maxY, $configMaxY, 'maxY'); - $this->maxY = $maxY; - - return $this; - } - - /** - * Sets whether we're allowed to scale up when generating a preview. It's capped by the maximum - * allowed scale factor set in the configuration - * - * @param bool $scalingUp - * - * @return \OC\Preview - */ - public function setScalingup($scalingUp) { - if ($this->getMaxScaleFactor() === 1) { - $scalingUp = false; - } - $this->scalingUp = $scalingUp; - - return $this; - } - - /** - * Set whether to cover or fill the specified dimensions - * - * @param string $mode - * - * @return \OC\Preview - */ - public function setMode($mode) { - $this->mode = $mode; - - return $this; - } - - /** - * Sets whether we need to generate a preview which keeps the aspect ratio of the original file - * - * @param bool $keepAspect - * - * @return \OC\Preview - */ - public function setKeepAspect($keepAspect) { - $this->keepAspect = $keepAspect; - - return $this; - } - - /** - * Makes sure we were given a file to preview and that it exists in the filesystem - * - * @return bool - */ - public function isFileValid() { - $file = $this->getFile(); - if ($file === '') { - \OCP\Util::writeLog('core', 'No filename passed', \OCP\Util::DEBUG); - - return false; - } - - if (!$this->getFileInfo() instanceof FileInfo) { - \OCP\Util::writeLog('core', 'File:"' . $file . '" not found', \OCP\Util::DEBUG); - - return false; - } - - return true; - } - - /** - * Deletes the preview of a file with specific width and height - * - * This should never delete the max preview, use deleteAllPreviews() instead - * - * @return bool - */ - public function deletePreview() { - $fileInfo = $this->getFileInfo(); - if ($fileInfo !== null && $fileInfo !== false) { - $fileId = $fileInfo->getId(); - - $previewPath = $this->buildCachePath($fileId); - if (!strpos($previewPath, 'max')) { - return $this->userView->unlink($previewPath); - } - } - - return false; - } - - /** - * Deletes all previews of a file - */ - public function deleteAllPreviews() { - $thumbnailMount = $this->userView->getMount($this->getThumbnailsFolder()); - $propagator = $thumbnailMount->getStorage()->getPropagator(); - $propagator->beginBatch(); - - $toDelete = $this->getChildren(); - $toDelete[] = $this->getFileInfo(); - - foreach ($toDelete as $delete) { - if ($delete instanceof FileInfo) { - /** @var \OCP\Files\FileInfo $delete */ - $fileId = $delete->getId(); - - // getId() might return null, e.g. when the file is a - // .ocTransferId*.part file from chunked file upload. - if (!empty($fileId)) { - $previewPath = $this->getPreviewPath($fileId); - $this->userView->rmdir($previewPath); - } - } - } - - $propagator->commitBatch(); - } - - /** - * Checks if a preview matching the asked dimensions or a bigger version is already cached - * - * * We first retrieve the size of the max preview since this is what we be used to create - * all our preview. If it doesn't exist we return false, so that it can be generated - * * Using the dimensions of the max preview, we calculate what the size of the new - * thumbnail should be - * * And finally, we look for a suitable candidate in the cache - * - * @param int $fileId fileId of the original file we need a preview of - * - * @return string|false path to the cached preview if it exists or false - */ - public function isCached($fileId) { - if (is_null($fileId)) { - return false; - } - - /** - * Phase 1: Looking for the max preview - */ - $previewPath = $this->getPreviewPath($fileId); - // We currently can't look for a single file due to bugs related to #16478 - $allThumbnails = $this->userView->getDirectoryContent($previewPath); - list($maxPreviewWidth, $maxPreviewHeight) = $this->getMaxPreviewSize($allThumbnails); - - // Only use the cache if we have a max preview - if (!is_null($maxPreviewWidth) && !is_null($maxPreviewHeight)) { - - /** - * Phase 2: Calculating the size of the preview we need to send back - */ - $this->maxPreviewWidth = $maxPreviewWidth; - $this->maxPreviewHeight = $maxPreviewHeight; - - list($previewWidth, $previewHeight) = $this->simulatePreviewDimensions(); - if (empty($previewWidth) || empty($previewHeight)) { - return false; - } - - $this->previewWidth = $previewWidth; - $this->previewHeight = $previewHeight; - - /** - * Phase 3: We look for a preview of the exact size - */ - // This gives us a calculated path to a preview of asked dimensions - // thumbnailFolder/fileId/-(-max|-with-aspect).png - $preview = $this->buildCachePath($fileId, $previewWidth, $previewHeight); - - // This checks if we have a preview of those exact dimensions in the cache - if ($this->thumbnailSizeExists($allThumbnails, basename($preview))) { - return $preview; - } - - /** - * Phase 4: We look for a larger preview, matching the aspect ratio - */ - if (($this->getMaxX() >= $maxPreviewWidth) - && ($this->getMaxY() >= $maxPreviewHeight) - ) { - // The preview we-re looking for is the exact size or larger than the max preview, - // so return that - return $this->buildCachePath($fileId, $maxPreviewWidth, $maxPreviewHeight); - } else { - // The last resort is to look for something bigger than what we've calculated, - // but still smaller than the max preview - return $this->isCachedBigger($fileId, $allThumbnails); - } - } - - return false; - } - - /** - * Returns the dimensions of the max preview - * - * @param FileInfo[] $allThumbnails the list of all our cached thumbnails - * - * @return int[] - */ - private function getMaxPreviewSize($allThumbnails) { - $maxPreviewX = null; - $maxPreviewY = null; - - foreach ($allThumbnails as $thumbnail) { - $name = $thumbnail['name']; - if (strpos($name, 'max')) { - list($maxPreviewX, $maxPreviewY) = $this->getDimensionsFromFilename($name); - break; - } - } - - return [$maxPreviewX, $maxPreviewY]; - } - - /** - * Check if a specific thumbnail size is cached - * - * @param FileInfo[] $allThumbnails the list of all our cached thumbnails - * @param string $name - * @return bool - */ - private function thumbnailSizeExists(array $allThumbnails, $name) { - - foreach ($allThumbnails as $thumbnail) { - if ($name === $thumbnail->getName()) { - return true; - } - } - - return false; - } - - /** - * Determines the size of the preview we should be looking for in the cache - * - * @return integer[] - */ - private function simulatePreviewDimensions() { - $askedWidth = $this->getMaxX(); - $askedHeight = $this->getMaxY(); - - if ($this->keepAspect) { - list($newPreviewWidth, $newPreviewHeight) = - $this->applyAspectRatio($askedWidth, $askedHeight); - } else { - list($newPreviewWidth, $newPreviewHeight) = $this->fixSize($askedWidth, $askedHeight); - } - - return [(int)$newPreviewWidth, (int)$newPreviewHeight]; - } - - /** - * Resizes the boundaries to match the aspect ratio - * - * @param int $askedWidth - * @param int $askedHeight - * - * @param int $originalWidth - * @param int $originalHeight - * @return integer[] - */ - private function applyAspectRatio($askedWidth, $askedHeight, $originalWidth = 0, $originalHeight = 0) { - if (!$originalWidth) { - $originalWidth = $this->maxPreviewWidth; - } - if (!$originalHeight) { - $originalHeight = $this->maxPreviewHeight; - } - $originalRatio = $originalWidth / $originalHeight; - // Defines the box in which the preview has to fit - $scaleFactor = $this->scalingUp ? $this->maxScaleFactor : 1; - $askedWidth = min($askedWidth, $originalWidth * $scaleFactor); - $askedHeight = min($askedHeight, $originalHeight * $scaleFactor); - - if ($askedWidth / $originalRatio < $askedHeight) { - // width restricted - $askedHeight = round($askedWidth / $originalRatio); - } else { - $askedWidth = round($askedHeight * $originalRatio); - } - - return [(int)$askedWidth, (int)$askedHeight]; - } - - /** - * Resizes the boundaries to cover the area - * - * @param int $askedWidth - * @param int $askedHeight - * @param int $previewWidth - * @param int $previewHeight - * @return integer[] - */ - private function applyCover($askedWidth, $askedHeight, $previewWidth, $previewHeight) { - $originalRatio = $previewWidth / $previewHeight; - // Defines the box in which the preview has to fit - $scaleFactor = $this->scalingUp ? $this->maxScaleFactor : 1; - $askedWidth = min($askedWidth, $previewWidth * $scaleFactor); - $askedHeight = min($askedHeight, $previewHeight * $scaleFactor); - - if ($askedWidth / $originalRatio > $askedHeight) { - // height restricted - $askedHeight = round($askedWidth / $originalRatio); - } else { - $askedWidth = round($askedHeight * $originalRatio); - } - - return [(int)$askedWidth, (int)$askedHeight]; - } - - /** - * Makes sure an upscaled preview doesn't end up larger than the max dimensions defined in the - * config - * - * @param int $askedWidth - * @param int $askedHeight - * - * @return integer[] - */ - private function fixSize($askedWidth, $askedHeight) { - if ($this->scalingUp) { - $askedWidth = min($this->configMaxWidth, $askedWidth); - $askedHeight = min($this->configMaxHeight, $askedHeight); - } - - return [(int)$askedWidth, (int)$askedHeight]; - } - - /** - * 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 - * @param FileInfo[] $allThumbnails the list of all our cached thumbnails - * - * @return string path to bigger thumbnail - */ - private function isCachedBigger($fileId, $allThumbnails) { - // This is used to eliminate any thumbnail narrower than what we need - $maxX = $this->getMaxX(); - - //array for usable cached thumbnails - $possibleThumbnails = $this->getPossibleThumbnails($allThumbnails); - - foreach ($possibleThumbnails as $width => $path) { - if ($width < $maxX) { - continue; - } else { - return $path; - } - } - - // At this stage, we didn't find a preview, so we return the max preview - return $this->buildCachePath($fileId, $this->maxPreviewWidth, $this->maxPreviewHeight); - } - - /** - * Get possible bigger thumbnails of the given image with the proper aspect ratio - * - * @param FileInfo[] $allThumbnails the list of all our cached thumbnails - * - * @return string[] an array of paths to bigger thumbnails - */ - private function getPossibleThumbnails($allThumbnails) { - if ($this->keepAspect) { - $wantedAspectRatio = (float)($this->maxPreviewWidth / $this->maxPreviewHeight); - } else { - $wantedAspectRatio = (float)($this->getMaxX() / $this->getMaxY()); - } - - //array for usable cached thumbnails - $possibleThumbnails = array(); - foreach ($allThumbnails as $thumbnail) { - $name = rtrim($thumbnail['name'], '.png'); - list($x, $y, $aspectRatio) = $this->getDimensionsFromFilename($name); - if (abs($aspectRatio - $wantedAspectRatio) >= 0.000001 - || $this->unscalable($x, $y) - ) { - continue; - } - $possibleThumbnails[$x] = $thumbnail['path']; - } - - ksort($possibleThumbnails); - - return $possibleThumbnails; - } - - /** - * Looks at the preview filename from the cache and extracts the size of the preview - * - * @param string $name - * - * @return array - */ - private function getDimensionsFromFilename($name) { - $size = explode('-', $name); - $x = (int)$size[0]; - $y = (int)$size[1]; - $aspectRatio = (float)($x / $y); - - return array($x, $y, $aspectRatio); - } - - /** - * @param int $x - * @param int $y - * - * @return bool - */ - private function unscalable($x, $y) { - - $maxX = $this->getMaxX(); - $maxY = $this->getMaxY(); - $scalingUp = $this->getScalingUp(); - $maxScaleFactor = $this->getMaxScaleFactor(); - - if ($x < $maxX || $y < $maxY) { - if ($scalingUp) { - $scaleFactor = $maxX / $x; - if ($scaleFactor > $maxScaleFactor) { - return true; - } - } else { - return true; - } - } - - return false; - } - - /** - * 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() { - if (!is_null($this->preview) && $this->preview->valid()) { - return $this->preview; - } - - $this->preview = null; - $fileInfo = $this->getFileInfo(); - if ($fileInfo === null || $fileInfo === false || !$fileInfo->isReadable()) { - return new \OC_Image(); - } - - $fileId = $fileInfo->getId(); - $cached = $this->isCached($fileId); - if ($cached) { - $this->getCachedPreview($fileId, $cached); - } - - if (is_null($this->preview)) { - $this->generatePreview($fileId); - } - - // We still don't have a preview, so we send back an empty object - if (is_null($this->preview)) { - $this->preview = new \OC_Image(); - } - - return $this->preview; - } - - /** - * Sends the preview, including the headers to client which requested it - * - * @param null|string $mimeTypeForHeaders the media type to use when sending back the reply - * - * @throws NotFoundException - * @throws PreviewNotAvailableException - */ - public function showPreview($mimeTypeForHeaders = null) { - // Check if file is valid - if ($this->isFileValid() === false) { - throw new NotFoundException('File not found.'); - } - - if (is_null($this->preview)) { - $this->getPreview(); - } - if ($this->preview instanceof \OCP\IImage) { - if ($this->preview->valid()) { - \OCP\Response::enableCaching(3600 * 24); // 24 hours - } else { - $this->getMimeIcon(); - } - $this->preview->show($mimeTypeForHeaders); - } - } - - /** - * 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; - - if (!is_null($this->preview)) { - // Size of the preview we calculated - $maxX = $this->previewWidth; - $maxY = $this->previewHeight; - // Size of the preview we retrieved from the cache - $previewX = (int)$this->preview->width(); - $previewY = (int)$this->preview->height(); - - // We don't have an exact match - 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) { - $image = $this->preview; - if (!($image instanceof \OCP\IImage)) { - \OCP\Util::writeLog( - 'core', '$this->preview is not an instance of \OCP\IImage', \OCP\Util::DEBUG - ); - - return; - } - $previewWidth = (int)$image->width(); - $previewHeight = (int)$image->height(); - $askedWidth = $this->getMaxX(); - $askedHeight = $this->getMaxY(); - - if ($this->mode === self::MODE_COVER) { - list($askedWidth, $askedHeight) = - $this->applyCover($askedWidth, $askedHeight, $previewWidth, $previewHeight); - } - - /** - * Phase 1: If required, adjust boundaries to keep aspect ratio - */ - if ($this->keepAspect) { - list($askedWidth, $askedHeight) = - $this->applyAspectRatio($askedWidth, $askedHeight, $previewWidth, $previewHeight); - } - - /** - * Phase 2: Resizes preview to try and match requirements. - * Takes the scaling ratio into consideration - */ - list($newPreviewWidth, $newPreviewHeight) = $this->scale( - $image, $askedWidth, $askedHeight, $previewWidth, $previewHeight - ); - - // The preview has been resized and should now have the asked dimensions - if ($newPreviewWidth === $askedWidth && $newPreviewHeight === $askedHeight) { - $this->storePreview($fileId, $newPreviewWidth, $newPreviewHeight); - - return; - } - - /** - * Phase 3: We're still not there yet, so we're clipping and filling - * to match the asked dimensions - */ - // It turns out the scaled preview is now too big, so we crop the image - if ($newPreviewWidth >= $askedWidth && $newPreviewHeight >= $askedHeight) { - $this->crop($image, $askedWidth, $askedHeight, $newPreviewWidth, $newPreviewHeight); - $this->storePreview($fileId, $askedWidth, $askedHeight); - - return; - } - - // At least one dimension of the scaled preview is too small, - // so we fill the space with a transparent background - if (($newPreviewWidth < $askedWidth || $newPreviewHeight < $askedHeight)) { - $this->cropAndFill( - $image, $askedWidth, $askedHeight, $newPreviewWidth, $newPreviewHeight - ); - $this->storePreview($fileId, $askedWidth, $askedHeight); - - return; - } - - // The preview is smaller, but we can't touch it - $this->storePreview($fileId, $newPreviewWidth, $newPreviewHeight); - } - - /** - * Calculates the new dimensions of the preview - * - * The new dimensions can be larger or smaller than the ones of the preview we have to resize - * - * @param \OCP\IImage $image - * @param int $askedWidth - * @param int $askedHeight - * @param int $previewWidth - * @param int $previewHeight - * - * @return int[] - */ - private function scale($image, $askedWidth, $askedHeight, $previewWidth, $previewHeight) { - $scalingUp = $this->getScalingUp(); - $maxScaleFactor = $this->getMaxScaleFactor(); - - $factorX = $askedWidth / $previewWidth; - $factorY = $askedHeight / $previewHeight; - - if ($factorX >= $factorY) { - $factor = $factorX; - } else { - $factor = $factorY; - } - - if ($scalingUp === false) { - if ($factor > 1) { - $factor = 1; - } - } - - // We cap when upscaling - if (!is_null($maxScaleFactor)) { - if ($factor > $maxScaleFactor) { - \OCP\Util::writeLog( - 'core', 'scale factor reduced from ' . $factor . ' to ' . $maxScaleFactor, - \OCP\Util::DEBUG - ); - $factor = $maxScaleFactor; - } - } - - $newPreviewWidth = round($previewWidth * $factor); - $newPreviewHeight = round($previewHeight * $factor); - - $image->preciseResize($newPreviewWidth, $newPreviewHeight); - $this->preview = $image; - - return [$newPreviewWidth, $newPreviewHeight]; - } - - /** - * Crops a preview which is larger than the dimensions we've received - * - * @param \OCP\IImage $image - * @param int $askedWidth - * @param int $askedHeight - * @param int $previewWidth - * @param int $previewHeight - */ - private function crop($image, $askedWidth, $askedHeight, $previewWidth, $previewHeight = null) { - $cropX = floor(abs($askedWidth - $previewWidth) * 0.5); - //don't crop previews on the Y axis, this sucks if it's a document. - //$cropY = floor(abs($y - $newPreviewHeight) * 0.5); - $cropY = 0; - $image->crop($cropX, $cropY, $askedWidth, $askedHeight); - $this->preview = $image; - } - - /** - * Crops an image if it's larger than the dimensions we've received and fills the empty space - * with a transparent background - * - * @param \OCP\IImage $image - * @param int $askedWidth - * @param int $askedHeight - * @param int $previewWidth - * @param int $previewHeight - */ - private function cropAndFill($image, $askedWidth, $askedHeight, $previewWidth, $previewHeight) { - if ($previewWidth > $askedWidth) { - $cropX = floor(($previewWidth - $askedWidth) * 0.5); - $image->crop($cropX, 0, $askedWidth, $previewHeight); - $previewWidth = $askedWidth; - } - - if ($previewHeight > $askedHeight) { - $cropY = floor(($previewHeight - $askedHeight) * 0.5); - $image->crop(0, $cropY, $previewWidth, $askedHeight); - $previewHeight = $askedHeight; - } - - // Creates a transparent background - $backgroundLayer = imagecreatetruecolor($askedWidth, $askedHeight); - imagealphablending($backgroundLayer, false); - $transparency = imagecolorallocatealpha($backgroundLayer, 0, 0, 0, 127); - imagefill($backgroundLayer, 0, 0, $transparency); - imagesavealpha($backgroundLayer, true); - - $image = $image->resource(); - - $mergeX = floor(abs($askedWidth - $previewWidth) * 0.5); - $mergeY = floor(abs($askedHeight - $previewHeight) * 0.5); - - // Pastes the preview on top of the background - imagecopy( - $backgroundLayer, $image, $mergeX, $mergeY, 0, 0, $previewWidth, - $previewHeight - ); - - $image = new \OC_Image($backgroundLayer); - - $this->preview = $image; - } - - /** - * Saves a preview in the cache to speed up future calls - * - * Do not nullify the preview as it might send the whole process in a loop - * - * @param int $fileId fileId of the original image - * @param int $previewWidth - * @param int $previewHeight - */ - private function storePreview($fileId, $previewWidth, $previewHeight) { - if (empty($previewWidth) || empty($previewHeight)) { - \OCP\Util::writeLog( - 'core', 'Cannot save preview of dimension ' . $previewWidth . 'x' . $previewHeight, - \OCP\Util::DEBUG - ); - - } else { - $cachePath = $this->buildCachePath($fileId, $previewWidth, $previewHeight); - $this->userView->file_put_contents($cachePath, $this->preview->data()); - } - } - - /** - * Returns the path to a preview based on its dimensions and aspect - * - * @param int $fileId - * @param int|null $maxX - * @param int|null $maxY - * - * @return string - */ - private function buildCachePath($fileId, $maxX = null, $maxY = null) { - if (is_null($maxX)) { - $maxX = $this->getMaxX(); - } - if (is_null($maxY)) { - $maxY = $this->getMaxY(); - } - - $previewPath = $this->getPreviewPath($fileId); - $previewPath = $previewPath . strval($maxX) . '-' . strval($maxY); - $isMaxPreview = - ($maxX === $this->maxPreviewWidth && $maxY === $this->maxPreviewHeight) ? true : false; - if ($isMaxPreview) { - $previewPath .= '-max'; - } - if ($this->keepAspect && !$isMaxPreview) { - $previewPath .= '-with-aspect'; - } - if ($this->mode === self::MODE_COVER) { - $previewPath .= '-cover'; - } - $previewPath .= '.png'; - - return $previewPath; - } - - /** - * Returns the path to the folder where the previews are stored, identified by the fileId - * - * @param int $fileId - * - * @return string - */ - private function getPreviewPath($fileId) { - return $this->getThumbnailsFolder() . '/' . $fileId . '/'; - } - - /** - * Asks the provider to send a preview of the file which respects the maximum dimensions - * defined in the configuration 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->configMaxWidth, $this->configMaxHeight, $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); - } - } - - /** - * Defines the media icon, for the media type of the original file, as the preview - * @throws PreviewNotAvailableException - */ - private function getMimeIcon() { - $image = new \OC_Image(); - $mimeIconWebPath = \OC::$server->getMimeTypeDetector()->mimeTypeIcon($this->mimeType); - if (empty(\OC::$WEBROOT)) { - $mimeIconServerPath = \OC::$SERVERROOT . $mimeIconWebPath; - } else { - $mimeIconServerPath = str_replace(\OC::$WEBROOT, \OC::$SERVERROOT, $mimeIconWebPath); - } - // we can't load SVGs into an image - if (substr($mimeIconWebPath, -4) === '.svg') { - throw new PreviewNotAvailableException('SVG mimetype cannot be rendered'); - } - $image->loadFromFile($mimeIconServerPath); - - $this->preview = $image; - } - - /** - * Stores the max preview in the cache - * - * @param string $previewPath path to the preview - */ - private function storeMaxPreview($previewPath) { - $maxPreviewExists = 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')) { - $maxPreviewExists = true; - break; - } - } - // We haven't found the max preview, so we create it - if (!$maxPreviewExists) { - $previewWidth = $preview->width(); - $previewHeight = $preview->height(); - $previewPath = $previewPath . strval($previewWidth) . '-' . strval($previewHeight); - $previewPath .= '-max.png'; - $this->userView->file_put_contents($previewPath, $preview->data()); - $this->maxPreviewWidth = $previewWidth; - $this->maxPreviewHeight = $previewHeight; - } - } - - /** - * Limits a dimension to the maximum dimension provided as argument - * - * @param int $dim - * @param int $maxDim - * @param string $dimName - * - * @return integer - */ - private function limitMaxDim($dim, $maxDim, $dimName) { - if (!is_null($maxDim)) { - if ($dim > $maxDim) { - \OCP\Util::writeLog( - 'core', $dimName . ' reduced from ' . $dim . ' to ' . $maxDim, \OCP\Util::DEBUG - ); - $dim = $maxDim; - } - } - - return $dim; - } - - /** - * @param array $args - */ - public static function post_write($args) { - self::post_delete($args, 'files/'); - } - - /** - * @param array $args - */ - public static function prepare_delete_files($args) { - self::prepare_delete($args, 'files/'); - } - - /** - * @param array $args - * @param string $prefix - */ - public static function prepare_delete(array $args, $prefix = '') { - $path = $args['path']; - if (substr($path, 0, 1) === '/') { - $path = substr($path, 1); - } - - $view = new \OC\Files\View('/' . \OC_User::getUser() . '/' . $prefix); - - $absPath = Files\Filesystem::normalizePath($view->getAbsolutePath($path)); - $fileInfo = $view->getFileInfo($path); - if ($fileInfo === false) { - return; - } - self::addPathToDeleteFileMapper($absPath, $fileInfo); - if ($view->is_dir($path)) { - $children = self::getAllChildren($view, $path); - self::$deleteChildrenMapper[$absPath] = $children; - } - } - - /** - * @param string $absolutePath - * @param \OCP\Files\FileInfo $info - */ - private static function addPathToDeleteFileMapper($absolutePath, $info) { - self::$deleteFileMapper[$absolutePath] = $info; - } - - /** - * @param \OC\Files\View $view - * @param string $path - * - * @return array - */ - private static function getAllChildren($view, $path) { - $children = $view->getDirectoryContent($path); - $childrensFiles = array(); - - $fakeRootLength = strlen($view->getRoot()); - - for ($i = 0; $i < count($children); $i++) { - $child = $children[$i]; - - $childsPath = substr($child->getPath(), $fakeRootLength); - - if ($view->is_dir($childsPath)) { - $children = array_merge( - $children, - $view->getDirectoryContent($childsPath) - ); - } else { - $childrensFiles[] = $child; - } - } - - return $childrensFiles; - } - - /** - * @param array $args - */ - public static function post_delete_files($args) { - self::post_delete($args, 'files/'); - } - - /** - * @param array $args - */ - public static function post_delete_versions($args) { - self::post_delete($args, 'files/'); - } - - /** - * @param array $args - * @param string $prefix - */ - public static function post_delete($args, $prefix = '') { - $path = Files\Filesystem::normalizePath($args['path']); - - $preview = new Preview(\OC_User::getUser(), $prefix, $path); - $preview->deleteAllPreviews(); - } - -} diff --git a/tests/lib/PreviewTest.php b/tests/lib/PreviewTest.php deleted file mode 100644 index 2d6f53e2ce8..00000000000 --- a/tests/lib/PreviewTest.php +++ /dev/null @@ -1,963 +0,0 @@ - - * @author Olivier Paroz - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace Test; - -use OC\Files\FileInfo; -use OC\Files\Filesystem; -use OC\Files\Storage\Temporary; -use OC\Files\View; -use OC\Preview; -use Test\Traits\MountProviderTrait; -use Test\Traits\UserTrait; - -/** - * Class PreviewTest - * - * @group DB - * - * @package Test - */ -class PreviewTest extends TestCase { - use UserTrait; - use MountProviderTrait; - - const TEST_PREVIEW_USER1 = "test-preview-user1"; - - /** @var View */ - private $rootView; - /** - * Note that using 756 with an image with a ratio of 1.6 brings interesting rounding issues - * - * @var int maximum width allowed for a preview - * */ - private $configMaxWidth = 756; - /** @var int maximum height allowed for a preview */ - private $configMaxHeight = 756; - private $keepAspect; - private $scalingUp; - - private $samples = []; - private $sampleFileId; - private $sampleFilename; - private $sampleWidth; - private $sampleHeight; - private $maxScaleFactor; - /** @var int width of the max preview */ - private $maxPreviewWidth; - /** @var int height of the max preview */ - private $maxPreviewHeight; - /** @var int height of the max preview, which is the same as the one of the original image */ - private $maxPreviewRatio; - private $cachedBigger = []; - - /** - * Make sure your configuration file doesn't contain any additional providers - */ - protected function setUp() { - parent::setUp(); - - $this->createUser(self::TEST_PREVIEW_USER1, self::TEST_PREVIEW_USER1); - $this->loginAsUser(self::TEST_PREVIEW_USER1); - - $storage = new Temporary([]); - Filesystem::mount($storage, [], '/' . self::TEST_PREVIEW_USER1 . '/'); - - $this->rootView = new View(''); - $this->rootView->mkdir('/' . self::TEST_PREVIEW_USER1); - $this->rootView->mkdir('/' . self::TEST_PREVIEW_USER1 . '/files'); - - // We simulate the max dimension set in the config - \OC::$server->getConfig() - ->setSystemValue('preview_max_x', $this->configMaxWidth); - \OC::$server->getConfig() - ->setSystemValue('preview_max_y', $this->configMaxHeight); - // Used to test upscaling - $this->maxScaleFactor = 2; - \OC::$server->getConfig() - ->setSystemValue('preview_max_scale_factor', $this->maxScaleFactor); - - // We need to enable the providers we're going to use in the tests - $providers = [ - 'OC\\Preview\\JPEG', - 'OC\\Preview\\PNG', - 'OC\\Preview\\GIF', - 'OC\\Preview\\TXT', - 'OC\\Preview\\Postscript' - ]; - \OC::$server->getConfig() - ->setSystemValue('enabledPreviewProviders', $providers); - - // Sample is 1680x1050 JPEG - $this->prepareSample('testimage.jpg', 1680, 1050); - // Sample is 2400x1707 EPS - $this->prepareSample('testimage.eps', 2400, 1707); - // Sample is 1200x450 PNG - $this->prepareSample('testimage-wide.png', 1200, 450); - // Sample is 64x64 GIF - $this->prepareSample('testimage.gif', 64, 64); - } - - protected function tearDown() { - $this->logout(); - - parent::tearDown(); - } - - /** - * Tests if a preview can be deleted - */ - public function testIsPreviewDeleted() { - - $sampleFile = '/' . self::TEST_PREVIEW_USER1 . '/files/test.txt'; - - $this->rootView->file_put_contents($sampleFile, 'dummy file data'); - - $x = 50; - $y = 50; - - $preview = new Preview(self::TEST_PREVIEW_USER1, 'files/', 'test.txt', $x, $y); - $preview->getPreview(); - - $fileInfo = $this->rootView->getFileInfo($sampleFile); - /** @var int $fileId */ - $fileId = $fileInfo['fileid']; - $thumbCacheFile = $this->buildCachePath($fileId, $x, $y, true); - - $this->assertSame( - true, $this->rootView->file_exists($thumbCacheFile), "$thumbCacheFile \n" - ); - - $preview->deletePreview(); - - $this->assertSame(false, $this->rootView->file_exists($thumbCacheFile)); - } - - /** - * Tests if all previews can be deleted - * - * We test this first to make sure we'll be able to cleanup after each preview generating test - */ - public function testAreAllPreviewsDeleted() { - - $sampleFile = '/' . self::TEST_PREVIEW_USER1 . '/files/test.txt'; - - $this->rootView->file_put_contents($sampleFile, 'dummy file data'); - - $x = 50; - $y = 50; - - $preview = new Preview(self::TEST_PREVIEW_USER1, 'files/', 'test.txt', $x, $y); - $preview->getPreview(); - - $fileInfo = $this->rootView->getFileInfo($sampleFile); - /** @var int $fileId */ - $fileId = $fileInfo['fileid']; - - $thumbCacheFolder = '/' . self::TEST_PREVIEW_USER1 . '/' . Preview::THUMBNAILS_FOLDER . - '/' . $fileId . '/'; - - $this->assertSame(true, $this->rootView->is_dir($thumbCacheFolder), "$thumbCacheFolder \n"); - - $preview->deleteAllPreviews(); - - $this->assertSame(false, $this->rootView->is_dir($thumbCacheFolder)); - } - - public function txtBlacklist() { - $txt = 'random text file'; - - return [ - ['txt', $txt, false], - ]; - } - - /** - * @dataProvider txtBlacklist - * - * @param $extension - * @param $data - * @param $expectedResult - */ - public function testIsTransparent($extension, $data, $expectedResult) { - - $x = 32; - $y = 32; - - $sample = '/' . self::TEST_PREVIEW_USER1 . '/files/test.' . $extension; - $this->rootView->file_put_contents($sample, $data); - $preview = new Preview( - self::TEST_PREVIEW_USER1, 'files/', 'test.' . $extension, $x, - $y - ); - $image = $preview->getPreview(); - $resource = $image->resource(); - - //http://stackoverflow.com/questions/5702953/imagecolorat-and-transparency - $colorIndex = imagecolorat($resource, 1, 1); - $colorInfo = imagecolorsforindex($resource, $colorIndex); - $this->assertSame( - $expectedResult, - $colorInfo['alpha'] === 127, - 'Failed asserting that only previews for text files are transparent.' - ); - } - - /** - * Tests if unsupported previews return an empty object - */ - public function testUnsupportedPreviewsReturnEmptyObject() { - $width = 400; - $height = 200; - - // Previews for odt files are not enabled - $imgData = file_get_contents(\OC::$SERVERROOT . '/tests/data/testimage.odt'); - $imgPath = '/' . self::TEST_PREVIEW_USER1 . '/files/testimage.odt'; - $this->rootView->file_put_contents($imgPath, $imgData); - - $preview = - new Preview(self::TEST_PREVIEW_USER1, 'files/', 'testimage.odt', $width, $height); - $preview->getPreview(); - $image = $preview->getPreview(); - - $this->assertSame(false, $image->valid()); - } - - /** - * We generate the data to use as it makes it easier to adjust in case we need to test - * something different - * - * @return array - */ - public static function dimensionsDataProvider() { - $data = []; - $samples = [ - [200, 800], - [200, 800], - [50, 400], - [4, 60], - ]; - $keepAspect = false; - $scalingUp = false; - - for ($a = 0; $a < sizeof($samples); $a++) { - for ($b = 0; $b < 2; $b++) { - for ($c = 0; $c < 2; $c++) { - for ($d = 0; $d < 4; $d++) { - $coordinates = [ - [ - -rand($samples[$a][0], $samples[$a][1]), - -rand($samples[$a][0], $samples[$a][1]) - ], - [ - rand($samples[$a][0], $samples[$a][1]), - rand($samples[$a][0], $samples[$a][1]) - ], - [ - -rand($samples[$a][0], $samples[$a][1]), - rand($samples[$a][0], $samples[$a][1]) - ], - [ - rand($samples[$a][0], $samples[$a][1]), - -rand($samples[$a][0], $samples[$a][1]) - ] - ]; - $row = [$a]; - $row[] = $coordinates[$d][0]; - $row[] = $coordinates[$d][1]; - $row[] = $keepAspect; - $row[] = $scalingUp; - $data[] = $row; - } - $scalingUp = !$scalingUp; - } - $keepAspect = !$keepAspect; - } - } - - return $data; - } - - /** - * Tests if a preview of max dimensions gets created - * - * @requires extension imagick - * @dataProvider dimensionsDataProvider - * - * @param int $sampleId - * @param int $widthAdjustment - * @param int $heightAdjustment - * @param bool $keepAspect - * @param bool $scalingUp - */ - public function testCreateMaxAndNormalPreviewsAtFirstRequest( - $sampleId, $widthAdjustment, $heightAdjustment, $keepAspect = false, $scalingUp = false - ) { - // Get the right sample for the experiment - $this->getSample($sampleId); - $sampleWidth = $this->sampleWidth; - $sampleHeight = $this->sampleHeight; - $sampleFileId = $this->sampleFileId; - - // Adjust the requested size so that we trigger various test cases - $previewWidth = $sampleWidth + $widthAdjustment; - $previewHeight = $sampleHeight + $heightAdjustment; - $this->keepAspect = $keepAspect; - $this->scalingUp = $scalingUp; - - // Generates the max preview - $preview = $this->createPreview($previewWidth, $previewHeight); - - // There should be no cached thumbnails - $thumbnailFolder = '/' . self::TEST_PREVIEW_USER1 . '/' . Preview::THUMBNAILS_FOLDER . - '/' . $sampleFileId; - $this->assertSame(false, $this->rootView->is_dir($thumbnailFolder)); - - $image = $preview->getPreview(); - $this->assertNotSame(false, $image); - - $maxThumbCacheFile = $this->buildCachePath( - $sampleFileId, $this->maxPreviewWidth, $this->maxPreviewHeight, true, '-max' - ); - - $this->assertSame( - true, $this->rootView->file_exists($maxThumbCacheFile), "$maxThumbCacheFile \n" - ); - - // We check the dimensions of the file we've just stored - $maxPreview = imagecreatefromstring($this->rootView->file_get_contents($maxThumbCacheFile)); - - $this->assertEquals($this->maxPreviewWidth, imagesx($maxPreview)); - $this->assertEquals($this->maxPreviewHeight, imagesy($maxPreview)); - - // A thumbnail of the asked dimensions should also have been created (within the constraints of the max preview) - list($limitedPreviewWidth, $limitedPreviewHeight) = - $this->simulatePreviewDimensions($previewWidth, $previewHeight); - - $actualWidth = $image->width(); - $actualHeight = $image->height(); - - $this->assertEquals( - (int)$limitedPreviewWidth, $image->width(), "$actualWidth x $actualHeight \n" - ); - $this->assertEquals((int)$limitedPreviewHeight, $image->height()); - - // And it should be cached - $this->checkCache($sampleFileId, $limitedPreviewWidth, $limitedPreviewHeight); - - $preview->deleteAllPreviews(); - } - - /** - * Tests if the second preview will be based off the cached max preview - * - * @requires extension imagick - * @dataProvider dimensionsDataProvider - * - * @param int $sampleId - * @param int $widthAdjustment - * @param int $heightAdjustment - * @param bool $keepAspect - * @param bool $scalingUp - */ - public function testSecondPreviewsGetCachedMax( - $sampleId, $widthAdjustment, $heightAdjustment, $keepAspect = false, $scalingUp = false - ) { - //$this->markTestSkipped('Not testing this at this time'); - - $this->getSample($sampleId); - $sampleWidth = $this->sampleWidth; - $sampleHeight = $this->sampleHeight; - $sampleFileId = $this->sampleFileId; - - //Creates the Max preview which will be used in the rest of the test - $this->createMaxPreview(); - - // Adjust the requested size so that we trigger various test cases - $previewWidth = $sampleWidth + $widthAdjustment; - $previewHeight = $sampleHeight + $heightAdjustment; - $this->keepAspect = $keepAspect; - $this->scalingUp = $scalingUp; - - $preview = $this->createPreview($previewWidth, $previewHeight); - - // A cache query should return the thumbnail of max dimension - $isCached = $preview->isCached($sampleFileId); - $cachedMaxPreview = $this->buildCachePath( - $sampleFileId, $this->maxPreviewWidth, $this->maxPreviewHeight, false, '-max' - ); - $this->assertSame($cachedMaxPreview, $isCached); - } - - /** - * Make sure that the max preview can never be deleted - * - * For this test to work, the preview we generate first has to be the size of max preview - */ - public function testMaxPreviewCannotBeDeleted() { - //$this->markTestSkipped('Not testing this at this time'); - - $this->keepAspect = true; - $this->getSample(0); - $fileId = $this->sampleFileId; - - //Creates the Max preview which we will try to delete - $preview = $this->createMaxPreview(); - - // We try to deleted the preview - $preview->deletePreview(); - $this->assertNotSame(false, $preview->isCached($fileId)); - - $preview->deleteAllPreviews(); - } - - public static function aspectDataProvider() { - $data = []; - $samples = 4; - $keepAspect = false; - $scalingUp = false; - for ($a = 0; $a < $samples; $a++) { - for ($b = 0; $b < 2; $b++) { - for ($c = 0; $c < 2; $c++) { - $row = [$a]; - $row[] = $keepAspect; - $row[] = $scalingUp; - $data[] = $row; - $scalingUp = !$scalingUp; - } - $keepAspect = !$keepAspect; - } - } - - return $data; - } - - /** - * We ask for a preview larger than what is set in the configuration, - * so we should be getting either the max preview or a preview the size - * of the dimensions set in the config - * - * @requires extension imagick - * @dataProvider aspectDataProvider - * - * @param int $sampleId - * @param bool $keepAspect - * @param bool $scalingUp - */ - public function testDoNotCreatePreviewsLargerThanConfigMax( - $sampleId, $keepAspect = false, $scalingUp = false - ) { - //$this->markTestSkipped('Not testing this at this time'); - - $this->getSample($sampleId); - - //Creates the Max preview which will be used in the rest of the test - $this->createMaxPreview(); - - // Now we will create the real preview - $previewWidth = 4000; - $previewHeight = 4000; - $this->keepAspect = $keepAspect; - $this->scalingUp = $scalingUp; - - // Tries to create the very large preview - $preview = $this->createPreview($previewWidth, $previewHeight); - - $image = $preview->getPreview(); - $this->assertNotSame(false, $image); - - list($expectedWidth, $expectedHeight) = - $this->simulatePreviewDimensions($previewWidth, $previewHeight); - $this->assertEquals($expectedWidth, $image->width()); - $this->assertEquals($expectedHeight, $image->height()); - - // A preview of the asked size should not have been created since it's larger that our max dimensions - $postfix = $this->getThumbnailPostfix($previewWidth, $previewHeight); - $thumbCacheFile = $this->buildCachePath( - $this->sampleFileId, $previewWidth, $previewHeight, false, $postfix - ); - $this->assertSame( - false, $this->rootView->file_exists($thumbCacheFile), "$thumbCacheFile \n" - ); - - $preview->deleteAllPreviews(); - } - - /** - * Makes sure we're getting the proper cached thumbnail - * - * When we start by generating a preview which keeps the aspect ratio - * 200-125-with-aspect - * 300-300 ✓ - * - * When we start by generating a preview of exact dimensions - * 200-200 ✓ - * 300-188-with-aspect - * - * @requires extension imagick - * @dataProvider aspectDataProvider - * - * @param int $sampleId - * @param bool $keepAspect - * @param bool $scalingUp - */ - public function testIsBiggerWithAspectRatioCached( - $sampleId, $keepAspect = false, $scalingUp = false - ) { - //$this->markTestSkipped('Not testing this at this time'); - - $previewWidth = 400; - $previewHeight = 400; - $this->getSample($sampleId); - $fileId = $this->sampleFileId; - $this->keepAspect = $keepAspect; - $this->scalingUp = $scalingUp; - - // Caching the max preview in our preview array for the test - $this->cachedBigger[] = $this->buildCachePath( - $fileId, $this->maxPreviewWidth, $this->maxPreviewHeight, false, '-max' - ); - - $this->getSmallerThanMaxPreview($fileId, $previewWidth, $previewHeight); - // We switch the aspect ratio, to generate a thumbnail we should not be picked up - $this->keepAspect = !$keepAspect; - $this->getSmallerThanMaxPreview($fileId, $previewWidth + 100, $previewHeight + 100); - - // Small thumbnails are always cropped - $this->keepAspect = false; - // Smaller previews should be based on the previous, larger preview, with the correct aspect ratio - $this->createThumbnailFromBiggerCachedPreview($fileId, 32, 32); - - // 2nd cache query should indicate that we have a cached copy of the exact dimension - $this->getCachedSmallThumbnail($fileId, 32, 32); - - // We create a preview in order to be able to delete the cache - $preview = $this->createPreview(rand(), rand()); - $preview->deleteAllPreviews(); - $this->cachedBigger = []; - } - - /** - * Initialises the preview - * - * @param int $width - * @param int $height - * - * @return Preview - */ - private function createPreview($width, $height) { - $preview = new Preview( - self::TEST_PREVIEW_USER1, 'files/', $this->sampleFilename, $width, - $height - ); - - $this->assertSame(true, $preview->isFileValid()); - - $preview->setKeepAspect($this->keepAspect); - $preview->setScalingup($this->scalingUp); - - return $preview; - } - - /** - * Creates the Max preview which will be used in the rest of the test - * - * @return Preview - */ - private function createMaxPreview() { - $this->keepAspect = true; - $preview = $this->createPreview($this->maxPreviewWidth, $this->maxPreviewHeight); - $preview->getPreview(); - - return $preview; - } - - /** - * Makes sure the preview which was just created has been saved to disk - * - * @param int $fileId - * @param int $previewWidth - * @param int $previewHeight - */ - private function checkCache($fileId, $previewWidth, $previewHeight) { - $postfix = $this->getThumbnailPostfix($previewWidth, $previewHeight); - - $thumbCacheFile = $this->buildCachePath( - $fileId, $previewWidth, $previewHeight, true, $postfix - ); - - $this->assertSame( - true, $this->rootView->file_exists($thumbCacheFile), "$thumbCacheFile \n" - ); - } - - /** - * Computes special filename postfixes - * - * @param int $width - * @param int $height - * - * @return string - */ - private function getThumbnailPostfix($width, $height) { - // Need to take care of special postfix added to the dimensions - $postfix = ''; - $isMaxPreview = ($width === $this->maxPreviewWidth - && $height === $this->maxPreviewHeight) ? true : false; - if ($isMaxPreview) { - $postfix = '-max'; - } - if ($this->keepAspect && !$isMaxPreview) { - $postfix = '-with-aspect'; - } - - return $postfix; - } - - private function getSmallerThanMaxPreview($fileId, $previewWidth, $previewHeight) { - $preview = $this->createPreview($previewWidth, $previewHeight); - - $image = $preview->getPreview(); - $this->assertNotSame(false, $image); - - // A thumbnail of the asked dimensions should also have been created (within the constraints of the max preview) - list($limitedPreviewWidth, $limitedPreviewHeight) = - $this->simulatePreviewDimensions($previewWidth, $previewHeight); - - $this->assertEquals($limitedPreviewWidth, $image->width()); - $this->assertEquals($limitedPreviewHeight, $image->height()); - - // And it should be cached - $this->checkCache($fileId, $limitedPreviewWidth, $limitedPreviewHeight); - - $this->cachedBigger[] = $preview->isCached($fileId); - } - - private function createThumbnailFromBiggerCachedPreview($fileId, $width, $height) { - $preview = $this->createPreview($width, $height); - - // A cache query should return a thumbnail of slightly larger dimensions - // and with the proper aspect ratio - $isCached = $preview->isCached($fileId); - $expectedCachedBigger = $this->getExpectedCachedBigger(); - - $this->assertSame($expectedCachedBigger, $isCached); - - $image = $preview->getPreview(); - $this->assertNotSame(false, $image); - } - - /** - * Picks the bigger cached preview with the correct aspect ratio or the max preview if it's - * smaller than that - * - * For non-upscaled images, we pick the only picture without aspect ratio - * - * @return string - */ - private function getExpectedCachedBigger() { - $foundPreview = null; - $foundWidth = null; - $foundHeight = null; - $maxPreview = null; - $maxWidth = null; - $maxHeight = null; - - foreach ($this->cachedBigger as $cached) { - $size = explode('-', basename($cached)); - $width = (int)$size[0]; - $height = (int)$size[1]; - - if (strpos($cached, 'max')) { - $maxWidth = $width; - $maxHeight = $height; - $maxPreview = $cached; - continue; - } - - // We pick the larger preview with no aspect ratio - if (!strpos($cached, 'aspect') && !strpos($cached, 'max')) { - $foundPreview = $cached; - $foundWidth = $width; - $foundHeight = $height; - } - } - if ($foundWidth > $maxWidth && $foundHeight > $maxHeight) { - $foundPreview = $maxPreview; - } - - return $foundPreview; - } - - /** - * A small thumbnail of exact dimensions should be in the cache - * - * @param int $fileId - * @param int $width - * @param int $height - */ - private function getCachedSmallThumbnail($fileId, $width, $height) { - $preview = $this->createPreview($width, $height); - - $isCached = $preview->isCached($fileId); - $thumbCacheFile = $this->buildCachePath($fileId, $width, $height); - - $this->assertSame($thumbCacheFile, $isCached, "$thumbCacheFile \n"); - } - - /** - * Builds the complete path to a cached thumbnail starting from the user folder - * - * @param int $fileId - * @param int $width - * @param int $height - * @param bool $user - * @param string $postfix - * - * @return string - */ - private function buildCachePath($fileId, $width, $height, $user = false, $postfix = '') { - $userPath = ''; - if ($user) { - $userPath = '/' . self::TEST_PREVIEW_USER1 . '/'; - } - - return $userPath . Preview::THUMBNAILS_FOLDER . '/' . $fileId - . '/' . $width . '-' . $height . $postfix . '.png'; - } - - /** - * Stores the sample in the filesystem and stores it in the $samples array - * - * @param string $fileName - * @param int $sampleWidth - * @param int $sampleHeight - */ - private function prepareSample($fileName, $sampleWidth, $sampleHeight) { - $imgData = file_get_contents(\OC::$SERVERROOT . '/tests/data/' . $fileName); - $imgPath = '/' . self::TEST_PREVIEW_USER1 . '/files/' . $fileName; - $this->rootView->file_put_contents($imgPath, $imgData); - $fileInfo = $this->rootView->getFileInfo($imgPath); - - list($maxPreviewWidth, $maxPreviewHeight) = - $this->setMaxPreview($sampleWidth, $sampleHeight); - - $this->samples[] = - [ - 'sampleFileId' => $fileInfo['fileid'], - 'sampleFileName' => $fileName, - 'sampleWidth' => $sampleWidth, - 'sampleHeight' => $sampleHeight, - 'maxPreviewWidth' => $maxPreviewWidth, - 'maxPreviewHeight' => $maxPreviewHeight - ]; - } - - /** - * Sets the variables used to define the boundaries which need to be respected when using a - * specific sample - * - * @param $sampleId - */ - private function getSample($sampleId) { - // Corrects a rounding difference when using the EPS (Imagick converted) sample - $filename = $this->samples[$sampleId]['sampleFileName']; - $splitFileName = pathinfo($filename); - $extension = $splitFileName['extension']; - $correction = ($extension === 'eps' && PHP_MAJOR_VERSION < 7) ? 1 : 0; - $maxPreviewHeight = $this->samples[$sampleId]['maxPreviewHeight']; - $maxPreviewHeight = $maxPreviewHeight - $correction; - - $this->sampleFileId = $this->samples[$sampleId]['sampleFileId']; - $this->sampleFilename = $this->samples[$sampleId]['sampleFileName']; - $this->sampleWidth = $this->samples[$sampleId]['sampleWidth']; - $this->sampleHeight = $this->samples[$sampleId]['sampleHeight']; - $this->maxPreviewWidth = $this->samples[$sampleId]['maxPreviewWidth']; - $this->maxPreviewHeight = $maxPreviewHeight; - $ratio = $this->maxPreviewWidth / $this->maxPreviewHeight; - $this->maxPreviewRatio = $ratio; - } - - /** - * Defines the size of the max preview - * - * @fixme the Imagick previews don't have the exact same size on disk as they're calculated here - * - * @param int $sampleWidth - * @param int $sampleHeight - * - * @return array - */ - private function setMaxPreview($sampleWidth, $sampleHeight) { - // Max previews are never scaled up - $this->scalingUp = false; - // Max previews always keep the aspect ratio - $this->keepAspect = true; - // We set this variable in order to be able to calculate the max preview with the proper aspect ratio - $this->maxPreviewRatio = $sampleWidth / $sampleHeight; - $maxPreviewWidth = min($sampleWidth, $this->configMaxWidth); - $maxPreviewHeight = min($sampleHeight, $this->configMaxHeight); - list($maxPreviewWidth, $maxPreviewHeight) = - $this->applyAspectRatio($maxPreviewWidth, $maxPreviewHeight); - - return [$maxPreviewWidth, $maxPreviewHeight]; - } - - /** - * Calculates the expected dimensions of the preview to be able to assess if we've got the - * right result - * - * @param int $askedWidth - * @param int $askedHeight - * - * @return array - */ - private function simulatePreviewDimensions($askedWidth, $askedHeight) { - $askedWidth = min($askedWidth, $this->configMaxWidth); - $askedHeight = min($askedHeight, $this->configMaxHeight); - - if ($this->keepAspect) { - // Defines the box in which the preview has to fit - $scaleFactor = $this->scalingUp ? $this->maxScaleFactor : 1; - $newPreviewWidth = min($askedWidth, $this->maxPreviewWidth * $scaleFactor); - $newPreviewHeight = min($askedHeight, $this->maxPreviewHeight * $scaleFactor); - list($newPreviewWidth, $newPreviewHeight) = - $this->applyAspectRatio($newPreviewWidth, $newPreviewHeight); - } else { - list($newPreviewWidth, $newPreviewHeight) = - $this->fixSize($askedWidth, $askedHeight); - } - - return [(int)$newPreviewWidth, (int)$newPreviewHeight]; - } - - /** - * Resizes the boundaries to match the aspect ratio - * - * @param int $askedWidth - * @param int $askedHeight - * - * @return \int[] - */ - private function applyAspectRatio($askedWidth, $askedHeight) { - $originalRatio = $this->maxPreviewRatio; - if ($askedWidth / $originalRatio < $askedHeight) { - $askedHeight = round($askedWidth / $originalRatio); - } else { - $askedWidth = round($askedHeight * $originalRatio); - } - - return [(int)$askedWidth, (int)$askedHeight]; - } - - /** - * Clips or stretches the dimensions so that they fit in the boundaries - * - * @param int $askedWidth - * @param int $askedHeight - * - * @return array - */ - private function fixSize($askedWidth, $askedHeight) { - if ($this->scalingUp) { - $askedWidth = min($this->configMaxWidth, $askedWidth); - $askedHeight = min($this->configMaxHeight, $askedHeight); - } - - return [(int)$askedWidth, (int)$askedHeight]; - } - - public function testKeepAspectRatio() { - $originalWidth = 1680; - $originalHeight = 1050; - $originalAspectRation = $originalWidth / $originalHeight; - - $preview = new Preview( - self::TEST_PREVIEW_USER1, 'files/', 'testimage.jpg', - 150, - 150 - ); - $preview->setKeepAspect(true); - $image = $preview->getPreview(); - - $aspectRatio = $image->width() / $image->height(); - $this->assertEquals(round($originalAspectRation, 2), round($aspectRatio, 2)); - - $this->assertLessThanOrEqual(150, $image->width()); - $this->assertLessThanOrEqual(150, $image->height()); - } - - public function testKeepAspectRatioCover() { - $originalWidth = 1680; - $originalHeight = 1050; - $originalAspectRation = $originalWidth / $originalHeight; - - $preview = new Preview( - self::TEST_PREVIEW_USER1, 'files/', 'testimage.jpg', - 150, - 150 - ); - $preview->setKeepAspect(true); - $preview->setMode(Preview::MODE_COVER); - $image = $preview->getPreview(); - - $aspectRatio = $image->width() / $image->height(); - $this->assertEquals(round($originalAspectRation, 2), round($aspectRatio, 2)); - - $this->assertGreaterThanOrEqual(150, $image->width()); - $this->assertGreaterThanOrEqual(150, $image->height()); - } - - public function testSetFileWithInfo() { - $info = new FileInfo('/foo', null, '/foo', ['mimetype' => 'foo/bar'], null); - $preview = new Preview(); - $preview->setFile('/foo', $info); - $this->assertEquals($info, $this->invokePrivate($preview, 'getFileInfo')); - } - - public function testIsCached() { - $sourceFile = __DIR__ . '/../data/testimage.png'; - $userId = $this->getUniqueID(); - $this->createUser($userId, 'pass'); - - $storage = new Temporary(); - $storage->mkdir('files'); - $this->registerMount($userId, $storage, '/' . $userId); - - \OC_Util::tearDownFS(); - \OC_Util::setupFS($userId); - $preview = new Preview($userId, 'files'); - $view = new View('/' . $userId . '/files'); - $view->file_put_contents('test.png', file_get_contents($sourceFile)); - $info = $view->getFileInfo('test.png'); - $preview->setFile('test.png', $info); - - $preview->setMaxX(64); - $preview->setMaxY(64); - - $this->assertFalse($preview->isCached($info->getId())); - - $preview->getPreview(); - - $this->assertEquals('thumbnails/' . $info->getId() . '/64-64.png', $preview->isCached($info->getId())); - } -} -- 2.39.5