diff --git a/Classes/Jobs/CreateImageVariantsJob.php b/Classes/Jobs/CreateImageVariantsJob.php new file mode 100644 index 0000000..58c857c --- /dev/null +++ b/Classes/Jobs/CreateImageVariantsJob.php @@ -0,0 +1,70 @@ +imageIdentifier = $imageIdentifier; + } + + public function execute(QueueInterface $queue, Message $message): bool + { + try { + $image = $this->imageRepository->findByIdentifier($this->imageIdentifier); + if ($image === null) { + $this->logger->notice(sprintf('%s Job skipped for image %s – image not found.', $this->getIdentifier(), $this->imageIdentifier), LogEnvironment::fromMethodName(__METHOD__)); + return true; + } + $variants = $this->assetVariantGenerator->createVariants($image); + $this->imageRepository->update($image); + $this->logger->debug(sprintf('%s Job succeeded for image %s – %d variants created.', $this->getIdentifier(), $this->imageIdentifier, count($variants)), LogEnvironment::fromMethodName(__METHOD__)); + } catch (\Exception $exception) { + $this->logger->error(sprintf('%s Job failed. Error message: %s', $this->getIdentifier(), $exception->getMessage()), LogEnvironment::fromMethodName(__METHOD__)); + return false; + } + + return true; + } + + public function getIdentifier(): string + { + return 'CreateImageVariants'; + } + + public function getLabel(): string + { + return sprintf('CreateImageVariantsJob (image: "%s")', $this->imageIdentifier); + } +} diff --git a/Classes/Package.php b/Classes/Package.php new file mode 100644 index 0000000..137d4ff --- /dev/null +++ b/Classes/Package.php @@ -0,0 +1,37 @@ +getSignalSlotDispatcher(); + + // handle asset variant creation in job queue + $dispatcher->connect(AssetService::class, 'assetCreated', function (AssetInterface $asset) use ($bootstrap) { + $configurationManager = $bootstrap->getObjectManager()->get(ConfigurationManager::class); + if ($configurationManager->getConfiguration(ConfigurationManager::CONFIGURATION_TYPE_SETTINGS, 'Neos.Media.autoCreateImageVariantPresets') === false) { + if ($asset instanceof Image) { + $persistenceManager = $bootstrap->getObjectManager()->get(PersistenceManagerInterface::class); + $job = new CreateImageVariantsJob( + $persistenceManager->getIdentifierByObject($asset) + ); + + $queueName = $configurationManager->getConfiguration(ConfigurationManager::CONFIGURATION_TYPE_SETTINGS, 'Flownative.Neos.AsyncImageVariants.jobQueue'); + $bootstrap->getObjectManager()->get(JobManager::class)->queue($queueName, $job); + } + } + }); + } +} diff --git a/Configuration/Settings.Media.yaml b/Configuration/Settings.Media.yaml new file mode 100644 index 0000000..b5b4678 --- /dev/null +++ b/Configuration/Settings.Media.yaml @@ -0,0 +1,4 @@ +Neos: + Media: + # these are created asynchronously using a job queue + autoCreateImageVariantPresets: false diff --git a/Configuration/Settings.yaml b/Configuration/Settings.yaml new file mode 100644 index 0000000..1bfa656 --- /dev/null +++ b/Configuration/Settings.yaml @@ -0,0 +1,5 @@ +Flownative: + Neos: + AsyncImageVariants: + # the queue to use for jobs + jobQueue: 'media-queue' diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d557681 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2024 Karsten Dambekalns / Flownative GmbH + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d309912 --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ +[![MIT license](http://img.shields.io/badge/license-MIT-brightgreen.svg)](http://opensource.org/licenses/MIT) +[![Packagist](https://img.shields.io/packagist/v/flownative/neos-asyncimagevariants.svg)](https://packagist.org/packages/flownative/neos-asyncimagevariants) +[![Packagist](https://img.shields.io/packagist/dm/flownative/neos-asyncimagevariants)](https://packagist.org/packages/flownative/neos-asyncimagevariants) +[![Maintenance level: Love](https://img.shields.io/badge/maintenance-%E2%99%A1%E2%99%A1-ff69b4.svg)](https://www.flownative.com/en/products/open-source.html) + +# Flownative.Neos.AsyncImageVariants + +## Description + +This [Flow](https://flow.neos.io) package allows to asynchronously generate image variants for Neos.Media images. + +It does this by switching off automatic variant creation (through settings) and wiring a slot to the `assetCreated` +signal emitted in the `AssetService`. That slot creates a job in the job queue that executes the asset variant +creation asynchronously. + +## Installation + +This is installed as a regular Flow package via Composer. For your existing project, simply include +`flownative/neos-asyncimagevariants` into the dependencies of your Flow or Neos distribution: + + composer require flownative/neos-asyncimagevariants + +## Configuration + +The package itself has one configuration option for the job queue name to use, it defaults to `media-queue`. + +```yaml +Flownative: + Neos: + AsyncImageVariants: + # the queue to use for jobs + jobQueue: 'media-queue' +``` + +That queue of course needs to be configured, e.g. like this: + +```yaml +Flowpack: + JobQueue: + Common: + queues: + 'media-queue': + className: 'Flowpack\JobQueue\Doctrine\Queue\DoctrineQueue' + executeIsolated: true + releaseOptions: + delay: 15 + options: + backendOptions: + driver: '%env:DATABASE_DRIVER%' + host: '%env:DATABASE_HOST%' + port: '%env:DATABASE_PORT%' + dbname: '%env:DATABASE_NAME%' + user: '%env:DATABASE_USER%' + password: '%env:DATABASE_PASSWORD%' +``` + +Make sure to run `./flow job:work media-queue` continuously in the background. + +## Troubleshooting + +- If things don't work as expected, check the system log. +- Check if jobs are queued by using `./flow queue:list` +- Run `./flow job:work media-queue --verbose --limit 1` to debug job execution diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..7c5d904 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,11 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| 1.x | :white_check_mark: | + +## Reporting a Vulnerability + +Please email us at team@flownative.com with details. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..6174e76 --- /dev/null +++ b/composer.json @@ -0,0 +1,24 @@ +{ + "name": "flownative/neos-asyncimagevariants", + "type": "neos-package", + "description": "A package that allows for asynchronous image variant creation in Neos.Media", + "license": "MIT", + "require": { + "php": "^8.2", + "neos/media": "^8.3", + "flowpack/jobqueue-common": "^3.3" + }, + "suggest": { + "flowpack/jobqueue-doctrine": "To store jobs", + "flowpack/jobqueue-redis": "To store jobs", + "flowpack/jobqueue-beanstalkd": "To store jobs" + }, + "autoload": { + "psr-4": { + "Flownative\\Neos\\AsyncImageVariants\\": "Classes" + } + }, + "extras": { + "installer-name": "Flownative.Neos.AsyncImageVariants" + } +}