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:
parent
27ddf6f59f
commit
c7250ae0a9
10 changed files with 53 additions and 86 deletions
|
@ -4,7 +4,7 @@ dbLocation = "./data/db.sqlite"
|
||||||
dbUser = "test"
|
dbUser = "test"
|
||||||
dbPass = "test"
|
dbPass = "test"
|
||||||
filesPath = "./data/files/"
|
filesPath = "./data/files/"
|
||||||
thumbsPath = "./data/thumbs/"
|
thumbsPath = "./public_html/thumbs/"
|
||||||
logsPath = "./data/logs/{yyyy}-{mm}.log"
|
logsPath = "./data/logs/{yyyy}-{mm}.log"
|
||||||
mediaPath = "./public_html/media/"
|
mediaPath = "./public_html/media/"
|
||||||
title = "szurubooru"
|
title = "szurubooru"
|
||||||
|
|
|
@ -6,6 +6,10 @@ ErrorDocument 403 /dispatch.php?request=error/http&code=403
|
||||||
ErrorDocument 404 /dispatch.php?request=error/http&code=404
|
ErrorDocument 404 /dispatch.php?request=error/http&code=404
|
||||||
ErrorDocument 500 /dispatch.php?request=error/http&code=500
|
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} !-f
|
||||||
RewriteCond %{REQUEST_FILENAME} !-d
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
RewriteRule ^.*$ /dispatch.php
|
RewriteRule ^.*$ /dispatch.php
|
||||||
|
|
|
@ -24,9 +24,6 @@ class JobArgs
|
||||||
|
|
||||||
const ARG_LOG_ID = 'log-id';
|
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_TEXT = 'new-text';
|
||||||
const ARG_NEW_STATE = 'new-state';
|
const ARG_NEW_STATE = 'new-state';
|
||||||
|
|
||||||
|
|
|
@ -19,17 +19,14 @@ class GetPostThumbJob extends AbstractJob
|
||||||
$name = $post->getName();
|
$name = $post->getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
$width = $this->hasArgument(JobArgs::ARG_THUMB_WIDTH) ? $this->getArgument(JobArgs::ARG_THUMB_WIDTH) : null;
|
$path = PostModel::tryGetWorkingThumbPath($name);
|
||||||
$height = $this->hasArgument(JobArgs::ARG_THUMB_HEIGHT) ? $this->getArgument(JobArgs::ARG_THUMB_HEIGHT) : null;
|
|
||||||
|
|
||||||
$path = PostModel::tryGetWorkingThumbPath($name, $width, $height);
|
|
||||||
if (!$path)
|
if (!$path)
|
||||||
{
|
{
|
||||||
$post = PostModel::getByName($name);
|
$post = PostModel::getByName($name);
|
||||||
$post = $this->postRetriever->retrieve();
|
$post = $this->postRetriever->retrieve();
|
||||||
|
|
||||||
$post->generateThumb($width, $height);
|
$post->generateThumb();
|
||||||
$path = PostModel::tryGetWorkingThumbPath($name, $width, $height);
|
$path = PostModel::tryGetWorkingThumbPath($name);
|
||||||
|
|
||||||
if (!$path)
|
if (!$path)
|
||||||
{
|
{
|
||||||
|
@ -43,15 +40,11 @@ class GetPostThumbJob extends AbstractJob
|
||||||
|
|
||||||
public function getRequiredArguments()
|
public function getRequiredArguments()
|
||||||
{
|
{
|
||||||
return JobArgs::Conjunction(
|
return $this->postRetriever->getRequiredArguments();
|
||||||
$this->postRetriever->getRequiredArguments(),
|
|
||||||
JobArgs::Optional(JobArgs::ARG_THUMB_WIDTH),
|
|
||||||
JobArgs::Optional(JobArgs::ARG_THUMB_HEIGHT));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRequiredPrivileges()
|
public function getRequiredPrivileges()
|
||||||
{
|
{
|
||||||
//privilege check removed to make thumbs faster
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -267,12 +267,9 @@ class PostController
|
||||||
$context->layoutName = 'layout-file';
|
$context->layoutName = 'layout-file';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function thumbView($name, $width = null, $height = null)
|
public function thumbView($name)
|
||||||
{
|
{
|
||||||
$ret = Api::run(new GetPostThumbJob(), [
|
$ret = Api::run(new GetPostThumbJob(), [JobArgs::ARG_POST_NAME => $name]);
|
||||||
JobArgs::ARG_POST_NAME => $name,
|
|
||||||
JobArgs::ARG_THUMB_WIDTH => $width,
|
|
||||||
JobArgs::ARG_THUMB_HEIGHT => $height]);
|
|
||||||
|
|
||||||
$context = getContext();
|
$context = getContext();
|
||||||
$context->transport->cacheDaysToLive = 365;
|
$context->transport->cacheDaysToLive = 365;
|
||||||
|
|
|
@ -328,24 +328,24 @@ final class PostEntity extends AbstractEntity implements IValidatable
|
||||||
return PostModel::getFullPath($this->getName());
|
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);
|
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']))
|
if (!in_array($mimeType, ['image/gif', 'image/png', 'image/jpeg']))
|
||||||
throw new SimpleException('Invalid thumbnail type "%s"', $mimeType);
|
throw new SimpleException('Invalid thumbnail type "%s"', $mimeType);
|
||||||
|
|
||||||
list ($imageWidth, $imageHeight) = getimagesize($srcPath);
|
$dstPath = $this->getThumbCustomSourcePath();
|
||||||
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();
|
|
||||||
|
|
||||||
TransferHelper::copy($srcPath, $dstPath);
|
TransferHelper::copy($srcPath, $dstPath);
|
||||||
|
|
||||||
|
$this->generateThumb();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function generateThumb($width = null, $height = null)
|
public function generateThumb()
|
||||||
{
|
{
|
||||||
list ($width, $height) = PostModel::validateThumbSize($width, $height);
|
$width = getConfig()->browsing->thumbWidth;
|
||||||
$srcPath = $this->getFullPath();
|
$height = getConfig()->browsing->thumbHeight;
|
||||||
$dstPath = $this->getThumbDefaultPath($width, $height);
|
$dstPath = $this->getThumbPath($width, $height);
|
||||||
|
|
||||||
|
if (file_exists($this->getThumbCustomSourcePath()))
|
||||||
|
return ThumbnailHelper::generateFromPath($this->getThumbCustomSourcePath(), $dstPath, $width, $height);
|
||||||
|
|
||||||
if ($this->getType()->toInteger() == PostType::Youtube)
|
if ($this->getType()->toInteger() == PostType::Youtube)
|
||||||
{
|
{
|
||||||
|
@ -388,7 +383,7 @@ final class PostEntity extends AbstractEntity implements IValidatable
|
||||||
}
|
}
|
||||||
else
|
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);
|
TransferHelper::copy($srcPath, $dstPath);
|
||||||
|
|
||||||
$thumbPath = $this->getThumbDefaultPath();
|
$thumbPath = $this->getThumbPath();
|
||||||
if (file_exists($thumbPath))
|
if (file_exists($thumbPath))
|
||||||
unlink($thumbPath);
|
unlink($thumbPath);
|
||||||
}
|
}
|
||||||
|
@ -467,7 +462,7 @@ final class PostEntity extends AbstractEntity implements IValidatable
|
||||||
$this->setImageWidth(null);
|
$this->setImageWidth(null);
|
||||||
$this->setImageHeight(null);
|
$this->setImageHeight(null);
|
||||||
|
|
||||||
$thumbPath = $this->getThumbDefaultPath();
|
$thumbPath = $this->getThumbPath();
|
||||||
if (file_exists($thumbPath))
|
if (file_exists($thumbPath))
|
||||||
unlink($thumbPath);
|
unlink($thumbPath);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
$path = PostModel::getThumbPath($name);
|
||||||
$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);
|
|
||||||
if (file_exists($path) and is_readable($path))
|
if (file_exists($path) and is_readable($path))
|
||||||
return $path;
|
return $path;
|
||||||
|
|
||||||
return null;
|
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, [
|
return TextHelper::absolutePath(TextHelper::replaceTokens($text, [
|
||||||
'fullpath' => getConfig()->main->thumbsPath . DS . $name,
|
'fullpath' => getConfig()->main->thumbsPath . DS . $name]));
|
||||||
'width' => $width,
|
|
||||||
'height' => $height]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function tryGetWorkingFullPath($name)
|
public static function tryGetWorkingFullPath($name)
|
||||||
|
|
|
@ -203,11 +203,7 @@ class ApiArgumentTest extends AbstractFullApiTest
|
||||||
|
|
||||||
public function testGetPostThumbJob()
|
public function testGetPostThumbJob()
|
||||||
{
|
{
|
||||||
$this->testArguments(new GetPostThumbJob(),
|
$this->testArguments(new GetPostThumbJob(), $this->getPostSafeSelector());
|
||||||
JobArgs::Conjunction(
|
|
||||||
JobArgs::Optional(JobArgs::ARG_THUMB_WIDTH),
|
|
||||||
JobArgs::Optional(JobArgs::ARG_THUMB_HEIGHT),
|
|
||||||
$this->getPostSafeSelector()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetUserJob()
|
public function testGetUserJob()
|
||||||
|
|
|
@ -19,20 +19,19 @@ class EditPostThumbJobTest extends AbstractTest
|
||||||
});
|
});
|
||||||
|
|
||||||
$this->assert->isTrue($post->hasCustomThumb());
|
$this->assert->isTrue($post->hasCustomThumb());
|
||||||
$this->assert->isNotNull($post->getThumbCustomPath());
|
$img = imagecreatefromjpeg($post->tryGetWorkingThumbPath());
|
||||||
$this->assert->areEqual($post->getThumbCustomPath(), $post->tryGetWorkingThumbPath());
|
$this->assert->areEqual(150, imagesx($img));
|
||||||
$this->assert->areEqual(
|
$this->assert->areEqual(150, imagesy($img));
|
||||||
file_get_contents($this->testSupport->getPath('thumb.jpg')),
|
imagedestroy($img);
|
||||||
file_get_contents($post->tryGetWorkingThumbPath()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFileInvalidDimensions()
|
public function testFileDifferentDimensions()
|
||||||
{
|
{
|
||||||
$this->grantAccess('editPostThumb');
|
$this->grantAccess('editPostThumb');
|
||||||
$post = $this->postMocker->mockSingle();
|
$post = $this->postMocker->mockSingle();
|
||||||
|
|
||||||
$this->assert->isFalse($post->hasCustomThumb());
|
$this->assert->isFalse($post->hasCustomThumb());
|
||||||
$this->assert->throws(function() use ($post)
|
$post = $this->assert->doesNotThrow(function() use ($post)
|
||||||
{
|
{
|
||||||
return Api::run(
|
return Api::run(
|
||||||
new EditPostThumbJob(),
|
new EditPostThumbJob(),
|
||||||
|
@ -41,9 +40,12 @@ class EditPostThumbJobTest extends AbstractTest
|
||||||
JobArgs::ARG_NEW_THUMB_CONTENT =>
|
JobArgs::ARG_NEW_THUMB_CONTENT =>
|
||||||
new ApiFileInput($this->testSupport->getPath('image.jpg'), 'test.jpg'),
|
new ApiFileInput($this->testSupport->getPath('image.jpg'), 'test.jpg'),
|
||||||
]);
|
]);
|
||||||
}, 'invalid thumbnail size');
|
});
|
||||||
|
|
||||||
$this->assert->isFalse($post->hasCustomThumb());
|
$this->assert->isTrue($post->hasCustomThumb());
|
||||||
$this->assert->areNotEqual($post->getThumbCustomPath(), $post->tryGetWorkingThumbPath());
|
$img = imagecreatefromjpeg($post->tryGetWorkingThumbPath());
|
||||||
|
$this->assert->areEqual(150, imagesx($img));
|
||||||
|
$this->assert->areEqual(150, imagesy($img));
|
||||||
|
imagedestroy($img);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue