Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/mail.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--appinfo/info.xml3
-rw-r--r--lib/BackgroundJob/OutboxWorkerJob.php57
-rw-r--r--lib/Db/LocalAttachmentMapper.php3
-rw-r--r--lib/Db/LocalMessageMapper.php33
-rw-r--r--lib/Db/RecipientMapper.php4
-rw-r--r--lib/Service/OutboxService.php54
-rw-r--r--tests/Integration/Service/OutboxServiceIntegrationTest.php39
-rw-r--r--tests/Unit/Service/OutboxServiceTest.php19
8 files changed, 207 insertions, 5 deletions
diff --git a/appinfo/info.xml b/appinfo/info.xml
index c26a173d6..fce914275 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -12,7 +12,7 @@
- **🙈 We’re not reinventing the wheel!** Based on the great [Horde](https://horde.org) libraries.
- **📬 Want to host your own mail server?** We don’t have to reimplement this as you could set up [Mail-in-a-Box](https://mailinabox.email)!
]]></description>
- <version>1.12.0-alpha.3</version>
+ <version>1.12.0-alpha.4</version>
<licence>agpl</licence>
<author>Greta Doçi</author>
<author homepage="https://github.com/nextcloud/groupware">Nextcloud Groupware Team</author>
@@ -34,6 +34,7 @@
</dependencies>
<background-jobs>
<job>OCA\Mail\BackgroundJob\CleanupJob</job>
+ <job>OCA\Mail\BackgroundJob\OutboxWorkerJob</job>
</background-jobs>
<repair-steps>
<post-migration>
diff --git a/lib/BackgroundJob/OutboxWorkerJob.php b/lib/BackgroundJob/OutboxWorkerJob.php
new file mode 100644
index 000000000..305adf8f7
--- /dev/null
+++ b/lib/BackgroundJob/OutboxWorkerJob.php
@@ -0,0 +1,57 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @copyright 2022 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2022 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\Mail\BackgroundJob;
+
+use OCA\Mail\Service\OutboxService;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\TimedJob;
+use function defined;
+use function method_exists;
+
+class OutboxWorkerJob extends TimedJob {
+
+ /** @var OutboxService */
+ private $outboxService;
+
+ public function __construct(ITimeFactory $time,
+ OutboxService $outboxService) {
+ parent::__construct($time);
+
+ // Run once per five minutes
+ $this->setInterval(5 * 60);
+ /**
+ * @todo remove checks with 24+
+ */
+ if (defined('\OCP\BackgroundJob\IJob::TIME_SENSITIVE') && method_exists($this, 'setTimeSensitivity')) {
+ $this->setTimeSensitivity(self::TIME_SENSITIVE);
+ }
+ $this->outboxService = $outboxService;
+ }
+
+ protected function run($argument): void {
+ $this->outboxService->flush();
+ }
+}
diff --git a/lib/Db/LocalAttachmentMapper.php b/lib/Db/LocalAttachmentMapper.php
index 3bb38a2f4..50bb25158 100644
--- a/lib/Db/LocalAttachmentMapper.php
+++ b/lib/Db/LocalAttachmentMapper.php
@@ -60,6 +60,9 @@ class LocalAttachmentMapper extends QBMapper {
* @return LocalAttachment[]
*/
public function findByLocalMessageIds(array $localMessageIds): array {
+ if (empty($localMessageIds)) {
+ return [];
+ }
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
diff --git a/lib/Db/LocalMessageMapper.php b/lib/Db/LocalMessageMapper.php
index 3b36381b6..71029e672 100644
--- a/lib/Db/LocalMessageMapper.php
+++ b/lib/Db/LocalMessageMapper.php
@@ -28,6 +28,7 @@ namespace OCA\Mail\Db;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\DB\Exception as DBException;
use Throwable;
+use function array_filter;
use function array_map;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
@@ -117,6 +118,38 @@ class LocalMessageMapper extends QBMapper {
}
/**
+ * Find all messages that should be sent
+ *
+ * @param int $time upper bound send time stamp
+ *
+ * @return LocalMessage[]
+ */
+ public function findDue(int $time): array {
+ $qb = $this->db->getQueryBuilder();
+ $select = $qb->select('*')
+ ->from($this->getTableName())
+ ->where(
+ $qb->expr()->isNotNull('send_at'),
+ $qb->expr()->lte('send_at', $qb->createNamedParameter($time, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT)
+ );
+ $messages = $this->findEntities($select);
+ $ids = array_map(function (LocalMessage $message) {
+ return $message->getId();
+ }, $messages);
+ $attachments = $this->attachmentMapper->findByLocalMessageIds($ids);
+ $recipients = $this->recipientMapper->findByLocalMessageIds($ids);
+ return array_map(static function ($message) use ($attachments, $recipients) {
+ $message->setAttachments(array_filter($attachments, function (LocalAttachment $attachment) use ($message) {
+ return $attachment->getLocalMessageId() === $message->getId();
+ }));
+ $message->setRecipients(array_filter($recipients, function (Recipient $recipient) use ($message) {
+ return $recipient->getLocalMessageId() === $message->getId();
+ }));
+ return $message;
+ }, $messages);
+ }
+
+ /**
* @param Recipient[] $to
* @param Recipient[] $cc
* @param Recipient[] $bcc
diff --git a/lib/Db/RecipientMapper.php b/lib/Db/RecipientMapper.php
index 8de6728ff..4f5b8bcc0 100644
--- a/lib/Db/RecipientMapper.php
+++ b/lib/Db/RecipientMapper.php
@@ -57,8 +57,10 @@ class RecipientMapper extends QBMapper {
* @return Recipient[]
*/
public function findByLocalMessageIds(array $localMessageIds): array {
+ if (empty($localMessageIds)) {
+ return [];
+ }
$qb = $this->db->getQueryBuilder();
-
$query = $qb->select('*')
->from($this->getTableName())
->where(
diff --git a/lib/Service/OutboxService.php b/lib/Service/OutboxService.php
index 90e778c8c..6c7d0470f 100644
--- a/lib/Service/OutboxService.php
+++ b/lib/Service/OutboxService.php
@@ -38,6 +38,9 @@ use OCA\Mail\IMAP\IMAPClientFactory;
use OCA\Mail\Service\Attachment\AttachmentService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\EventDispatcher\IEventDispatcher;
+use OCP\AppFramework\Utility\ITimeFactory;
+use Psr\Log\LoggerInterface;
+use Throwable;
class OutboxService implements ILocalMailboxService {
@@ -59,18 +62,33 @@ class OutboxService implements ILocalMailboxService {
/** @var IMailManager */
private $mailManager;
+ /** @var AccountService */
+ private $accountService;
+
+ /** @var ITimeFactory */
+ private $timeFactory;
+
+ /** @var LoggerInterface */
+ private $logger;
+
public function __construct(IMailTransmission $transmission,
LocalMessageMapper $mapper,
AttachmentService $attachmentService,
IEventDispatcher $eventDispatcher,
IMAPClientFactory $clientFactory,
- IMailManager $mailManager) {
+ IMailManager $mailManager,
+ AccountService $accountService,
+ ITimeFactory $timeFactory,
+ LoggerInterface $logger) {
$this->transmission = $transmission;
$this->mapper = $mapper;
$this->attachmentService = $attachmentService;
$this->eventDispatcher = $eventDispatcher;
$this->clientFactory = $clientFactory;
$this->mailManager = $mailManager;
+ $this->timeFactory = $timeFactory;
+ $this->logger = $logger;
+ $this->accountService = $accountService;
}
/**
@@ -153,4 +171,38 @@ class OutboxService implements ILocalMailboxService {
new OutboxMessageCreatedEvent($account, $message)
);
}
+
+ public function flush(): void {
+ $messages = $this->mapper->findDue(
+ $this->timeFactory->getTime()
+ );
+
+ $accountIds = array_unique(array_map(function ($message) {
+ return $message->getAccountId();
+ }, $messages));
+
+ $accounts = array_combine($accountIds, array_map(function ($accountId) {
+ return $this->accountService->findById($accountId);
+ }, $accountIds));
+
+ foreach ($messages as $message) {
+ try {
+ // TODO: memoize accounts
+ $this->sendMessage(
+ $message,
+ $accounts[$message->getAccountId()],
+ );
+ $this->logger->debug('Outbox message {id} sent', [
+ 'id' => $message->getId(),
+ ]);
+ } catch (Throwable $e) {
+ // Failure of one message should not stop sending other messages
+ // Log and continue
+ $this->logger->warning('Could not send outbox message {id}: ' . $e->getMessage(), [
+ 'id' => $message->getId(),
+ 'exception' => $e,
+ ]);
+ }
+ }
+ }
}
diff --git a/tests/Integration/Service/OutboxServiceIntegrationTest.php b/tests/Integration/Service/OutboxServiceIntegrationTest.php
index 501433d16..cbfa17c13 100644
--- a/tests/Integration/Service/OutboxServiceIntegrationTest.php
+++ b/tests/Integration/Service/OutboxServiceIntegrationTest.php
@@ -37,6 +37,7 @@ use OCA\Mail\Db\MailAccount;
use OCA\Mail\Db\MailboxMapper;
use OCA\Mail\Db\MessageMapper;
use OCA\Mail\IMAP\IMAPClientFactory;
+use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\Attachment\AttachmentService;
use OCA\Mail\Service\Attachment\AttachmentStorage;
use OCA\Mail\Service\OutboxService;
@@ -45,11 +46,13 @@ use OCA\Mail\Tests\Integration\Framework\ImapTest;
use OCA\Mail\Tests\Integration\Framework\ImapTestAccount;
use OCA\Mail\Tests\Integration\TestCase;
use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\AppFramework\Utility\ITimeFactory;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Folder;
use OCP\IServerContainer;
use OCP\IUser;
use Psr\Container\ContainerInterface;
+use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
class OutboxServiceIntegrationTest extends TestCase {
@@ -84,6 +87,12 @@ class OutboxServiceIntegrationTest extends TestCase {
/** @var Folder */
private $userFolder;
+ /** @var */
+ private $accountService;
+
+ /** @var ITimeFactory */
+ private $timeFactory;
+
protected function setUp(): void {
parent::setUp();
@@ -109,6 +118,8 @@ class OutboxServiceIntegrationTest extends TestCase {
$this->transmission = OC::$server->get(IMailTransmission::class);
$this->eventDispatcher = OC::$server->get(IEventDispatcher::class);
$this->clientFactory = OC::$server->get(IMAPClientFactory::class);
+ $this->accountService = OC::$server->get(AccountService::class);
+ $this->timeFactory = OC::$server->get(ITimeFactory::class);
$this->db = \OC::$server->getDatabaseConnection();
$qb = $this->db->getQueryBuilder();
@@ -121,7 +132,10 @@ class OutboxServiceIntegrationTest extends TestCase {
$this->attachmentService,
$this->eventDispatcher,
$this->clientFactory,
- $mailManager
+ $mailManager,
+ $this->accountService,
+ $this->timeFactory,
+ $this->createMock(LoggerInterface::class)
);
}
@@ -348,4 +362,27 @@ class OutboxServiceIntegrationTest extends TestCase {
$this->expectException(DoesNotExistException::class);
$this->outbox->getMessage($message->getId(), $this->user->getUID());
}
+
+ public function testSaveAndFlush(): void {
+ $message = new LocalMessage();
+ $message->setType(LocalMessage::TYPE_OUTGOING);
+ $message->setAccountId($this->account->getId());
+ $message->setSubject('subject');
+ $message->setBody('message');
+ $message->setHtml(true);
+ $message->setSendAt(100);
+
+ $to = [[
+ 'label' => 'Penny',
+ 'email' => 'library@stardewvalley.com'
+ ]];
+
+ $saved = $this->outbox->saveMessage(new Account($this->account), $message, $to, [], []);
+ $this->assertNotEmpty($saved->getRecipients());
+ $this->assertEmpty($saved->getAttachments());
+
+ $this->outbox->flush();
+ $this->expectException(DoesNotExistException::class);
+ $this->outbox->getMessage($saved->getId(), $this->user->getUID());
+ }
}
diff --git a/tests/Unit/Service/OutboxServiceTest.php b/tests/Unit/Service/OutboxServiceTest.php
index c743f635b..78b857bc5 100644
--- a/tests/Unit/Service/OutboxServiceTest.php
+++ b/tests/Unit/Service/OutboxServiceTest.php
@@ -36,6 +36,7 @@ use OCA\Mail\Db\LocalMessageMapper;
use OCA\Mail\Db\Recipient;
use OCA\Mail\Exception\ClientException;
use OCA\Mail\IMAP\IMAPClientFactory;
+use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\Attachment\AttachmentService;
use OCA\Mail\Service\MailTransmission;
use OCA\Mail\Service\OutboxService;
@@ -43,6 +44,7 @@ use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\DB\Exception;
use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
class OutboxServiceTest extends TestCase {
@@ -71,6 +73,15 @@ class OutboxServiceTest extends TestCase {
/** @var IMailManager|MockObject */
private $mailManager;
+ /** @var AccountService|MockObject */
+ private $accountService;
+
+ /** @var ITimeFactory|MockObject */
+ private $timeFactory;
+
+ /** @var MockObject|LoggerInterface */
+ private $logger;
+
protected function setUp(): void {
parent::setUp();
@@ -79,13 +90,19 @@ class OutboxServiceTest extends TestCase {
$this->attachmentService = $this->createMock(AttachmentService::class);
$this->clientFactory = $this->createMock(IMAPClientFactory::class);
$this->mailManager = $this->createMock(IMailManager::class);
+ $this->accountService = $this->createMock(AccountService::class);
+ $this->timeFactory = $this->createMock(ITimeFactory::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
$this->outboxService = new OutboxService(
$this->transmission,
$this->mapper,
$this->attachmentService,
$this->createMock(EventDispatcher::class),
$this->clientFactory,
- $this->mailManager
+ $this->mailManager,
+ $this->accountService,
+ $this->timeFactory,
+ $this->logger,
);
$this->userId = 'linus';
$this->time = $this->createMock(ITimeFactory::class);