diff --git a/lib/BackgroundJob/IMipMessageJob.php b/lib/BackgroundJob/IMipMessageJob.php index bb0fa95fda..48a5f5904e 100644 --- a/lib/BackgroundJob/IMipMessageJob.php +++ b/lib/BackgroundJob/IMipMessageJob.php @@ -32,16 +32,14 @@ use function method_exists; class IMipMessageJob extends TimedJob { - - /** @var IMipService */ - private $iMipService; + private IMipService $iMipService; public function __construct(ITimeFactory $time, IMipService $iMipService) { parent::__construct($time); // Run once per hour - $this->setInterval(60*60); + $this->setInterval(60 * 60); /** * @todo remove checks with 24+ */ diff --git a/lib/Db/MessageMapper.php b/lib/Db/MessageMapper.php index 6ad21e2f13..c81407fe7b 100644 --- a/lib/Db/MessageMapper.php +++ b/lib/Db/MessageMapper.php @@ -1239,7 +1239,7 @@ public function findIMipMessages(): array { $qb->expr()->eq('imip_message', $qb->createNamedParameter(true, IQueryBuilder::PARAM_BOOL), IQueryBuilder::PARAM_BOOL), $qb->expr()->eq('imip_processed', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL), IQueryBuilder::PARAM_BOOL), $qb->expr()->eq('imip_error', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL), IQueryBuilder::PARAM_BOOL), - $qb->expr()->gt('sent_at', ($this->timeFactory->getTime() - 60*60*24*14), $qb->createNamedParameter(false, IQueryBuilder::PARAM_INT)), + $qb->expr()->gt('sent_at', ($this->timeFactory->getTime() - 60 * 60 * 24 * 14), $qb->createNamedParameter(false, IQueryBuilder::PARAM_INT)), ); return $this->findEntities($select); diff --git a/lib/Migration/Version1140Date20220630113356.php b/lib/Migration/Version1140Date20220630113356.php index c4a80d7568..2384c75162 100644 --- a/lib/Migration/Version1140Date20220630113356.php +++ b/lib/Migration/Version1140Date20220630113356.php @@ -44,19 +44,19 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt $schema = $schemaClosure(); $messagesTable = $schema->getTable('mail_messages'); - if(!$messagesTable->hasColumn('imip_message')) { + if (!$messagesTable->hasColumn('imip_message')) { $messagesTable->addColumn('imip_message', 'boolean', [ 'notnull' => false, 'default' => false, ]); } - if(!$messagesTable->hasColumn('imip_processed')) { + if (!$messagesTable->hasColumn('imip_processed')) { $messagesTable->addColumn('imip_processed', 'boolean', [ 'notnull' => false, 'default' => false, ]); } - if(!$messagesTable->hasColumn('imip_error')) { + if (!$messagesTable->hasColumn('imip_error')) { $messagesTable->addColumn('imip_error', 'boolean', [ 'notnull' => false, 'default' => false, diff --git a/lib/Service/IMipService.php b/lib/Service/IMipService.php index 527a362d83..6974de450d 100644 --- a/lib/Service/IMipService.php +++ b/lib/Service/IMipService.php @@ -26,20 +26,23 @@ namespace OCA\Mail\Service; -use OCA\Mail\Db\MailAccountMapper; +use OCA\Mail\Account; use OCA\Mail\Db\Mailbox; use OCA\Mail\Db\MailboxMapper; use OCA\Mail\Db\Message; use OCA\Mail\Db\MessageMapper; +use OCA\Mail\Exception\ServiceException; use OCP\AppFramework\Db\DoesNotExistException; use OCP\Calendar\IManager; +use Psr\Log\LoggerInterface; class IMipService { - private MessageMapper $messageMapper; private MailboxMapper $mailboxMapper; private AccountService $accountService; private MailManager $mailManager; + private IManager $calendarManager; + private LoggerInterface $logger; public function __construct( MessageMapper $messageMapper, @@ -47,31 +50,33 @@ public function __construct( AccountService $accountService, MailManager $mailManager, IManager $manager, + LoggerInterface $logger, ) { $this->messageMapper = $messageMapper; $this->mailboxMapper = $mailboxMapper; $this->accountService = $accountService; $this->mailManager = $mailManager; + $this->calendarManager = $manager; + $this->logger = $logger; } public function process() { $messages = $this->messageMapper->findIMipMessages(); + // Collect all mailboxes in memory $mailboxIds = array_unique(array_map(function (Message $message) { return $message->getMailboxId(); }, $messages)); $mailboxes = array_combine($mailboxIds, array_map(function (int $mailboxId) { try { - $mailbox = $this->mailboxMapper->findById($mailboxId); - if ($mailbox->getSpecialUse() === '["sent"]' || $mailbox->getSpecialUse() === '["drafts"]') { - return null; - } + return $this->mailboxMapper->findById($mailboxId); } catch (DoesNotExistException $e) { return null; } }, $mailboxIds)); + // Collect all accounts in memory $accountIds = array_unique(array_map(function (Mailbox $mailbox) { return $mailbox->getAccountId(); }, $mailboxes)); @@ -84,25 +89,57 @@ public function process() { } }, $accountIds)); + // build the updated messages on a per-account basis, so we can bulk update for each account + $processedMessages = []; + foreach ($messages as $message) { + /** @var Mailbox $mailbox */ + $mailbox = $mailboxes[$message->getMailboxId()]; + /** @var Account $account */ + $account = $accounts[$mailbox->getAccountId()]; + // mailbox not in collated array, maybe specal use? + // no processing for drafts and sent items + if ($mailbox->getSpecialUse() === '["sent"]' || $mailbox->getSpecialUse() === '["drafts"]') { // does this need more use cases? Also probably won't work? @todo + $message->setImipProcessed(true); // Silently drop from passing to DAV and mark as processed, so we won't run into this message again. + $processedMessages[$account->getId()] = $message; + continue; + } - foreach ($messages as $message) { - $imapMessage = $this->mailManager->getImapMessage($accounts[$message->getAccountId()], $mailboxes[$message->getMailboxId()], $message->getUid()); + try { + $imapMessage = $this->mailManager->getImapMessage($account, $mailbox, $message->getUid()); + } catch (ServiceException $e) { + $message->setImipError(true); + $processedMessages[$account->getId()] = $message; + continue; + } - if(empty($imapMessage->scheduling)) { + if (empty($imapMessage->scheduling)) { // No scheduling info, maybe the DB is wrong $message->setImipError(true); - $this->messageMapper->update($message); + $processedMessages[$account->getId()] = $message; continue; } - if($imapMessage->scheduling['method'] === 'REPLY' || $imapMessage->scheduling['method'] == 'CANCEL') { - // automatically process this - - $message->setImipProcessed(true); - $this->messageMapper->update($message); + $principalUri = ''; + $sender = $imapMessage->getFrom()->first()->getEmail(); + $recipient = $account->getEmail(); + $processed = false; + if ($imapMessage->scheduling['method'] === 'REPLY') { + $processed = $this->calendarManager->handleIMipReply($principalUri, $sender, $recipient, $imapMessage->scheduling['content']); + } elseif ($imapMessage->scheduling['method'] === 'CANCEL') { + $processed = $this->calendarManager->handleIMipCancel($principalUri, $sender, $recipient, $imapMessage->scheduling['content']); } + $message->setImipProcessed($processed); + $message->setImipError(!$processed); + $processedMessages[$account->getId()] = $message; } + foreach ($processedMessages as $accountId => $messages) { + try { + $this->messageMapper->updateBulk($accounts[$accountId], false, $messages); + } catch (\Throwable $e) { + $this->logger->error('Could not update iMip messages for account ' . $accountId, ['exception' => $e]); + } + } } }