Improve media index so it works with multiple external locations

This commit is contained in:
Matias Griese
2022-03-31 21:30:25 +03:00
parent b06e925f99
commit f4dd10a439
4 changed files with 250 additions and 34 deletions

View File

@@ -0,0 +1,145 @@
<?php
/**
* @package Grav\Common\Media
*
* @copyright Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Media;
use Grav\Framework\File\Formatter\JsonFormatter;
use Grav\Framework\File\JsonFile;
/**
* Media Index class
*/
class MediaIndex
{
/** @var array */
protected static $instances = [];
/** @var string */
protected $filepath;
/** @var array */
protected $indexes;
/** @var int */
protected $modified = 0;
/** @var JsonFile */
protected $file;
/**
* @param string $filepath
* @return MediaIndex
*/
public static function getInstance(string $filepath): MediaIndex
{
if (!isset(static::$instances[$filepath])) {
static::$instances[$filepath] = new static($filepath);
}
return static::$instances[$filepath];
}
/**
* @param string $id
* @param bool $reload
* @return array
*/
public function get(string $id, bool $reload = false): array
{
$indexes = $this->getIndexes($reload);
$index = $indexes[$id] ?? [];
return [$index, $this->modified];
}
/**
* @param bool $block
* @return void
*/
public function lock(bool $block = true): void
{
$this->getFile()->lock($block);
}
/**
* @param int|null $timestamp
* @return void
*/
public function touch(string $id, ?int $timestamp = null): void
{
$this->getFile()->touch($timestamp);
}
/**
* @param string $id
* @param array $data
* @return void
*/
public function save(string $id, array $index): void
{
$file = $this->getFile();
$file->lock();
$index = array_filter($index, static function($val) { return $val !== null; } );
$indexes = $file->exists() ? $file->load() : ['version' => 1];
$version = $indexes['version'] ?? null;
if ($version !== 1) {
$indexes = [];
}
$indexes[$id] = $index;
$file->save($indexes);
$file->unlock();
$this->indexes = $indexes;
$this->modified = $file->getModificationTime();
}
protected function getIndexes(bool $reload = true): array
{
if (!isset($this->indexes) || $reload) {
// Read media index file.
$file = $this->getFile();
if ($file->exists()) {
$this->indexes = $file->load();
$version = $this->indexes['version'] ?? null;
if ($version !== 1) {
$this->indexes = [];
}
$this->modified = $file->getModificationTime();
} else {
$this->indexes = [];
$this->modified = 0;
}
}
return $this->indexes;
}
/**
* Get index file, which stores media file index.
*
* @return JsonFile
*/
protected function getFile(): JsonFile
{
if (!isset($this->file)) {
$this->file = new JsonFile($this->filepath, new JsonFormatter(['encode_options' => JSON_PRETTY_PRINT]));
}
return $this->file;
}
/**
* @param string $filepath
*/
private function __construct(string $filepath)
{
$this->filepath = $filepath;
}
}

View File

@@ -17,12 +17,11 @@ use Grav\Common\Language\Language;
use Grav\Common\Media\Interfaces\MediaCollectionInterface;
use Grav\Common\Media\Interfaces\MediaObjectInterface;
use Grav\Common\Media\Interfaces\MediaUploadInterface;
use Grav\Common\Media\MediaIndex;
use Grav\Common\Media\Traits\MediaUploadTrait;
use Grav\Common\Page\Pages;
use Grav\Common\Utils;
use Grav\Framework\Compat\Serializable;
use Grav\Framework\File\Formatter\JsonFormatter;
use Grav\Framework\File\JsonFile;
use InvalidArgumentException;
use PHPExif\Reader\Reader;
use RocketTheme\Toolbox\ArrayTraits\Export;
@@ -78,6 +77,19 @@ abstract class AbstractMedia implements ExportInterface, MediaCollectionInterfac
/** @var bool Hack to make Iterator work together with unset(). */
private $iteratorUnset = false;
/**
* @return string
*/
public function getId(): string
{
return md5($this->getType() . ':' . $this->path);
}
/**
* @return string
*/
abstract public function getType(): string;
/**
* Return media path.
*
@@ -358,10 +370,23 @@ abstract class AbstractMedia implements ExportInterface, MediaCollectionInterfac
*/
public function updateIndex(array $files): void
{
[$index,] = $this->loadIndex();
$files = array_merge($files, $index['files']);
ksort($files, SORT_NATURAL);
$this->saveIndex($files);
$mediaIndex = $this->getIndex();
if (!$mediaIndex) {
return;
}
$mediaIndex->lock();
$id = $this->getId();
$index = $mediaIndex->get($id, true);
// Add new files and remove the old ones.
$files += $index['files'] ?? [];
$files = array_filter($files, static function($val) { return $val !== null; } );
$index = $this->generateIndex($files);
$mediaIndex->save($id, $index);
}
/**
@@ -803,18 +828,39 @@ abstract class AbstractMedia implements ExportInterface, MediaCollectionInterfac
return $medium;
}
/**
* @param array $files
* @param string|null $checksum
* @param int|null $timestamp
* @return array
*/
protected function generateIndex(array $files, string $checksum = null, ?int $timestamp = null): array
{
ksort($files, SORT_NATURAL);
return [
'type' => $this->getType(),
'version' => static::VERSION,
'checksum' => $checksum ?? md5(serialize($files)),
'timestamp' => $timestamp ?? time(),
'folder' => $this->path,
'url' => $this->url,
'files' => $files,
];
}
/**
* Get index file, which stores media file index.
*
* @return JsonFile|null
* @return MediaIndex|null
*/
protected function getIndexFile(): ?JsonFile
protected function getIndex(): ?MediaIndex
{
if (null === $this->indexFolder || null === $this->indexFile) {
return null;
}
return new JsonFile($this->indexFolder . '/' . $this->indexFile, new JsonFormatter(['encode_options' => JSON_PRETTY_PRINT]));
return MediaIndex::getInstance($this->indexFolder . '/' . $this->indexFile);
}
/**
@@ -823,12 +869,14 @@ abstract class AbstractMedia implements ExportInterface, MediaCollectionInterfac
protected function loadIndex(): array
{
// Read media index file.
$indexFile = $this->getIndexFile();
if (!($indexFile && $indexFile->exists())) {
$mediaIndex = $this->getIndex();
if (!$mediaIndex || !$this->exists) {
return [[], 0];
}
$index = $indexFile->load();
$id = $this->getId();
[$index, $modified] = $mediaIndex->get($id);
$version = $index['version'] ?? null;
$folder = $index['folder'] ?? null;
$type = $index['type'] ?? null;
@@ -836,7 +884,7 @@ abstract class AbstractMedia implements ExportInterface, MediaCollectionInterfac
return [[], 0];
}
return [$index, $indexFile->getModificationTime()];
return [$index, $modified];
}
/**
@@ -845,12 +893,13 @@ abstract class AbstractMedia implements ExportInterface, MediaCollectionInterfac
*/
protected function touchIndex(?int $timestamp = null): void
{
$index = $this->getIndexFile();
if (!$index || !$this->exists) {
$mediaIndex = $this->getIndex();
if (!$mediaIndex || !$this->exists) {
return;
}
$id = $this->getId();
$index->touch($timestamp);
$mediaIndex->touch($id, $timestamp);
}
/**
@@ -861,22 +910,15 @@ abstract class AbstractMedia implements ExportInterface, MediaCollectionInterfac
*/
protected function saveIndex(array $files, string $checksum = null, ?int $timestamp = null): void
{
$index = $this->getIndexFile();
if (!$index || !$this->exists) {
$mediaIndex = $this->getIndex();
if (!$mediaIndex || !$this->exists) {
return;
}
$data = [
'type' => $this->config['type'] ?? 'local',
'version' => static::VERSION,
'checksum' => $checksum ?? md5(serialize($files)),
'timestamp' => $timestamp ?? time(),
'folder' => $this->path,
'url' => $this->url,
'files' => $files,
];
$id = $this->getId();
$index = $this->generateIndex($files, $checksum, $timestamp);
$index->save($data);
$mediaIndex->save($id, $index);
}
/**

View File

@@ -13,8 +13,6 @@ use FilesystemIterator;
use Grav\Common\Data\Blueprint;
use Grav\Common\Filesystem\Folder;
use Grav\Common\Media\Interfaces\MediaObjectInterface;
use Grav\Framework\File\Formatter\JsonFormatter;
use Grav\Framework\File\JsonFile;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use RuntimeException;
use function is_array;
@@ -25,6 +23,14 @@ use function is_array;
*/
abstract class LocalMedia extends AbstractMedia
{
/**
* @return string
*/
public function getType(): string
{
return 'local';
}
/**
* Return media path.
*

View File

@@ -24,6 +24,7 @@ use Grav\Framework\Filesystem\Filesystem;
use Grav\Framework\Flex\FlexDirectory;
use Grav\Framework\Form\FormFlashFile;
use Psr\Http\Message\UploadedFileInterface;
use RocketTheme\Toolbox\Event\Event;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use RuntimeException;
use function array_key_exists;
@@ -46,6 +47,8 @@ trait FlexMediaTrait
/** @var array */
protected $_uploads = [];
/** @var array */
protected $_mediaSettings = [];
/**
* @return string|null
@@ -103,7 +106,9 @@ trait FlexMediaTrait
/** @var MediaFactory $factory */
$factory = Grav::instance()['media_factory'];
$params = [
$params = $settings['media'] ?? [];
$params += [
'object' => $this,
'path' => $settings[$var],
'order' => [],
'load' => true
@@ -128,6 +133,10 @@ trait FlexMediaTrait
return null;
}
if (isset($this->_mediaSettings[$field])) {
return $this->_mediaSettings[$field];
}
// Load settings for the field.
$schema = $this->getBlueprint()->schema();
$settings = (array)$schema->getProperty($field);
@@ -151,13 +160,27 @@ trait FlexMediaTrait
// Set media folder for media fields.
if (isset($var)) {
$folder = $settings[$var] ?? '';
if (in_array(rtrim($folder, '/'), ['', '@self', 'self@', '@self@'], true)) {
$settings['type'] = 'local';
$token = $settings[$var] ?? '';
if (in_array(rtrim($token, '/'), ['', '@self', 'self@', '@self@'], true)) {
$settings[$var] = $this->getMediaFolder();
$settings['self'] = true;
} else {
$settings[$var] = Utils::getPathFromToken($folder, $this);
$event = new Event([
'token' => $token,
'object' => $this,
'field' => $field,
'type' => $type,
'settings' => &$settings, // Value can be changed.
'uri' => &$uri // Value will be set to here.
]);
Grav::instance()->fireEvent('onGetFlexMediaFieldSettings', $event);
$settings[$var] = $uri ?? Utils::getPathFromToken($token, $this);
$settings['self'] = false;
$this->_mediaSettings[$field] = $settings;
}
}