Skip to content

Commit

Permalink
Merge pull request #42 from punktDeForks/feature/exchangeable-storage
Browse files Browse the repository at this point in the history
FEATURE: Exchangeable proxy aware storages and targets
  • Loading branch information
mficzel authored Oct 28, 2020
2 parents ac541a1 + 999244b commit 7d111fa
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 106 deletions.
102 changes: 7 additions & 95 deletions Classes/ResourceManagement/ProxyAwareFileSystemSymlinkTarget.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,124 +3,36 @@

use Neos\Flow\Annotations as Flow;
use Neos\Flow\Core\Bootstrap;
use Neos\Flow\ResourceManagement\CollectionInterface;
use Neos\Flow\ResourceManagement\PersistentResource;
use Neos\Flow\Mvc\Routing\UriBuilder;
use Neos\Flow\ResourceManagement\ResourceManager;
use Neos\Flow\ResourceManagement\ResourceRepository;
use Neos\Flow\ResourceManagement\Target\Exception;
use Neos\Flow\ResourceManagement\Target\FileSystemSymlinkTarget;
use Neos\Flow\Mvc\Routing\UriBuilder;
use Neos\Flow\Http\HttpRequestHandlerInterface;
use Neos\Flow\Mvc\ActionRequest;
use Sitegeist\MagicWand\Domain\Service\ConfigurationService;
use Sitegeist\MagicWand\ResourceManagement\ProxyAwareWritableFileSystemStorage;
use Neos\Flow\ResourceManagement\Storage\StorageObject;

class ProxyAwareFileSystemSymlinkTarget extends FileSystemSymlinkTarget
class ProxyAwareFileSystemSymlinkTarget extends FileSystemSymlinkTarget implements ProxyAwareTargetInterface
{
use ProxyAwareTargetTrait;

/**
* @Flow\Inject
* @var Bootstrap
*/
protected $bootstrap;

/**
* @var ConfigurationService
* @Flow\Inject
*/
protected $configurationService;

/**
* @var UriBuilder
* @Flow\Inject
*/
protected $uriBuilder;

/**
* @var ResourceRepository
* @Flow\Inject
*/
protected $resourceRepository;

/**
* @var ResourceManager
* @Flow\Inject
*/
protected $resourceManager;

public function initializeObject() {
// intialize uribuilder with request
$requestHandler = $this->bootstrap->getActiveRequestHandler();
if ($requestHandler instanceof HttpRequestHandlerInterface) {
$request = new ActionRequest($requestHandler->getHttpRequest());
$this->uriBuilder->setRequest($request);
}
parent::initializeObject();
}

/**
* Publishes the whole collection to this target
*
* @param CollectionInterface $collection The collection to publish
* @param callable $callback Function called after each resource publishing
* @return void
*/
public function publishCollection(CollectionInterface $collection, callable $callback = null)
{
if (!$this->configurationService->getCurrentConfigurationByPath('resourceProxy')) {
return parent::publishCollection($collection, $callback);
}

/**
* @var ProxyAwareWritableFileSystemStorage $storage
*/
$storage = $collection->getStorage();
if (!$storage instanceof ProxyAwareWritableFileSystemStorage) {
return parent::publishCollection($collection, $callback);
}

foreach ($collection->getObjects($callback) as $object) {
/** @var StorageObject $object */
if ($storage->resourceIsPresentInStorage($object) === false) {
// this storage ignores resources that are not yet in the filesystem as they
// are optimistically created during read operations
continue;
}
$sourceStream = $object->getStream();
$this->publishFile($sourceStream, $this->getRelativePublicationPathAndFilename($object));
fclose($sourceStream);
}
}

/**
* @param PersistentResource $resource
* @return string
* @throws Exception
* @var ConfigurationService
* @Flow\Inject
*/
public function getPublicPersistentResourceUri(PersistentResource $resource)
{
if (!$this->configurationService->getCurrentConfigurationByPath('resourceProxy')) {
return parent::getPublicPersistentResourceUri($resource);
}

$collection = $this->resourceManager->getCollection($resource->getCollectionName());
$storage = $collection->getStorage();

if (!$storage instanceof ProxyAwareWritableFileSystemStorage) {
return parent::getPublicPersistentResourceUri($resource);
}

if ($storage->resourceIsPresentInStorage($resource)) {
return parent::getPublicPersistentResourceUri($resource);
}

// build uri to resoucre controller that will fetch and publish
// the resource asynchronously
return $this->uriBuilder->uriFor(
'index',
['resourceIdentifier' => $resource],
'Resource',
'Sitegeist.MagicWand'
);
}
protected $configurationService;
}
40 changes: 40 additions & 0 deletions Classes/ResourceManagement/ProxyAwareFileSystemTarget.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);

namespace Sitegeist\MagicWand\ResourceManagement;

use Neos\Flow\Annotations as Flow;
use Neos\Flow\Core\Bootstrap;
use Neos\Flow\Mvc\Routing\UriBuilder;
use Neos\Flow\ResourceManagement\ResourceManager;
use Neos\Flow\ResourceManagement\Target\FileSystemTarget;
use Sitegeist\MagicWand\Domain\Service\ConfigurationService;

class ProxyAwareFileSystemTarget extends FileSystemTarget implements ProxyAwareTargetInterface
{
use ProxyAwareTargetTrait;

/**
* @Flow\Inject
* @var Bootstrap
*/
protected $bootstrap;

/**
* @var UriBuilder
* @Flow\Inject
*/
protected $uriBuilder;

/**
* @var ResourceManager
* @Flow\Inject
*/
protected $resourceManager;

/**
* @var ConfigurationService
* @Flow\Inject
*/
protected $configurationService;
}
11 changes: 11 additions & 0 deletions Classes/ResourceManagement/ProxyAwareStorageInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);

namespace Sitegeist\MagicWand\ResourceManagement;

use Neos\Flow\ResourceManagement\ResourceMetaDataInterface;

interface ProxyAwareStorageInterface
{
public function resourceIsPresentInStorage(ResourceMetaDataInterface $resource): bool;
}
9 changes: 9 additions & 0 deletions Classes/ResourceManagement/ProxyAwareTargetInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);

namespace Sitegeist\MagicWand\ResourceManagement;


interface ProxyAwareTargetInterface
{
}
99 changes: 99 additions & 0 deletions Classes/ResourceManagement/ProxyAwareTargetTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

namespace Sitegeist\MagicWand\ResourceManagement;

use Neos\Flow\Annotations as Flow;
use Neos\Flow\Core\Bootstrap;
use Neos\Flow\Http\Exception as HttpException;
use Neos\Flow\Http\HttpRequestHandlerInterface;
use Neos\Flow\Mvc\ActionRequest;
use Neos\Flow\Mvc\Routing\Exception\MissingActionNameException;
use Neos\Flow\Mvc\Routing\UriBuilder;
use Neos\Flow\ResourceManagement\CollectionInterface;
use Neos\Flow\ResourceManagement\PersistentResource;
use Neos\Flow\ResourceManagement\ResourceManager;
use Neos\Flow\ResourceManagement\Storage\StorageObject;
use Neos\Flow\ResourceManagement\Target\Exception;
use Sitegeist\MagicWand\Domain\Service\ConfigurationService;

trait ProxyAwareTargetTrait
{
public function initializeObject()
{
// initialize uriBuilder with request
$requestHandler = $this->bootstrap->getActiveRequestHandler();
if ($requestHandler instanceof HttpRequestHandlerInterface) {
$request = ActionRequest::fromHttpRequest($requestHandler->getComponentContext()->getHttpRequest());
$this->uriBuilder->setRequest($request);
}
parent::initializeObject();
}

/**
* @param CollectionInterface $collection The collection to publish
* @param callable $callback Function called after each resource publishing
* @return void
*/
public function publishCollection(CollectionInterface $collection, callable $callback = null)
{
if (!$this->configurationService->getCurrentConfigurationByPath('resourceProxy')) {
parent::publishCollection($collection, $callback);
return;
}

/**
* @var ProxyAwareWritableFileSystemStorage $storage
*/
$storage = $collection->getStorage();
if (!$storage instanceof ProxyAwareStorageInterface) {
parent::publishCollection($collection, $callback);
return;
}

foreach ($collection->getObjects($callback) as $object) {
/** @var StorageObject $object */
if ($storage->resourceIsPresentInStorage($object) === false) {
// this storage ignores resources that are not yet in the filesystem as they
// are optimistically created during read operations
continue;
}
$sourceStream = $object->getStream();
$this->publishFile($sourceStream, $this->getRelativePublicationPathAndFilename($object));
fclose($sourceStream);
}
}

/**
* @param PersistentResource $resource
* @return string
* @throws Exception
* @throws HttpException
* @throws MissingActionNameException
*/
public function getPublicPersistentResourceUri(PersistentResource $resource)
{
if (!$this->configurationService->getCurrentConfigurationByPath('resourceProxy')) {
return parent::getPublicPersistentResourceUri($resource);
}

$collection = $this->resourceManager->getCollection($resource->getCollectionName());
$storage = $collection->getStorage();

if (!$storage instanceof ProxyAwareStorageInterface) {
return parent::getPublicPersistentResourceUri($resource);
}

if ($storage->resourceIsPresentInStorage($resource)) {
return parent::getPublicPersistentResourceUri($resource);
}

// build uri to resource controller that will fetch and publish
// the resource asynchronously
return $this->uriBuilder->uriFor(
'index',
['resourceIdentifier' => $resource],
'Resource',
'Sitegeist.MagicWand'
);
}
}
21 changes: 11 additions & 10 deletions Classes/ResourceManagement/ProxyAwareWritableFileSystemStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
use Sitegeist\MagicWand\Domain\Service\ConfigurationService;
use Neos\Utility\Files;

class ProxyAwareWritableFileSystemStorage extends WritableFileSystemStorage
class ProxyAwareWritableFileSystemStorage extends WritableFileSystemStorage implements ProxyAwareStorageInterface
{
/**
* @var ConfigurationService
Expand All @@ -26,11 +26,12 @@ class ProxyAwareWritableFileSystemStorage extends WritableFileSystemStorage
protected $resourceManager;

/**
* @param PersistentResource $resource
* @return string
* @param ResourceMetaDataInterface $resource
* @return bool
*/
public function resourceIsPresentInStorage(ResourceMetaDataInterface $resource) {
$path = $this->getStoragePathAndFilenameByHash($resource->getSha1());
public function resourceIsPresentInStorage(ResourceMetaDataInterface $resource): bool
{
$path = $this->getStoragePathAndFilenameByHash($resource->getSha1());
return file_exists($path);
}

Expand All @@ -57,7 +58,7 @@ public function getStreamByResource(PersistentResource $resource)

$curlEngine = new CurlEngine();
$curlOptions = $resourceProxyConfiguration['curlOptions'] ?? [];
foreach($curlOptions as $key => $value) {
foreach ($curlOptions as $key => $value) {
$curlEngine->setOption(constant($key), $value);
}

Expand All @@ -67,16 +68,16 @@ public function getStreamByResource(PersistentResource $resource)
$subdivideHashPathSegment = $resourceProxyConfiguration['subdivideHashPathSegment'] ?? false;
if ($subdivideHashPathSegment) {
$sha1Hash = $resource->getSha1();
$uri = $resourceProxyConfiguration['baseUri'] .'/_Resources/Persistent/' . $sha1Hash[0] . '/' . $sha1Hash[1] . '/' . $sha1Hash[2] . '/' . $sha1Hash[3] . '/' . $sha1Hash . '/' . rawurlencode($resource->getFilename());
$uri = $resourceProxyConfiguration['baseUri'] . '/_Resources/Persistent/' . $sha1Hash[0] . '/' . $sha1Hash[1] . '/' . $sha1Hash[2] . '/' . $sha1Hash[3] . '/' . $sha1Hash . '/' . rawurlencode($resource->getFilename());
} else {
$uri = $resourceProxyConfiguration['baseUri'] .'/_Resources/Persistent/' . $resource->getSha1() . '/' . rawurlencode($resource->getFilename());
$uri = $resourceProxyConfiguration['baseUri'] . '/_Resources/Persistent/' . $resource->getSha1() . '/' . rawurlencode($resource->getFilename());
}

$response = $browser->request($uri);

if ($response->getStatusCode() == 200 ) {
if ($response->getStatusCode() == 200) {
$stream = $response->getBody()->detach();
$targetPathAndFilename = $this->getStoragePathAndFilenameByHash($resource->getSha1());
$targetPathAndFilename = $this->getStoragePathAndFilenameByHash($resource->getSha1());
if (!file_exists(dirname($targetPathAndFilename))) {
Files::createDirectoryRecursively(dirname($targetPathAndFilename));
}
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ section of your composer.json**.

* Wilhelm Behncke - [email protected]
* Martin Ficzel - [email protected]
* ... and others

*The development and the public-releases of this package is generously sponsored by our employer https://www.sitegeist.de.*

Expand Down Expand Up @@ -108,6 +109,15 @@ the commands.**
```
**Note:** Use this command on a regular basis, because your stash tends to grow **very** large.
## Resource proxies
While cloning the database to your local dev system is manageable even for larger projects, downloading all the assets is often not an option.
For this case the package offers the concept of resource proxies. Once activated, only the resources that are actually used are downloaded just at the moment they are rendered.
This is done by custom implementations of `WritableFileSystemStorage` and `ProxyAwareFileSystemSymlinkTarget` and works out of the box if you use this storage and target in you local development environment.
If you use other local storages, for example a local S3 storage, you can easily build your own proxy aware versions implementing the interfaces `ProxyAwareStorageInterface` and `ProxyAwareTargetInterface`of this package.
## Installation
Sitegeist.Magicwand is available via packagist. Just add `"sitegeist/magicwand" : "~1.0"` to the require-dev section of the composer.json or run `composer require --dev sitegeist/magicwand`. We use semantic-versioning so every breaking change will increase the major-version number.
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
}
],
"require": {
"neos/flow": "~4.0 || ~5.0 || ~6.0 || dev-master"
"neos/flow": "~6.0 || dev-master"
},
"autoload": {
"psr-4": {
Expand Down

0 comments on commit 7d111fa

Please sign in to comment.