Skip to content

Commit

Permalink
fix: perform bulk message actions
Browse files Browse the repository at this point in the history
Signed-off-by: SebastianKrupinski <[email protected]>
  • Loading branch information
SebastianKrupinski committed Oct 22, 2024
1 parent 1c9b6e5 commit 313785b
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 1 deletion.
5 changes: 5 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,11 @@
'url' => '/api/messages/{id}/flags',
'verb' => 'PUT'
],
[
'name' => 'messages#changeFlags',
'url' => '/api/messages/flags',
'verb' => 'PUT'
],
[
'name' => 'messages#setTag',
'url' => '/api/messages/{id}/tags/{imapLabel}',
Expand Down
21 changes: 20 additions & 1 deletion lib/Controller/MessagesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\AiIntegrations\AiIntegrationsService;
use OCA\Mail\Service\ItineraryService;
use OCA\Mail\Service\MessageOperationService;
use OCA\Mail\Service\SmimeService;
use OCA\Mail\Service\SnoozeService;
use OCP\AppFramework\Controller;
Expand Down Expand Up @@ -94,7 +95,8 @@ public function __construct(string $appName,
IDkimService $dkimService,
IUserPreferences $preferences,
SnoozeService $snoozeService,
AiIntegrationsService $aiIntegrationService) {
AiIntegrationsService $aiIntegrationService,
private MessageOperationService $messageOperationService) {
parent::__construct($appName, $request);
$this->accountService = $accountService;
$this->mailManager = $mailManager;
Expand Down Expand Up @@ -782,6 +784,23 @@ public function setFlags(int $id, array $flags): JSONResponse {
return new JSONResponse();
}

/**
*
* @NoAdminRequired
*
* @param array<int,int> $identifiers
* @param array<int,string> $flags
*
* @return JSONResponse
*/
#[TrapError]
public function changeFlags(array $identifiers, array $flags): JSONResponse {

$this->messageOperationService->changeFlags($this->currentUserId, $identifiers, $flags);

Check failure on line 799 in lib/Controller/MessagesController.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-master

InvalidArgument

lib/Controller/MessagesController.php:799:83: InvalidArgument: Argument 3 of OCA\Mail\Service\MessageOperationService::changeFlags expects array<string, bool>, but array<int, string> provided (see https://psalm.dev/004)
// TODO: add proper responses
return new JSONResponse();
}

/**
* @NoAdminRequired
*
Expand Down
19 changes: 19 additions & 0 deletions lib/Db/MailAccountMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,25 @@ public function findById(int $id): MailAccount {
return $this->findEntity($query);
}

/**
* Finds all mail accounts by account ids
*
* @param array<int,int> $identifiers
*
* @return array<int,MailAccount>
*/
public function findByIds(array $identifiers): array {

$cmd = $this->db->getQueryBuilder();
$cmd->select('*')
->from($this->getTableName())
->where(
$cmd->expr()->in('id', $cmd->createNamedParameter($identifiers, IQueryBuilder::PARAM_STR_ARRAY), IQueryBuilder::PARAM_STR_ARRAY)
);

return $this->findEntities($cmd);
}

/**
* Finds all Mail Accounts by user id existing for this user
*
Expand Down
22 changes: 22 additions & 0 deletions lib/Db/MessageMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,28 @@ public function findUidsForIds(Mailbox $mailbox, array $ids) {
}, array_chunk($ids, 1000));
}

/**
* @param array<int,int> $identifiers
*
* @return array
*/
public function findMailboxAndUid(array $identifiers): array {

if ($identifiers === []) {
return [];
}

$cmd = $this->db->getQueryBuilder();
$cmd->select('mailbox_id', 'uid')
->from($this->getTableName())
->where(
$cmd->expr()->in('id', $cmd->createNamedParameter($identifiers, IQueryBuilder::PARAM_STR_ARRAY), IQueryBuilder::PARAM_STR_ARRAY)
);

return $cmd->executeQuery()->fetchAll();

}

/**
* @param Account $account
*
Expand Down
36 changes: 36 additions & 0 deletions lib/IMAP/MessageMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,24 @@ public function addFlag(Horde_Imap_Client_Socket $client,
);
}

/**
* @throws Horde_Imap_Client_Exception
*/
public function addFlags(
Horde_Imap_Client_Socket $client,
Mailbox $mailbox,
array $uids,
array $flags
): void {
$client->store(
$mailbox->getName(),
[
'ids' => new Horde_Imap_Client_Ids($uids),
'add' => $flags,
]
);
}

/**
* @throws Horde_Imap_Client_Exception
*/
Expand All @@ -435,6 +453,24 @@ public function removeFlag(Horde_Imap_Client_Socket $client,
);
}

/**
* @throws Horde_Imap_Client_Exception
*/
public function removeFlags(
Horde_Imap_Client_Socket $client,
Mailbox $mailbox,
array $uids,
array $flags
): void {
$client->store(
$mailbox->getName(),
[
'ids' => new Horde_Imap_Client_Ids($uids),
'remove' => $flags,
]
);
}

/**
* @param Horde_Imap_Client_Socket $client
* @param Mailbox $mailbox
Expand Down
103 changes: 103 additions & 0 deletions lib/Service/MessageOperationService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Mail\Service;

use Horde_Imap_Client_Exception;
use OCA\Mail\Account;
use OCA\Mail\Db\MailAccountMapper;
use OCA\Mail\Db\MailboxMapper;
use OCA\Mail\Db\MessageMapper;
use OCA\Mail\IMAP\IMAPClientFactory;
use OCA\Mail\IMAP\MessageMapper as ImapMessageMapper;
use OCA\Mail\Service\MailManager;

class MessageOperationService {

public function __construct(
protected IMAPClientFactory $clientFactory,
protected MailAccountMapper $accountMapper,
protected MailboxMapper $mailboxMapper,
protected MessageMapper $messageMapper,
protected MailManager $mailManager,
protected ImapMessageMapper $imapMessageMapper
) {}

// group messages by mailbox ['mailbox_id' => [message_id, message_id]]
protected function groupByMailbox(array $collection) {
return array_reduce($collection, function ($carry, $pair) {
if (!isset($carry[$pair['mailbox_id']])) {
$carry[$pair['mailbox_id']] = [];
}
$carry[$pair['mailbox_id']][] = $pair['uid'];
return $carry;
}, []);
}

// group mailboxes by account ['account_id' => [mailbox object]]
protected function groupByAccount(array $collection) {
return array_reduce($collection, function ($carry, $entry) {
if (!isset($carry[$entry->getAccountId()])) {
$carry[$entry->getAccountId()] = [];
}
$carry[$entry->getAccountId()][] = $entry;
return $carry;
}, []);
}

/**
* @param string $userId system user id
* @param array<int,int> $identifiers message ids
* @param array<string,bool> $flags message flags
*/
public function changeFlags(string $userId, array $identifiers, array $flags): void {

// retrieve meta data [uid, mailbox_id] for all messages
$messages = $this->groupByMailbox($this->messageMapper->findMailboxAndUid($identifiers));
// retrieve all mailboxes
$mailboxes = $this->groupByAccount($this->mailboxMapper->findByIds(array_keys($messages)));
// retrieve all accounts
$accounts = $this->accountMapper->findByIds(array_keys($mailboxes));

foreach ($accounts as $account) {
$account = new Account($account);
// determine if account belongs to the user and skip if not
if ($account->getUserId() != $userId) {
continue;
}

$client = $this->clientFactory->getClient($account);

try {
foreach ($mailboxes[$account->getId()] as $mailbox) {
foreach ($flags as $flag => $value) {
$value = filter_var($value, FILTER_VALIDATE_BOOLEAN);
// Only send system flags to the IMAP server as other flags might not be supported
$imapFlags = $this->mailManager->filterFlags($client, $account, $flag, $mailbox->getName());
if (empty($imapFlags)) {
continue;
}
if ($value) {
$this->imapMessageMapper->addFlags($client, $mailbox, $messages[$mailbox->getId()], $imapFlags);
} else {
$this->imapMessageMapper->removeFlags($client, $mailbox, $messages[$mailbox->getId()], $imapFlags);
}
}
}

} catch (Horde_Imap_Client_Exception $e) {
// TODO: Add proper error handling
} finally {
$client->logout();
}
}

}

}

0 comments on commit 313785b

Please sign in to comment.