summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRoeland Jago Douma <roeland@famdouma.nl>2016-08-04 19:41:04 +0200
committerRoeland Jago Douma <roeland@famdouma.nl>2016-11-03 14:00:32 +0100
commit958c1289b15f5bd877dd7ff3b7cf3540a0c5198a (patch)
tree94dea79e9d3fb96aa8528743960de15271d4c8b4
parentb129adfb58eb98a37278dbd5a2f30b52c90cb4fc (diff)
downloadnextcloud-server-958c1289b15f5bd877dd7ff3b7cf3540a0c5198a.tar.gz
nextcloud-server-958c1289b15f5bd877dd7ff3b7cf3540a0c5198a.zip
New preview generator
Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
-rw-r--r--core/ajax/preview.php28
-rw-r--r--core/ajax/preview2.php36
-rw-r--r--core/routes.php2
-rw-r--r--lib/private/Preview.php2
-rw-r--r--lib/private/Preview2.php366
5 files changed, 423 insertions, 11 deletions
diff --git a/core/ajax/preview.php b/core/ajax/preview.php
index 6cfba6aef30..aac623b5ce6 100644
--- a/core/ajax/preview.php
+++ b/core/ajax/preview.php
@@ -49,19 +49,27 @@ if ($maxX === 0 || $maxY === 0) {
exit;
}
-$info = \OC\Files\Filesystem::getFileInfo($file);
+$folder = \OC::$server->getUserFolder();
-if (!$info instanceof OCP\Files\FileInfo || !$always && !\OC::$server->getPreviewManager()->isAvailable($info)) {
+try {
+ $file = $folder->get($file);
+} catch (\OCP\Files\NotFoundException $e) {
+ return \OC_Response::setStatus(404);
+}
+
+if (!$file instanceof OCP\Files\File || !$always && !\OC::$server->getPreviewManager()->isAvailable($file)) {
\OC_Response::setStatus(404);
} else if (!$info->isReadable()) {
\OC_Response::setStatus(403);
} else {
- $preview = new \OC\Preview(\OC_User::getUser(), 'files');
- $preview->setFile($file, $info);
- $preview->setMaxX($maxX);
- $preview->setMaxY($maxY);
- $preview->setScalingUp($scalingUp);
- $preview->setMode($mode);
- $preview->setKeepAspect($keepAspect);
- $preview->showPreview();
+ $preview = new \OC\Preview2(
+ \OC::$server->getRootFolder(),
+ \OC::$server->getConfig(),
+ \OC::$server->getPreviewManager(),
+ $file
+ );
+ $image = $preview->getPreview($maxX, $maxY, !$keepAspect, $mode);
+
+ header('Content-Type: ' . $image->getMimeType());
+ echo $image->getContent();
}
diff --git a/core/ajax/preview2.php b/core/ajax/preview2.php
new file mode 100644
index 00000000000..b2bbf64c6d2
--- /dev/null
+++ b/core/ajax/preview2.php
@@ -0,0 +1,36 @@
+<?php
+
+\OC_Util::checkLoggedIn();
+\OC::$server->getSession()->close();
+
+$file = array_key_exists('file', $_GET) ? (string)$_GET['file'] : '';
+$maxX = array_key_exists('x', $_GET) ? (int)$_GET['x'] : '32';
+$maxY = array_key_exists('y', $_GET) ? (int)$_GET['y'] : '32';
+$keepAspect = array_key_exists('a', $_GET) ? true : false;
+$always = array_key_exists('forceIcon', $_GET) ? (bool)$_GET['forceIcon'] : true;
+$mode = array_key_exists('mode', $_GET) ? $_GET['mode'] : 'fill';
+
+if ($file === '') {
+ //400 Bad Request
+ \OC_Response::setStatus(400);
+ \OCP\Util::writeLog('core-preview', 'No file parameter was passed', \OCP\Util::DEBUG);
+ exit;
+}
+
+if ($maxX === 0 || $maxY === 0) {
+ //400 Bad Request
+ \OC_Response::setStatus(400);
+ \OCP\Util::writeLog('core-preview', 'x and/or y set to 0', \OCP\Util::DEBUG);
+ exit;
+}
+
+$userFolder = \OC::$server->getUserFolder();
+$file = $userFolder->get($file);
+
+$p = new \OC\Preview2(\OC::$server->getRootFolder(),
+ \OC::$server->getConfig(),
+ \OC::$server->getPreviewManager(),
+ $file);
+
+$p->getPreview($maxX, $maxY, !$keepAspect, $mode);
+
diff --git a/core/routes.php b/core/routes.php
index 7978001af7d..4b73a86042a 100644
--- a/core/routes.php
+++ b/core/routes.php
@@ -70,6 +70,8 @@ $this->create('search_ajax_search', '/core/search')
// Routing
$this->create('core_ajax_preview', '/core/preview')
->actionInclude('core/ajax/preview.php');
+$this->create('core_ajax_preview2', '/core/preview2')
+ ->actionInclude('core/ajax/preview2.php');
$this->create('core_ajax_preview', '/core/preview.png')
->actionInclude('core/ajax/preview.php');
$this->create('core_ajax_update', '/core/ajax/update.php')
diff --git a/lib/private/Preview.php b/lib/private/Preview.php
index ccaec738caf..caa1e89bacc 100644
--- a/lib/private/Preview.php
+++ b/lib/private/Preview.php
@@ -125,7 +125,7 @@ class Preview {
$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', 2);
+ $this->maxScaleFactor = $sysConfig->getSystemValue('preview_max_scale_factor', 1);
//save parameters
$this->setFile($file);
diff --git a/lib/private/Preview2.php b/lib/private/Preview2.php
new file mode 100644
index 00000000000..11e699a2e72
--- /dev/null
+++ b/lib/private/Preview2.php
@@ -0,0 +1,366 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016, Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OC;
+
+use OC\Files\View;
+use OCP\Files\File;
+use OCP\Files\Folder;
+use OCP\Files\IRootFolder;
+use OCP\Files\NotFoundException;
+use OCP\IConfig;
+use OCP\IImage;
+use OCP\Image;
+use OCP\IPreview;
+use OCP\Preview\IProvider;
+
+class Preview2 {
+ //the thumbnail folder
+ const THUMBNAILS_FOLDER = 'thumbnails';
+
+ const MODE_FILL = 'fill';
+ const MODE_COVER = 'cover';
+
+ /** @var IRootFolder*/
+ private $rootFolder;
+ /** @var File */
+ private $file;
+ /** @var IPreview */
+ private $previewManager;
+ /** @var IConfig */
+ private $config;
+
+ public function __construct(
+ IRootFolder $rootFolder,
+ IConfig $config,
+ IPreview $previewManager,
+ File $file
+ ) {
+ $this->rootFolder = $rootFolder;
+ $this->config = $config;
+ $this->file = $file;
+ $this->previewManager = $previewManager;
+ }
+
+ /**
+ * 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
+ *
+ * @param int $width
+ * @param int $height
+ * @param bool $crop
+ * @param string $mode
+ * @return File
+ * @throws NotFoundException
+ */
+ public function getPreview($width = -1, $height = -1, $crop = false, $mode = Preview2::MODE_FILL) {
+ if (!$this->previewManager->isMimeSupported($this->file->getMimeType())) {
+ throw new NotFoundException();
+ }
+
+ /*
+ * Get the preview folder
+ * TODO: Separate preview creation from storing previews
+ */
+ $previewFolder = $this->getPreviewFolder();
+
+ // Get the max preview and infer the max preview sizes from that
+ $maxPreview = $this->getMaxPreview($previewFolder);
+ list($maxWidth, $maxHeight) = $this->getPreviewSize($maxPreview);
+
+ // Calculate the preview size
+ list($width, $height) = $this->calculateSize($width, $height, $crop, $mode, $maxWidth, $maxHeight);
+
+ // Try to get a cached preview. Else generate (and store) one
+ try {
+ $file = $this->getCachedPreview($previewFolder, $width, $height, $crop);
+ } catch (NotFoundException $e) {
+ $file = $this->generatePreview($previewFolder, $maxPreview, $width, $height, $crop, $maxWidth, $maxHeight);
+ }
+
+ return $file;
+ }
+
+ /**
+ * @param Folder $previewFolder
+ * @return File
+ * @throws NotFoundException
+ */
+ private function getMaxPreview(Folder $previewFolder) {
+ $nodes = $previewFolder->getDirectoryListing();
+
+ /** @var File $node */
+ foreach ($nodes as $node) {
+ if (strpos($node->getName(), 'max')) {
+ return $node;
+ }
+ }
+
+ $previewProviders = $this->previewManager->getProviders();
+ foreach ($previewProviders as $supportedMimeType => $providers) {
+ if (!preg_match($supportedMimeType, $this->file->getMimeType())) {
+ continue;
+ }
+
+ foreach ($providers as $provider) {
+ $provider = $provider();
+ if (!($provider instanceof IProvider)) {
+ continue;
+ }
+
+ list($view, $path) = $this->getViewAndPath($this->file);
+
+ $maxWidth = (int)$this->config->getSystemValue('preview_max_x', 2048);
+ $maxHeight = (int)$this->config->getSystemValue('preview_max_y', 2048);
+
+ $preview = $provider->getThumbnail($path, $maxWidth, $maxHeight, false, $view);
+
+ if (!($preview instanceof IImage)) {
+ continue;
+ }
+
+ $path = strval($preview->width()) . '-' . strval($preview->height()) . '-max.png';
+ $file = $previewFolder->newFile($path);
+ $file->putContent($preview->data());
+
+ return $file;
+ }
+ }
+
+ throw new NotFoundException();
+ }
+
+ /**
+ * @param File $file
+ * @return int[]
+ */
+ private function getPreviewSize(File $file) {
+ $size = explode('-', $file->getName());
+ return [(int)$size[0], (int)$size[1]];
+ }
+
+ /**
+ * @param int $width
+ * @param int $height
+ * @param bool $crop
+ * @return string
+ */
+ private function generatePath($width, $height, $crop) {
+ $path = strval($width) . '-' . strval($height);
+ if ($crop) {
+ $path .= '-crop';
+ }
+ $path .= '.png';
+ return $path;
+ }
+
+ /**
+ * @param File $file
+ * @return array
+ * This is required to create the old view and path
+ */
+ private function getViewAndPath(File $file) {
+ $owner = $file->getOwner()->getUID();
+
+ $userFolder = $this->rootFolder->getUserFolder($owner)->getParent();
+ $nodes = $userFolder->getById($file->getId());
+
+ $file = $nodes[0];
+
+ $view = new View($userFolder->getPath());
+ $path = $userFolder->getRelativePath($file->getPath());
+
+ return [$view, $path];
+ }
+
+ /**
+ * @param int $width
+ * @param int $height
+ * @param bool $crop
+ * @param string $mode
+ * @param int $maxWidth
+ * @param int $maxHeight
+ * @return int[]
+ */
+ private function calculateSize($width, $height, $crop, $mode, $maxWidth, $maxHeight) {
+
+ /*
+ * If we are not cropping we have to make sure the requested image
+ * respects the aspect ratio of the original.
+ */
+ if (!$crop) {
+ $ratio = $maxHeight / $maxWidth;
+
+ if ($width === -1) {
+ $width = $height / $ratio;
+ }
+ if ($height === -1) {
+ $height = $width * $ratio;
+ }
+
+ $ratioH = $height / $maxHeight;
+ $ratioW = $width / $maxWidth;
+
+ /*
+ * Fill means that the $height and $width are the max
+ * Cover means min.
+ */
+ if ($mode === self::MODE_FILL) {
+ if ($ratioH > $ratioW) {
+ $height = $width * $ratio;
+ } else {
+ $width = $height / $ratio;
+ }
+ } else if ($mode === self::MODE_COVER) {
+ if ($ratioH > $ratioW) {
+ $width = $height / $ratio;
+ } else {
+ $height = $width * $ratio;
+ }
+ }
+ }
+
+ if ($height !== $maxHeight && $width !== $maxWidth) {
+ /*
+ * Scale to the nearest power of two
+ */
+ $pow2height = pow(2, ceil(log($height) / log(2)));
+ $pow2width = pow(2, ceil(log($width) / log(2)));
+
+ $ratioH = $height / $pow2height;
+ $ratioW = $width / $pow2width;
+
+ if ($ratioH < $ratioW) {
+ $width = $pow2width;
+ $height = $height / $ratioW;
+ } else {
+ $height = $pow2height;
+ $width = $width / $ratioH;
+ }
+ }
+
+ /*
+ * Make sure the requested height and width fall within the max
+ * of the preview.
+ */
+ if ($height > $maxHeight) {
+ $ratio = $height / $maxHeight;
+ $height = $maxHeight;
+ $width = $width / $ratio;
+ }
+ if ($width > $maxWidth) {
+ $ratio = $width / $maxWidth;
+ $width = $maxWidth;
+ $height = $height / $ratio;
+ }
+
+ return [(int)round($width), (int)round($height)];
+ }
+
+ /**
+ * @param Folder $previewFolder
+ * @param File $maxPreview
+ * @param int $width
+ * @param int $height
+ * @param bool $crop
+ * @param int $maxWidth,
+ * @param int $maxHeight
+ * @return File
+ * @throws NotFoundException
+ */
+ private function generatePreview(Folder $previewFolder, File $maxPreview, $width, $height, $crop, $maxWidth, $maxHeight) {
+ $preview = new Image($maxPreview->getContent());
+
+ if ($crop) {
+ if ($height !== $preview->height() && $width !== $preview->width()) {
+ //Resize
+ $widthR = $preview->width() / $width;
+ $heightR = $preview->height() / $height;
+
+ if ($widthR > $heightR) {
+ $scaleH = $height;
+ $scaleW = $maxWidth / $heightR;
+ } else {
+ $scaleH = $maxHeight / $widthR;
+ $scaleW = $width;
+ }
+ $preview->preciseResize(round($scaleW), round($scaleH));
+ }
+ $cropX = floor(abs($width - $preview->width()) * 0.5);
+ $cropY = 0;
+ $preview->crop($cropX, $cropY, $width, $height);
+ } else {
+ $preview->resize(max($width, $height));
+ }
+
+ $path = $this->generatePath($width, $height, $crop);
+ $file = $previewFolder->newFile($path);
+ $file->putContent($preview->data());
+
+ return $file;
+ }
+
+ /**
+ * @param Folder $previewFolder
+ * @param int $width
+ * @param int $height
+ * @param bool $crop
+ * @return File
+ *
+ * @throws NotFoundException
+ */
+ private function getCachedPreview(Folder $previewFolder, $width, $height, $crop) {
+ $path = $this->generatePath($width, $height, $crop);
+
+ return $previewFolder->get($path);
+ }
+
+ /**
+ * Get the specific preview folder for this file
+ *
+ * @return Folder
+ */
+ private function getPreviewFolder() {
+ $user = $this->file->getOwner();
+ $user = $user->getUID();
+
+ $previewRoot = $this->rootFolder->getUserFolder($user);
+ $previewRoot = $previewRoot->getParent();
+
+ try {
+ /** @var Folder $previewRoot */
+ $previewRoot = $previewRoot->get(self::THUMBNAILS_FOLDER);
+ } catch (NotFoundException $e) {
+ $previewRoot = $previewRoot->newFolder(self::THUMBNAILS_FOLDER);
+ }
+
+ try {
+ $previewFolder = $previewRoot->get($this->file->getId());
+ } catch (NotFoundException $e) {
+ $previewFolder = $previewRoot->newFolder($this->file->getId());
+ }
+
+ return $previewFolder;
+ }
+}