Improved thumbnail generating

- Moved thumbs folder to public_html/
- Users can supply custom thumbs of any size and the system will treat
  them like normal image
- Removed distinction between various thumb sizes in file system
- Introduced custom rewrite rule, which isn't exactly good-looking, but
  its benefits far outweigh its shortcomings
- Loading up to 75 times faster (was: 100-300ms, is: 4-10ms on my
  machine) thanks to removal of PHP proxying
This commit is contained in:
Marcin Kurczewski 2014-05-14 23:44:48 +02:00
parent 27ddf6f59f
commit c7250ae0a9
10 changed files with 53 additions and 86 deletions

View file

@ -4,7 +4,7 @@ dbLocation = "./data/db.sqlite"
dbUser = "test"
dbPass = "test"
filesPath = "./data/files/"
thumbsPath = "./data/thumbs/"
thumbsPath = "./public_html/thumbs/"
logsPath = "./data/logs/{yyyy}-{mm}.log"
mediaPath = "./public_html/media/"
title = "szurubooru"

View file

@ -6,6 +6,10 @@ ErrorDocument 403 /dispatch.php?request=error/http&code=403
ErrorDocument 404 /dispatch.php?request=error/http&code=404
ErrorDocument 500 /dispatch.php?request=error/http&code=500
RewriteCond %{DOCUMENT_ROOT}/thumbs/$1.thumb -f
RewriteRule ^/?post/(.*)/thumb/?$ /thumbs/$1.thumb
RewriteRule ^/?thumbs/(.*).thumb - [L,T=image/jpeg]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^.*$ /dispatch.php

View file

@ -24,9 +24,6 @@ class JobArgs
const ARG_LOG_ID = 'log-id';
const ARG_THUMB_WIDTH = 'thumb-width';
const ARG_THUMB_HEIGHT = 'thumb-height';
const ARG_NEW_TEXT = 'new-text';
const ARG_NEW_STATE = 'new-state';

View file

@ -19,17 +19,14 @@ class GetPostThumbJob extends AbstractJob
$name = $post->getName();
}
$width = $this->hasArgument(JobArgs::ARG_THUMB_WIDTH) ? $this->getArgument(JobArgs::ARG_THUMB_WIDTH) : null;
$height = $this->hasArgument(JobArgs::ARG_THUMB_HEIGHT) ? $this->getArgument(JobArgs::ARG_THUMB_HEIGHT) : null;
$path = PostModel::tryGetWorkingThumbPath($name, $width, $height);
$path = PostModel::tryGetWorkingThumbPath($name);
if (!$path)
{
$post = PostModel::getByName($name);
$post = $this->postRetriever->retrieve();
$post->generateThumb($width, $height);
$path = PostModel::tryGetWorkingThumbPath($name, $width, $height);
$post->generateThumb();
$path = PostModel::tryGetWorkingThumbPath($name);
if (!$path)
{
@ -43,15 +40,11 @@ class GetPostThumbJob extends AbstractJob
public function getRequiredArguments()
{
return JobArgs::Conjunction(
$this->postRetriever->getRequiredArguments(),
JobArgs::Optional(JobArgs::ARG_THUMB_WIDTH),
JobArgs::Optional(JobArgs::ARG_THUMB_HEIGHT));
return $this->postRetriever->getRequiredArguments();
}
public function getRequiredPrivileges()
{
//privilege check removed to make thumbs faster
return false;
}
}

View file

@ -267,12 +267,9 @@ class PostController
$context->layoutName = 'layout-file';
}
public function thumbView($name, $width = null, $height = null)
public function thumbView($name)
{
$ret = Api::run(new GetPostThumbJob(), [
JobArgs::ARG_POST_NAME => $name,
JobArgs::ARG_THUMB_WIDTH => $width,
JobArgs::ARG_THUMB_HEIGHT => $height]);
$ret = Api::run(new GetPostThumbJob(), [JobArgs::ARG_POST_NAME => $name]);
$context = getContext();
$context->transport->cacheDaysToLive = 365;

View file

@ -328,24 +328,24 @@ final class PostEntity extends AbstractEntity implements IValidatable
return PostModel::getFullPath($this->getName());
}
public function tryGetWorkingThumbPath($width = null, $height = null)
public function tryGetWorkingThumbPath()
{
return PostModel::tryGetWorkingThumbPath($this->getName(), $width, $height);
return PostModel::tryGetWorkingThumbPath($this->getName());
}
public function getThumbCustomPath($width = null, $height = null)
public function getThumbCustomSourcePath()
{
return PostModel::getThumbCustomPath($this->getName(), $width, $height);
return PostModel::getThumbCustomSourcePath($this->getName());
}
public function getThumbDefaultPath($width = null, $height = null)
public function getThumbPath()
{
return PostModel::getThumbDefaultPath($this->getName(), $width, $height);
return PostModel::getThumbPath($this->getName());
}
public function hasCustomThumb($width = null, $height = null)
public function hasCustomThumb()
{
$thumbPath = $this->getThumbCustomPath($width, $height);
$thumbPath = $this->getThumbCustomSourcePath();
return file_exists($thumbPath);
}
@ -357,26 +357,21 @@ final class PostEntity extends AbstractEntity implements IValidatable
if (!in_array($mimeType, ['image/gif', 'image/png', 'image/jpeg']))
throw new SimpleException('Invalid thumbnail type "%s"', $mimeType);
list ($imageWidth, $imageHeight) = getimagesize($srcPath);
if ($imageWidth != $config->browsing->thumbWidth
or $imageHeight != $config->browsing->thumbHeight)
{
throw new SimpleException(
'Invalid thumbnail size (should be %dx%d)',
$config->browsing->thumbWidth,
$config->browsing->thumbHeight);
}
$dstPath = $this->getThumbCustomPath();
$dstPath = $this->getThumbCustomSourcePath();
TransferHelper::copy($srcPath, $dstPath);
$this->generateThumb();
}
public function generateThumb($width = null, $height = null)
public function generateThumb()
{
list ($width, $height) = PostModel::validateThumbSize($width, $height);
$srcPath = $this->getFullPath();
$dstPath = $this->getThumbDefaultPath($width, $height);
$width = getConfig()->browsing->thumbWidth;
$height = getConfig()->browsing->thumbHeight;
$dstPath = $this->getThumbPath($width, $height);
if (file_exists($this->getThumbCustomSourcePath()))
return ThumbnailHelper::generateFromPath($this->getThumbCustomSourcePath(), $dstPath, $width, $height);
if ($this->getType()->toInteger() == PostType::Youtube)
{
@ -388,7 +383,7 @@ final class PostEntity extends AbstractEntity implements IValidatable
}
else
{
return ThumbnailHelper::generateFromPath($srcPath, $dstPath, $width, $height);
return ThumbnailHelper::generateFromPath($this->getFullPath(), $dstPath, $width, $height);
}
}
@ -445,7 +440,7 @@ final class PostEntity extends AbstractEntity implements IValidatable
TransferHelper::copy($srcPath, $dstPath);
$thumbPath = $this->getThumbDefaultPath();
$thumbPath = $this->getThumbPath();
if (file_exists($thumbPath))
unlink($thumbPath);
}
@ -467,7 +462,7 @@ final class PostEntity extends AbstractEntity implements IValidatable
$this->setImageWidth(null);
$this->setImageHeight(null);
$thumbPath = $this->getThumbDefaultPath();
$thumbPath = $this->getThumbPath();
if (file_exists($thumbPath))
unlink($thumbPath);

View file

@ -255,46 +255,29 @@ final class PostModel extends AbstractCrudModel
public static function validateThumbSize($width, $height)
public static function tryGetWorkingThumbPath($name)
{
$width = $width === null ? getConfig()->browsing->thumbWidth : $width;
$height = $height === null ? getConfig()->browsing->thumbHeight : $height;
$width = min(1000, max(1, $width));
$height = min(1000, max(1, $height));
return [$width, $height];
}
public static function tryGetWorkingThumbPath($name, $width = null, $height = null)
{
$path = PostModel::getThumbCustomPath($name, $width, $height);
if (file_exists($path) and is_readable($path))
return $path;
$path = PostModel::getThumbDefaultPath($name, $width, $height);
$path = PostModel::getThumbPath($name);
if (file_exists($path) and is_readable($path))
return $path;
return null;
}
public static function getThumbCustomPath($name, $width = null, $height = null)
public static function getThumbCustomSourcePath($name)
{
return self::getThumbPathTokenized('{fullpath}.custom', $name, $width, $height);
return self::getThumbPathTokenized('{fullpath}.thumb_source', $name);
}
public static function getThumbDefaultPath($name, $width = null, $height = null)
public static function getThumbPath($name)
{
return self::getThumbPathTokenized('{fullpath}-{width}x{height}.default', $name, $width, $height);
return self::getThumbPathTokenized('{fullpath}.thumb', $name);
}
private static function getThumbPathTokenized($text, $name, $width = null, $height = null)
private static function getThumbPathTokenized($text, $name)
{
list ($width, $height) = self::validateThumbSize($width, $height);
return TextHelper::absolutePath(TextHelper::replaceTokens($text, [
'fullpath' => getConfig()->main->thumbsPath . DS . $name,
'width' => $width,
'height' => $height]));
'fullpath' => getConfig()->main->thumbsPath . DS . $name]));
}
public static function tryGetWorkingFullPath($name)

View file

@ -203,11 +203,7 @@ class ApiArgumentTest extends AbstractFullApiTest
public function testGetPostThumbJob()
{
$this->testArguments(new GetPostThumbJob(),
JobArgs::Conjunction(
JobArgs::Optional(JobArgs::ARG_THUMB_WIDTH),
JobArgs::Optional(JobArgs::ARG_THUMB_HEIGHT),
$this->getPostSafeSelector()));
$this->testArguments(new GetPostThumbJob(), $this->getPostSafeSelector());
}
public function testGetUserJob()

View file

@ -19,20 +19,19 @@ class EditPostThumbJobTest extends AbstractTest
});
$this->assert->isTrue($post->hasCustomThumb());
$this->assert->isNotNull($post->getThumbCustomPath());
$this->assert->areEqual($post->getThumbCustomPath(), $post->tryGetWorkingThumbPath());
$this->assert->areEqual(
file_get_contents($this->testSupport->getPath('thumb.jpg')),
file_get_contents($post->tryGetWorkingThumbPath()));
$img = imagecreatefromjpeg($post->tryGetWorkingThumbPath());
$this->assert->areEqual(150, imagesx($img));
$this->assert->areEqual(150, imagesy($img));
imagedestroy($img);
}
public function testFileInvalidDimensions()
public function testFileDifferentDimensions()
{
$this->grantAccess('editPostThumb');
$post = $this->postMocker->mockSingle();
$this->assert->isFalse($post->hasCustomThumb());
$this->assert->throws(function() use ($post)
$post = $this->assert->doesNotThrow(function() use ($post)
{
return Api::run(
new EditPostThumbJob(),
@ -41,9 +40,12 @@ class EditPostThumbJobTest extends AbstractTest
JobArgs::ARG_NEW_THUMB_CONTENT =>
new ApiFileInput($this->testSupport->getPath('image.jpg'), 'test.jpg'),
]);
}, 'invalid thumbnail size');
});
$this->assert->isFalse($post->hasCustomThumb());
$this->assert->areNotEqual($post->getThumbCustomPath(), $post->tryGetWorkingThumbPath());
$this->assert->isTrue($post->hasCustomThumb());
$img = imagecreatefromjpeg($post->tryGetWorkingThumbPath());
$this->assert->areEqual(150, imagesx($img));
$this->assert->areEqual(150, imagesy($img));
imagedestroy($img);
}
}