From 581c7d4ddc764f8aecd2786e4fa1a76e54ed1072 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=A4u=C3=9Fler?= Date: Wed, 6 Mar 2024 17:30:53 +0100 Subject: [PATCH] [FEATURE] Consider recover timeout in flush queue command --- Classes/Command/FlushQueueCommand.php | 137 +++++++++++++----- Classes/Exception/CommandFailureException.php | 44 ++++++ README.md | 5 +- 3 files changed, 146 insertions(+), 40 deletions(-) create mode 100644 Classes/Exception/CommandFailureException.php diff --git a/Classes/Command/FlushQueueCommand.php b/Classes/Command/FlushQueueCommand.php index 8441cbd..74759aa 100644 --- a/Classes/Command/FlushQueueCommand.php +++ b/Classes/Command/FlushQueueCommand.php @@ -23,8 +23,10 @@ namespace CPSIT\Typo3Mailqueue\Command; +use CPSIT\Typo3Mailqueue\Exception; use CPSIT\Typo3Mailqueue\Mail; use Symfony\Component\Console; +use Symfony\Component\Mailer; use TYPO3\CMS\Core; /** @@ -58,6 +60,12 @@ protected function configure(): void Console\Input\InputOption::VALUE_REQUIRED, 'Maximum number of mails to send in one iteration', ); + $this->addOption( + 'recover-timeout', + 'r', + Console\Input\InputOption::VALUE_REQUIRED, + 'Timeout in seconds for recovering mails that have taken too long to send', + ); } protected function initialize(Console\Input\InputInterface $input, Console\Output\OutputInterface $output): void @@ -78,70 +86,123 @@ protected function initialize(Console\Input\InputInterface $input, Console\Outpu protected function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output): int { $limit = $input->getOption('limit'); + $recoverTimeout = $input->getOption('recover-timeout'); $transport = $this->mailer->getTransport(); - // Early return if unsupported mail transport is configured - if (!($transport instanceof Mail\Transport\QueueableTransport)) { - $message = sprintf( - 'The configured mail transport "%s" is not supported. Please configure a mail spooler as mail transport.', - $transport::class, - ); + try { + // Early return if unsupported mail transport is configured + if (!($transport instanceof Mail\Transport\QueueableTransport)) { + throw new Exception\CommandFailureException( + sprintf( + 'The configured mail transport "%s" is not supported. Please configure a mail spooler as mail transport.', + $transport::class, + ), + self::INVALID, + ); + } - $this->io->error($message); - $this->writeJsonResult([ - 'error' => $message, - ]); + $mailQueue = $transport->getMailQueue()->get(); + $numberOfMailsInQueue = count($mailQueue); - return self::INVALID; - } + // Early return if mail queue is empty + if ($numberOfMailsInQueue === 0) { + $message = 'No mails are currently in queue.'; - $mailQueue = $transport->getMailQueue(); - $numberOfMailsInQueue = count($mailQueue); - $realTransport = $this->mailer->getRealTransport(); + $this->io->success($message); + $this->writeJsonResult([ + 'result' => $message, + ]); + + return self::SUCCESS; + } - // Early return if mail queue is empty - if ($numberOfMailsInQueue === 0) { - $message = 'No mails are currently in queue.'; + // Limit number of sent mails from command option or send all mails + if ($limit !== null) { + $limit = (int)$limit; + } else { + $limit = $numberOfMailsInQueue; + } + + // Early return if invalid limit is provided + if ($limit < 1) { + throw new Exception\CommandFailureException( + 'Limit must be a number greater than or equal to 1.', + self::INVALID, + ); + } - $this->io->success($message); + $this->recover($recoverTimeout, $transport); + $this->flushQueue($limit, $mailQueue, $transport); + } catch (Exception\CommandFailureException $exception) { + $this->io->error($exception->getMessage()); $this->writeJsonResult([ - 'result' => $message, + 'error' => $exception->getMessage(), ]); - return self::SUCCESS; + return $exception->statusCode; } - // Limit number of sent mails from command option or send all mails - if ($limit !== null) { - $limit = (int)$limit; - } else { - $limit = $numberOfMailsInQueue; - } + return self::SUCCESS; + } - // Early return if invalid limit is provided - if ($limit < 1) { - $message = 'Limit must be a number greater than or equal to 1.'; + /** + * @throws Exception\CommandFailureException + */ + private function recover(?string $recoverTimeout, Mail\Transport\QueueableTransport $transport): void + { + if ($recoverTimeout !== null) { + $recoverTimeout = (int)$recoverTimeout; - $this->io->error($message); - $this->writeJsonResult([ - 'error' => $message, - ]); + // Show warning if non-recoverable transport is used with recover timeout + if (!($transport instanceof Mail\Transport\RecoverableTransport)) { + $this->io->warning('You passed --recover-timeout to a non-recoverable mail transport.'); + } - return self::INVALID; + // Early return if invalid recover timeout is provided + if ($recoverTimeout < 1) { + throw new Exception\CommandFailureException( + 'Recover timeout must be a number greater than or equal to 1.', + self::INVALID, + ); + } } + // Recover mails that have taken too long to send + if ($transport instanceof Mail\Transport\RecoverableTransport) { + if ($recoverTimeout !== null) { + $transport->recover($recoverTimeout); + } else { + $transport->recover(); + } + } + } + + /** + * @param list $mailQueueItems + * @throws Exception\CommandFailureException + */ + private function flushQueue(int $limit, array $mailQueueItems, Mail\Transport\QueueableTransport $transport): void + { $this->io->section('Flushing mail queue'); + $numberOfMailsInQueue = count($mailQueueItems); + $realTransport = $this->mailer->getRealTransport(); $progressBar = $this->io->createProgressBar($limit); // Send mails from queue - foreach ($progressBar->iterate($mailQueue, $limit) as $i => $item) { + foreach ($progressBar->iterate($mailQueueItems, $limit) as $i => $item) { if ($i >= $limit) { $progressBar->finish(); break; } - $transport->dequeue($item, $realTransport); + try { + $transport->dequeue($item, $realTransport); + } catch (Mailer\Exception\TransportExceptionInterface $exception) { + $this->io->newLine(2); + + throw new Exception\CommandFailureException($exception->getMessage(), self::FAILURE); + } --$numberOfMailsInQueue; } @@ -172,8 +233,6 @@ protected function execute(Console\Input\InputInterface $input, Console\Output\O 'sent' => $limit, 'remaining' => $numberOfMailsInQueue, ]); - - return self::SUCCESS; } /** diff --git a/Classes/Exception/CommandFailureException.php b/Classes/Exception/CommandFailureException.php new file mode 100644 index 0000000..bdfc4b6 --- /dev/null +++ b/Classes/Exception/CommandFailureException.php @@ -0,0 +1,44 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace CPSIT\Typo3Mailqueue\Exception; + +/** + * CommandFailureException + * + * @author Elias Häußler + * @license GPL-2.0-or-later + * @internal + */ +final class CommandFailureException extends Exception +{ + /** + * @param positive-int $statusCode + */ + public function __construct( + string $message, + public readonly int $statusCode, + ) { + parent::__construct($message, 1709740886); + } +} diff --git a/README.md b/README.md index 79a4db1..6306b61 100644 --- a/README.md +++ b/README.md @@ -120,12 +120,15 @@ with the configured real transport. The extension provides a console command to flush the mail queue: ```bash -typo3 mailqueue:flushqueue [-l|--limit] [-j|--json] +typo3 mailqueue:flushqueue [-l|--limit] [-r|--recover-timeout] [-j|--json] ``` The number of mails to be sent can be limited with `--limit` (or `-l`). If no limit is passed, the whole mail queue is flushed. +For transports implementing the `RecoverableTransport` interface, the recover +timeout can be configured with `--recover-timeout` (or `-r`). + When using `--json` (or `-j`), user-oriented output is written to stderr and result messages are written in JSON format to stdout.