Skip to content

Commit

Permalink
Merge pull request #9 from CPS-IT/feature/recoverable-timeout
Browse files Browse the repository at this point in the history
[FEATURE] Consider recover timeout in flush queue command
  • Loading branch information
eliashaeussler authored Mar 6, 2024
2 parents 0a83455 + 581c7d4 commit b0128bd
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 40 deletions.
137 changes: 98 additions & 39 deletions Classes/Command/FlushQueueCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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
Expand All @@ -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<Mail\Queue\MailQueueItem> $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;
}
Expand Down Expand Up @@ -172,8 +233,6 @@ protected function execute(Console\Input\InputInterface $input, Console\Output\O
'sent' => $limit,
'remaining' => $numberOfMailsInQueue,
]);

return self::SUCCESS;
}

/**
Expand Down
44 changes: 44 additions & 0 deletions Classes/Exception/CommandFailureException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

/*
* This file is part of the TYPO3 CMS extension "mailqueue".
*
* Copyright (C) 2024 Elias Häußler <e.haeussler@familie-redlich.de>
*
* 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 <https://www.gnu.org/licenses/>.
*/

namespace CPSIT\Typo3Mailqueue\Exception;

/**
* CommandFailureException
*
* @author Elias Häußler <e.haeussler@familie-redlich.de>
* @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);
}
}
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down

0 comments on commit b0128bd

Please sign in to comment.