Postpone image processing to cache image requests

This commit is contained in:
Matias Griese
2022-04-21 20:26:21 +03:00
parent 34653a6b4c
commit f8ef9c41c5
6 changed files with 203 additions and 4 deletions

View File

@@ -12,6 +12,8 @@ namespace Grav\Common\Media\Factories;
use Grav\Common\Media\Interfaces\MediaCollectionInterface;
use Grav\Common\Media\Interfaces\MediaFactoryInterface;
use Grav\Common\Page\Media;
use Grav\Common\Utils;
use RuntimeException;
/**
*
@@ -38,4 +40,40 @@ class LocalMediaFactory implements MediaFactoryInterface
return new Media($path, $order, $load);
}
/**
* @param string $type
* @param string $path
* @return string
*/
public function readFile(string $type, string $path): string
{
$filepath = GRAV_WEBROOT . '/' . $path;
error_clear_last();
$contents = @file_get_contents($filepath);
if (false === $contents) {
throw new RuntimeException('Reading media file failed: ' . (error_get_last()['message'] ?? sprintf('Cannot read %s', Utils::basename($filepath))));
}
return $contents;
}
/**
* @param string $type
* @param string $path
* @return resource
*/
public function readStream(string $type, string $path)
{
$filepath = GRAV_WEBROOT . '/' . $path;
error_clear_last();
$contents = @fopen($filepath, 'rb');
if (false === $contents) {
throw new RuntimeException('Reading media file failed: ' . (error_get_last()['message'] ?? sprintf('Cannot open %s', Utils::basename($filepath))));
}
return $contents;
}
}

View File

@@ -13,6 +13,7 @@ use Grav\Common\Grav;
use Grav\Common\Media\Events\MediaEventSubscriber;
use Grav\Common\Media\Interfaces\MediaCollectionInterface;
use Grav\Common\Media\Interfaces\MediaFactoryInterface;
use RuntimeException;
use Symfony\Component\EventDispatcher\EventDispatcher;
/**
@@ -96,4 +97,48 @@ final class MediaFactory implements MediaFactoryInterface
return null;
}
/**
* @param string $uri
* @return string
*/
public function readFile(string $uri): string
{
if (preg_match('{^(?:media-([^:]+)://)?(.*)$}', $uri, $matches)) {
$type = str_replace('-', '_', $matches[1]);
$filepath = $matches[2];
} else {
$type = 'local';
$filepath = $uri;
}
$factory = $this->collectionTypes[$type] ?? null;
if ($factory) {
return $factory->readFile($type, $filepath);
}
throw new RuntimeException(sprintf('Reading media file failed: type %s does not exist', $type), 500);
}
/**
* @param string $uri
* @return resource
*/
public function readStream(string $uri)
{
if (preg_match('{^(?:media-([^:]+)://)?(.*)$}', $uri, $matches)) {
$type = str_replace('-', '_', $matches[1]);
$filepath = $matches[2];
} else {
$type = 'local';
$filepath = $uri;
}
$factory = $this->collectionTypes[$type] ?? null;
if ($factory) {
return $factory->readStream($type, $filepath);
}
throw new RuntimeException(sprintf('Reading media file failed: type %s does not exist', $type), 500);
}
}

View File

@@ -18,6 +18,8 @@ use Grav\Framework\File\Formatter\JsonFormatter;
use Grav\Framework\File\JsonFile;
use Grav\Framework\Image\Adapter\GdAdapter;
use Grav\Framework\Image\Image;
use Grav\Framework\Psr7\Response;
use Psr\Http\Message\ResponseInterface;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use RuntimeException;
use function func_num_args;
@@ -58,6 +60,70 @@ trait ImageMediaTrait
/** @var string */
protected $sizes = '100vw';
/**
* @param string $path
* @return array|null
*/
protected static function createImageFromCache(string $path): ?array
{
$filepath = GRAV_WEBROOT . $path;
$cachepath = "{$filepath}.json";
$file = static::getCacheMetaFile($cachepath);
if (!$file->exists()) {
return null;
}
$data = $file->load();
$format = $data['extra']['format'] ?? 'jpg';
$mime = $data['extra']['mime'] ?? '';
$quality = $data['extra']['quality'] ?? 80;
$mediaUri = $data['extra']['media-uri'] ?? null;
$mediaFactory = Grav::instance()['media_factory'];
$fileData = $mediaFactory->readFile($mediaUri);
$adapter = GdAdapter::createFromString($fileData);
$image = Image::createFromArray($data);
$image->setAdapter($adapter);
$filepath = $image->save($filepath, $format, $quality);
$image->freeAdapter();
$time = filemtime($filepath);
$size = filesize($filepath);
return [$filepath, $mime, $time, $size];
}
/**
* @param string $path
* @return ResponseInterface
*/
public static function createImageResponseFromCache(string $path): ResponseInterface
{
[$filepath, $mime, $time, $size] = static::createImageFromCache($path);
if (is_file($filepath)) {
$code = 200;
} else {
$code = 404;
// TODO: customizable 404 image?
$filepath = GRAV_WEBROOT . '/system/images/media/thumb-jpg.png';
$mime = 'image/png';
$time = filemtime($filepath);
$size = filesize($filepath);
}
$body = fopen($filepath, 'rb');
$headers = [
'Content-Type' => $mime,
'Last-Modified' => gmdate('D, d M Y H:i:s', $time) . ' GMT',
'ETag' => sprintf('%x-%x', $size, $time)
];
return new Response($code, $headers, $body);
}
/**
* Also unset the image on destruct.
*/
@@ -599,9 +665,12 @@ trait ImageMediaTrait
$format = $extension;
}
$image = $this->image;
$image->extra['format'] = $format;
$image->extra['quality'] = $quality;
$image->extra['media-uri'] = $this->getMedia()->getMediaUri($this->filename);
$image->extra['mime'] = $this->mime;
$data = $image->jsonSerialize();
$hash = $data['hash'];
@@ -618,14 +687,15 @@ trait ImageMediaTrait
$imageFile = '/' . $locator->getResource($imageFile, false);
$cacheFile = $locator->getResource($cacheFile, true);
$file = $this->getCacheMetaFile($cacheFile);
$file = static::getCacheMetaFile($cacheFile);
if (!$file->exists()) {
$file->save($data);
} else {
$file->touch();
}
return $this->generateCacheImage(GRAV_WEBROOT . $imageFile);
return GRAV_WEBROOT . $imageFile;
//return $this->generateCacheImage(GRAV_WEBROOT . $imageFile);
}
/**
@@ -663,7 +733,7 @@ trait ImageMediaTrait
* @param string $filepath
* @return JsonFile
*/
protected function getCacheMetaFile(string $filepath): JsonFile
protected static function getCacheMetaFile(string $filepath): JsonFile
{
$formatter = new JsonFormatter(['encode_options' => JSON_PRETTY_PRINT]);

View File

@@ -112,6 +112,18 @@ abstract class AbstractMedia implements ExportInterface, MediaCollectionInterfac
*/
abstract public function getUrl(string $filename): string;
/**
* @param string $filename
* @return string
*/
public function getMediaUri(string $filename): string
{
$schema = 'media-' . str_replace('_', '-', $this->getName());
$path = $this->getRealPath($filename);
return "{$schema}://{$path}";
}
/**
* @return bool
*/

View File

@@ -13,7 +13,6 @@ use Grav\Common\Config\Config;
use Grav\Common\Data\Blueprint;
use Grav\Common\Media\Interfaces\ImageManipulateInterface;
use Grav\Common\Media\Interfaces\ImageMediaInterface;
use Grav\Common\Media\Interfaces\MediaCollectionInterface;
use Grav\Common\Media\Interfaces\MediaLinkInterface;
use Grav\Common\Media\Traits\ImageLoadingTrait;
use Grav\Common\Media\Traits\ImageMediaTrait;

View File

@@ -13,6 +13,7 @@ use Grav\Common\Config\Config;
use Grav\Common\Debugger;
use Grav\Common\Errors\Errors;
use Grav\Common\Grav;
use Grav\Common\Page\Medium\ImageMedium;
use Grav\Common\Page\Pages;
use Grav\Common\Plugins;
use Grav\Common\Session;
@@ -30,6 +31,7 @@ use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use function defined;
use function dirname;
use function in_array;
/**
@@ -98,6 +100,14 @@ class InitializeProcessor extends ProcessorBase
// Load plugins.
$this->initializePlugins();
// Image handling can return response right away.
$response = $this->handleImageRequest($request);
if ($response) {
$this->stopTimer('_init');
return $response;
}
// Load pages.
$this->initializePages($config);
@@ -331,6 +341,31 @@ class InitializeProcessor extends ProcessorBase
return null;
}
/**
* @param ServerRequestInterface $request
* @return ResponseInterface|null
*/
protected function handleImageRequest(ServerRequestInterface $request): ?ResponseInterface
{
// Handle clockwork API calls.
$uri = $request->getUri();
$server = $request->getServerParams();
$basePath = str_replace('\\', '/', dirname(parse_url($server['SCRIPT_NAME'], PHP_URL_PATH)));
if ($basePath === '/') {
$basePath = '';
}
$imagePath = $basePath . '/images/';
$path = $uri->getPath();
if (str_starts_with($path, $imagePath)) {
$path = mb_substr($path, mb_strlen($basePath));
return ImageMedium::createImageResponseFromCache($path);
}
return null;
}
/**
* @param Config $config
*/