diff options
author | Christoph Wurst <christoph@winzerhof-wurst.at> | 2020-08-11 22:10:49 +0300 |
---|---|---|
committer | Christoph Wurst <christoph@winzerhof-wurst.at> | 2020-08-24 21:21:40 +0300 |
commit | 9498ebac6eccde201526b9a6131a76c02ca5db62 (patch) | |
tree | 7256f1fc6de4dee185f5cc116e45247cf27f28cb | |
parent | 430500712496242526eed2aedb5afc42d60ca1b9 (diff) |
Rework the routing
Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
87 files changed, 2062 insertions, 1797 deletions
diff --git a/appinfo/routes.php b/appinfo/routes.php index 5529db11f..ea6758ce2 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -30,98 +30,123 @@ return [ 'verb' => 'GET' ], [ + 'name' => 'page#setup', + 'url' => '/setup', + 'verb' => 'GET' + ], + [ + 'name' => 'page#keyboardShortcuts', + 'url' => '/keyboard-shortcuts', + 'verb' => 'GET' + ], + [ + 'name' => 'page#accountSettings', + 'url' => '/accounts/{id}/settings', + 'verb' => 'GET' + ], + [ + 'name' => 'page#mailbox', + 'url' => '/box/{id}', + 'verb' => 'GET' + ], + [ + 'name' => 'page#thread', + 'url' => '/box/{mailboxId}/thread/{id}', + 'verb' => 'GET' + ], + [ + 'name' => 'page#index', + 'url' => '/', + 'verb' => 'GET' + ], + [ 'name' => 'page#compose', 'url' => '/compose', 'verb' => 'GET' ], [ 'name' => 'accounts#send', - 'url' => '/api/accounts/{accountId}/send', + 'url' => '/api/accounts/{id}/send', 'verb' => 'POST' ], [ 'name' => 'accounts#draft', - 'url' => '/api/accounts/{accountId}/draft', + 'url' => '/api/accounts/{id}/draft', 'verb' => 'POST' ], [ 'name' => 'accounts#patchAccount', - 'url' => '/api/accounts/{accountId}', + 'url' => '/api/accounts/{id}', 'verb' => 'PATCH' ], [ 'name' => 'accounts#updateSignature', - 'url' => '/api/accounts/{accountId}/signature', + 'url' => '/api/accounts/{id}/signature', 'verb' => 'PUT' ], [ 'name' => 'accounts#getQuota', - 'url' => '/api/accounts/{accountId}/quota', + 'url' => '/api/accounts/{id}/quota', 'verb' => 'GET' ], [ - 'name' => 'folders#sync', - 'url' => '/api/accounts/{accountId}/folders/{folderId}/sync', + 'name' => 'mailboxes#sync', + 'url' => '/api/mailboxes/{id}/sync', 'verb' => 'POST' ], [ - 'name' => 'folders#clearCache', - 'url' => '/api/accounts/{accountId}/folders/{folderId}/sync', + 'name' => 'mailboxes#clearCache', + 'url' => '/api/mailboxes/{id}/sync', 'verb' => 'DELETE' ], [ - 'name' => 'folders#markAllAsRead', - 'url' => '/api/accounts/{accountId}/folders/{folderId}/read', + 'name' => 'mailboxes#markAllAsRead', + 'url' => '/api/mailboxes/{id}/read', 'verb' => 'POST' ], [ - 'name' => 'folders#stats', - 'url' => '/api/accounts/{accountId}/folders/{folderId}/stats', + 'name' => 'mailboxes#stats', + 'url' => '/api/mailboxes/{id}/stats', 'verb' => 'GET' ], [ - 'name' => 'folders#delete', - 'url' => '/api/accounts/{accountId}/folders/{folderId}', - 'verb' => 'DELETE' - ], - [ 'name' => 'messages#downloadAttachment', - 'url' => '/api/accounts/{accountId}/folders/{folderId}/messages/{messageId}/attachment/{attachmentId}', + 'url' => '/api/messages/{id}/attachment/{attachmentId}', 'verb' => 'GET' ], [ 'name' => 'messages#saveAttachment', - 'url' => '/api/accounts/{accountId}/folders/{folderId}/messages/{messageId}/attachment/{attachmentId}', + 'url' => '/api/messages/{id}/attachment/{attachmentId}', 'verb' => 'POST' ], [ 'name' => 'messages#getBody', - 'url' => '/api/accounts/{accountId}/folders/{folderId}/messages/{messageId}/body', + 'url' => '/api/messages/{id}/body', 'verb' => 'GET' ], [ 'name' => 'messages#getSource', - 'url' => '/api/accounts/{accountId}/folders/{folderId}/messages/{messageId}/source', + 'url' => '/api/messages/{id}/source', 'verb' => 'GET' ], [ 'name' => 'messages#getHtmlBody', - 'url' => '/api/accounts/{accountId}/folders/{folderId}/messages/{messageId}/html', + 'url' => '/api/messages/{id}/html', 'verb' => 'GET' ], [ 'name' => 'messages#getThread', - 'url' => '/api/accounts/{accountId}/message/{id}/thread', + 'url' => '/api/messages/{id}/thread', 'verb' => 'GET' ], [ 'name' => 'messages#setFlags', - 'url' => '/api/accounts/{accountId}/folders/{folderId}/messages/{messageId}/flags', + 'url' => '/api/messages/{id}/flags', 'verb' => 'PUT' ], [ 'name' => 'messages#move', - 'url' => '/api/accounts/{accountId}/folders/{folderId}/messages/{id}/move', + 'url' => '/api/messages/{id}/move', 'verb' => 'POST' ], [ @@ -159,9 +184,9 @@ return [ 'accounts' => ['url' => '/api/accounts'], 'aliases' => ['url' => '/api/accounts/{accountId}/aliases'], 'autoComplete' => ['url' => '/api/autoComplete'], - 'folders' => ['url' => '/api/accounts/{accountId}/folders'], 'localAttachments' => ['url' => '/api/attachments'], - 'messages' => ['url' => '/api/accounts/{accountId}/folders/{folderId}/messages'], + 'mailboxes' => ['url' => '/api/mailboxes'], + 'messages' => ['url' => '/api/messages'], 'preferences' => ['url' => '/api/preferences'], ] ]; diff --git a/lib/Attachment.php b/lib/Attachment.php index 33acaace9..1770587be 100644 --- a/lib/Attachment.php +++ b/lib/Attachment.php @@ -32,13 +32,13 @@ class Attachment { /** * @param \Horde_Imap_Client_Socket $conn * @param \Horde_Imap_Client_Mailbox $mailBox - * @param int $messageId + * @param int $messageUid * @param string $attachmentId */ - public function __construct($conn, $mailBox, $messageId, $attachmentId) { + public function __construct($conn, $mailBox, $messageUid, $attachmentId) { $this->conn = $conn; $this->mailBox = $mailBox; - $this->messageId = $messageId; + $this->messageUid = $messageUid; $this->attachmentId = $attachmentId; $this->load(); @@ -53,7 +53,7 @@ class Attachment { * @var \Horde_Imap_Client_Mailbox */ private $mailBox; - private $messageId; + private $messageUid; private $attachmentId; /** @@ -67,13 +67,13 @@ class Attachment { $fetch_query->mimeHeader($this->attachmentId); // $list is an array of Horde_Imap_Client_Data_Fetch objects. - $ids = new \Horde_Imap_Client_Ids($this->messageId); + $ids = new \Horde_Imap_Client_Ids($this->messageUid); $headers = $this->conn->fetch($this->mailBox, $fetch_query, ['ids' => $ids]); /** @var $fetch Horde_Imap_Client_Data_Fetch */ - if (!isset($headers[$this->messageId])) { + if (!isset($headers[$this->messageUid])) { throw new DoesNotExistException('Unable to load the attachment.'); } - $fetch = $headers[$this->messageId]; + $fetch = $headers[$this->messageUid]; /** @var \Horde_Mime_Headers $mimeHeaders */ $mimeHeaders = $fetch->getMimeHeader($this->attachmentId, Horde_Imap_Client_Data_Fetch::HEADER_PARSE); diff --git a/lib/Contracts/IMailManager.php b/lib/Contracts/IMailManager.php index 553aea52b..212aee8a8 100644 --- a/lib/Contracts/IMailManager.php +++ b/lib/Contracts/IMailManager.php @@ -25,16 +25,28 @@ namespace OCA\Mail\Contracts; use OCA\Mail\Account; use OCA\Mail\Db\Mailbox; +use OCA\Mail\Db\Message; use OCA\Mail\Exception\ClientException; use OCA\Mail\Exception\ServiceException; use OCA\Mail\Folder; use OCA\Mail\IMAP\FolderStats; use OCA\Mail\Model\IMAPMessage; use OCA\Mail\Service\Quota; +use OCP\AppFramework\Db\DoesNotExistException; interface IMailManager { /** + * @param string $uid + * @param int $id + * + * @return Mailbox + * + * @throws DoesNotExistException + */ + public function getMailbox(string $uid, int $id): Mailbox; + + /** * @param Account $account * * @return Mailbox[] @@ -47,44 +59,63 @@ interface IMailManager { * @param Account $account * @param string $name * - * @return Folder + * @return Mailbox * * @throws ServiceException */ - public function createFolder(Account $account, string $name): Folder; + public function createMailbox(Account $account, string $name): Mailbox; /** * @param Account $account - * @param string $folderId + * @param Mailbox $mailbox * * @return FolderStats + */ + public function getMailboxStats(Account $account, Mailbox $mailbox): FolderStats; + + /** + * @param Mailbox $mailbox + * @param $uid * - * @throws ServiceException + * @return int|null + */ + public function getMessageIdForUid(Mailbox $mailbox, $uid): ?int; + + /** + * @param string $uid + * @param int $id + * + * @return Mailbox + * + * @throws ClientException */ - public function getFolderStats(Account $account, string $folderId): FolderStats; + public function getMessage(string $uid, int $id): Message; /** * @param Account $account * @param string $mb - * @param int $id + * @param int $uid * * @return string * @throws ClientException * @throws ServiceException */ - public function getSource(Account $account, string $mb, int $id): string; + public function getSource(Account $account, string $mb, int $uid): ?string; /** * @param Account $account - * @param string $mailbox - * @param int $id + * @param Mailbox $mailbox + * @param int $uid * @param bool $loadBody * * @return IMAPMessage * * @throws ServiceException */ - public function getMessage(Account $account, string $mailbox, int $id, bool $loadBody = false): IMAPMessage; + public function getImapMessage(Account $account, + Mailbox $mailbox, + int $uid, + bool $loadBody = false): IMAPMessage; /** * @param Account $account @@ -97,14 +128,17 @@ interface IMailManager { /** * @param Account $sourceAccount * @param string $sourceFolderId - * @param int $messageId + * @param int $uid * @param Account $destinationAccount * @param string $destFolderId * * @throws ServiceException */ - public function moveMessage(Account $sourceAccount, string $sourceFolderId, int $messageId, - Account $destinationAccount, string $destFolderId); + public function moveMessage(Account $sourceAccount, + string $sourceFolderId, + int $uid, + Account $destinationAccount, + string $destFolderId); /** * @param Account $account @@ -119,11 +153,9 @@ interface IMailManager { * Mark all messages of a folder as read * * @param Account $account - * @param string $folderId - * - * @throws ServiceException + * @param Mailbox $mailbox */ - public function markFolderAsRead(Account $account, string $folderId): void; + public function markFolderAsRead(Account $account, Mailbox $mailbox): void; /** * @param Account $account @@ -146,9 +178,9 @@ interface IMailManager { /** * @param Account $account - * @param string $folderId + * @param Mailbox $mailbox * * @throws ServiceException */ - public function deleteMailbox(Account $account, string $folderId): void; + public function deleteMailbox(Account $account, Mailbox $mailbox): void; } diff --git a/lib/Contracts/IMailSearch.php b/lib/Contracts/IMailSearch.php index 9487334b2..c751e2c53 100644 --- a/lib/Contracts/IMailSearch.php +++ b/lib/Contracts/IMailSearch.php @@ -26,6 +26,7 @@ declare(strict_types=1); namespace OCA\Mail\Contracts; use OCA\Mail\Account; +use OCA\Mail\Db\Mailbox; use OCA\Mail\Db\Message; use OCA\Mail\Exception\ClientException; use OCA\Mail\Exception\ServiceException; @@ -43,18 +44,25 @@ interface IMailSearch { * @throws ClientException * @throws ServiceException */ - public function findMessage(Account $account, string $mailboxName, int $uid): Message; + public function findMessage(Account $account, + Mailbox $mailbox, + Message $message): Message; /** * @param Account $account - * @param string $mailboxName + * @param Mailbox $mailbox * @param string|null $filter * @param int|null $cursor + * @param int|null $limit * * @return Message[] * * @throws ClientException * @throws ServiceException */ - public function findMessages(Account $account, string $mailboxName, ?string $filter, ?int $cursor, ?int $limit): array; + public function findMessages(Account $account, + Mailbox $mailbox, + ?string $filter, + ?int $cursor, + ?int $limit): array; } diff --git a/lib/Contracts/IMailTransmission.php b/lib/Contracts/IMailTransmission.php index ff1d04138..d4e4c3c57 100644 --- a/lib/Contracts/IMailTransmission.php +++ b/lib/Contracts/IMailTransmission.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace OCA\Mail\Contracts; use OCA\Mail\Db\Alias; +use OCA\Mail\Db\Message; use OCA\Mail\Exception\ServiceException; use OCA\Mail\Model\NewMessageData; use OCA\Mail\Model\RepliedMessageData; @@ -33,27 +34,27 @@ interface IMailTransmission { /** * Send a new message or reply to an existing one * - * @param string $userId * @param NewMessageData $message - * @param RepliedMessageData $reply + * @param RepliedMessageData|null $reply * @param Alias|null $alias + * @param Message|null $draft * * @throws ServiceException */ public function sendMessage(NewMessageData $message, RepliedMessageData $reply = null, Alias $alias = null, - int $draftUID = null); + Message $draft = null); /** * Save a message draft * * @param NewMessageData $message - * @param int $draftUID + * @param Message|null $previousDraft * - * @return int + * @return array * * @throws ServiceException */ - public function saveDraft(NewMessageData $message, int $draftUID = null): int; + public function saveDraft(NewMessageData $message, Message $previousDraft = null): array; } diff --git a/lib/Controller/AccountsController.php b/lib/Controller/AccountsController.php index e5c8a94bb..94221eb09 100644 --- a/lib/Controller/AccountsController.php +++ b/lib/Controller/AccountsController.php @@ -30,8 +30,10 @@ declare(strict_types=1); namespace OCA\Mail\Controller; use Exception; +use Horde_Imap_Client; use OCA\Mail\Contracts\IMailManager; use OCA\Mail\Contracts\IMailTransmission; +use OCA\Mail\Db\Mailbox; use OCA\Mail\Exception\ClientException; use OCA\Mail\Exception\ServiceException; use OCA\Mail\Http\JsonResponse as MailJsonResponse; @@ -41,6 +43,7 @@ use OCA\Mail\Service\AccountService; use OCA\Mail\Service\AliasesService; use OCA\Mail\Service\GroupsIntegration; use OCA\Mail\Service\SetupService; +use OCA\Mail\Service\Sync\SyncService; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\JSONResponse; @@ -77,17 +80,37 @@ class AccountsController extends Controller { /** @var IMailManager */ private $mailManager; + /** @var SyncService */ + private $syncService; + + /** + * AccountsController constructor. + * + * @param string $appName + * @param IRequest $request + * @param AccountService $accountService + * @param GroupsIntegration $groupsIntegration + * @param $UserId + * @param ILogger $logger + * @param IL10N $l10n + * @param AliasesService $aliasesService + * @param IMailTransmission $mailTransmission + * @param SetupService $setup + * @param IMailManager $mailManager + * @param SyncService $syncService + */ public function __construct(string $appName, - IRequest $request, - AccountService $accountService, - GroupsIntegration $groupsIntegration, - $UserId, - ILogger $logger, - IL10N $l10n, - AliasesService $aliasesService, - IMailTransmission $mailTransmission, - SetupService $setup, - IMailManager $mailManager + IRequest $request, + AccountService $accountService, + GroupsIntegration $groupsIntegration, + $UserId, + ILogger $logger, + IL10N $l10n, + AliasesService $aliasesService, + IMailTransmission $mailTransmission, + SetupService $setup, + IMailManager $mailManager, + SyncService $syncService ) { parent::__construct($appName, $request); $this->accountService = $accountService; @@ -99,6 +122,7 @@ class AccountsController extends Controller { $this->mailTransmission = $mailTransmission; $this->setup = $setup; $this->mailManager = $mailManager; + $this->syncService = $syncService; } /** @@ -123,13 +147,13 @@ class AccountsController extends Controller { * @NoAdminRequired * @TrapError * - * @param int $accountId + * @param int $id * * @return JSONResponse * @throws ClientException */ - public function show($accountId): JSONResponse { - return new JSONResponse($this->accountService->find($this->currentUserId, $accountId)); + public function show(int $id): JSONResponse { + return new JSONResponse($this->accountService->find($this->currentUserId, $id)); } /** @@ -198,7 +222,7 @@ class AccountsController extends Controller { * @NoAdminRequired * @TrapError * - * @param int $accountId + * @param int $id * @param string|null $editorMode * @param int|null $order * @param bool|null $showSubscribedOnly @@ -207,11 +231,11 @@ class AccountsController extends Controller { * * @throws ClientException */ - public function patchAccount(int $accountId, + public function patchAccount(int $id, string $editorMode = null, int $order = null, bool $showSubscribedOnly = null): JSONResponse { - $account = $this->accountService->find($this->currentUserId, $accountId); + $account = $this->accountService->find($this->currentUserId, $id); if ($account === null) { return new JSONResponse(null, Http::STATUS_FORBIDDEN); @@ -236,7 +260,7 @@ class AccountsController extends Controller { * @NoAdminRequired * @TrapError * - * @param int $accountId + * @param int $id * @param string|null $signature * * @return JSONResponse @@ -244,8 +268,8 @@ class AccountsController extends Controller { * @throws ClientException * @throws ServiceException */ - public function updateSignature(int $accountId, string $signature = null): JSONResponse { - $this->accountService->updateSignature($accountId, $this->currentUserId, $signature); + public function updateSignature(int $id, string $signature = null): JSONResponse { + $this->accountService->updateSignature($id, $this->currentUserId, $signature); return new JSONResponse(); } @@ -259,7 +283,7 @@ class AccountsController extends Controller { * * @throws ClientException */ - public function destroy($id): JSONResponse { + public function destroy(int $id): JSONResponse { $this->accountService->delete($this->currentUserId, $id); return new JSONResponse(); } @@ -315,13 +339,13 @@ class AccountsController extends Controller { * @NoAdminRequired * @TrapError * - * @param int $accountId + * @param int $id * @param string $subject * @param string $body * @param string $to * @param string $cc * @param string $bcc - * @param int|null $draftUID + * @param int|null $draftId * @param string|null $folderId * @param int|null $messageId * @param mixed $attachments @@ -332,19 +356,19 @@ class AccountsController extends Controller { * @throws ClientException * @throws ServiceException */ - public function send(int $accountId, + public function send(int $id, string $subject, string $body, string $to, string $cc, string $bcc, bool $isHtml = true, - int $draftUID = null, + int $draftId = null, string $folderId = null, int $messageId = null, array $attachments = [], int $aliasId = null): JSONResponse { - $account = $this->accountService->find($this->currentUserId, $accountId); + $account = $this->accountService->find($this->currentUserId, $id); $alias = $aliasId ? $this->aliasesService->find($aliasId, $this->currentUserId) : null; $expandedTo = $this->groupsIntegration->expand($to); @@ -357,8 +381,16 @@ class AccountsController extends Controller { $repliedMessageData = new RepliedMessageData($account, base64_decode($folderId), $messageId); } + $draft = null; + if ($draftId !== null) { + try { + $draft = $this->mailManager->getMessage($this->currentUserId, $draftId); + } catch (ClientException $e) { + $this->logger->info("Draft " . $draftId . " could not be loaded: " . $e->getMessage()); + } + } try { - $this->mailTransmission->sendMessage($messageData, $repliedMessageData, $alias, $draftUID); + $this->mailTransmission->sendMessage($messageData, $repliedMessageData, $alias, $draft); return new JSONResponse(); } catch (ServiceException $ex) { $this->logger->error('Sending mail failed: ' . $ex->getMessage()); @@ -370,7 +402,7 @@ class AccountsController extends Controller { * @NoAdminRequired * @TrapError * - * @param int $accountId + * @param int $id * @param string $subject * @param string $body * @param string $to @@ -382,27 +414,43 @@ class AccountsController extends Controller { * * @throws ClientException */ - public function draft(int $accountId, - string $subject = null, + public function draft(int $id, + string $subject, string $body, string $to, string $cc, string $bcc, bool $isHtml = true, - int $draftUID = null): JSONResponse { - if ($draftUID === null) { - $this->logger->info("Saving a new draft in account <$accountId>"); + int $draftId = null): JSONResponse { + if ($draftId === null) { + $this->logger->info("Saving a new draft in account <$id>"); } else { - $this->logger->info("Updating draft <$draftUID> in account <$accountId>"); + $this->logger->info("Updating draft <$draftId> in account <$id>"); } - $account = $this->accountService->find($this->currentUserId, $accountId); + $account = $this->accountService->find($this->currentUserId, $id); + $previousDraft = null; + if ($draftId !== null) { + try { + $previousDraft = $this->mailManager->getMessage($this->currentUserId, $draftId); + } catch (ClientException $e) { + $this->logger->info("Draft " . $draftId . " could not be loaded: " . $e->getMessage()); + } + } $messageData = NewMessageData::fromRequest($account, $to, $cc, $bcc, $subject, $body, [], $isHtml); try { - $newUID = $this->mailTransmission->saveDraft($messageData, $draftUID); + /** @var Mailbox $draftsMailbox */ + [, $draftsMailbox, $newUID] = $this->mailTransmission->saveDraft($messageData, $previousDraft); + $this->syncService->syncMailbox( + $account, + $draftsMailbox, + Horde_Imap_Client::SYNC_NEWMSGS, + [], + false + ); return new JSONResponse([ - 'uid' => $newUID, + 'id' => $this->mailManager->getMessageIdForUid($draftsMailbox, $newUID) ]); } catch (ServiceException $ex) { $this->logger->error('Saving draft failed: ' . $ex->getMessage()); @@ -413,13 +461,13 @@ class AccountsController extends Controller { /** * @NoAdminRequired * - * @param int $accountId + * @param int $id * * @return JSONResponse * @throws ClientException */ - public function getQuota(int $accountId): JSONResponse { - $account = $this->accountService->find($this->currentUserId, $accountId); + public function getQuota(int $id): JSONResponse { + $account = $this->accountService->find($this->currentUserId, $id); $quota = $this->mailManager->getQuota($account); if ($quota === null) { diff --git a/lib/Controller/AliasesController.php b/lib/Controller/AliasesController.php index cda86bd65..ca9d61589 100644 --- a/lib/Controller/AliasesController.php +++ b/lib/Controller/AliasesController.php @@ -54,9 +54,10 @@ class AliasesController extends Controller { * @TrapError * * @param int $accountId + * * @return JSONResponse */ - public function index($accountId): JSONResponse { + public function index(int $accountId): JSONResponse { return new JSONResponse($this->aliasService->findAll($accountId, $this->currentUser->getUID())); } @@ -83,7 +84,7 @@ class AliasesController extends Controller { * @param int $id * @return JSONResponse */ - public function destroy($id): JSONResponse { + public function destroy(int $id): JSONResponse { return new JSONResponse($this->aliasService->delete($id, $this->currentUser->getUID())); } @@ -94,9 +95,10 @@ class AliasesController extends Controller { * @param int $accountId * @param string $alias * @param string $aliasName + * * @return JSONResponse */ - public function create($accountId, $alias, $aliasName): JSONResponse { + public function create(int $accountId, string $alias, string $aliasName): JSONResponse { return new JSONResponse($this->aliasService->create($accountId, $alias, $aliasName), Http::STATUS_CREATED); } } diff --git a/lib/Controller/FoldersController.php b/lib/Controller/MailboxesController.php index 2527658e5..c7ad55007 100644 --- a/lib/Controller/FoldersController.php +++ b/lib/Controller/MailboxesController.php @@ -31,8 +31,6 @@ use OCA\Mail\Exception\IncompleteSyncException; use OCA\Mail\Exception\MailboxNotCachedException; use OCA\Mail\Exception\ServiceException; use OCA\Mail\Service\Sync\SyncService; -use function base64_decode; -use function is_array; use OCA\Mail\Contracts\IMailManager; use OCA\Mail\Exception\NotImplemented; use OCA\Mail\Service\AccountService; @@ -41,7 +39,7 @@ use OCP\AppFramework\Http; use OCP\AppFramework\Http\JSONResponse; use OCP\IRequest; -class FoldersController extends Controller { +class MailboxesController extends Controller { /** @var AccountService */ private $accountService; @@ -66,7 +64,7 @@ class FoldersController extends Controller { public function __construct(string $appName, IRequest $request, AccountService $accountService, - $UserId, + ?string $UserId, IMailManager $mailManager, SyncService $syncService) { parent::__construct($appName, $request); @@ -94,7 +92,7 @@ class FoldersController extends Controller { return new JSONResponse([ 'id' => $accountId, 'email' => $account->getEmail(), - 'folders' => $mailboxes, + 'mailboxes' => $mailboxes, 'delimiter' => reset($mailboxes)->getDelimiter(), ]); } @@ -103,30 +101,28 @@ class FoldersController extends Controller { * @NoAdminRequired * @TrapError * - * @param int $accountId - * @param string $folderId - * @param string $syncToken - * @param int[] $uids + * @param int $id + * @param int[] $ids + * + * @param bool $init + * @param string|null $query * * @return JSONResponse * @throws ClientException * @throws ServiceException */ - public function sync(int $accountId, string $folderId, array $uids = [], bool $init = false, string $query = null): JSONResponse { - $account = $this->accountService->find($this->currentUserId, $accountId); - - if (empty($accountId) || empty($folderId) || !is_array($uids)) { - return new JSONResponse(null, Http::STATUS_BAD_REQUEST); - } + public function sync(int $id, array $ids = [], bool $init = false, string $query = null): JSONResponse { + $mailbox = $this->mailManager->getMailbox($this->currentUserId, $id); + $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId()); try { $syncResponse = $this->syncService->syncMailbox( $account, - base64_decode($folderId), + $mailbox, Horde_Imap_Client::SYNC_NEWMSGSUIDS | Horde_Imap_Client::SYNC_FLAGSUIDS | Horde_Imap_Client::SYNC_VANISHEDUIDS, - array_map(function ($uid) { - return (int) $uid; - }, $uids), + array_map(function ($id) { + return (int) $id; + }, $ids), !$init, $query ); @@ -143,21 +139,17 @@ class FoldersController extends Controller { * @NoAdminRequired * @TrapError * - * @param int $accountId - * @param string $folderId + * @param string $id * * @return JSONResponse * @throws ClientException * @throws ServiceException */ - public function clearCache(int $accountId, string $folderId): JSONResponse { - $account = $this->accountService->find($this->currentUserId, $accountId); + public function clearCache(int $id): JSONResponse { + $mailbox = $this->mailManager->getMailbox($this->currentUserId, $id); + $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId()); - if (empty($accountId) || empty($folderId)) { - return new JSONResponse(null, Http::STATUS_BAD_REQUEST); - } - - $this->syncService->clearCache($account, base64_decode($folderId)); + $this->syncService->clearCache($account, $mailbox); return new JSONResponse(null); } @@ -165,43 +157,37 @@ class FoldersController extends Controller { * @NoAdminRequired * @TrapError * - * @param int $accountId - * @param string $folderId + * @param int $id + * * @return JSONResponse * * @throws ClientException */ - public function markAllAsRead(int $accountId, string $folderId): JSONResponse { - $account = $this->accountService->find($this->currentUserId, $accountId); - - if (empty($accountId) || empty($folderId)) { - return new JSONResponse(null, Http::STATUS_BAD_REQUEST); - } + public function markAllAsRead(int $id): JSONResponse { + $mailbox = $this->mailManager->getMailbox($this->currentUserId, $id); + $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId()); - $syncResponse = $this->mailManager->markFolderAsRead($account, base64_decode($folderId)); + $this->mailManager->markFolderAsRead($account, $mailbox); - return new JSONResponse($syncResponse); + return new JSONResponse(null); } /** * @NoAdminRequired * @TrapError * - * @param int $accountId - * @param string $folderId + * @param int $id * * @return JSONResponse * * @throws ClientException + * @throws ServiceException */ - public function stats(int $accountId, string $folderId): JSONResponse { - $account = $this->accountService->find($this->currentUserId, $accountId); - - if (empty($accountId) || empty($folderId)) { - return new JSONResponse(null, Http::STATUS_BAD_REQUEST); - } + public function stats(int $id): JSONResponse { + $mailbox = $this->mailManager->getMailbox($this->currentUserId, $id); + $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId()); - $stats = $this->mailManager->getFolderStats($account, base64_decode($folderId)); + $stats = $this->mailManager->getMailboxStats($account, $mailbox); return new JSONResponse($stats); } @@ -232,19 +218,24 @@ class FoldersController extends Controller { public function create(int $accountId, string $name) { $account = $this->accountService->find($this->currentUserId, $accountId); - return new JSONResponse($this->mailManager->createFolder($account, $name)); + return new JSONResponse($this->mailManager->createMailbox($account, $name)); } /** * @NoAdminRequired * @TrapError - * @param int $accountId - * @param string $folderId + * + * @param int $id + * + * @return JSONResponse + * @throws ClientException * @throws ServiceException */ - public function delete(int $accountId, string $folderId): JSONResponse { - $account = $this->accountService->find($this->currentUserId, $accountId); - $this->mailManager->deleteMailbox($account, base64_decode($folderId)); + public function destroy(int $id): JSONResponse { + $mailbox = $this->mailManager->getMailbox($this->currentUserId, $id); + $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId()); + + $this->mailManager->deleteMailbox($account, $mailbox); return new JSONResponse(); } } diff --git a/lib/Controller/MessagesController.php b/lib/Controller/MessagesController.php index ef6357835..df602bf94 100755 --- a/lib/Controller/MessagesController.php +++ b/lib/Controller/MessagesController.php @@ -39,7 +39,6 @@ use OCA\Mail\Http\AttachmentDownloadResponse; use OCA\Mail\Http\HtmlResponse; use OCA\Mail\Model\IMAPMessage; use OCA\Mail\Service\AccountService; -use OCA\Mail\Service\IMailBox; use OCA\Mail\Service\ItineraryService; use OCP\AppFramework\Controller; use OCP\AppFramework\Db\DoesNotExistException; @@ -55,7 +54,6 @@ use OCP\ILogger; use OCP\IRequest; use OCP\IURLGenerator; use function array_map; -use function base64_decode; class MessagesController extends Controller { @@ -131,29 +129,33 @@ class MessagesController extends Controller { * @NoAdminRequired * @TrapError * - * @param int $accountId - * @param string $folderId + * @param int $mailboxId * @param int $cursor * @param string $filter + * @param int|null $limit * * @return JSONResponse * * @throws ClientException * @throws ServiceException */ - public function index(int $accountId, string $folderId, int $cursor = null, string $filter = null, int $limit = null): JSONResponse { + public function index(int $mailboxId, + int $cursor = null, + string $filter = null, + int $limit = null): JSONResponse { try { - $account = $this->accountService->find($this->currentUserId, $accountId); + $mailbox = $this->mailManager->getMailbox($this->currentUserId, $mailboxId); + $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId()); } catch (DoesNotExistException $e) { return new JSONResponse(null, Http::STATUS_FORBIDDEN); } - $this->logger->debug("loading messages of folder <$folderId>"); + $this->logger->debug("loading messages of folder <$mailboxId>"); return new JSONResponse( $this->mailSearch->findMessages( $account, - base64_decode($folderId), + $mailbox, $filter === '' ? null : $filter, $cursor, $limit @@ -174,20 +176,22 @@ class MessagesController extends Controller { * @throws ClientException * @throws ServiceException */ - public function show(int $accountId, string $folderId, int $id): JSONResponse { + public function show(int $id): JSONResponse { try { - $account = $this->accountService->find($this->currentUserId, $accountId); + $message = $this->mailManager->getMessage($this->currentUserId, $id); + $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId()); + $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId()); } catch (DoesNotExistException $e) { return new JSONResponse(null, Http::STATUS_FORBIDDEN); } - $this->logger->debug("loading message of folder <$folderId>"); + $this->logger->debug("loading message <$id>"); return new JSONResponse( $this->mailSearch->findMessage( $account, - base64_decode($folderId), - $id + $mailbox, + $message ) ); } @@ -196,40 +200,40 @@ class MessagesController extends Controller { * @NoAdminRequired * @TrapError * - * @param int $accountId - * @param string $folderId - * @param int $messageId + * @param int $id * * @return JSONResponse * * @throws ClientException * @throws ServiceException */ - public function getBody(int $accountId, string $folderId, int $messageId): JSONResponse { + public function getBody(int $id): JSONResponse { try { - $account = $this->accountService->find($this->currentUserId, $accountId); + $message = $this->mailManager->getMessage($this->currentUserId, $id); + $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId()); + $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId()); } catch (DoesNotExistException $e) { return new JSONResponse(null, Http::STATUS_FORBIDDEN); } - $json = $this->mailManager->getMessage( + $json = $this->mailManager->getImapMessage( $account, - base64_decode($folderId), - $messageId, + $mailbox, + $message->getUid(), true - )->getFullMessage( - $accountId, - base64_decode($folderId), - $messageId - ); + )->getFullMessage($id); $json['itineraries'] = $this->itineraryService->extract( $account, - base64_decode($folderId), - $messageId + $mailbox->getName(), + $message->getUid() ); - $json['attachments'] = array_map(function ($a) use ($accountId, $folderId, $messageId) { - return $this->enrichDownloadUrl($accountId, $folderId, $messageId, $a); + $json['attachments'] = array_map(function ($a) use ($id) { + return $this->enrichDownloadUrl( + $id, + $a + ); }, $json['attachments']); + $json['databaseId'] = $message->getId(); return new JSONResponse($json); } @@ -239,14 +243,19 @@ class MessagesController extends Controller { * @NoCSRFRequired * @TrapError * - * @param int $accountId - * @param string $folderId + * @param int $id * * @return JSONResponse * @throws ClientException */ - public function getThread(int $accountId, int $id): JSONResponse { - $account = $this->accountService->find($this->currentUserId, $accountId); + public function getThread(int $id): JSONResponse { + try { + $message = $this->mailManager->getMessage($this->currentUserId, $id); + $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId()); + $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId()); + } catch (DoesNotExistException $e) { + return new JSONResponse(null, Http::STATUS_FORBIDDEN); + } return new JSONResponse($this->mailManager->getThread($account, $id)); } @@ -255,10 +264,7 @@ class MessagesController extends Controller { * @NoAdminRequired * @TrapError * - * @param int $accountId - * @param string $folderId * @param int $id - * @param int $destAccountId * @param string $destFolderId * * @return JSONResponse @@ -266,12 +272,24 @@ class MessagesController extends Controller { * @throws ClientException * @throws ServiceException */ - public function move($accountId, $folderId, $id, $destAccountId, $destFolderId): JSONResponse { - $srcAccount = $this->accountService->find($this->currentUserId, $accountId); - $dstAccount = $this->accountService->find($this->currentUserId, - $destAccountId); - $this->mailManager->moveMessage($srcAccount, base64_decode($folderId), $id, - $dstAccount, base64_decode($destFolderId)); + public function move(int $id, int $destFolderId): JSONResponse { + try { + $message = $this->mailManager->getMessage($this->currentUserId, $id); + $srcMailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId()); + $dstMailbox = $this->mailManager->getMailbox($this->currentUserId, $destFolderId); + $srcAccount = $this->accountService->find($this->currentUserId, $srcMailbox->getAccountId()); + $dstAccount = $this->accountService->find($this->currentUserId, $dstMailbox->getAccountId()); + } catch (DoesNotExistException $e) { + return new JSONResponse(null, Http::STATUS_FORBIDDEN); + } + + $this->mailManager->moveMessage( + $srcAccount, + $srcMailbox->getName(), + $message->getUid(), + $dstAccount, + $dstMailbox->getName() + ); return new JSONResponse(); } @@ -284,17 +302,23 @@ class MessagesController extends Controller { * @param string $folderId * @param int $messageId * - * @return HtmlResponse|TemplateResponse + * @return JSONResponse * @throws ServiceException */ - public function getSource(int $accountId, string $folderId, int $messageId): JSONResponse { - $account = $this->accountService->find($this->currentUserId, $accountId); + public function getSource(int $id): JSONResponse { + try { + $message = $this->mailManager->getMessage($this->currentUserId, $id); + $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId()); + $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId()); + } catch (DoesNotExistException $e) { + return new JSONResponse(null, Http::STATUS_FORBIDDEN); + } $response = new JSONResponse([ 'source' => $this->mailManager->getSource( $account, - base64_decode($folderId), - $messageId + $mailbox->getName(), + $message->getUid() ) ]); @@ -309,18 +333,18 @@ class MessagesController extends Controller { * @NoCSRFRequired * @TrapError * - * @param int $accountId - * @param string $folderId - * @param int $messageId + * @param int $id * * @return HtmlResponse|TemplateResponse * * @throws ClientException */ - public function getHtmlBody(int $accountId, string $folderId, int $messageId): Response { + public function getHtmlBody(int $id): Response { try { try { - $account = $this->accountService->find($this->currentUserId, $accountId); + $message = $this->mailManager->getMessage($this->currentUserId, $id); + $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId()); + $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId()); } catch (DoesNotExistException $e) { return new TemplateResponse( $this->appName, @@ -331,12 +355,14 @@ class MessagesController extends Controller { } $htmlResponse = new HtmlResponse( - $this->mailManager->getMessage( + $this->mailManager->getImapMessage( $account, - base64_decode($folderId), - $messageId, + $mailbox, + $message->getUid(), true - )->getHtmlBody($accountId, base64_decode($folderId), $messageId) + )->getHtmlBody( + $id + ) ); // Harden the default security policy @@ -369,19 +395,25 @@ class MessagesController extends Controller { * * @param int $accountId * @param string $folderId - * @param int $messageId + * @param int $id * @param int $attachmentId * - * @return AttachmentDownloadResponse + * @return Response * * @throws ClientException * @throws ServiceException */ - public function downloadAttachment(int $accountId, string $folderId, int $messageId, - string $attachmentId) { - $mailBox = $this->getFolder($accountId, $folderId); - - $attachment = $mailBox->getAttachment($messageId, $attachmentId); + public function downloadAttachment(int $id, + string $attachmentId): Response { + try { + $message = $this->mailManager->getMessage($this->currentUserId, $id); + $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId()); + $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId()); + } catch (DoesNotExistException $e) { + return new JSONResponse(null, Http::STATUS_FORBIDDEN); + } + $folder = $account->getMailbox($mailbox->getName()); + $attachment = $folder->getAttachment($message->getUid(), $attachmentId); // Body party and embedded messages do not have a name if ($attachment->getName() === null) { @@ -404,9 +436,7 @@ class MessagesController extends Controller { * @NoAdminRequired * @TrapError * - * @param int $accountId - * @param string $folderId - * @param int $messageId + * @param int $id * @param int $attachmentId * @param string $targetPath * @@ -415,14 +445,22 @@ class MessagesController extends Controller { * @throws ClientException * @throws ServiceException */ - public function saveAttachment(int $accountId, string $folderId, int $messageId, - string $attachmentId, string $targetPath) { - $mailBox = $this->getFolder($accountId, $folderId); + public function saveAttachment(int $id, + string $attachmentId, + string $targetPath) { + try { + $message = $this->mailManager->getMessage($this->currentUserId, $id); + $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId()); + $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId()); + } catch (DoesNotExistException $e) { + return new JSONResponse(null, Http::STATUS_FORBIDDEN); + } + $folder = $account->getMailbox($mailbox->getName()); if ($attachmentId === '0') { // Save all attachments /* @var $m IMAPMessage */ - $m = $mailBox->getMessage($messageId); + $m = $folder->getMessage($id); $attachmentIds = array_map(function ($a) { return $a['id']; }, $m->attachments); @@ -430,11 +468,11 @@ class MessagesController extends Controller { $attachmentIds = [$attachmentId]; } - foreach ($attachmentIds as $attachmentId) { - $attachment = $mailBox->getAttachment($messageId, $attachmentId); + foreach ($attachmentIds as $aid) { + $attachment = $folder->getAttachment($message->getUid(), $attachmentId); $fileName = $attachment->getName() ?? $this->l10n->t('Embedded message %s', [ - $attachmentId, + $aid, ]) . '.eml'; $fileParts = pathinfo($fileName); $fileName = $fileParts['filename']; @@ -456,9 +494,7 @@ class MessagesController extends Controller { * @NoAdminRequired * @TrapError * - * @param int $accountId - * @param string $folderId - * @param string $messageId + * @param string $id * @param array $flags * * @return JSONResponse @@ -466,12 +502,18 @@ class MessagesController extends Controller { * @throws ClientException * @throws ServiceException */ - public function setFlags(int $accountId, string $folderId, int $messageId, array $flags): JSONResponse { - $account = $this->accountService->find($this->currentUserId, $accountId); + public function setFlags(int $id, array $flags): JSONResponse { + try { + $message = $this->mailManager->getMessage($this->currentUserId, $id); + $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId()); + $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId()); + } catch (DoesNotExistException $e) { + return new JSONResponse(null, Http::STATUS_FORBIDDEN); + } foreach ($flags as $flag => $value) { $value = filter_var($value, FILTER_VALIDATE_BOOLEAN); - $this->mailManager->flagMessage($account, base64_decode($folderId), $messageId, $flag, $value); + $this->mailManager->flagMessage($account, $mailbox->getName(), $message->getUid(), $flag, $value); } return new JSONResponse(); } @@ -489,49 +531,36 @@ class MessagesController extends Controller { * @throws ClientException * @throws ServiceException */ - public function destroy(int $accountId, string $folderId, int $id): JSONResponse { - $this->logger->debug("deleting message <$id> of folder <$folderId>, account <$accountId>"); - + public function destroy(int $id): JSONResponse { try { - $account = $this->accountService->find($this->currentUserId, $accountId); + $message = $this->mailManager->getMessage($this->currentUserId, $id); + $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId()); + $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId()); } catch (DoesNotExistException $e) { return new JSONResponse(null, Http::STATUS_FORBIDDEN); } - $this->mailManager->deleteMessage($account, base64_decode($folderId), $id); - return new JSONResponse(); - } + $this->logger->debug("deleting message <$id>"); - /** - * @param int $accountId - * @param string $folderId - * - * @return IMailBox - * @deprecated - * - * @throws ClientException - * @throws ServiceException - */ - private function getFolder(int $accountId, string $folderId): IMailBox { - $account = $this->accountService->find($this->currentUserId, $accountId); - return $account->getMailbox(base64_decode($folderId)); + $this->mailManager->deleteMessage( + $account, + $mailbox->getName(), + $message->getUid() + ); + return new JSONResponse(); } /** - * @param int $accountId - * @param string $folderId - * @param int $messageId + * @param int $id * @param array $attachment * * @return array */ - private function enrichDownloadUrl(int $accountId, string $folderId, int $messageId, + private function enrichDownloadUrl(int $id, array $attachment) { $downloadUrl = $this->urlGenerator->linkToRoute('mail.messages.downloadAttachment', [ - 'accountId' => $accountId, - 'folderId' => $folderId, - 'messageId' => $messageId, + 'id' => $id, 'attachmentId' => $attachment['id'], ]); $downloadUrl = $this->urlGenerator->getAbsoluteURL($downloadUrl); diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php index 219bb16ff..fe9181760 100644 --- a/lib/Controller/PageController.php +++ b/lib/Controller/PageController.php @@ -117,13 +117,13 @@ class PageController extends Controller { $this->currentUserId); try { $mailboxes = $this->mailManager->getMailboxes($mailAccount); - $json['folders'] = $mailboxes; + $json['mailboxes'] = $mailboxes; } catch (Exception $ex) { $this->logger->logException($ex, [ - 'message' => 'Could not load account folders: ' . $ex->getMessage(), + 'message' => 'Could not load account mailboxes: ' . $ex->getMessage(), 'level' => ILogger::FATAL, ]); - $json['folders'] = []; + $json['mailboxes'] = []; $json['error'] = true; } $accountsJson[] = $json; @@ -160,6 +160,56 @@ class PageController extends Controller { * @NoAdminRequired * @NoCSRFRequired * + * @return TemplateResponse + */ + public function setup(): TemplateResponse { + return $this->index(); + } + + /** + * @NoAdminRequired + * @NoCSRFRequired + * + * @return TemplateResponse + */ + public function keyboardShortcuts(): TemplateResponse { + return $this->index(); + } + + /** + * @NoAdminRequired + * @NoCSRFRequired + * + * @return TemplateResponse + */ + public function accountSettings(int $id): TemplateResponse { + return $this->index(); + } + + /** + * @NoAdminRequired + * @NoCSRFRequired + * + * @return TemplateResponse + */ + public function mailbox(int $id): TemplateResponse { + return $this->index(); + } + + /** + * @NoAdminRequired + * @NoCSRFRequired + * + * @return TemplateResponse + */ + public function thread(int $mailboxId, int $id): TemplateResponse { + return $this->index(); + } + + /** + * @NoAdminRequired + * @NoCSRFRequired + * * @param string $uri * * @return RedirectResponse diff --git a/lib/Db/MailAccount.php b/lib/Db/MailAccount.php index 36fb54cdf..c63abe27c 100644 --- a/lib/Db/MailAccount.php +++ b/lib/Db/MailAccount.php @@ -153,6 +153,7 @@ class MailAccount extends Entity { */ public function toJson() { $result = [ + 'id' => $this->getId(), 'accountId' => $this->getId(), 'name' => $this->getName(), 'order' => $this->getOrder(), diff --git a/lib/Db/Mailbox.php b/lib/Db/Mailbox.php index 2a877ee64..0da25c85f 100644 --- a/lib/Db/Mailbox.php +++ b/lib/Db/Mailbox.php @@ -121,12 +121,14 @@ class Mailbox extends Entity implements JsonSerializable { return [ 'databaseId' => $this->getId(), 'id' => base64_encode($this->getName()), + 'name' => $this->getName(), 'accountId' => $this->accountId, 'displayName' => $this->getName(), 'attributes' => json_decode($this->attributes ?? '[]', true) ?? [], 'delimiter' => $this->delimiter, 'specialUse' => $specialUse, 'specialRole' => $specialUse[0] ?? 0, + 'mailboxes' => [], ]; } } diff --git a/lib/Db/MailboxMapper.php b/lib/Db/MailboxMapper.php index d2361b927..0b2ae36ce 100644 --- a/lib/Db/MailboxMapper.php +++ b/lib/Db/MailboxMapper.php @@ -85,6 +85,34 @@ class MailboxMapper extends QBMapper { } /** + * @param int $id + * @param string $uid + * + * @return Mailbox + * + * @throws DoesNotExistException + * @throws ServiceException + */ + public function findByUid(int $id, string $uid): Mailbox { + $qb = $this->db->getQueryBuilder(); + + $select = $qb->select('mb.*') + ->from($this->getTableName(), 'mb') + ->join('mb', 'mail_accounts', 'a', $qb->expr()->eq('mb.account_id', 'a.id', IQueryBuilder::PARAM_INT)) + ->where( + $qb->expr()->eq('a.user_id', $qb->createNamedParameter($uid)), + $qb->expr()->eq('mb.id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT) + ); + + try { + return $this->findEntity($select); + } catch (MultipleObjectsReturnedException $e) { + // Not possible due to DB constraints + throw new ServiceException("The impossible has happened", 42, $e); + } + } + + /** * @throws DoesNotExistException */ public function findSpecial(Account $account, string $specialUse): Mailbox { diff --git a/lib/Db/Message.php b/lib/Db/Message.php index 8f028a284..95248b132 100644 --- a/lib/Db/Message.php +++ b/lib/Db/Message.php @@ -126,6 +126,7 @@ class Message extends Entity implements JsonSerializable { $this->bcc = new AddressList([]); $this->addType('uid', 'integer'); + $this->addType('mailboxId', 'integer'); $this->addType('sentAt', 'integer'); $this->addType('flagAnswered', 'bool'); $this->addType('flagDeleted', 'bool'); @@ -252,6 +253,7 @@ class Message extends Entity implements JsonSerializable { 'to' => $this->getTo()->jsonSerialize(), 'cc' => $this->getCc()->jsonSerialize(), 'bcc' => $this->getBcc()->jsonSerialize(), + 'mailboxId' => $this->getMailboxId(), ]; } } diff --git a/lib/Db/MessageMapper.php b/lib/Db/MessageMapper.php index e6593bb8c..77d0564ed 100644 --- a/lib/Db/MessageMapper.php +++ b/lib/Db/MessageMapper.php @@ -31,6 +31,7 @@ use OCA\Mail\Address; use OCA\Mail\AddressList; use OCA\Mail\IMAP\Threading\DatabaseMessage; use OCA\Mail\Service\Search\SearchQuery; +use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\QBMapper; use OCP\AppFramework\Utility\ITimeFactory; use OCP\DB\QueryBuilder\IQueryBuilder; @@ -51,6 +52,11 @@ class MessageMapper extends QBMapper { $this->timeFactory = $timeFactory; } + /** + * @param IQueryBuilder $query + * + * @return int[] + */ private function findUids(IQueryBuilder $query): array { $result = $query->execute(); $uids = array_map(function (array $row) { @@ -61,6 +67,21 @@ class MessageMapper extends QBMapper { return $uids; } + /** + * @param IQueryBuilder $query + * + * @return int[] + */ + private function findIds(IQueryBuilder $query): array { + $result = $query->execute(); + $uids = array_map(function (array $row) { + return (int)$row['id']; + }, $result->fetchAll()); + $result->closeCursor(); + + return $uids; + } + public function findHighestUid(Mailbox $mailbox): ?int { $query = $this->db->getQueryBuilder(); @@ -78,12 +99,64 @@ class MessageMapper extends QBMapper { return $max; } + public function findByUserId(string $uid, int $id): Message { + $query = $this->db->getQueryBuilder(); + + $query->select('m.*') + ->from($this->getTableName(), 'm') + ->join('m', 'mail_mailboxes', 'mb', $query->expr()->eq('m.mailbox_id', 'mb.id', IQueryBuilder::PARAM_INT)) + ->join('m', 'mail_accounts', 'a', $query->expr()->eq('mb.account_id', 'a.id', IQueryBuilder::PARAM_INT)) + ->where( + $query->expr()->eq('a.user_id', $query->createNamedParameter($uid)), + $query->expr()->eq('m.id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT) + ); + + $results = $this->findRecipients($this->findEntities($query)); + if (empty($results)) { + throw new DoesNotExistException("Message $id does not exist"); + } + return $results[0]; + } + public function findAllUids(Mailbox $mailbox): array { $query = $this->db->getQueryBuilder(); $query->select('uid') ->from($this->getTableName()) - ->where($query->expr()->eq('mailbox_id', $query->createNamedParameter($mailbox->getId()))); + ->where($query->expr()->eq('mailbox_id', $query->createNamedParameter($mailbox->getId(), IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT)); + + return $this->findUids($query); + } + + public function findAllIds(Mailbox $mailbox): array { + $query = $this->db->getQueryBuilder(); + + $query->select('id') + ->from($this->getTableName()) + ->where($query->expr()->eq('mailbox_id', $query->createNamedParameter($mailbox->getId(), IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT)); + + return $this->findIds($query); + } + + /** + * @param Mailbox $mailbox + * @param int[] $ids + * + * @return int[] + */ + public function findUidsForIds(Mailbox $mailbox, array $ids) { + if (empty($ids)) { + // Shortcut for empty sets + return []; + } + + $query = $this->db->getQueryBuilder(); + $query->select('uid') + ->from($this->getTableName()) + ->where( + $query->expr()->eq('mailbox_id', $query->createNamedParameter($mailbox->getId(), IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT), + $query->expr()->in('id', $query->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY), IQueryBuilder::PARAM_INT_ARRAY) + ); return $this->findUids($query); } @@ -382,11 +455,11 @@ class MessageMapper extends QBMapper { * * @return int[] */ - public function findUidsByQuery(Mailbox $mailbox, SearchQuery $query, ?int $limit, array $uids = null): array { + public function findIdsByQuery(Mailbox $mailbox, SearchQuery $query, ?int $limit, array $uids = null): array { $qb = $this->db->getQueryBuilder(); $select = $qb - ->selectDistinct('m.uid') + ->selectDistinct('m.id') ->addSelect('m.sent_at') ->from($this->getTableName(), 'm'); @@ -477,7 +550,7 @@ class MessageMapper extends QBMapper { } return array_map(function (Message $message) { - return $message->getUid(); + return $message->getId(); }, $this->findEntities($select)); } @@ -503,6 +576,28 @@ class MessageMapper extends QBMapper { } /** + * @param int[] $ids + * + * @return Message[] + */ + public function findByIds(array $ids): array { + if (empty($ids)) { + return []; + } + $qb = $this->db->getQueryBuilder(); + + $select = $qb + ->select('*') + ->from($this->getTableName()) + ->where( + $qb->expr()->in('id', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY), IQueryBuilder::PARAM_INT_ARRAY) + ) + ->orderBy('sent_at', 'desc'); + + return $this->findRecipients($this->findEntities($select)); + } + + /** * @param Message[] $messages * * @return Message[] @@ -558,18 +653,26 @@ class MessageMapper extends QBMapper { * * @return Message[] */ - public function findNewUids(Mailbox $mailbox, int $highest): array { + public function findNewIds(Mailbox $mailbox, array $ids): array { $qb = $this->db->getQueryBuilder(); + $sub = $this->db->getQueryBuilder(); + $subSelect = $sub + ->select($sub->func()->max('uid')) + ->from($this->getTableName()) + ->where( + $sub->expr()->eq('mailbox_id', $qb->createNamedParameter($mailbox->getId(), IQueryBuilder::PARAM_INT)), + $sub->expr()->in('id', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY), IQueryBuilder::PARAM_INT_ARRAY) + ); $select = $qb - ->select('uid') + ->select('id') ->from($this->getTableName()) ->where( $qb->expr()->eq('mailbox_id', $qb->createNamedParameter($mailbox->getId(), IQueryBuilder::PARAM_INT)), - $qb->expr()->gt('uid', $qb->createNamedParameter($highest, IQueryBuilder::PARAM_INT)) + $qb->expr()->gt('uid', $qb->createFunction('(' . $subSelect->getSQL() . ')'), IQueryBuilder::PARAM_INT) ); - return $this->findUids($select); + return $this->findIds($select); } public function findChanged(Mailbox $mailbox, int $since): array { @@ -644,4 +747,22 @@ class MessageMapper extends QBMapper { ->where($qb4->expr()->in('id', $qb4->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY), IQueryBuilder::PARAM_INT_ARRAY)); $recipientsQuery->execute(); } + + public function getIdForUid(Mailbox $mailbox, $uid): ?int { + $qb = $this->db->getQueryBuilder(); + + $select = $qb + ->select('m.id') + ->from($this->getTableName(), 'm') + ->where( + $qb->expr()->eq('mailbox_id', $qb->createNamedParameter($mailbox->getId()), IQueryBuilder::PARAM_INT), + $qb->expr()->eq('uid', $qb->createNamedParameter($uid, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT) + ); + $result = $select->execute(); + $rows = $result->fetchAll(); + if (empty($rows)) { + return null; + } + return (int) $rows[0]['id']; + } } diff --git a/lib/Events/DraftSavedEvent.php b/lib/Events/DraftSavedEvent.php index f8b6368da..677b4556c 100644 --- a/lib/Events/DraftSavedEvent.php +++ b/lib/Events/DraftSavedEvent.php @@ -26,6 +26,7 @@ declare(strict_types=1); namespace OCA\Mail\Events; use OCA\Mail\Account; +use OCA\Mail\Db\Message; use OCA\Mail\Model\NewMessageData; use OCP\EventDispatcher\Event; @@ -37,16 +38,16 @@ class DraftSavedEvent extends Event { /** @var NewMessageData */ private $newMessageData; - /** @var int|null */ - private $draftUid; + /** @var Message|null */ + private $draft; public function __construct(Account $account, NewMessageData $newMessageData, - ?int $draftUid) { + ?Message $draft) { parent::__construct(); $this->account = $account; $this->newMessageData = $newMessageData; - $this->draftUid = $draftUid; + $this->draft = $draft; } public function getAccount(): Account { @@ -57,7 +58,7 @@ class DraftSavedEvent extends Event { return $this->newMessageData; } - public function getDraftUid(): ?int { - return $this->draftUid; + public function getDraft(): ?Message { + return $this->draft; } } diff --git a/lib/Events/MessageSentEvent.php b/lib/Events/MessageSentEvent.php index 563467c4c..a2973cb8c 100644 --- a/lib/Events/MessageSentEvent.php +++ b/lib/Events/MessageSentEvent.php @@ -27,6 +27,7 @@ namespace OCA\Mail\Events; use Horde_Mime_Mail; use OCA\Mail\Account; +use OCA\Mail\Db\Message; use OCA\Mail\Model\IMessage; use OCA\Mail\Model\NewMessageData; use OCA\Mail\Model\RepliedMessageData; @@ -43,8 +44,8 @@ class MessageSentEvent extends Event { /** @var null|RepliedMessageData */ private $repliedMessageData; - /** @var int|null */ - private $draftUid; + /** @var Message|null */ + private $draft; /** @var IMessage */ private $message; @@ -55,14 +56,14 @@ class MessageSentEvent extends Event { public function __construct(Account $account, NewMessageData $newMessageData, ?RepliedMessageData $repliedMessageData, - ?int $draftUid, + ?Message $draft, IMessage $message, Horde_Mime_Mail $mail) { parent::__construct(); $this->account = $account; $this->newMessageData = $newMessageData; $this->repliedMessageData = $repliedMessageData; - $this->draftUid = $draftUid; + $this->draft = $draft; $this->message = $message; $this->mail = $mail; } @@ -79,8 +80,8 @@ class MessageSentEvent extends Event { return $this->repliedMessageData; } - public function getDraftUid(): ?int { - return $this->draftUid; + public function getDraft(): ?Message { + return $this->draft; } public function getMessage(): IMessage { diff --git a/lib/Events/SaveDraftEvent.php b/lib/Events/SaveDraftEvent.php index 1f5590281..830bfe976 100644 --- a/lib/Events/SaveDraftEvent.php +++ b/lib/Events/SaveDraftEvent.php @@ -26,6 +26,7 @@ declare(strict_types=1); namespace OCA\Mail\Events; use OCA\Mail\Account; +use OCA\Mail\Db\Message; use OCA\Mail\Model\NewMessageData; use OCP\EventDispatcher\Event; @@ -37,16 +38,16 @@ class SaveDraftEvent extends Event { /** @var NewMessageData */ private $newMessageData; - /** @var int|null */ - private $draftUid; + /** @var Message|null */ + private $draft; public function __construct(Account $account, NewMessageData $newMessageData, - ?int $draftUid) { + ?Message $draft) { parent::__construct(); $this->account = $account; $this->newMessageData = $newMessageData; - $this->draftUid = $draftUid; + $this->draft = $draft; } public function getAccount(): Account { @@ -57,7 +58,7 @@ class SaveDraftEvent extends Event { return $this->newMessageData; } - public function getDraftUid(): ?int { - return $this->draftUid; + public function getDraft(): ?Message { + return $this->draft; } } diff --git a/lib/IMAP/MessageMapper.php b/lib/IMAP/MessageMapper.php index 2d0f3cee8..3b4fde341 100644 --- a/lib/IMAP/MessageMapper.php +++ b/lib/IMAP/MessageMapper.php @@ -364,14 +364,14 @@ class MessageMapper { /** * @param Horde_Imap_Client_Socket $client * @param string $mailbox - * @param int $id + * @param int $uid * * @return string|null * @throws ServiceException */ public function getSource(Horde_Imap_Client_Socket $client, string $mailbox, - int $id): ?string { + int $uid): ?string { $query = new Horde_Imap_Client_Fetch_Query(); $query->uid(); $query->fullText([ @@ -380,7 +380,7 @@ class MessageMapper { try { $result = iterator_to_array($client->fetch($mailbox, $query, [ - 'ids' => new Horde_Imap_Client_Ids($id), + 'ids' => new Horde_Imap_Client_Ids($uid), ]), false); } catch (Horde_Imap_Client_Exception $e) { throw new ServiceException("Could not fetch message source: " . $e->getMessage(), $e->getCode(), $e); @@ -399,13 +399,13 @@ class MessageMapper { public function getHtmlBody(Horde_Imap_Client_Socket $client, string $mailbox, - int $id): ?string { + int $uid): ?string { $messageQuery = new Horde_Imap_Client_Fetch_Query(); $messageQuery->envelope(); $messageQuery->structure(); $result = $client->fetch($mailbox, $messageQuery, [ - 'ids' => new Horde_Imap_Client_Ids([$id]), + 'ids' => new Horde_Imap_Client_Ids([$uid]), ]); if (($message = $result->first()) === null) { @@ -435,7 +435,7 @@ class MessageMapper { } $parts = $client->fetch($mailbox, $partsQuery, [ - 'ids' => new Horde_Imap_Client_Ids([$id]), + 'ids' => new Horde_Imap_Client_Ids([$uid]), ]); foreach ($parts as $part) { @@ -456,12 +456,12 @@ class MessageMapper { public function getRawAttachments(Horde_Imap_Client_Socket $client, string $mailbox, - int $id): array { + int $uid): array { $messageQuery = new Horde_Imap_Client_Fetch_Query(); $messageQuery->structure(); $result = $client->fetch($mailbox, $messageQuery, [ - 'ids' => new Horde_Imap_Client_Ids([$id]), + 'ids' => new Horde_Imap_Client_Ids([$uid]), ]); if (($structureResult = $result->first()) === null) { @@ -488,7 +488,7 @@ class MessageMapper { } $parts = $client->fetch($mailbox, $partsQuery, [ - 'ids' => new Horde_Imap_Client_Ids([$id]), + 'ids' => new Horde_Imap_Client_Ids([$uid]), ]); if (($messageData = $parts->first()) === null) { throw new DoesNotExistException('Message does not exist'); diff --git a/lib/IMAP/Sync/Response.php b/lib/IMAP/Sync/Response.php index 9e95b342e..d12625775 100644 --- a/lib/IMAP/Sync/Response.php +++ b/lib/IMAP/Sync/Response.php @@ -24,14 +24,15 @@ declare(strict_types=1); namespace OCA\Mail\IMAP\Sync; use JsonSerializable; +use OCA\Mail\Db\Message; use OCA\Mail\Model\IMAPMessage; class Response implements JsonSerializable { - /** @var IMAPMessage[] */ + /** @var IMAPMessage|Message[] */ private $newMessages; - /** @var IMAPMessage[] */ + /** @var IMAPMessage|Message[] */ private $changedMessages; /** @var int[] */ @@ -39,8 +40,8 @@ class Response implements JsonSerializable { /** * @param string $syncToken - * @param IMAPMessage[] $newMessages - * @param IMAPMessage[] $changedMessages + * @param IMAPMessage|Message[] $newMessages + * @param IMAPMessage|Message[] $changedMessages * @param int[] $vanishedMessageUids */ public function __construct(array $newMessages = [], @@ -52,14 +53,14 @@ class Response implements JsonSerializable { } /** - * @return IMAPMessage[] + * @return IMAPMessage|Message[] */ public function getNewMessages(): array { return $this->newMessages; } /** - * @return IMAPMessage[] + * @return IMAPMessage|Message[] */ public function getChangedMessages(): array { return $this->changedMessages; diff --git a/lib/IMAP/Threading/ThreadBuilder.php b/lib/IMAP/Threading/ThreadBuilder.php index 72283423f..8053d045d 100644 --- a/lib/IMAP/Threading/ThreadBuilder.php +++ b/lib/IMAP/Threading/ThreadBuilder.php @@ -110,6 +110,9 @@ class ThreadBuilder { // Step 1.C //$parentId = $message->getReferences()[count($message->getReferences()) - 1] ?? null; //$container->setParent($idTable[$parentId] ?? null); + if ($parent === $container) { + throw new \Exception("about to run into a nasty endless loop"); + } if ($parent === null || !$parent->hasAncestor($container)) { $container->setParent($parent); } diff --git a/lib/Listener/DeleteDraftListener.php b/lib/Listener/DeleteDraftListener.php index 0e76adec6..9758a0fa0 100644 --- a/lib/Listener/DeleteDraftListener.php +++ b/lib/Listener/DeleteDraftListener.php @@ -30,6 +30,7 @@ use Horde_Imap_Client_Exception; use OCA\Mail\Account; use OCA\Mail\Db\Mailbox; use OCA\Mail\Db\MailboxMapper; +use OCA\Mail\Db\Message; use OCA\Mail\Events\DraftSavedEvent; use OCA\Mail\Events\MessageSentEvent; use OCA\Mail\IMAP\IMAPClientFactory; @@ -70,17 +71,18 @@ class DeleteDraftListener implements IEventListener { } public function handle(Event $event): void { - if ($event instanceof DraftSavedEvent && $event->getDraftUid() !== null) { - $this->deleteDraft($event->getAccount(), $event->getDraftUid()); - } elseif ($event instanceof MessageSentEvent && $event->getDraftUid() !== null) { - $this->deleteDraft($event->getAccount(), $event->getDraftUid()); + if ($event instanceof DraftSavedEvent && $event->getDraft() !== null) { + $this->deleteDraft($event->getAccount(), $event->getDraft()); + } elseif ($event instanceof MessageSentEvent && $event->getDraft() !== null) { + $this->deleteDraft($event->getAccount(), $event->getDraft()); } } /** - * @param DraftSavedEvent $event + * @param Account $account + * @param Message $draft */ - private function deleteDraft(Account $account, int $draftUid): void { + private function deleteDraft(Account $account, Message $draft): void { $client = $this->imapClientFactory->getClient($account); $draftsMailbox = $this->getDraftsMailbox($account); @@ -88,7 +90,7 @@ class DeleteDraftListener implements IEventListener { $this->messageMapper->addFlag( $client, $draftsMailbox, - $draftUid, + $draft->getUid(), // TODO: the UID could be from another mailbox Horde_Imap_Client::FLAG_DELETED ); } catch (Horde_Imap_Client_Exception $e) { diff --git a/lib/Mailbox.php b/lib/Mailbox.php index 09daef680..5aecf8330 100644 --- a/lib/Mailbox.php +++ b/lib/Mailbox.php @@ -295,12 +295,13 @@ class Mailbox implements IMailBox { } /** - * @param int $messageId + * @param int $messageUid * @param string $attachmentId + * * @return Attachment */ - public function getAttachment(int $messageId, string $attachmentId): Attachment { - return new Attachment($this->conn, $this->mailBox, $messageId, $attachmentId); + public function getAttachment(int $messageUid, string $attachmentId): Attachment { + return new Attachment($this->conn, $this->mailBox, $messageUid, $attachmentId); } /** diff --git a/lib/Model/IMAPMessage.php b/lib/Model/IMAPMessage.php index 1dae7a1d0..8b7ae12a3 100644 --- a/lib/Model/IMAPMessage.php +++ b/lib/Model/IMAPMessage.php @@ -48,7 +48,6 @@ use OCA\Mail\Service\Html; use OCP\AppFramework\Db\DoesNotExistException; use OCP\Files\File; use OCP\Files\SimpleFS\ISimpleFile; -use function base64_encode; use function in_array; use function mb_convert_encoding; @@ -422,15 +421,17 @@ class IMAPMessage implements IMessage, JsonSerializable { } /** + * @param int $id + * * @return array */ - public function getFullMessage(int $accountId, string $mailbox, int $id): array { + public function getFullMessage(int $id): array { $mailBody = $this->plainMessage; $data = $this->jsonSerialize(); if ($this->hasHtmlMessage) { $data['hasHtmlBody'] = true; - $data['body'] = $this->getHtmlBody($accountId, $mailbox, $id); + $data['body'] = $this->getHtmlBody($id); } else { $mailBody = $this->htmlService->convertLinks($mailBody); list($mailBody, $signature) = $this->htmlService->parseMailBody($mailBody); @@ -462,17 +463,13 @@ class IMAPMessage implements IMessage, JsonSerializable { } /** - * @param int $accountId - * @param string $folderId - * @param int $messageId + * @param int $id * * @return string */ - public function getHtmlBody(int $accountId, string $folderId, int $messageId): string { + public function getHtmlBody(int $id): string { return $this->htmlService->sanitizeHtmlMailBody($this->htmlMessage, [ - 'accountId' => $accountId, - 'folderId' => base64_encode($folderId), - 'messageId' => $messageId, + 'id' => $id, ], function ($cid) { $match = array_filter($this->attachments, function ($a) use ($cid) { diff --git a/lib/Service/AccountService.php b/lib/Service/AccountService.php index 83a4a6977..36fbf11a1 100644 --- a/lib/Service/AccountService.php +++ b/lib/Service/AccountService.php @@ -89,25 +89,25 @@ class AccountService { /** * @param string $uid - * @param int $accountId + * @param int $id * * @return Account * @throws ClientException */ - public function find(string $uid, int $accountId): Account { + public function find(string $uid, int $id): Account { if ($this->accounts !== null) { foreach ($this->accounts as $account) { - if ($account->getId() === $accountId) { + if ($account->getId() === $id) { return $account; } } - throw new ClientException("Account $accountId does not exist or you don\'t have permission to access it"); + throw new ClientException("Account $id does not exist or you don\'t have permission to access it"); } try { - return new Account($this->mapper->find($uid, $accountId)); + return new Account($this->mapper->find($uid, $id)); } catch (DoesNotExistException $e) { - throw new ClientException("Account $accountId does not exist or you don\'t have permission to access it"); + throw new ClientException("Account $id does not exist or you don\'t have permission to access it"); } } diff --git a/lib/Service/IMailBox.php b/lib/Service/IMailBox.php index 093e3d7f1..d75c5c562 100644 --- a/lib/Service/IMailBox.php +++ b/lib/Service/IMailBox.php @@ -47,11 +47,12 @@ interface IMailBox { public function getMessage(int $id, bool $loadHtmlMessageBody = false); /** - * @param int $messageId + * @param int $messageUid * @param string $attachmentId + * * @return Attachment */ - public function getAttachment(int $messageId, string $attachmentId): Attachment; + public function getAttachment(int $messageUid, string $attachmentId): Attachment; /** * @param int $flags diff --git a/lib/Service/MailManager.php b/lib/Service/MailManager.php index 08cceb812..09d04dcc3 100644 --- a/lib/Service/MailManager.php +++ b/lib/Service/MailManager.php @@ -30,6 +30,7 @@ use OCA\Mail\Account; use OCA\Mail\Contracts\IMailManager; use OCA\Mail\Db\Mailbox; use OCA\Mail\Db\MailboxMapper; +use OCA\Mail\Db\Message; use OCA\Mail\Db\MessageMapper as DbMessageMapper; use OCA\Mail\Events\BeforeMessageDeletedEvent; use OCA\Mail\Events\MessageDeletedEvent; @@ -100,6 +101,14 @@ class MailManager implements IMailManager { $this->eventDispatcher = $eventDispatcher; } + public function getMailbox(string $uid, int $id): Mailbox { + try { + return $this->mailboxMapper->findByUid($id, $uid); + } catch (DoesNotExistException $e) { + throw new ClientException("Mailbox $id does not exist", 0, $e); + } + } + /** * @param Account $account * @@ -116,10 +125,10 @@ class MailManager implements IMailManager { * @param Account $account * @param string $name * - * @return Folder + * @return Mailbox * @throws ServiceException */ - public function createFolder(Account $account, string $name): Folder { + public function createMailbox(Account $account, string $name): Mailbox { $client = $this->imapClientFactory->getClient($account); $folder = $this->folderMapper->createFolder($client, $account, $name); @@ -132,30 +141,33 @@ class MailManager implements IMailManager { $this->mailboxSync->sync($account, true); - return $folder; + return $this->mailboxMapper->find($account, $name); } /** * @param Account $account - * @param string $folderId + * @param Mailbox $mailbox * * @return FolderStats + * @throws Horde_Imap_Client_Exception */ - public function getFolderStats(Account $account, string $folderId): FolderStats { + public function getMailboxStats(Account $account, Mailbox $mailbox): FolderStats { $client = $this->imapClientFactory->getClient($account); - return $this->folderMapper->getFoldersStatusAsObject($client, $folderId); + return $this->folderMapper->getFoldersStatusAsObject($client, $mailbox->getName()); } - public function getMessage(Account $account, string $mailbox, int $id, bool $loadBody = false): IMAPMessage { + public function getImapMessage(Account $account, + Mailbox $mailbox, + int $uid, + bool $loadBody = false): IMAPMessage { $client = $this->imapClientFactory->getClient($account); - $mailbox = $this->mailboxMapper->find($account, $mailbox); try { return $this->imapMessageMapper->find( $client, $mailbox->getName(), - $id, + $uid, $loadBody ); } catch (Horde_Imap_Client_Exception|DoesNotExistException $e) { @@ -167,24 +179,32 @@ class MailManager implements IMailManager { return $this->dbMessageMapper->findThread($account, $messageId); } + public function getMessageIdForUid(Mailbox $mailbox, $uid): ?int { + return $this->dbMessageMapper->getIdForUid($mailbox, $uid); + } + + public function getMessage(string $uid, int $id): Message { + return $this->dbMessageMapper->findByUserId($uid, $id); + } + /** * @param Account $account * @param string $mb - * @param int $id + * @param int $uid * * @return string * * @throws ClientException * @throws ServiceException */ - public function getSource(Account $account, string $mailbox, int $id): string { + public function getSource(Account $account, string $mailbox, int $uid): ?string { $client = $this->imapClientFactory->getClient($account); try { return $this->imapMessageMapper->getSource( $client, $mailbox, - $id + $uid ); } catch (Horde_Imap_Client_Exception|DoesNotExistException $e) { throw new ServiceException("Could not load message", 0, $e); @@ -194,7 +214,7 @@ class MailManager implements IMailManager { /** * @param Account $sourceAccount * @param string $sourceFolderId - * @param int $messageId + * @param int $uid * @param Account $destinationAccount * @param string $destFolderId * @@ -204,7 +224,7 @@ class MailManager implements IMailManager { */ public function moveMessage(Account $sourceAccount, string $sourceFolderId, - int $messageId, + int $uid, Account $destinationAccount, string $destFolderId) { if ($sourceAccount->getId() === $destinationAccount->getId()) { @@ -212,7 +232,7 @@ class MailManager implements IMailManager { $sourceAccount, $sourceFolderId, $destFolderId, - $messageId + $uid ); } else { throw new ServiceException('It is not possible to move across accounts yet'); @@ -284,10 +304,10 @@ class MailManager implements IMailManager { $this->imapMessageMapper->move($client, $sourceFolderId, $messageId, $destFolderId); } - public function markFolderAsRead(Account $account, string $folderId): void { + public function markFolderAsRead(Account $account, Mailbox $mailbox): void { $client = $this->imapClientFactory->getClient($account); - $this->imapMessageMapper->markAllRead($client, $folderId); + $this->imapMessageMapper->markAllRead($client, $mailbox->getName()); } public function flagMessage(Account $account, string $mailbox, int $uid, string $flag, bool $value): void { @@ -371,18 +391,14 @@ class MailManager implements IMailManager { /** * @param Account $account - * @param string $folderId + * @param Mailbox $mailbox + * * @throws ServiceException */ public function deleteMailbox(Account $account, - string $folderId): void { - try { - $mailbox = $this->mailboxMapper->find($account, $folderId); - } catch (DoesNotExistException $e) { - throw new ServiceException("Source mailbox $folderId does not exist", 0, $e); - } + Mailbox $mailbox): void { $client = $this->imapClientFactory->getClient($account); - $this->folderMapper->delete($client, $folderId); + $this->folderMapper->delete($client, $mailbox->getName()); $this->mailboxMapper->delete($mailbox); } } diff --git a/lib/Service/MailTransmission.php b/lib/Service/MailTransmission.php index 9db9c1d4a..e8ff9d135 100644 --- a/lib/Service/MailTransmission.php +++ b/lib/Service/MailTransmission.php @@ -39,6 +39,7 @@ use OCA\Mail\Contracts\IAttachmentService; use OCA\Mail\Contracts\IMailTransmission; use OCA\Mail\Db\Alias; use OCA\Mail\Db\MailboxMapper; +use OCA\Mail\Db\Message; use OCA\Mail\Events\DraftSavedEvent; use OCA\Mail\Events\MessageSentEvent; use OCA\Mail\Events\SaveDraftEvent; @@ -102,22 +103,10 @@ class MailTransmission implements IMailTransmission { $this->logger = $logger; } - /** - * Send a new message or reply to an existing one - * - * @param NewMessageData $messageData - * @param RepliedMessageData $replyData - * @param Alias|null $alias - * @param int|null $draftUID - * - * @throws ServiceException - * - * @return void - */ public function sendMessage(NewMessageData $messageData, RepliedMessageData $replyData = null, Alias $alias = null, - int $draftUID = null) { + Message $draft = null) { $account = $messageData->getAccount(); if ($replyData !== null) { @@ -178,17 +167,22 @@ class MailTransmission implements IMailTransmission { $this->eventDispatcher->dispatch( MessageSentEvent::class, - new MessageSentEvent($account, $messageData, $replyData, $draftUID, $message, $mail) + new MessageSentEvent($account, $messageData, $replyData, $draft, $message, $mail) ); } /** + * @param NewMessageData $message + * @param Message|null $previousDraft + * + * @return array + * * @throws ServiceException */ - public function saveDraft(NewMessageData $message, int $draftUID = null): int { + public function saveDraft(NewMessageData $message, Message $previousDraft = null): array { $this->eventDispatcher->dispatch( SaveDraftEvent::class, - new SaveDraftEvent($message->getAccount(), $message, $draftUID) + new SaveDraftEvent($message->getAccount(), $message, $previousDraft) ); $account = $message->getAccount(); @@ -243,10 +237,10 @@ class MailTransmission implements IMailTransmission { $this->eventDispatcher->dispatch( DraftSavedEvent::class, - new DraftSavedEvent($account, $message, $draftUID) + new DraftSavedEvent($account, $message, $previousDraft) ); - return $newUid; + return [$account, $draftsMailbox, $newUid]; } private function buildReplyMessage(Account $account, diff --git a/lib/Service/Search/MailSearch.php b/lib/Service/Search/MailSearch.php index cead97321..85b021704 100644 --- a/lib/Service/Search/MailSearch.php +++ b/lib/Service/Search/MailSearch.php @@ -75,32 +75,26 @@ class MailSearch implements IMailSearch { $this->logger = $logger; } - public function findMessage(Account $account, string $mailboxName, int $uid): Message { - try { - $mailbox = $this->mailboxMapper->find($account, $mailboxName); - } catch (DoesNotExistException $e) { - throw new ServiceException('Mailbox does not exist', 0, $e); - } - - $messages = $this->previewEnhancer->process( + public function findMessage(Account $account, + Mailbox $mailbox, + Message $message): Message { + $processed = $this->previewEnhancer->process( $account, $mailbox, - $this->messageMapper->findByUids( - $mailbox, - [$uid] - ) + [$message] ); - if (empty($messages)) { + if (empty($processed)) { throw new DoesNotExistException("Message does not exist"); } - return $messages[0]; + return $processed[0]; } /** * @param Account $account - * @param string $mailboxName + * @param Mailbox $mailbox * @param string|null $filter * @param int|null $cursor + * @param int|null $limit * * @return Message[] * @@ -108,16 +102,10 @@ class MailSearch implements IMailSearch { * @throws ServiceException */ public function findMessages(Account $account, - string $mailboxName, + Mailbox $mailbox, ?string $filter, ?int $cursor, ?int $limit): array { - try { - $mailbox = $this->mailboxMapper->find($account, $mailboxName); - } catch (DoesNotExistException $e) { - throw new ServiceException('Mailbox does not exist', 0, $e); - } - if ($mailbox->hasLocks()) { throw MailboxLockedException::from($mailbox); } @@ -141,9 +129,8 @@ class MailSearch implements IMailSearch { return $this->previewEnhancer->process( $account, $mailbox, - $this->messageMapper->findByUids( - $mailbox, - $this->getUids($account, $mailbox, $query, $limit) + $this->messageMapper->findByIds( + $this->getIds($account, $mailbox, $query, $limit) ) ); } @@ -153,9 +140,9 @@ class MailSearch implements IMailSearch { * * @throws ServiceException */ - private function getUids(Account $account, Mailbox $mailbox, SearchQuery $query, ?int $limit): array { + private function getIds(Account $account, Mailbox $mailbox, SearchQuery $query, ?int $limit): array { if (empty($query->getTextTokens())) { - return $this->messageMapper->findUidsByQuery($mailbox, $query, $limit); + return $this->messageMapper->findIdsByQuery($mailbox, $query, $limit); } $fromImap = $this->imapSearchProvider->findMatches( @@ -163,6 +150,6 @@ class MailSearch implements IMailSearch { $mailbox, $query ); - return $this->messageMapper->findUidsByQuery($mailbox, $query, $limit, $fromImap); + return $this->messageMapper->findIdsByQuery($mailbox, $query, $limit, $fromImap); } } diff --git a/lib/Service/Sync/SyncService.php b/lib/Service/Sync/SyncService.php index 484ce597b..385a66b84 100644 --- a/lib/Service/Sync/SyncService.php +++ b/lib/Service/Sync/SyncService.php @@ -38,10 +38,8 @@ use OCA\Mail\IMAP\PreviewEnhancer; use OCA\Mail\IMAP\Sync\Response; use OCA\Mail\Service\Search\FilterStringParser; use OCA\Mail\Service\Search\SearchQuery; -use OCP\AppFramework\Db\DoesNotExistException; use function array_diff; use function array_map; -use function end; class SyncService { @@ -74,46 +72,36 @@ class SyncService { /** * @param Account $account - * @param string $mailboxId + * @param Mailbox $mailbox * * @throws MailboxLockedException * @throws ServiceException */ public function clearCache(Account $account, - string $mailboxId): void { - try { - $mailbox = $this->mailboxMapper->find($account, $mailboxId); - } catch (DoesNotExistException $e) { - throw new ServiceException('Mailbox to sync does not exist in the database', 0, $e); - } - + Mailbox $mailbox): void { $this->synchronizer->clearCache($account, $mailbox); } /** * @param Account $account - * @param string $mailboxId + * @param Mailbox $mailbox * @param int $criteria - * @param array $knownUids + * @param int[] $knownIds * @param bool $partialOnly * + * @param string|null $filter + * * @return Response * @throws ClientException * @throws MailboxNotCachedException * @throws ServiceException */ public function syncMailbox(Account $account, - string $mailboxId, + Mailbox $mailbox, int $criteria, - array $knownUids, + array $knownIds, bool $partialOnly, string $filter = null): Response { - try { - $mailbox = $this->mailboxMapper->find($account, $mailboxId); - } catch (DoesNotExistException $e) { - throw new ServiceException('Mailbox to sync does not exist in the database', 0, $e); - } - if ($partialOnly && !$mailbox->isCached()) { throw MailboxNotCachedException::from($mailbox); } @@ -122,52 +110,57 @@ class SyncService { $account, $mailbox, $criteria, - $knownUids, + $this->messageMapper->findUidsForIds($mailbox, $knownIds), !$partialOnly ); $query = $filter === null ? null : $this->filterStringParser->parse($filter); - - return $this->getDatabaseSyncChanges($account, $mailbox, $knownUids, $query); + return $this->getDatabaseSyncChanges( + $account, + $mailbox, + $knownIds, + $query + ); } /** * @param Account $account * @param Mailbox $mailbox - * @param array $knownUids + * @param int[] $knownIds * @param SearchQuery $query * * @return Response * @todo does not work with text token search queries * */ - private function getDatabaseSyncChanges(Account $account, Mailbox $mailbox, array $knownUids, ?SearchQuery $query): Response { - if (empty($knownUids)) { - $newUids = $this->messageMapper->findAllUids($mailbox); + private function getDatabaseSyncChanges(Account $account, + Mailbox $mailbox, + array $knownIds, + ?SearchQuery $query): Response { + if (empty($knownIds)) { + $newIds = $this->messageMapper->findAllIds($mailbox); } else { - sort($knownUids, SORT_NUMERIC); - $last = end($knownUids); - $newUids = $this->messageMapper->findNewUids($mailbox, $last); + $newIds = $this->messageMapper->findNewIds($mailbox, $knownIds); } if ($query !== null) { // Filter new messages to those that also match the current filter - $newUids = $this->messageMapper->findUidsByQuery($mailbox, $query, null, $newUids); + $newIds = $this->messageMapper->findIdsByQuery($mailbox, $query, null, $newIds); } - $new = $this->messageMapper->findByUids($mailbox, $newUids); + $new = $this->messageMapper->findByIds($newIds); // TODO: $changed = $this->messageMapper->findChanged($mailbox, $uids); if ($query !== null) { - $changedUids = $this->messageMapper->findUidsByQuery($mailbox, $query, null, $knownUids); + $changedIds = $this->messageMapper->findIdsByQuery($mailbox, $query, null, $knownIds); } else { - $changedUids = $knownUids; + $changedIds = $knownIds; } - $changed = $this->messageMapper->findByUids($mailbox, $changedUids); + $changed = $this->messageMapper->findByIds($changedIds); - $stillKnownUids = array_map(static function (Message $msg) { - return $msg->getUid(); + $stillKnownIds = array_map(static function (Message $msg) { + return $msg->getId(); }, $changed); - $vanished = array_values(array_diff($knownUids, $stillKnownUids)); + $vanished = array_values(array_diff($knownIds, $stillKnownIds)); return new Response( $this->previewEnhancer->process($account, $mailbox, $new), diff --git a/package-lock.json b/package-lock.json index 2aeec20c3..8c996d577 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4687,7 +4687,7 @@ }, "domelementtype": { "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "resolved": "http://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" }, "domexception": { diff --git a/src/components/Address.vue b/src/components/Address.vue index 092d272a0..0840480b4 100644 --- a/src/components/Address.vue +++ b/src/components/Address.vue @@ -28,9 +28,8 @@ export default { return { name: 'message', params: { - accountId: this.$route.params.accountId, - folderId: this.$route.params.folderId, - messageUuid: 'new', + mailboxId: this.$route.params.mailboxId, + threadId: 'new', }, query: { to: this.email, diff --git a/src/components/Composer.vue b/src/components/Composer.vue index bcc4c43a6..7eda5beff 100644 --- a/src/components/Composer.vue +++ b/src/components/Composer.vue @@ -405,7 +405,7 @@ export default { }, }, watch: { - '$route.params.messageUuid'() { + '$route.params.threadId'() { this.reset() }, allRecipients() { @@ -497,29 +497,28 @@ export default { return `"${recipient.label}" <${recipient.email}>` } }, - getMessageData(uid) { + getMessageData(id) { return { account: this.selectedAlias.id, aliasId: this.selectedAlias.aliasId, to: this.selectTo.map(this.recipientToRfc822).join(', '), cc: this.selectCc.map(this.recipientToRfc822).join(', '), bcc: this.selectBcc.map(this.recipientToRfc822).join(', '), - draftUID: uid, + draftId: id, subject: this.subjectVal, body: this.encrypt ? plain(this.bodyVal) : html(this.bodyVal), attachments: this.attachments, - folderId: this.replyTo ? this.replyTo.folderId : undefined, - messageId: this.replyTo ? this.replyTo.uid : undefined, + messageId: this.replyTo ? this.replyTo.databaseId : undefined, isHtml: !this.editorPlainText, } }, saveDraft(data) { this.savingDraft = true this.draftsPromise = this.draftsPromise - .then((uid) => { - const draftData = data(uid) + .then((id) => { + const draftData = data(id) if ( - !uid + !id && !draftData.subject && !draftData.body && !draftData.cc @@ -531,7 +530,7 @@ export default { // and fires an input event logger.debug('Nothing substantial to save, ignoring draft save') this.savingDraft = false - return uid + return id } return this.draft(draftData) }) diff --git a/src/components/Envelope.vue b/src/components/Envelope.vue index 46b911778..aad27d236 100644 --- a/src/components/Envelope.vue +++ b/src/components/Envelope.vue @@ -1,7 +1,7 @@ <template> <router-link class="app-content-list-item" :class="{seen: data.flags.seen, draft, selected: selected}" :to="link"> <div - v-if="folder.isUnified" + v-if="mailbox.isUnified" class="mail-message-account-color" :style="{'background-color': accountColor}" /> <div @@ -99,7 +99,7 @@ export default { type: Object, required: true, }, - folder: { + mailbox: { type: Object, required: true, }, @@ -126,17 +126,16 @@ export default { }, link() { if (this.draft) { - // TODO: does not work with a unified drafts folder - // the query should also contain the account and folder + // TODO: does not work with a unified drafts mailbox + // the query should also contain the account and mailbox // id for that to work return { name: 'message', params: { - accountId: this.$route.params.accountId, - folderId: this.$route.params.folderId, + mailboxId: this.$route.params.mailboxId, filter: this.$route.params.filter ? this.$route.params.filter : undefined, - messageUuid: 'new', - draftUid: this.data.uid, + threadId: 'new', + draftId: this.data.databaseId, }, exact: true, } @@ -144,36 +143,35 @@ export default { return { name: 'message', params: { - accountId: this.$route.params.accountId, - folderId: this.$route.params.folderId, + mailboxId: this.$route.params.mailboxId, filter: this.$route.params.filter ? this.$route.params.filter : undefined, - messageUuid: this.data.uuid, + threadId: this.data.databaseId, }, exact: true, } } }, addresses() { - // Show recipients' label/address in a sent folder - if (this.folder.specialRole === 'sent') { + // Show recipients' label/address in a sent mailbox + if (this.mailbox.specialRole === 'sent') { const recipients = [this.data.to, this.data.cc].flat().map(function(recipient) { return recipient.label ? recipient.label : recipient.email }) return recipients.length > 0 ? recipients.join(', ') : t('mail', 'Blind copy recipients only') } - // Show sender label/address in other folder types + // Show sender label/address in other mailbox types return this.data.from.length === 0 ? '?' : this.data.from[0].label || this.data.from[0].email }, avatarEmail() { - // Show first recipients' avatar in a sent folder (or undefined when sent to Bcc only) - if (this.folder.specialRole === 'sent') { + // Show first recipients' avatar in a sent mailbox (or undefined when sent to Bcc only) + if (this.mailbox.specialRole === 'sent') { const recipients = [this.data.to, this.data.cc].flat().map(function(recipient) { return recipient.email }) return recipients.length > 0 ? recipients[0] : undefined } - // Show sender avatar in other folder types + // Show sender avatar in other mailbox types if (this.data.from.length > 0) { return this.data.from[0].email } else { @@ -203,9 +201,7 @@ export default { onDelete() { this.$emit('delete') this.$store.dispatch('deleteMessage', { - accountId: this.data.accountId, - folderId: this.data.folderId, - uid: this.data.uid, + id: this.data.databaseId, }) }, }, diff --git a/src/components/EnvelopeList.vue b/src/components/EnvelopeList.vue index aa2d0d6b8..c790495f8 100644 --- a/src/components/EnvelopeList.vue +++ b/src/components/EnvelopeList.vue @@ -33,12 +33,12 @@ :class="{refreshing: refreshing}" /> <Envelope v-for="env in envelopes" - :key="env.uuid" + :key="env.databaseId" :data="env" - :folder="folder" + :mailbox="mailbox" :selected="isEnvelopeSelected(envelopes.indexOf(env))" :select-mode="selectMode" - @delete="$emit('delete', env.uuid)" + @delete="$emit('delete', env.databaseId)" @update:selected="onEnvelopeSelectToggle(env, ...$event)" /> <div v-if="loadMoreButton && !loadingMore" @@ -57,6 +57,7 @@ import Actions from '@nextcloud/vue/dist/Components/Actions' import ActionButton from '@nextcloud/vue/dist/Components/ActionButton' import Envelope from './Envelope' +import logger from '../logger' export default { name: 'EnvelopeList', @@ -70,7 +71,7 @@ export default { type: Object, required: true, }, - folder: { + mailbox: { type: Object, required: true, }, @@ -148,7 +149,7 @@ export default { deleteAllSelected() { this.selection.forEach((envelopeId) => { // Navigate if the message being deleted is the one currently viewed - if (this.envelopes[envelopeId].uuid === this.$route.params.messageUuid) { + if (this.envelopes[envelopeId].databaseId === this.$route.params.threadId) { let next if (envelopeId === 0) { next = this.envelopes[envelopeId + 1] @@ -160,14 +161,16 @@ export default { this.$router.push({ name: 'message', params: { - accountId: this.$route.params.accountId, - folderId: this.$route.params.folderId, - messageUuid: next.uuid, + mailboxId: this.$route.params.mailboxId, + threadId: next.databaseId, }, }) } } - this.$store.dispatch('deleteMessage', this.envelopes[envelopeId]) + logger.info(`deleting message ${this.envelopes[envelopeId].databaseId}`) + this.$store.dispatch('deleteMessage', { + id: this.envelopes[envelopeId].databaseId, + }) }) this.unselectAll() }, diff --git a/src/components/Mailbox.vue b/src/components/Mailbox.vue index 3f8ab8066..8621fa2bf 100644 --- a/src/components/Mailbox.vue +++ b/src/components/Mailbox.vue @@ -31,7 +31,7 @@ <EnvelopeList v-else :account="account" - :folder="folder" + :mailbox="mailbox" :search-query="searchQuery" :envelopes="envelopesToShow" :refreshing="refreshing" @@ -70,7 +70,7 @@ export default { type: Object, required: true, }, - folder: { + mailbox: { type: Object, required: true, }, @@ -115,7 +115,7 @@ export default { }, computed: { envelopes() { - return this.$store.getters.getEnvelopes(this.account.id, this.folder.id, this.searchQuery) + return this.$store.getters.getEnvelopes(this.mailbox.databaseId, this.searchQuery) }, envelopesToShow() { if (this.paginate === 'manual' && !this.expanded) { @@ -134,7 +134,7 @@ export default { account() { this.loadEnvelopes() }, - folder() { + mailbox() { this.loadEnvelopes() }, searchQuery() { @@ -163,8 +163,7 @@ export default { this.$store .dispatch('syncEnvelopes', { - accountId: this.account.id, - folderId: this.folder.id, + mailboxId: this.mailbox.databaseId, query: this.searchQuery, init: true, }) @@ -175,15 +174,14 @@ export default { }) }, async loadEnvelopes() { - logger.debug('fetching envelopes') + logger.debug(`fetching envelopes for mailbox ${this.mailbox.databaseId} and query ${this.searchQuery}`, this.mailbox) this.loadingEnvelopes = true this.loadingCacheInitialization = false this.error = false try { const envelopes = await this.$store.dispatch('fetchEnvelopes', { - accountId: this.account.id, - folderId: this.folder.id, + mailboxId: this.mailbox.databaseId, query: this.searchQuery, limit: this.initialPageSize, }) @@ -196,15 +194,15 @@ export default { // Show first message const first = envelopes[0] - // Keep the selected account-folder combination, but navigate to the message - // (it's not a bug that we don't use first.accountId and first.folderId here) + // Keep the selected account-mailbox combination, but navigate to the message + // (it's not a bug that we don't use first.accountId and first.mailboxId here) + logger.debug('showing the first message of mailbox ' + this.$route.params.mailboxId) this.$router.replace({ name: 'message', params: { - accountId: this.$route.params.accountId, - folderId: this.$route.params.folderId, + mailboxId: this.$route.params.mailboxId, filter: this.$route.params.filter ? this.$route.params.filter : undefined, - messageUuid: first.uuid, + threadId: first.databaseId, }, }) } @@ -248,8 +246,7 @@ export default { try { const envelopes = await this.$store.dispatch('fetchNextEnvelopePage', { - accountId: this.account.accountId, - folderId: this.folder.id, + mailboxId: this.mailbox.databaseId, envelopes: this.envelopes, query: this.searchQuery, }) @@ -265,23 +262,18 @@ export default { }, handleShortcut(e) { const envelopes = this.envelopes - const currentUuid = this.$route.params.messageUuid - - if (!currentUuid) { - logger.debug('ignoring shortcut: no envelope selected') - return - } - - const current = envelopes.filter((e) => e.uuid === currentUuid) - if (current.length === 0) { - logger.debug('ignoring shortcut: currently displayed messages is not in current envelope list') - return - } + const currentId = parseInt(this.$route.params.threadId, 10) - const env = current[0] + const env = envelopes.find((e) => e.databaseId === currentId) const idx = envelopes.indexOf(env) let next + if (e.srcKey !== 'refresh' && !env) { + logger.debug('envelope is not in the list, ignoring shortcut', { + srcKey: e.srcKey, + }) + } + switch (e.srcKey) { case 'next': case 'prev': @@ -300,26 +292,23 @@ export default { return } - // Keep the selected account-folder combination, but navigate to a different message - // (it's not a bug that we don't use next.accountId and next.folderId here) + // Keep the selected account-mailbox combination, but navigate to a different message + // (it's not a bug that we don't use next.accountId and next.mailboxId here) this.$router.push({ name: 'message', params: { - accountId: this.$route.params.accountId, - folderId: this.$route.params.folderId, + mailboxId: this.$route.params.mailboxId, filter: this.$route.params.filter ? this.$route.params.filter : undefined, - messageUuid: next.uuid, + threadId: next.databaseId, }, }) break case 'del': logger.debug('deleting', { env }) - this.onDelete(env.uuid) + this.onDelete(env.databaseId) this.$store .dispatch('deleteMessage', { - accountId: env.accountId, - folderId: env.folderId, - uid: env.uid, + id: env.databaseId, }) .catch((error) => logger.error('could not delete envelope', { @@ -365,7 +354,7 @@ export default { try { await this.$store.dispatch('syncEnvelopes', { accountId: this.account.accountId, - folderId: this.folder.id, + mailboxId: this.mailbox.databaseId, query: this.searchQuery, }) } catch (error) { @@ -381,14 +370,14 @@ export default { this.refreshing = false } }, - onDelete(uuid) { - const idx = findIndex(propEq('uuid', uuid), this.envelopes) + onDelete(id) { + const idx = findIndex(propEq('databaseId', id), this.envelopes) if (idx === -1) { logger.debug('envelope to delete does not exist in envelope list') return } this.envelopes.splice(idx, 1) - if (uuid !== this.$route.params.messageUuid) { + if (id !== this.$route.params.threadId) { logger.debug('other message open, not jumping to the next/previous message') return } @@ -399,15 +388,14 @@ export default { return } - // Keep the selected account-folder combination, but navigate to a different message - // (it's not a bug that we don't use next.accountId and next.folderId here) + // Keep the selected mailbox, but navigate to a different message + // (it's not a bug that we don't use next.mailboxId here) this.$router.push({ name: 'message', params: { - accountId: this.$route.params.accountId, - folderId: this.$route.params.folderId, + mailboxId: this.$route.params.mailboxId, filter: this.$route.params.filter ? this.$route.params.filter : undefined, - messageUuid: next.uuid, + threadId: next.databaseId, }, }) }, @@ -421,13 +409,12 @@ export default { }, async loadMailbox() { // When the account is unified or inbox, return nothing, else sync the mailbox - if (this.account.isUnified || this.folder.specialRole === 'inbox') { + if (this.account.isUnified || this.mailbox.specialRole === 'inbox') { return } try { await this.$store.dispatch('syncEnvelopes', { - accountId: this.$route.params.accountId, - folderId: this.$route.params.folderId, + mailboxId: this.$route.params.mailboxId, query: this.searchQuery, }) diff --git a/src/components/MailboxMessage.vue b/src/components/MailboxMessage.vue index bcbf066a1..a45c8b6fc 100644 --- a/src/components/MailboxMessage.vue +++ b/src/components/MailboxMessage.vue @@ -11,9 +11,9 @@ :infinite-scroll-distance="10" @shortkey.native="onShortcut"> <Mailbox - v-if="!folder.isPriorityInbox" + v-if="!mailbox.isPriorityInbox" :account="account" - :folder="folder" + :mailbox="mailbox" :search-query="query" :bus="bus" /> <template v-else> @@ -27,7 +27,7 @@ <Mailbox class="nameimportant" :account="unifiedAccount" - :folder="unifiedInbox" + :mailbox="unifiedInbox" :search-query="appendToSearch('is:important')" :paginate="'manual'" :is-priority-inbox="true" @@ -38,7 +38,7 @@ <Mailbox class="namestarred" :account="unifiedAccount" - :folder="unifiedInbox" + :mailbox="unifiedInbox" :search-query="appendToSearch('is:starred not:important')" :paginate="'manual'" :is-priority-inbox="true" @@ -48,7 +48,7 @@ <Mailbox class="nameother" :account="unifiedAccount" - :folder="unifiedInbox" + :mailbox="unifiedInbox" :open-first="false" :search-query="appendToSearch('not:starred not:important')" :is-priority-inbox="true" @@ -102,7 +102,7 @@ export default { type: Object, required: true, }, - folder: { + mailbox: { type: Object, required: true, }, @@ -129,13 +129,13 @@ export default { return this.$store.getters.getAccount(UNIFIED_ACCOUNT_ID) }, unifiedInbox() { - return this.$store.getters.getFolder(UNIFIED_ACCOUNT_ID, UNIFIED_INBOX_ID) + return this.$store.getters.getMailbox(UNIFIED_INBOX_ID) }, hasMessages() { - // it actually should be `return this.$store.getters.getEnvelopes(this.account.id, this.folder.id).length > 0` - // but for some reason Vue doesn't track the dependencies on reactive data then and messages in subfolders can't + // it actually should be `return this.$store.getters.getEnvelopes(this.account.id, this.mailbox.databaseId).length > 0` + // but for some reason Vue doesn't track the dependencies on reactive data then and messages in submailboxes can't // be opened then - const list = this.folder.envelopeLists[normalizedEnvelopeListId(this.searchQuery)] + const list = this.mailbox.envelopeLists[normalizedEnvelopeListId(this.searchQuery)] if (list === undefined) { return false @@ -143,7 +143,7 @@ export default { return list.length > 0 }, showMessage() { - return (this.folder.isPriorityInbox === true || this.hasMessages) && this.$route.name === 'message' + return (this.mailbox.isPriorityInbox === true || this.hasMessages) && this.$route.name === 'message' }, query() { if (this.$route.params.filter === 'starred') { @@ -156,17 +156,19 @@ export default { }, newMessage() { return ( - this.$route.params.messageUuid === 'new' - || this.$route.params.messageUuid === 'reply' - || this.$route.params.messageUuid === 'replyAll' + this.$route.params.threadId === 'new' + || this.$route.params.threadId === 'reply' + || this.$route.params.threadId === 'replyAll' ) }, }, created() { this.alive = true - // eslint-disable-next-line no-new - new OCA.Search(this.searchProxy, this.clearSearchProxy) + window.addEventListener('DOMContentLoaded', (event) => { + // eslint-disable-next-line no-new + new OCA.Search(this.searchProxy, this.clearSearchProxy) + }) }, beforeDestroy() { this.alive = false @@ -174,16 +176,15 @@ export default { methods: { hideMessage() { this.$router.replace({ - name: 'folder', + name: 'mailbox', params: { - accountId: this.account.id, - folderId: this.folder.id, + mailboxId: this.mailbox.databaseId, filter: this.$route.params.filter ? this.$route.params.filter : undefined, }, }) }, - deleteMessage(envelopeUid) { - this.bus.$emit('delete', envelopeUid) + deleteMessage(id) { + this.bus.$emit('delete', id) }, onScroll(event) { logger.debug('scroll', { event }) diff --git a/src/components/Message.vue b/src/components/Message.vue index 54efdfdef..209ee080d 100644 --- a/src/components/Message.vue +++ b/src/components/Message.vue @@ -156,19 +156,10 @@ export default { return isPgpgMessage(this.message.hasHtmlBody ? html(this.message.body) : plain(this.message.body)) }, htmlUrl() { - return generateUrl('/apps/mail/api/accounts/{accountId}/folders/{folderId}/messages/{uid}/html', { - accountId: this.message.accountId, - folderId: this.message.folderId, - uid: this.message.uid, + return generateUrl('/apps/mail/api/messages/{id}/html', { + id: this.envelope.databaseId, }) }, - replyTo() { - return { - accountId: this.message.accountId, - folderId: this.message.folderId, - messageId: this.message.uid, - } - }, hasMultipleRecipients() { return this.replyRecipient.to.concat(this.replyRecipient.cc).length > 1 }, @@ -177,9 +168,8 @@ export default { $route(to, from) { if ( from.name === to.name - && Number.parseInt(from.params.accountId, 10) === Number.parseInt(to.params.accountId, 10) - && from.params.folderId === to.params.folderId - && from.params.messageUuid === to.params.messageUuid + && from.params.mailboxId === to.params.mailboxId + && from.params.threadId === to.params.threadId && from.params.filter === to.params.filter ) { logger.debug('navigated but the message is still the same') @@ -201,18 +191,21 @@ export default { this.replyRecipient = {} this.replySubject = '' - const messageUuid = this.$route.params.messageUuid + const threadId = this.$route.params.threadId try { const [envelope, message] = await Promise.all([ - this.$store.dispatch('fetchEnvelope', messageUuid), - this.$store.dispatch('fetchMessage', messageUuid), + this.$store.dispatch('fetchEnvelope', threadId), + this.$store.dispatch('fetchMessage', threadId), ]) logger.debug('envelope and message fetched', { envelope, message }) // TODO: add timeout so that message isn't flagged when only viewed - // for a few seconds - if (message && message.uuid !== this.$route.params.messageUuid) { - logger.debug("User navigated away, loaded message won't be shown nor flagged as seen") + // for a few seconds + if (envelope && envelope.databaseId !== parseInt(this.$route.params.threadId, 10)) { + logger.debug("User navigated away, loaded message won't be shown nor flagged as seen", { + messageId: envelope.databaseId, + threadId: this.$route.params.threadId, + }) return } @@ -220,13 +213,13 @@ export default { this.message = message if (envelope === undefined || message === undefined) { - logger.info('message could not be found', { messageUuid, envelope, message }) + logger.info('message could not be found', { threadId, envelope, message }) this.errorMessage = getRandomMessageErrorMessage() this.loading = false return } - const account = this.$store.getters.getAccount(message.accountId) + const account = this.$store.getters.getAccount(envelope.accountId) this.replyRecipient = buildReplyRecipients(message, { label: account.name, email: account.emailAddress, @@ -240,7 +233,7 @@ export default { return this.$store.dispatch('toggleEnvelopeSeen', envelope) } } catch (error) { - logger.error('could not load message ', { messageUuid, error }) + logger.error('could not load message ', { threadId, error }) if (error.isError) { this.errorMessage = t('mail', 'Could not load your message') this.error = error @@ -252,13 +245,12 @@ export default { this.$router.push({ name: 'message', params: { - accountId: this.$route.params.accountId, - folderId: this.$route.params.folderId, - messageUuid: 'reply', + mailboxId: this.$route.params.mailboxId, + threadId: 'reply', filter: this.$route.params.filter ? this.$route.params.filter : undefined, }, query: { - uuid: this.message.uuid, + messageId: this.$route.params.threadId, }, }) }, @@ -266,13 +258,12 @@ export default { this.$router.push({ name: 'message', params: { - accountId: this.$route.params.accountId, - folderId: this.$route.params.folderId, - messageUuid: 'replyAll', + mailboxId: this.$route.params.mailboxId, + threadId: 'replyAll', filter: this.$route.params.filter ? this.$route.params.filter : undefined, }, query: { - uuid: this.message.uuid, + messageId: this.$route.params.threadId, }, }) }, @@ -280,13 +271,12 @@ export default { this.$router.push({ name: 'message', params: { - accountId: this.$route.params.accountId, - folderId: this.$route.params.folderId, - messageUuid: 'new', + mailboxId: this.$route.params.mailboxId, + threadId: 'new', filter: this.$route.params.filter ? this.$route.params.filter : undefined, }, query: { - uuid: this.message.uuid, + messageId: this.$route.params.threadId, }, }) }, @@ -297,11 +287,9 @@ export default { this.$store.dispatch('toggleEnvelopeJunk', this.envelope) }, onDelete() { - this.$emit('delete', this.envelope.uid) + this.$emit('delete', this.envelope.databaseId) this.$store.dispatch('deleteMessage', { - accountId: this.message.accountId, - folderId: this.message.folderId, - uid: this.message.uid, + id: this.envelope.databaseId, }) }, async onShowSource() { @@ -309,10 +297,8 @@ export default { try { const resp = await axios.get( - generateUrl('/apps/mail/api/accounts/{accountId}/folders/{folderId}/messages/{uid}/source', { - accountId: this.message.accountId, - folderId: this.message.folderId, - uid: this.message.uid, + generateUrl('/apps/mail/api/messages/{id}/source', { + id: this.envelope.databaseId, }) ) diff --git a/src/components/MessageAttachment.vue b/src/components/MessageAttachment.vue index 5e6274d16..50494b1e2 100644 --- a/src/components/MessageAttachment.vue +++ b/src/components/MessageAttachment.vue @@ -57,7 +57,6 @@ import { translate as t } from '@nextcloud/l10n' import { getFilePickerBuilder } from '@nextcloud/dialogs' import PopoverMenu from '@nextcloud/vue/dist/Components/PopoverMenu' -import { parseUuid } from '../util/EnvelopeUidParser' import Logger from '../logger' import { downloadAttachment, saveAttachmentToFiles } from '../service/AttachmentService' @@ -140,10 +139,10 @@ export default { return formatFileSize(size) }, saveToCloud() { - const saveAttachment = (accountId, folderId, messageId, attachmentId) => (directory) => { - return saveAttachmentToFiles(accountId, folderId, messageId, attachmentId, directory) + const saveAttachment = (id, attachmentId) => (directory) => { + return saveAttachmentToFiles(id, attachmentId, directory) } - const { accountId, folderId, uid } = parseUuid(this.$route.params.messageUuid) + const id = this.$route.params.threadId const picker = getFilePickerBuilder(t('mail', 'Choose a folder to store the attachment in')) .setMultiSelect(false) .addMimeTypeFilter('httpd/unix-directory') @@ -157,7 +156,7 @@ export default { this.savingToCloud = true return dest }) - .then(saveAttachment(accountId, folderId, uid, this.id)) + .then(saveAttachment(id, this.id)) .then(() => Logger.info('saved')) .catch((e) => Logger.error('not saved', { error: e })) .then(() => (this.savingToCloud = false)) diff --git a/src/components/MessageAttachments.vue b/src/components/MessageAttachments.vue index aef44c87c..4e2700f81 100644 --- a/src/components/MessageAttachments.vue +++ b/src/components/MessageAttachments.vue @@ -48,7 +48,6 @@ <script> import { getFilePickerBuilder } from '@nextcloud/dialogs' -import { parseUuid } from '../util/EnvelopeUidParser' import { saveAttachmentsToFiles } from '../service/AttachmentService' import MessageAttachment from './MessageAttachment' @@ -84,10 +83,10 @@ export default { .setType(1) .build() - const saveAttachments = (accountId, folderId, messageId) => (directory) => { - return saveAttachmentsToFiles(accountId, folderId, messageId, directory) + const saveAttachments = (id) => (directory) => { + return saveAttachmentsToFiles(id, directory) } - const { accountId, folderId, uid } = parseUuid(this.$route.params.messageUuid) + const id = this.$route.params.threadId return picker .pick() @@ -95,7 +94,7 @@ export default { this.savingToCloud = true return dest }) - .then(saveAttachments(accountId, folderId, uid)) + .then(saveAttachments(id)) .then(() => Logger.info('saved')) .catch((error) => Logger.error('not saved', { error })) .then(() => (this.savingToCloud = false)) diff --git a/src/components/Navigation.vue b/src/components/Navigation.vue index 082f3ea2b..ea60fd027 100644 --- a/src/components/Navigation.vue +++ b/src/components/Navigation.vue @@ -32,24 +32,24 @@ v-if="group.account" :key="group.account.id" :account="group.account" - :first-folder="group.folders[0]" + :first-mailbox="group.mailboxes[0]" :is-first="isFirst(group.account)" :is-last="isLast(group.account)" /> - <template v-for="item in group.folders"> - <NavigationFolder + <template v-for="item in group.mailboxes"> + <NavigationMailbox v-show=" !group.isCollapsible || !group.account.collapsed || SHOW_COLLAPSED.indexOf(item.specialRole) !== -1 " - :key="item.key" + :key="item.databaseId" :account="group.account" - :folder="item" /> - <NavigationFolder + :mailbox="item" /> + <NavigationMailbox v-if="!group.account.isUnified && item.specialRole === 'inbox'" - :key="item.key + '-starred'" + :key="item.databaseId + '-starred'" :account="group.account" - :folder="item" + :mailbox="item" filter="starred" /> </template> <NavigationAccountExpandCollapse @@ -74,7 +74,7 @@ import AppNavigationSpacer from '@nextcloud/vue/dist/Components/AppNavigationSpa import logger from '../logger' import NavigationAccount from './NavigationAccount' import NavigationAccountExpandCollapse from './NavigationAccountExpandCollapse' -import NavigationFolder from './NavigationFolder' +import NavigationMailbox from './NavigationMailbox' import AppSettingsMenu from '../components/AppSettingsMenu' @@ -90,7 +90,7 @@ export default { AppSettingsMenu, NavigationAccount, NavigationAccountExpandCollapse, - NavigationFolder, + NavigationMailbox, }, data() { return { @@ -100,16 +100,16 @@ export default { computed: { menu() { return this.$store.getters.accounts.map((account) => { - const folders = this.$store.getters.getFolders(account.id) - const nonSpecialRoleFolders = folders.filter( - (folder) => SHOW_COLLAPSED.indexOf(folder.specialRole) === -1 + const mailboxes = this.$store.getters.getMailboxes(account.id) + const nonSpecialRoleMailboxes = mailboxes.filter( + (mailbox) => SHOW_COLLAPSED.indexOf(mailbox.specialRole) === -1 ) - const isCollapsible = nonSpecialRoleFolders.length > 1 + const isCollapsible = nonSpecialRoleMailboxes.length > 1 return { id: account.id, account, - folders, + mailboxes, isCollapsible, } }) @@ -119,11 +119,10 @@ export default { onNewMessage() { const accountId = this.$route.params.accountId || this.$store.getters.accounts[0].id - // FIXME: this assumes that there's at least one folder - const folderId = this.$route.params.folderId || this.$store.getters.getFolders(accountId)[0].id + const mailboxId = this.$route.params.mailboxId || this.$store.getters.getMailboxes(accountId)[0]?.databaseId if ( this.$router.currentRoute.name === 'message' - && this.$router.currentRoute.params.messageUuid === 'new' + && this.$router.currentRoute.params.getMailboxes === 'new' ) { // If we already show the composer, navigating to it would be pointless (and doesn't work) // instead trigger an event to reset the composer @@ -135,10 +134,9 @@ export default { .push({ name: 'message', params: { - accountId, - folderId, + mailboxId, filter: this.$route.params.filter ? this.$route.params.filter : undefined, - messageUuid: 'new', + threadId: 'new', }, }) .catch((err) => { diff --git a/src/components/NavigationAccount.vue b/src/components/NavigationAccount.vue index 3bdbb7b4e..463fb1bef 100644 --- a/src/components/NavigationAccount.vue +++ b/src/components/NavigationAccount.vue @@ -27,7 +27,7 @@ :icon="iconError" :menu-open.sync="menuOpen" :title="account.emailAddress" - :to="firstFolderRoute" + :to="firstMailboxRoute" :exact="true" @update:menuOpen="onMenuToggle"> <!-- Color dot --> @@ -47,10 +47,10 @@ @update:checked="changeShowSubscribedOnly"> {{ t('mail', 'Show only subscribed folders') }} </ActionCheckbox> - <ActionButton v-if="!editing" icon="icon-folder" @click="openCreateFolder"> + <ActionButton v-if="!editing" icon="icon-folder" @click="openCreateMailbox"> {{ t('mail', 'Add folder') }} </ActionButton> - <ActionInput v-if="editing" icon="icon-folder" @submit.prevent.stop="createFolder" /> + <ActionInput v-if="editing" icon="icon-folder" @submit.prevent.stop="createMailbox" /> <ActionText v-if="showSaving" icon="icon-loading-small"> {{ t('mail', 'Saving') }} </ActionText> @@ -98,7 +98,7 @@ export default { type: Object, required: true, }, - firstFolder: { + firstMailbox: { type: Object, required: true, }, @@ -135,12 +135,11 @@ export default { }, } }, - firstFolderRoute() { + firstMailboxRoute() { return { - name: 'folder', + name: 'mailbox', params: { - accountId: this.account.id, - folderId: this.firstFolder.id, + mailboxId: this.firstMailbox.databaseId, }, } }, @@ -168,22 +167,22 @@ export default { }, }, methods: { - createFolder(e) { + createMailbox(e) { this.editing = true const name = e.target.elements[1].value - logger.info('creating folder ' + name) + logger.info('creating mailbox ' + name) this.menuOpen = false this.$store - .dispatch('createFolder', { account: this.account, name }) - .then(() => logger.info(`folder ${name} created`)) + .dispatch('createMailbox', { account: this.account, name }) + .then(() => logger.info(`mailbox ${name} created`)) .catch((error) => { - logger.error('could not create folder', { error }) + logger.error('could not create mailbox', { error }) throw error }) this.editing = false this.showSaving = false }, - openCreateFolder() { + openCreateMailbox() { this.editing = true this.showSaving = false }, @@ -243,7 +242,7 @@ export default { }) .then(() => { this.savingShowOnlySubscribed = false - logger.info('show only subscribed folders updated to ' + onlySubscribed) + logger.info('show only subscribed mailboxes updated to ' + onlySubscribed) }) .catch((error) => { logger.error('could not update subscription mode', { error }) diff --git a/src/components/NavigationFolder.vue b/src/components/NavigationMailbox.vue index e81f56d7f..e25945110 100644 --- a/src/components/NavigationFolder.vue +++ b/src/components/NavigationMailbox.vue @@ -22,69 +22,69 @@ <template> <AppNavigationItem v-if="visible" - :id="genId(folder)" - :key="genId(folder)" + :id="genId(mailbox)" + :key="genId(mailbox)" :allow-collapse="true" :menu-open.sync="menuOpen" :force-menu="true" :icon="icon" :title="title" :to="to" - :open.sync="showSubFolders" + :open.sync="showSubMailboxes" @update:menuOpen="onMenuToggle"> <!-- actions --> <template slot="actions"> <template> <ActionText - v-if="!account.isUnified && folder.specialRole !== 'flagged'" + v-if="!account.isUnified && mailbox.specialRole !== 'flagged'" icon="icon-info" - :title="folderId"> + :title="mailbox.name"> {{ statsText }} </ActionText> <ActionButton - v-if="folder.specialRole !== 'flagged'" + v-if="mailbox.specialRole !== 'flagged'" icon="icon-mail" :title="t('mail', 'Mark all as read')" :disabled="loadingMarkAsRead" @click="markAsRead"> - {{ t('mail', 'Mark all messages of this folder as read') }} + {{ t('mail', 'Mark all messages of this mailbox as read') }} </ActionButton> <ActionButton - v-if="!editing && top && !account.isUnified && folder.specialRole !== 'flagged'" + v-if="!editing && top && !account.isUnified && mailbox.specialRole !== 'flagged'" icon="icon-folder" - @click="openCreateFolder"> + @click="openCreateMailbox"> {{ t('mail', 'Add subfolder') }} </ActionButton> - <ActionInput v-if="editing" icon="icon-folder" @submit.prevent.stop="createFolder" /> + <ActionInput v-if="editing" icon="icon-folder" @submit.prevent.stop="createMailbox" /> <ActionText v-if="showSaving" icon="icon-loading-small"> {{ t('mail', 'Saving') }} </ActionText> <ActionButton - v-if="debug && !account.isUnified && folder.specialRole !== 'flagged'" + v-if="debug && !account.isUnified && mailbox.specialRole !== 'flagged'" icon="icon-settings" :title="t('mail', 'Clear cache')" :disabled="clearingCache" @click="clearCache"> {{ t('mail', 'Clear locally cached data, in case there are issues with synchronization.') }} </ActionButton> - <ActionButton v-if="!account.isUnified && !folder.specialRole" icon="icon-delete" @click="deleteFolder"> + <ActionButton v-if="!account.isUnified && !mailbox.specialRole" icon="icon-delete" @click="deleteMailbox"> {{ t('mail', 'Delete folder') }} </ActionButton> </template> </template> - <AppNavigationCounter v-if="folder.unread" slot="counter"> - {{ folder.unread }} + <AppNavigationCounter v-if="mailbox.unread" slot="counter"> + {{ mailbox.unread }} </AppNavigationCounter> - <!-- subfolders --> - <NavigationFolder - v-for="subFolder in subFolders" - :key="genId(subFolder)" + <!-- submailboxes --> + <NavigationMailbox + v-for="subMailbox in subMailboxes" + :key="genId(subMailbox)" :account="account" - :folder="subFolder" + :mailbox="subMailbox" :top="false" /> </AppNavigationItem> </template> @@ -97,13 +97,13 @@ import ActionInput from '@nextcloud/vue/dist/Components/ActionInput' import ActionText from '@nextcloud/vue/dist/Components/ActionText' import { clearCache } from '../service/MessageService' -import { getFolderStats } from '../service/FolderService' +import { getMailboxStatus } from '../service/MailboxService' import logger from '../logger' import { translatePlural as n } from '@nextcloud/l10n' import { translate as translateMailboxName } from '../i18n/MailboxTranslator' export default { - name: 'NavigationFolder', + name: 'NavigationMailbox', components: { AppNavigationItem, AppNavigationCounter, @@ -116,7 +116,7 @@ export default { type: Object, required: true, }, - folder: { + mailbox: { type: Object, required: true, }, @@ -133,12 +133,12 @@ export default { data() { return { debug: window?.OC?.debug || false, - folderStats: undefined, + mailboxStats: undefined, loadingMarkAsRead: false, clearingCache: false, showSaving: false, editing: false, - showSubFolders: false, + showSubMailboxes: false, menuOpen: false, } }, @@ -146,58 +146,54 @@ export default { visible() { return ( this.account.showSubscribedOnly === false - || (this.folder.attributes && this.folder.attributes.includes('\\subscribed')) + || (this.mailbox.attributes && this.mailbox.attributes.includes('\\subscribed')) ) }, title() { if (this.filter === 'starred') { // Little hack to trick the translation logic into a different path return translateMailboxName({ - ...this.folder, + ...this.mailbox, specialUse: ['flagged'], }) } - return translateMailboxName(this.folder) - }, - folderId() { - return atob(this.folder.id) + return translateMailboxName(this.mailbox) }, icon() { if (this.filter === 'starred') { return 'icon-flagged' - } else if (this.folder.isPriorityInbox) { + } else if (this.mailbox.isPriorityInbox) { return 'icon-important' } - return this.folder.specialRole ? 'icon-' + this.folder.specialRole : 'icon-folder' + return this.mailbox.specialRole ? 'icon-' + this.mailbox.specialRole : 'icon-folder' }, to() { return { - name: 'folder', + name: 'mailbox', params: { - accountId: this.account.id, - folderId: this.folder.id, + mailboxId: this.mailbox.databaseId, filter: this.filter ? this.filter : undefined, }, } }, - subFolders() { - return this.$store.getters.getSubfolders(this.account.id, this.folder.id) + subMailboxes() { + return this.$store.getters.getSubMailboxes(this.mailbox.databaseId) }, statsText() { - if (this.folderStats && 'total' in this.folderStats && 'unread' in this.folderStats) { - if (this.folderStats.unread === 0) { - return n('mail', '{total} message', '{total} messages', this.folderStats.total, { - total: this.folderStats.total, + if (this.mailboxStats && 'total' in this.mailboxStats && 'unread' in this.mailboxStats) { + if (this.mailboxStats.unread === 0) { + return n('mail', '{total} message', '{total} messages', this.mailboxStats.total, { + total: this.mailboxStats.total, }) } else { return n( 'mail', '{unread} unread of {total}', '{unread} unread of {total}', - this.folderStats.unread, + this.mailboxStats.unread, { - total: this.folderStats.total, - unread: this.folderStats.unread, + total: this.mailboxStats.total, + unread: this.mailboxStats.unread, } ) } @@ -207,12 +203,12 @@ export default { }, methods: { /** - * Generate unique key id for a specific folder - * @param {Object} folder the folder to gen id for + * Generate unique key id for a specific mailbox + * @param {Object} mailbox the mailbox to gen id for * @returns {string} */ - genId(folder) { - return 'account-' + this.account.id + '_' + folder.id + genId(mailbox) { + return 'mailbox-' + mailbox.databaseId }, /** @@ -221,51 +217,51 @@ export default { */ onMenuToggle(open) { if (open) { - this.fetchFolderStats() + this.fetchMailboxStats() } }, /** - * Fetch folder unread/read stats + * Fetch mailbox unread/read stats */ - async fetchFolderStats() { - this.folderStats = null - if (this.account.isUnified || this.folder.specialRole === 'flagged') { + async fetchMailboxStats() { + this.mailboxStats = null + if (this.account.isUnified || this.mailbox.specialRole === 'flagged') { return } try { - const stats = await getFolderStats(this.account.id, this.folder.id) - logger.debug('loaded folder stats', { stats }) - this.folderStats = stats + const stats = await getMailboxStatus(this.mailbox.databaseId) + logger.debug(`loaded mailbox stats for ${this.mailbox.databaseId}`, { stats }) + this.mailboxStats = stats } catch (error) { - this.folderStats = { error: true } - logger.error(`could not load folder stats for ${this.folder.id}`, error) + this.mailboxStats = { error: true } + logger.error(`could not load mailbox stats for ${this.mailbox.databaseId}`, error) } }, - async createFolder(e) { + async createMailbox(e) { this.editing = true const name = e.target.elements[1].value - const withPrefix = atob(this.folder.id) + this.folder.delimiter + name - logger.info(`creating folder ${withPrefix} as subfolder of ${this.folder.id}`) + const withPrefix = atob(this.mailbox.databaseId) + this.mailbox.delimiter + name + logger.info(`creating mailbox ${withPrefix} as submailbox of ${this.mailbox.databaseId}`) this.menuOpen = false try { - await this.$store.dispatch('createFolder', { + await this.$store.dispatch('createMailbox', { account: this.account, name: withPrefix, }) } catch (error) { - logger.error(`could not create folder ${withPrefix}`, { error }) + logger.error(`could not create mailbox ${withPrefix}`, { error }) throw error } finally { this.editing = false this.showSaving = false } - logger.info(`folder ${withPrefix} created`) - this.showSubFolders = true + logger.info(`mailbox ${withPrefix} created`) + this.showSubMailboxes = true }, - openCreateFolder() { + openCreateMailbox() { this.editing = true this.showSaving = false }, @@ -273,12 +269,12 @@ export default { this.loadingMarkAsRead = true this.$store - .dispatch('markFolderRead', { + .dispatch('markMailboxRead', { accountId: this.account.id, - folderId: this.folder.id, + mailboxId: this.mailbox.databaseId, }) - .then(() => logger.info(`folder ${this.folder.id} marked as read`)) - .catch((error) => logger.error(`could not mark folder ${this.folder.id} as read`, { error })) + .then(() => logger.info(`mailbox ${this.mailbox.databaseId} marked as read`)) + .catch((error) => logger.error(`could not mark mailbox ${this.mailbox.databaseId} as read`, { error })) .then(() => (this.loadingMarkAsRead = false)) }, async clearCache() { @@ -286,10 +282,10 @@ export default { this.clearingCache = true logger.debug('clearing message cache', { accountId: this.account.id, - folderId: this.folder.id, + mailboxId: this.mailbox.databaseId, }) - await clearCache(this.account.id, this.folder.id) + await clearCache(this.account.id, this.mailbox.databaseId) // TODO: there might be a nicer way to handle this window.location.reload(false) @@ -297,28 +293,26 @@ export default { this.clearCache = false } }, - deleteFolder() { - const id = this.folder.id - logger.info('delete folder', { folder: this.folder }) + deleteMailbox() { + const id = this.mailbox.databaseId + logger.info('delete mailbox', { mailbox: this.mailbox }) OC.dialogs.confirmDestructive( - t('mail', 'The folder and all messages in it will be deleted.', { - folderId: this.folderId, - }), + t('mail', 'The folder and all messages in it will be deleted.'), t('mail', 'Delete folder'), { type: OC.dialogs.YES_NO_BUTTONS, - confirm: t('mail', 'Delete folder {folderId}', { folderId: this.folderId }), + confirm: t('mail', 'Delete folder {name}', { name: this.mailbox.displayName }), confirmClasses: 'error', cancel: t('mail', 'Cancel'), }, (result) => { if (result) { return this.$store - .dispatch('deleteFolder', { account: this.account, folder: this.folder }) + .dispatch('deleteMailbox', { mailbox: this.mailbox }) .then(() => { - logger.info(`folder ${id} deleted`) + logger.info(`mailbox ${id} deleted`) }) - .catch((error) => logger.error('could not delete folder', { error })) + .catch((error) => logger.error('could not delete mailbox', { error })) } } ) diff --git a/src/components/NewMessageDetail.vue b/src/components/NewMessageDetail.vue index 09d812173..d9ea48fc3 100644 --- a/src/components/NewMessageDetail.vue +++ b/src/components/NewMessageDetail.vue @@ -25,6 +25,7 @@ import AppContentDetails from '@nextcloud/vue/dist/Components/AppContentDetails' import Axios from '@nextcloud/axios' import { generateUrl } from '@nextcloud/router' +import { translate as t } from '@nextcloud/l10n' import { buildForwardSubject, buildReplySubject, buildRecipients as buildReplyRecipients } from '../ReplyBuilder' import Composer from './Composer' @@ -64,13 +65,15 @@ export default { subject: this.draft.subject, body: this.draft.hasHtmlBody ? html(this.draft.body) : plain(this.draft.body), } - } else if (this.$route.query.uuid !== undefined) { + } else if (this.$route.query.messageId !== undefined) { // Forward or reply to a message const message = this.original logger.debug('forwarding or replying to message', { message }) - if (this.$route.params.messageUuid === 'reply') { - logger.debug('simple reply') + if (this.$route.params.threadId === 'reply') { + logger.debug('simple reply', { + message, + }) return { accountId: message.accountId, @@ -81,7 +84,7 @@ export default { originalBody: this.originalBody, replyTo: message, } - } else if (this.$route.params.messageUuid === 'replyAll') { + } else if (this.$route.params.threadId === 'replyAll') { logger.debug('replying to all', { original: this.original }) const account = this.$store.getters.getAccount(message.accountId) const recipients = buildReplyRecipients(message, { @@ -112,6 +115,9 @@ export default { } } else { // New or mailto: message + logger.debug('composing a new message or handling a mailto link', { + threadId: this.$route.params.threadId, + }) let accountId // Only preselect an account when we're not in a unified mailbox @@ -134,10 +140,14 @@ export default { // `saveDraft` replaced the current URL with the updated draft UID // in that case we don't really start a new draft but just keep the // URL consistent, hence not loading anything - if (this.draft && to.name === 'message' && to.params.draftUid === this.draft.uid) { + if (to.name === 'message' && this.draft && to.params.draftId === parseInt(this.draft.databaseId, 10)) { logger.debug('detected navigation to current (new) draft UID, not reloading') return } + logger.debug('the draft ID changed, we have to fetch the draft', { + currentId: this.draft.databaseId, + newId: to.params.draftId, + }) this.fetchMessage() }, @@ -159,30 +169,33 @@ export default { ] }, fetchMessage() { - if (this.$route.params.draftUid !== undefined) { - return this.fetchDraftMessage(this.$route.params.draftUid) - } else if (this.$route.query.uuid !== undefined) { - return this.fetchOriginalMessage(this.$route.query.uuid) + if (this.$route.params.draftId !== undefined) { + return this.fetchDraftMessage(this.$route.params.draftId) + } else if (this.$route.query.messageId !== undefined) { + return this.fetchOriginalMessage(this.$route.query.messageId) } }, - fetchDraftMessage(draftUid) { + fetchDraftMessage(id) { this.loading = true this.draft = undefined this.error = undefined this.errorMessage = '' this.$store - .dispatch('fetchMessage', draftUid) + .dispatch('fetchMessage', id) .then((draft) => { - if (draft.uid !== this.$route.params.draftUid) { - logger.debug("User navigated away, loaded draft won't be shown") + if (draft.databaseId !== parseInt(this.$route.params.draftId, 10)) { + logger.debug("User navigated away, loaded draft won't be shown", { + draft, + draftId: this.$route.params.draftId, + }) return } this.draft = draft if (this.draft === undefined) { - logger.info('draft could not be found', { draftUid }) + logger.info('draft could not be found', { id }) this.errorMessage = getRandomMessageErrorMessage() this.loading = false return @@ -191,7 +204,7 @@ export default { this.loading = false }) .catch((error) => { - logger.error('could not load draft ' + draftUid, { error }) + logger.error(`could not load draft ${id}`, { error }) if (error.isError) { this.errorMessage = t('mail', 'Could not load your draft') this.error = error @@ -199,15 +212,21 @@ export default { } }) }, - async fetchOriginalMessage(uid) { + async fetchOriginalMessage(id) { this.loading = true this.error = undefined this.errorMessage = '' + logger.debug(`fetching original message ${id}`) + try { - const message = await this.$store.dispatch('fetchMessage', uid) - if (message.uuid !== this.$route.query.uuid) { - logger.debug("User navigated away, loaded original message won't be used") + const message = await this.$store.dispatch('fetchMessage', id) + if (message.databaseId !== parseInt(this.$route.query.messageId, 10)) { + logger.debug("User navigated away, loaded original message won't be used", { + message, + messageId: message.databaseId, + urlId: this.$route.query.messageId, + }) return } @@ -218,10 +237,8 @@ export default { if (message.hasHtmlBody) { logger.debug('original message has HTML body') const resp = await Axios.get( - generateUrl('/apps/mail/api/accounts/{accountId}/folders/{folderId}/messages/{uid}/html', { - accountId: message.accountId, - folderId: message.folderId, - uid: message.uid, + generateUrl('/apps/mail/api/messages/{id}/html', { + id, }) ) @@ -229,7 +246,7 @@ export default { } this.originalBody = body } catch (error) { - logger.error('could not load original message ' + uid, { error }) + logger.error('could not load original message ' + id, { error }) if (error.isError) { this.errorMessage = t('mail', 'Could not load original message') this.error = error @@ -239,41 +256,17 @@ export default { this.loading = false } }, - saveDraft(data) { - if (data.draftUID === undefined && this.draft) { - logger.debug('draft data does not have a draftUID, adding one') - data.draftUID = this.draft.id + async saveDraft(data) { + if (data.draftId === undefined && this.draft) { + logger.debug('draft data does not have a draftId, adding one', { draft: this.draft, data, id: this.draft.databaseId }) + data.draftId = this.draft.databaseId } const dataForServer = { ...data, body: data.isHtml ? data.body.value : toPlain(data.body).value, } - return saveDraft(data.account, dataForServer).then(({ uid }) => { - if (this.draft === undefined) { - return uid - } - - logger.info('replacing draft ' + this.draft.uid + ' with ' + uid) - const update = { - draft: this.draft, - uid, - data, - } - return this.$store - .dispatch('replaceDraft', update) - .then(() => - this.$router.replace({ - name: 'message', - params: { - accountId: this.$route.params.accountId, - folderId: this.$route.params.folderId, - messageUuid: 'new', - draftUid: this.draft.uid, - }, - }) - ) - .then(() => uid) - }) + const { id } = await saveDraft(data.account, dataForServer) + return id }, sendMessage(data) { logger.debug('sending message', { data }) diff --git a/src/i18n/MailboxTranslator.js b/src/i18n/MailboxTranslator.js index 6c150493a..b5ada8457 100644 --- a/src/i18n/MailboxTranslator.js +++ b/src/i18n/MailboxTranslator.js @@ -21,28 +21,28 @@ import { translate as t } from '@nextcloud/l10n' -const translateSpecial = (folder) => { - if (folder.specialUse.includes('all')) { +const translateSpecial = (mailbox) => { + if (mailbox.specialUse.includes('all')) { // TRANSLATORS: translated mail box name return t('mail', 'All') } - if (folder.specialUse.includes('archive')) { + if (mailbox.specialUse.includes('archive')) { // TRANSLATORS: translated mail box name return t('mail', 'Archive') } - if (folder.specialUse.includes('drafts')) { + if (mailbox.specialUse.includes('drafts')) { // TRANSLATORS: translated mail box name return t('mail', 'Drafts') } - if (folder.specialUse.includes('flagged')) { + if (mailbox.specialUse.includes('flagged')) { // TRANSLATORS: translated mail box name return t('mail', 'Favorites') } - if (folder.specialUse.includes('inbox')) { - if (folder.isPriorityInbox) { + if (mailbox.specialUse.includes('inbox')) { + if (mailbox.isPriorityInbox) { // TRANSLATORS: translated mail box name return t('mail', 'Priority inbox') - } else if (folder.isUnified) { + } else if (mailbox.isUnified) { // TRANSLATORS: translated mail box name return t('mail', 'All inboxes') } else { @@ -50,28 +50,28 @@ const translateSpecial = (folder) => { return t('mail', 'Inbox') } } - if (folder.specialUse.includes('junk')) { + if (mailbox.specialUse.includes('junk')) { // TRANSLATORS: translated mail box name return t('mail', 'Junk') } - if (folder.specialUse.includes('sent')) { + if (mailbox.specialUse.includes('sent')) { // TRANSLATORS: translated mail box name return t('mail', 'Sent') } - if (folder.specialUse.includes('trash')) { + if (mailbox.specialUse.includes('trash')) { // TRANSLATORS: translated mail box name return t('mail', 'Trash') } - throw new Error(`unknown special use ${folder.specialUse}`) + throw new Error(`unknown special use ${mailbox.specialUse}`) } -export const translate = (folder) => { - if (folder.specialUse.length > 0) { +export const translate = (mailbox) => { + if (mailbox.specialUse.length > 0) { try { - return translateSpecial(folder) + return translateSpecial(mailbox) } catch (e) { - console.error('could not translate special folder', e) + console.error('could not translate special mailbox', e) } } - return folder.displayName + return mailbox.displayName } diff --git a/src/imap/MailboxHierarchy.js b/src/imap/MailboxHierarchy.js index e90b1ce7b..c418c3e0b 100644 --- a/src/imap/MailboxHierarchy.js +++ b/src/imap/MailboxHierarchy.js @@ -21,8 +21,8 @@ const getParentId = (mailbox, hasPrefix) => { const top = hasPrefix ? 1 : 0 - const hierarchy = atob(mailbox.id).split(mailbox.delimiter) - if (hierarchy.length <= top + 1 || atob(mailbox.id) === 'INBOX/FLAGGED') { + const hierarchy = mailbox.name.split(mailbox.delimiter) + if (hierarchy.length <= top + 1 || mailbox.name === 'INBOX/FLAGGED') { return } if (hasPrefix) { @@ -40,7 +40,7 @@ export const buildMailboxHierarchy = (mailboxes, havePrefix) => { const cloned = mailboxes.map((mailbox) => { return { - folders: [], + mailboxes: [], ...mailbox, } }) @@ -52,9 +52,9 @@ export const buildMailboxHierarchy = (mailboxes, havePrefix) => { } const parentId = getParentId(mailbox, havePrefix) - const parent = cloned.filter((mailbox) => atob(mailbox.id) === parentId)[0] + const parent = cloned.filter((mailbox) => mailbox.name === parentId)[0] if (parent) { - parent.folders.push(mailbox) + parent.mailboxes.push(mailbox) } }) diff --git a/src/imap/MailboxPrefix.js b/src/imap/MailboxPrefix.js index 291a2eb02..68a49b4bf 100644 --- a/src/imap/MailboxPrefix.js +++ b/src/imap/MailboxPrefix.js @@ -24,7 +24,7 @@ const PREFIX = 'INBOX' export const havePrefix = (mailboxes) => { return ( mailboxes.filter((mailbox) => { - const hierarchy = mailbox.id.split(mailbox.delimiter) + const hierarchy = mailbox.name.split(mailbox.delimiter) if (hierarchy.length < 1 || hierarchy[0] !== PREFIX) { return false } diff --git a/src/router.js b/src/router.js index 487bd81d5..ff1665887 100644 --- a/src/router.js +++ b/src/router.js @@ -10,6 +10,7 @@ const Setup = () => import('./views/Setup') Vue.use(Router) export default new Router({ + mode: 'history', base: generateUrl('/apps/mail/'), linkActiveClass: 'active', routes: [ @@ -24,12 +25,12 @@ export default new Router({ component: Home, }, { - path: '/accounts/:accountId/folders/:filter?/:folderId', - name: 'folder', + path: '/box/:filter?/:mailboxId', + name: 'mailbox', component: Home, }, { - path: '/accounts/:accountId/folders/:filter?/:folderId/message/:messageUuid/:draftUid?', + path: '/box/:filter?/:mailboxId/thread/:threadId/:draftId?', name: 'message', component: Home, }, diff --git a/src/service/AliasService.js b/src/service/AliasService.js index cd07aa15c..6f0391b51 100644 --- a/src/service/AliasService.js +++ b/src/service/AliasService.js @@ -2,12 +2,13 @@ import { generateUrl } from '@nextcloud/router' import axios from '@nextcloud/axios' export const createAlias = async(account, data) => { - const url = generateUrl('/apps/mail/api/accounts/{id}/aliases', { - id: account.accountId, - }) + const url = generateUrl('/apps/mail/api/aliases') return axios - .post(url, data) + .post(url, { + accountId: account.id, + ...data, + }) .then((resp) => resp.data) .catch((e) => { if (e.response && e.response.status === 400) { diff --git a/src/service/AttachmentService.js b/src/service/AttachmentService.js index 3f47cfef6..7bfe27455 100644 --- a/src/service/AttachmentService.js +++ b/src/service/AttachmentService.js @@ -22,13 +22,11 @@ import Axios from '@nextcloud/axios' import { generateUrl } from '@nextcloud/router' -export function saveAttachmentToFiles(accountId, folderId, messageId, attachmentId, directory) { +export function saveAttachmentToFiles(id, attachmentId, directory) { const url = generateUrl( - 'apps/mail/api/accounts/{accountId}/folders/{folderId}/messages/{messageId}/attachment/{attachmentId}', + '/apps/mail/api/messages/{id}/attachment/{attachmentId}', { - accountId, - folderId, - messageId, + id, attachmentId, } ) @@ -38,8 +36,8 @@ export function saveAttachmentToFiles(accountId, folderId, messageId, attachment }) } -export function saveAttachmentsToFiles(accountId, folderId, messageId, directory) { - return saveAttachmentToFiles(accountId, folderId, messageId, 0, directory) +export function saveAttachmentsToFiles(id, directory) { + return saveAttachmentToFiles(id, 0, directory) } export function downloadAttachment(url) { diff --git a/src/service/FolderService.js b/src/service/FolderService.js deleted file mode 100644 index d1ec0243e..000000000 --- a/src/service/FolderService.js +++ /dev/null @@ -1,50 +0,0 @@ -import { generateUrl } from '@nextcloud/router' -import Axios from '@nextcloud/axios' - -export function fetchAll(accountId) { - const url = generateUrl('/apps/mail/api/accounts/{accountId}/folders', { - accountId, - }) - - // FIXME: this return format is weird and should be avoided - // TODO: respect `resp.data.delimiter` value - return Axios.get(url).then((resp) => resp.data.folders) -} - -export function create(accountId, name) { - const url = generateUrl('/apps/mail/api/accounts/{accountId}/folders', { - accountId, - }) - - const data = { - name, - } - return Axios.post(url, data).then((resp) => resp.data) -} - -export function getFolderStats(accountId, folderId) { - const url = generateUrl('/apps/mail/api/accounts/{accountId}/folders/{folderId}/stats', { - accountId, - folderId, - }) - - return Axios.get(url).then((resp) => resp.data) -} - -export function markFolderRead(accountId, folderId) { - const url = generateUrl('/apps/mail/api/accounts/{accountId}/folders/{folderId}/read', { - accountId, - folderId, - }) - - return Axios.post(url).then((resp) => resp.data) -} - -export const deleteFolder = async(accountId, folderId) => { - const url = generateUrl('/apps/mail/api/accounts/{accountId}/folders/{folderId}', { - accountId, - folderId, - }) - - await Axios.delete(url) -} diff --git a/src/service/MailboxService.js b/src/service/MailboxService.js new file mode 100644 index 000000000..0b0e38c4e --- /dev/null +++ b/src/service/MailboxService.js @@ -0,0 +1,48 @@ +import { generateUrl } from '@nextcloud/router' +import axios from '@nextcloud/axios' + +export async function fetchAll(accountId) { + const url = generateUrl('/apps/mail/api/mailboxes?accountId={accountId}', { + accountId, + }) + + const resp = await axios.get(url) + + // FIXME: this return format is weird and should be avoided + // TODO: respect `resp.data.delimiter` value + return resp.data.mailboxes +} + +export function create(accountId, name) { + const url = generateUrl('/apps/mail/api/mailboxes') + + const data = { + accountId, + name, + } + return axios.post(url, data).then((resp) => resp.data) +} + +export function getMailboxStatus(id) { + const url = generateUrl('/apps/mail/api/mailboxes/{id}/stats', { + id, + }) + + return axios.get(url).then((resp) => resp.data) +} + +export function markMailboxRead(id) { + const url = generateUrl('/apps/mail/api/mailboxes/{id}/read', { + id, + }) + + return axios.post(url).then((resp) => resp.data) +} + +export const deleteMailbox = async(id) => { + const url = generateUrl('/apps/mail/api/mailboxes/{id}', { + id, + }) + + await axios.delete(url) +} diff --git a/src/service/MessageService.js b/src/service/MessageService.js index 57f94b911..d9ee16bac 100644 --- a/src/service/MessageService.js +++ b/src/service/MessageService.js @@ -1,28 +1,24 @@ import { generateUrl } from '@nextcloud/router' import axios from '@nextcloud/axios' -import { curry, map } from 'ramda' +import { curry } from 'ramda' import { parseErrorResponse } from '../http/ErrorResponseParser' import { convertAxiosError } from '../errors/convert' import SyncIncompleteError from '../errors/SyncIncompleteError' -const amendEnvelopeWithIds = curry((accountId, folderId, envelope) => ({ +const amendEnvelopeWithIds = curry((accountId, envelope) => ({ accountId, - folderId, - uuid: `${accountId}-${folderId}-${envelope.uid}`, ...envelope, })) -export function fetchEnvelope(accountId, folderId, uid) { - const url = generateUrl('/apps/mail/api/accounts/{accountId}/folders/{folderId}/messages/{uid}', { - accountId, - folderId, - uid, +export function fetchEnvelope(id) { + const url = generateUrl('/apps/mail/api/messages/{id}', { + id, }) return axios .get(url) - .then((resp) => amendEnvelopeWithIds(accountId, folderId, resp.data)) + .then((resp) => resp.data) .catch((error) => { if (error.response && error.response.status === 404) { return undefined @@ -31,12 +27,11 @@ export function fetchEnvelope(accountId, folderId, uid) { }) } -export function fetchEnvelopes(accountId, folderId, query, cursor, limit) { - const url = generateUrl('/apps/mail/api/accounts/{accountId}/folders/{folderId}/messages', { - accountId, - folderId, - }) - const params = {} +export function fetchEnvelopes(mailboxId, query, cursor, limit) { + const url = generateUrl('/apps/mail/api/messages') + const params = { + mailboxId, + } if (query) { params.filter = query @@ -53,21 +48,19 @@ export function fetchEnvelopes(accountId, folderId, query, cursor, limit) { params, }) .then((resp) => resp.data) - .then(map(amendEnvelopeWithIds(accountId, folderId))) .catch((error) => { throw convertAxiosError(error) }) } -export async function syncEnvelopes(accountId, folderId, uids, query, init = false) { - const url = generateUrl('/apps/mail/api/accounts/{accountId}/folders/{folderId}/sync', { - accountId, - folderId, +export async function syncEnvelopes(accountId, id, ids, query, init = false) { + const url = generateUrl('/apps/mail/api/mailboxes/{id}/sync', { + id, }) try { const response = await axios.post(url, { - uids, + ids, query, init, }) @@ -76,7 +69,7 @@ export async function syncEnvelopes(accountId, folderId, uids, query, init = fal throw new SyncIncompleteError() } - const amend = amendEnvelopeWithIds(accountId, folderId) + const amend = amendEnvelopeWithIds(accountId) return { newMessages: response.data.newMessages.map(amend), changedMessages: response.data.changedMessages.map(amend), @@ -87,10 +80,9 @@ export async function syncEnvelopes(accountId, folderId, uids, query, init = fal } } -export async function clearCache(accountId, folderId) { - const url = generateUrl('/apps/mail/api/accounts/{accountId}/folders/{folderId}/sync', { - accountId, - folderId, +export async function clearCache(accountId, id) { + const url = generateUrl('/apps/mail/api/mailboxes/{id}/sync', { + id, }) try { @@ -104,38 +96,34 @@ export async function clearCache(accountId, folderId) { } } -export function setEnvelopeFlag(accountId, folderId, uid, flag, value) { - const url = generateUrl('/apps/mail/api/accounts/{accountId}/folders/{folderId}/messages/{uid}/flags', { - accountId, - folderId, - uid, +export function setEnvelopeFlag(id, flag, value) { + const url = generateUrl('/apps/mail/api/messages/{id}/flags', { + id, }) - const flags = {} - flags[flag] = value - return axios .put(url, { - flags, + flags: { + [flag]: value, + }, }) } -export function fetchMessage(accountId, folderId, id) { - const url = generateUrl('/apps/mail/api/accounts/{accountId}/folders/{folderId}/messages/{id}/body', { - accountId, - folderId, +export async function fetchMessage(id) { + const url = generateUrl('/apps/mail/api/messages/{id}/body', { id, }) - return axios - .get(url) - .then((resp) => resp.data) - .catch((error) => { - if (error.response && error.response.status === 404) { - return undefined - } - return Promise.reject(parseErrorResponse(error.response)) - }) + try { + const resp = await axios.get(url) + return resp.data + } catch (error) { + if (error.response && error.response.status === 404) { + return undefined + } + + throw parseErrorResponse(error.response) + } } export async function saveDraft(accountId, data) { @@ -154,10 +142,8 @@ export function sendMessage(accountId, data) { return axios.post(url, data).then((resp) => resp.data) } -export function deleteMessage(accountId, folderId, id) { - const url = generateUrl('/apps/mail/api/accounts/{accountId}/folders/{folderId}/messages/{id}', { - accountId, - folderId, +export function deleteMessage(id) { + const url = generateUrl('/apps/mail/api/messages/{id}', { id, }) diff --git a/src/store/actions.js b/src/store/actions.js index 344b643c1..d0a62e26e 100644 --- a/src/store/actions.js +++ b/src/store/actions.js @@ -51,11 +51,11 @@ import { updateSignature, } from '../service/AccountService' import { - create as createFolder, - fetchAll as fetchAllFolders, - markFolderRead, - deleteFolder, -} from '../service/FolderService' + create as createMailbox, + deleteMailbox, + fetchAll as fetchAllMailboxes, + markMailboxRead, +} from '../service/MailboxService' import { deleteMessage, fetchEnvelope, @@ -68,22 +68,21 @@ import { createAlias, deleteAlias } from '../service/AliasService' import logger from '../logger' import { normalizedEnvelopeListId } from './normalization' import { showNewMessagesNotification } from '../service/NotificationService' -import { parseUuid } from '../util/EnvelopeUidParser' import { matchError } from '../errors/match' import SyncIncompleteError from '../errors/SyncIncompleteError' import MailboxLockedError from '../errors/MailboxLockedError' import { wait } from '../util/wait' -import { UNIFIED_ACCOUNT_ID, UNIFIED_INBOX_ID } from './constants' +import { UNIFIED_INBOX_ID } from './constants' const PAGE_SIZE = 20 const sliceToPage = slice(0, PAGE_SIZE) -const findIndividualFolders = curry((getFolders, specialRole) => +const findIndividualMailboxes = curry((getMailboxes, specialRole) => pipe( filter(complement(prop('isUnified'))), - map(prop('accountId')), - map(getFolders), + map(prop('id')), + map(getMailboxes), flatten, filter(propEq('specialRole', specialRole)) ) @@ -114,13 +113,13 @@ export default { }, createAccount({ commit }, config) { return createAccount(config).then((account) => { - logger.debug(`account ${account.id} created, fetching folders …`, account) - return fetchAllFolders(account.id) - .then((folders) => { - account.folders = folders + logger.debug(`account ${account.id} created, fetching mailboxes …`, account) + return fetchAllMailboxes(account.id) + .then((mailboxes) => { + account.mailboxes = mailboxes commit('addAccount', account) }) - .then(() => console.info("new account's folders fetched")) + .then(() => console.info("new account's mailboxes fetched")) .then(() => account) }) }, @@ -152,16 +151,15 @@ export default { throw err }) }, - async deleteFolder({ commit }, { account, folder }) { - await deleteFolder(account.id, folder.id) - commit('removeFolder', { accountId: account.id, folderId: folder.id }) + async deleteMailbox({ commit }, { mailbox }) { + await deleteMailbox(mailbox.databaseId) + commit('removeMailbox', { id: mailbox.databaseId }) }, - createFolder({ commit }, { account, name }) { - return createFolder(account.id, name).then((folder) => { - console.debug(`folder ${name} created for account ${account.id}`, { folder }) - commit('addFolder', { account, folder }) - commit('expandAccount', account.id) - }) + async createMailbox({ commit }, { account, name }) { + const mailbox = await createMailbox(account.id, name) + console.debug(`mailbox ${name} created for account ${account.id}`, { mailbox }) + commit('addMailbox', { account, mailbox }) + commit('expandAccount', account.id) }, moveAccount({ commit, getters }, { account, up }) { const accounts = getters.accounts @@ -185,61 +183,56 @@ export default { }) ) }, - markFolderRead({ getters, dispatch }, { accountId, folderId }) { - const folder = getters.getFolder(accountId, folderId) + markMailboxRead({ getters, dispatch }, { accountId, mailboxId }) { + const mailbox = getters.getMailbox(mailboxId) - if (folder.isUnified) { - const findIndividual = findIndividualFolders(getters.getFolders, folder.specialRole) - const individualFolders = findIndividual(getters.accounts) + if (mailbox.isUnified) { + const findIndividual = findIndividualMailboxes(getters.getMailboxes, mailbox.specialRole) + const individualMailboxes = findIndividual(getters.accounts) return Promise.all( - individualFolders.map((f) => - dispatch('markFolderRead', { - accountId: f.accountId, - folderId: f.id, + individualMailboxes.map((mb) => + dispatch('markMailboxRead', { + accountId: mb.accountId, + mailboxId: mb.databaseId, }) ) ) } - return markFolderRead(accountId, folderId).then( + return markMailboxRead(mailboxId).then( dispatch('syncEnvelopes', { accountId, - folderId, + mailboxId, }) ) }, - fetchEnvelope({ commit, getters }, uuid) { - const { accountId, folderId, uid } = parseUuid(uuid) - - const cached = getters.getEnvelope(accountId, folderId, uid) + fetchEnvelope({ commit, getters }, id) { + const cached = getters.getEnvelope(id) if (cached) { - logger.debug(`using cached value for envelope ${uuid}`) + logger.debug(`using cached value for envelope ${id}`) return cached } - return fetchEnvelope(accountId, folderId, uid).then((envelope) => { + return fetchEnvelope(id).then((envelope) => { // Only commit if not undefined (not found) if (envelope) { commit('addEnvelope', { - accountId, - folderId, envelope, }) } // Always use the object from the store - return getters.getEnvelope(accountId, folderId, uid) + return getters.getEnvelope(id) }) }, - fetchEnvelopes({ state, commit, getters, dispatch }, { accountId, folderId, query }) { - const folder = getters.getFolder(accountId, folderId) + fetchEnvelopes({ state, commit, getters, dispatch }, { mailboxId, query }) { + const mailbox = getters.getMailbox(mailboxId) - if (folder.isUnified) { + if (mailbox.isUnified) { const fetchIndividualLists = pipe( - map((f) => + map((mb) => dispatch('fetchEnvelopes', { - accountId: f.accountId, - folderId: f.id, + mailboxId: mb.databaseId, query, }) ), @@ -247,7 +240,7 @@ export default { andThen(map(sliceToPage)) ) const fetchUnifiedEnvelopes = pipe( - findIndividualFolders(getters.getFolders, folder.specialRole), + findIndividualMailboxes(getters.getMailboxes, mailbox.specialRole), fetchIndividualLists, andThen(combineEnvelopeLists), andThen(sliceToPage), @@ -255,8 +248,6 @@ export default { tap( map((envelope) => commit('addEnvelope', { - accountId, - folderId, envelope, query, }) @@ -274,31 +265,29 @@ export default { tap( map((envelope) => commit('addEnvelope', { - accountId, - folderId, query, envelope, }) ) ) ) - )(accountId, folderId, query, undefined, PAGE_SIZE) + )(mailboxId, query, undefined, PAGE_SIZE) }, - fetchNextEnvelopePage({ commit, getters, dispatch }, { accountId, folderId, query, rec = true }) { - const folder = getters.getFolder(accountId, folderId) + fetchNextEnvelopePage({ commit, getters, dispatch }, { mailboxId, query, rec = true }) { + const mailbox = getters.getMailbox(mailboxId) - if (folder.isUnified) { - const getIndivisualLists = curry((query, f) => getters.getEnvelopes(f.accountId, f.id, query)) - const individualCursor = curry((query, f) => - prop('dateInt', last(getters.getEnvelopes(f.accountId, f.id, query))) + if (mailbox.isUnified) { + const getIndivisualLists = curry((query, m) => getters.getEnvelopes(m.databaseId, query)) + const individualCursor = curry((query, m) => + prop('dateInt', last(getters.getEnvelopes(m.databaseId, query))) ) - const cursor = individualCursor(query, folder) + const cursor = individualCursor(query, mailbox) if (cursor === undefined) { throw new Error('Unified list has no tail') } const nextLocalUnifiedEnvelopePage = pipe( - findIndividualFolders(getters.getFolders, folder.specialRole), + findIndividualMailboxes(getters.getMailboxes, mailbox.specialRole), map(getIndivisualLists(query)), combineEnvelopeLists, filter( @@ -317,39 +306,35 @@ export default { return nextPage.length < PAGE_SIZE || (c <= head(nextPage).dateInt && c >= last(nextPage).dateInt) }) - const foldersToFetch = (accounts) => + const mailboxesToFetch = (accounts) => pipe( - findIndividualFolders(getters.getFolders, folder.specialRole), + findIndividualMailboxes(getters.getMailboxes, mailbox.specialRole), filter(needsFetch(query, nextLocalUnifiedEnvelopePage(accounts))) )(accounts) - const fs = foldersToFetch(getters.accounts) + const mbs = mailboxesToFetch(getters.accounts) - if (rec && fs.length) { + if (rec && mbs.length) { return pipe( - map((f) => + map((mb) => dispatch('fetchNextEnvelopePage', { - accountId: f.accountId, - folderId: f.id, + mailboxId: mb.databaseId, query, }) ), Promise.all.bind(Promise), andThen(() => dispatch('fetchNextEnvelopePage', { - accountId, - folderId, + mailboxId, query, rec: false, }) ) - )(fs) + )(mbs) } const page = nextLocalUnifiedEnvelopePage(getters.accounts) page.map((envelope) => commit('addEnvelope', { - accountId, - folderId, query, envelope, }) @@ -357,29 +342,27 @@ export default { return page } - const list = folder.envelopeLists[normalizedEnvelopeListId(query)] + const list = mailbox.envelopeLists[normalizedEnvelopeListId(query)] if (list === undefined) { - console.warn("envelope list is not defined, can't fetch next page", accountId, folderId, query) + console.warn("envelope list is not defined, can't fetch next page", mailboxId, query) return Promise.resolve([]) } const lastEnvelopeId = last(list) if (typeof lastEnvelopeId === 'undefined') { - console.error('folder is empty', list) - return Promise.reject(new Error('Local folder has no envelopes, cannot determine cursor')) + console.error('mailbox is empty', list) + return Promise.reject(new Error('Local mailbox has no envelopes, cannot determine cursor')) } - const lastEnvelope = getters.getEnvelopeById(lastEnvelopeId) + const lastEnvelope = getters.getEnvelope(lastEnvelopeId) if (typeof lastEnvelope === 'undefined') { - return Promise.reject(new Error('Cannot find last envelope. Required for the folder cursor')) + return Promise.reject(new Error('Cannot find last envelope. Required for the mailbox cursor')) } - return fetchEnvelopes(accountId, folderId, query, lastEnvelope.dateInt, PAGE_SIZE).then((envelopes) => { - logger.debug(`fetched ${envelopes.length} messages for the next page of ${accountId}:${folderId}`, { + return fetchEnvelopes(mailboxId, query, lastEnvelope.dateInt, PAGE_SIZE).then((envelopes) => { + logger.debug(`fetched ${envelopes.length} messages for the next page of mailbox ${mailboxId}`, { envelopes, }) envelopes.forEach((envelope) => commit('addEnvelope', { - accountId, - folderId, query, envelope, }) @@ -387,22 +370,22 @@ export default { return envelopes }) }, - syncEnvelopes({ commit, getters, dispatch }, { accountId, folderId, query, init = false }) { - const folder = getters.getFolder(accountId, folderId) + syncEnvelopes({ commit, getters, dispatch }, { mailboxId, query, init = false }) { + // TODO: use mailboxId + const mailbox = getters.getMailbox(mailboxId) - if (folder.isUnified) { + if (mailbox.isUnified) { return Promise.all( getters.accounts .filter((account) => !account.isUnified) .map((account) => Promise.all( getters - .getFolders(account.id) - .filter((f) => f.specialRole === folder.specialRole) - .map((folder) => + .getMailboxes(account.id) + .filter((mb) => mb.specialRole === mailbox.specialRole) + .map((mailbox) => dispatch('syncEnvelopes', { - accountId: account.id, - folderId: folder.id, + mailboxId: mailbox.databaseId, query, init, }) @@ -410,19 +393,18 @@ export default { ) ) ) - } else if (folder.isPriorityInbox && query === undefined) { + } else if (mailbox.isPriorityInbox && query === undefined) { return Promise.all( getters.accounts .filter((account) => !account.isUnified) .map((account) => Promise.all( getters - .getFolders(account.id) - .filter((f) => f.specialRole === folder.specialRole) - .map((folder) => + .getMailboxes(account.id) + .filter((mb) => mb.specialRole === mailbox.specialRole) + .map((mailbox) => dispatch('syncEnvelopes', { - accountId: account.id, - folderId: folder.id, + mailboxId: mailbox.databaseId, query, init, }) @@ -432,20 +414,17 @@ export default { ) } - const uids = getters.getEnvelopes(accountId, folderId, query).map((env) => env.uid) - - return syncEnvelopes(accountId, folderId, uids, query, init) + const ids = getters.getEnvelopes(mailboxId, query).map((env) => env.databaseId) + return syncEnvelopes(mailbox.accountId, mailboxId, ids, query, init) .then((syncData) => { - const unifiedFolder = getters.getUnifiedFolder(folder.specialRole) + const unifiedMailbox = getters.getUnifiedMailbox(mailbox.specialRole) syncData.newMessages.forEach((envelope) => { commit('addEnvelope', { - accountId, - folderId, envelope, query, }) - if (unifiedFolder) { + if (unifiedMailbox) { commit('updateEnvelope', { envelope, }) @@ -456,11 +435,9 @@ export default { envelope, }) }) - syncData.vanishedMessages.forEach((uid) => { + syncData.vanishedMessages.forEach((id) => { commit('removeEnvelope', { - accountId, - folderId, - uid, + id, }) // Already removed from unified inbox }) @@ -471,11 +448,11 @@ export default { return matchError(error, { [SyncIncompleteError.getName()]() { console.warn('(initial) sync is incomplete, retriggering') - return dispatch('syncEnvelopes', { accountId, folderId, query, init }) + return dispatch('syncEnvelopes', { mailboxId, query, init }) }, [MailboxLockedError.getName()](error) { logger.info('Sync failed because the mailbox is locked, retriggering', { error }) - return wait(1500).then(() => dispatch('syncEnvelopes', { accountId, folderId, query, init })) + return wait(1500).then(() => dispatch('syncEnvelopes', { mailboxId, query, init })) }, default(error) { console.error('Could not sync envelopes: ' + error.message, error) @@ -489,22 +466,20 @@ export default { .filter((a) => !a.isUnified) .map((account) => { return Promise.all( - getters.getFolders(account.id).map(async(folder) => { - if (folder.specialRole !== 'inbox') { + getters.getMailboxes(account.id).map(async(mailbox) => { + if (mailbox.specialRole !== 'inbox') { return } - const list = folder.envelopeLists[normalizedEnvelopeListId(undefined)] + const list = mailbox.envelopeLists[normalizedEnvelopeListId(undefined)] if (list === undefined) { await dispatch('fetchEnvelopes', { - accountId: account.id, - folderId: folder.id, + mailboxId: mailbox.databaseId, }) } return await dispatch('syncEnvelopes', { - accountId: account.id, - folderId: folder.id, + mailboxId: mailbox.databaseId, }) }) ) @@ -520,19 +495,17 @@ export default { logger.info('updating priority inbox') for (const query of ['is:important not:starred', 'is:starred not:important', 'not:starred not:important']) { logger.info("sync'ing priority inbox section", { query }) - const folder = getters.getFolder(UNIFIED_ACCOUNT_ID, UNIFIED_INBOX_ID) - const list = folder.envelopeLists[normalizedEnvelopeListId(query)] + const mailbox = getters.getMailbox(UNIFIED_INBOX_ID) + const list = mailbox.envelopeLists[normalizedEnvelopeListId(query)] if (list === undefined) { await dispatch('fetchEnvelopes', { - accountId: UNIFIED_ACCOUNT_ID, - folderId: UNIFIED_INBOX_ID, + mailboxId: UNIFIED_INBOX_ID, query, }) } await dispatch('syncEnvelopes', { - accountId: UNIFIED_ACCOUNT_ID, - folderId: UNIFIED_INBOX_ID, + mailboxId: UNIFIED_INBOX_ID, query, }) } @@ -549,7 +522,7 @@ export default { value: !oldState, }) - setEnvelopeFlag(envelope.accountId, envelope.folderId, envelope.uid, 'flagged', !oldState).catch((e) => { + setEnvelopeFlag(envelope.databaseId, 'flagged', !oldState).catch((e) => { console.error('could not toggle message flagged state', e) // Revert change @@ -569,7 +542,7 @@ export default { value: !oldState, }) - setEnvelopeFlag(envelope.accountId, envelope.folderId, envelope.uid, 'important', !oldState).catch((e) => { + setEnvelopeFlag(envelope.databaseId, 'important', !oldState).catch((e) => { console.error('could not toggle message important state', e) // Revert change @@ -589,7 +562,7 @@ export default { value: !oldState, }) - setEnvelopeFlag(envelope.accountId, envelope.folderId, envelope.uid, 'seen', !oldState).catch((e) => { + setEnvelopeFlag(envelope.databaseId, 'seen', !oldState).catch((e) => { console.error('could not toggle message seen state', e) // Revert change @@ -609,7 +582,7 @@ export default { value: !oldState, }) - setEnvelopeFlag(envelope.accountId, envelope.folderId, envelope.uid, 'junk', !oldState).catch((e) => { + setEnvelopeFlag(envelope.databaseId, 'junk', !oldState).catch((e) => { console.error('could not toggle message junk state', e) // Revert change @@ -629,7 +602,7 @@ export default { value: favFlag, }) - setEnvelopeFlag(envelope.accountId, envelope.folderId, envelope.uid, 'flagged', favFlag).catch((e) => { + setEnvelopeFlag(envelope.databaseId, 'flagged', favFlag).catch((e) => { console.error('could not favorite/unfavorite message ' + envelope.uid, e) // Revert change @@ -649,7 +622,7 @@ export default { value: seenFlag, }) - setEnvelopeFlag(envelope.accountId, envelope.folderId, envelope.uid, 'unseen', seenFlag).catch((e) => { + setEnvelopeFlag(envelope.databaseId, 'unseen', seenFlag).catch((e) => { console.error('could not mark message ' + envelope.uid + ' seen/unseen', e) // Revert change @@ -660,51 +633,34 @@ export default { }) }) }, - fetchMessage({ commit }, uuid) { - const { accountId, folderId, uid } = parseUuid(uuid) - return fetchMessage(accountId, folderId, uid).then((message) => { - // Only commit if not undefined (not found) - if (message) { - commit('addMessage', { - accountId, - folderId, - message, - }) - } + async fetchMessage({ getters, commit }, id) { + const message = await fetchMessage(id) + // Only commit if not undefined (not found) + if (message) { + commit('addMessage', { + message, + }) + } - return message - }) - }, - replaceDraft({ getters, commit }, { draft, uid, data }) { - commit('updateDraft', { - draft, - data, - newUid: uid, - }) + return message }, - deleteMessage({ getters, commit }, { accountId, folderId, uid }) { - commit('removeEnvelope', { accountId, folderId, uid }) - - return deleteMessage(accountId, folderId, uid) - .then(() => { - const folder = getters.getFolder(accountId, folderId) - if (!folder) { - logger.error('could not find folder', { accountId, folderId }) - return - } - commit('removeMessage', { accountId, folder, uid }) - console.debug('message removed') - }) - .catch((err) => { - console.error('could not delete message', err) - const envelope = getters.getEnvelope(accountId, folderId, uid) - if (envelope) { - commit('addEnvelope', { accountId, folderId, envelope }) - } else { - logger.error('could not find envelope', { accountId, folderId, uid }) - } - throw err - }) + async deleteMessage({ getters, commit }, { id }) { + commit('removeEnvelope', { id }) + + try { + await deleteMessage(id) + commit('removeMessage', { id }) + console.debug('message removed') + } catch (err) { + console.error('could not delete message', err) + const envelope = getters.getEnvelope(id) + if (envelope) { + commit('addEnvelope', { envelope }) + } else { + logger.error('could not find envelope', { id }) + } + throw err + } }, async createAlias({ commit }, { account, aliasToAdd }) { const alias = await createAlias(account, aliasToAdd) diff --git a/src/store/constants.js b/src/store/constants.js index ca08968b1..ff683a2e4 100644 --- a/src/store/constants.js +++ b/src/store/constants.js @@ -18,10 +18,7 @@ * 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/>. */ -import { normalizedFolderId } from './normalization' export const UNIFIED_ACCOUNT_ID = 0 -export const UNIFIED_INBOX_ID = btoa('inbox') -export const UNIFIED_INBOX_UID = normalizedFolderId(UNIFIED_ACCOUNT_ID, UNIFIED_INBOX_ID) -export const PRIORITY_INBOX_ID = btoa('priority') -export const PRIORITY_INBOX_UID = UNIFIED_ACCOUNT_ID + '-' + PRIORITY_INBOX_ID +export const UNIFIED_INBOX_ID = 'unified' +export const PRIORITY_INBOX_ID = 'priority' diff --git a/src/store/getters.js b/src/store/getters.js index b6e6e29de..752840857 100644 --- a/src/store/getters.js +++ b/src/store/getters.js @@ -22,7 +22,7 @@ import { defaultTo, head } from 'ramda' import { UNIFIED_ACCOUNT_ID } from './constants' -import { normalizedEnvelopeListId, normalizedFolderId, normalizedMessageId } from './normalization' +import { normalizedEnvelopeListId } from './normalization' export const getters = { getPreference: (state) => (key, def) => { @@ -34,35 +34,32 @@ export const getters = { accounts: (state) => { return state.accountList.map((id) => state.accounts[id]) }, - getFolder: (state) => (accountId, folderId) => { - return state.folders[normalizedFolderId(accountId, folderId)] + getMailbox: (state) => (id) => { + return state.mailboxes[id] }, - getFolders: (state) => (accountId) => { - return state.accounts[accountId].folders.map((folderId) => state.folders[folderId]) + getMailboxes: (state) => (accountId) => { + return state.accounts[accountId].mailboxes.map((id) => state.mailboxes[id]) }, - getSubfolders: (state, getters) => (accountId, folderId) => { - const folder = getters.getFolder(accountId, folderId) + getSubMailboxes: (state, getters) => (id) => { + const mailbox = getters.getMailbox(id) - return folder.folders.map((id) => state.folders[id]) + return mailbox.mailboxes.map((id) => state.mailboxes[id]) }, - getUnifiedFolder: (state) => (specialRole) => { + getUnifiedMailbox: (state) => (specialRole) => { return head( - state.accounts[UNIFIED_ACCOUNT_ID].folders - .map((folderId) => state.folders[folderId]) - .filter((folder) => folder.specialRole === specialRole) + state.accounts[UNIFIED_ACCOUNT_ID].mailboxes + .map((id) => state.mailboxes[id]) + .filter((mailbox) => mailbox.specialRole === specialRole) ) }, - getEnvelope: (state) => (accountId, folderId, uid) => { - return state.envelopes[normalizedMessageId(accountId, folderId, uid)] - }, - getEnvelopeById: (state) => (id) => { + getEnvelope: (state) => (id) => { return state.envelopes[id] }, - getEnvelopes: (state, getters) => (accountId, folderId, query) => { - const list = getters.getFolder(accountId, folderId).envelopeLists[normalizedEnvelopeListId(query)] || [] + getEnvelopes: (state, getters) => (mailboxId, query) => { + const list = getters.getMailbox(mailboxId).envelopeLists[normalizedEnvelopeListId(query)] || [] return list.map((msgId) => state.envelopes[msgId]) }, - getMessage: (state) => (accountId, folderId, uid) => { - return state.messages[normalizedMessageId(accountId, folderId, uid)] + getMessage: (state) => (id) => { + return state.messages[id] }, } diff --git a/src/store/index.js b/src/store/index.js index df0915e25..35af89ce6 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -25,9 +25,7 @@ import Vuex from 'vuex' import { UNIFIED_ACCOUNT_ID, UNIFIED_INBOX_ID, - UNIFIED_INBOX_UID, PRIORITY_INBOX_ID, - PRIORITY_INBOX_UID, } from './constants' import actions from './actions' import { getters } from './getters' @@ -44,7 +42,7 @@ export default new Vuex.Store({ id: UNIFIED_ACCOUNT_ID, accountId: UNIFIED_ACCOUNT_ID, isUnified: true, - folders: [PRIORITY_INBOX_UID, UNIFIED_INBOX_UID], + mailboxes: [PRIORITY_INBOX_ID, UNIFIED_INBOX_ID], collapsed: false, emailAddress: '', name: '', @@ -52,27 +50,29 @@ export default new Vuex.Store({ }, }, accountList: [UNIFIED_ACCOUNT_ID], - folders: { - [UNIFIED_INBOX_UID]: { + mailboxes: { + [UNIFIED_INBOX_ID]: { id: UNIFIED_INBOX_ID, + databaseId: UNIFIED_INBOX_ID, accountId: 0, attributes: ['\\subscribed'], isUnified: true, specialUse: ['inbox'], specialRole: 'inbox', unread: 0, - folders: [], + mailboxes: [], envelopeLists: {}, }, - [PRIORITY_INBOX_UID]: { + [PRIORITY_INBOX_ID]: { id: PRIORITY_INBOX_ID, + databaseId: PRIORITY_INBOX_ID, accountId: 0, attributes: ['\\subscribed'], isPriorityInbox: true, specialUse: ['inbox'], specialRole: 'inbox', unread: 0, - folders: [], + mailboxes: [], envelopeLists: {}, }, }, diff --git a/src/store/mutations.js b/src/store/mutations.js index c2f20bfe0..64e57b303 100644 --- a/src/store/mutations.js +++ b/src/store/mutations.js @@ -25,16 +25,15 @@ import Vue from 'vue' import { buildMailboxHierarchy } from '../imap/MailboxHierarchy' import { havePrefix } from '../imap/MailboxPrefix' -import { normalizedFolderId, normalizedMessageId, normalizedEnvelopeListId } from './normalization' import { sortMailboxes } from '../imap/MailboxSorter' +import { normalizedEnvelopeListId } from './normalization' import { UNIFIED_ACCOUNT_ID } from './constants' -const addFolderToState = (state, account) => (folder) => { - const id = normalizedFolderId(account.id, folder.id) - folder.accountId = account.id - folder.envelopeLists = {} - Vue.set(state.folders, id, folder) - return id +const addMailboxToState = (state, account) => (mailbox) => { + mailbox.accountId = account.id + mailbox.envelopeLists = {} + Vue.set(state.mailboxes, mailbox.databaseId, mailbox) + return mailbox.databaseId } const sortAccounts = (accounts) => { @@ -55,16 +54,16 @@ export default { sortAccounts(state.accountList.concat([account.id]).map((id) => state.accounts[id])).map((a) => a.id) ) - // Save the folders to the store, but only keep IDs in the account's folder list - const folders = buildMailboxHierarchy(sortMailboxes(account.folders || []), havePrefix(account.folders)) - Vue.set(account, 'folders', []) - const addToState = addFolderToState(state, account) - folders.forEach((folder) => { - // Add all folders (including subfolders to state, but only toplevel to account - const id = addToState(folder) - Vue.set(folder, 'folders', folder.folders.map(addToState)) + // Save the mailboxes to the store, but only keep IDs in the account's mailboxes list + const mailboxes = buildMailboxHierarchy(sortMailboxes(account.mailboxes), havePrefix(account.mailboxes)) + Vue.set(account, 'mailboxes', []) + const addToState = addMailboxToState(state, account) + mailboxes.forEach((mailbox) => { + // Add all mailboxes (including submailboxes to state, but only toplevel to account + const id = addToState(mailbox) + Vue.set(mailbox, 'mailboxes', mailbox.mailboxes.map(addToState)) - account.folders.push(id) + account.mailboxes.push(id) }) }, editAccount(state, account) { @@ -87,61 +86,68 @@ export default { expandAccount(state, accountId) { state.accounts[accountId].collapsed = false }, - addFolder(state, { account, folder }) { + addMailbox(state, { account, mailbox }) { // Flatten the existing ones before updating the hierarchy - const existing = account.folders.map((id) => state.folders[id]) - existing.forEach((folder) => { - if (!folder.folders) { + const existing = account.mailboxes.map((id) => state.mailboxes[id]) + existing.forEach((mailbox) => { + if (!mailbox.mailboxes) { return } - folder.folders.map((id) => existing.push(state.folders[id])) - folder.folders = [] + mailbox.mailboxes.map((id) => existing.push(state.mailboxes[id])) + mailbox.mailboxes = [] }) - // Save the folders to the store, but only keep IDs in the account's folder list - existing.push(folder) - const folders = buildMailboxHierarchy(sortMailboxes(existing), havePrefix(existing)) - Vue.set(account, 'folders', []) - const addToState = addFolderToState(state, account) - folders.forEach((folder) => { - // Add all folders (including subfolders to state, but only toplevel to account - const id = addToState(folder) - Vue.set(folder, 'folders', folder.folders.map(addToState)) + // Save the mailboxes to the store, but only keep IDs in the account's mailboxes list + existing.push(mailbox) + const mailboxes = buildMailboxHierarchy(sortMailboxes(existing), havePrefix(existing)) + Vue.set(account, 'mailboxes', []) + const addToState = addMailboxToState(state, account) + mailboxes.forEach((mailbox) => { + // Add all mailboxes (including submailboxes to state, but only toplevel to account + const id = addToState(mailbox) + Vue.set(mailbox, 'mailboxes', mailbox.mailboxes.map(addToState)) - account.folders.push(id) + account.mailboxes.push(id) }) }, - removeFolder(state, { accountId, folderId }) { - const account = state.accounts[accountId] - const id = normalizedFolderId(accountId, folderId) - Vue.delete(state.folders, id) - account.folders = account.folders.filter((fId) => fId !== id) - account.folders.forEach((fId) => { - const folder = state.folders[fId] - if (folder.folders) { - folder.folders = folder.folders.filter((fId) => fId !== id) + removeMailbox(state, { id }) { + const mailbox = state.mailboxes[id] + if (mailbox === undefined) { + throw new Error(`Mailbox ${id} does not exist`) + } + const account = state.accounts[mailbox.accountId] + if (account === undefined) { + throw new Error(`Account ${mailbox.accountId} of mailbox ${id} is unknown`) + } + Vue.delete(state.mailboxes, id) + account.mailboxes = account.mailboxes.filter((mbId) => mbId !== id) + account.mailboxes.forEach((fId) => { + const mailbox = state.mailboxes[fId] + if (mailbox.mailboxes) { + mailbox.mailboxes = mailbox.mailboxes.filter((mbId) => mbId !== id) } }) }, - addEnvelope(state, { accountId, folderId, query, envelope }) { - const folder = state.folders[normalizedFolderId(accountId, folderId)] - Vue.set(state.envelopes, envelope.uuid, envelope) + addEnvelope(state, { query, envelope }) { + const mailbox = state.mailboxes[envelope.mailboxId] + Vue.set(state.envelopes, envelope.databaseId, envelope) + Vue.set(envelope, 'accountId', mailbox.accountId) const listId = normalizedEnvelopeListId(query) - const existing = folder.envelopeLists[listId] || [] - const uuidToDateInt = (uuid) => state.envelopes[uuid].dateInt - const sortedUniqByDateInt = sortedUniqBy(uuidToDateInt) - const orderByDateInt = orderBy(uuidToDateInt, 'desc') - Vue.set(folder.envelopeLists, listId, sortedUniqByDateInt(orderByDateInt(existing.concat([envelope.uuid])))) + const existing = mailbox.envelopeLists[listId] || [] + const idToDateInt = (id) => state.envelopes[id].dateInt + const sortedUniqByDateInt = sortedUniqBy(idToDateInt) + const orderByDateInt = orderBy(idToDateInt, 'desc') + Vue.set(mailbox.envelopeLists, listId, sortedUniqByDateInt(orderByDateInt(existing.concat([envelope.databaseId])))) const unifiedAccount = state.accounts[UNIFIED_ACCOUNT_ID] - unifiedAccount.folders - .map((fId) => state.folders[fId]) - .filter((f) => f.specialRole && f.specialRole === folder.specialRole) - .forEach((folder) => { - const existing = folder.envelopeLists[listId] || [] + unifiedAccount.mailboxes + .map((mbId) => state.mailboxes[mbId]) + .filter((mb) => mb.specialRole && mb.specialRole === mailbox.specialRole) + .forEach((mailbox) => { + const existing = mailbox.envelopeLists[listId] || [] Vue.set( - folder.envelopeLists, + mailbox.envelopeLists, listId, - sortedUniqByDateInt(orderByDateInt(existing.concat([envelope.uuid]))) + sortedUniqByDateInt(orderByDateInt(existing.concat([envelope.databaseId]))) ) }) }, @@ -155,82 +161,56 @@ export default { flagEnvelope(state, { envelope, flag, value }) { envelope.flags[flag] = value }, - removeEnvelope(state, { accountId, folderId, uid }) { - const folder = state.folders[normalizedFolderId(accountId, folderId)] - for (const listId in folder.envelopeLists) { - if (!Object.hasOwnProperty.call(folder.envelopeLists, listId)) { + removeEnvelope(state, { id }) { + const envelope = state.envelopes[id] + if (!envelope) { + console.warn('envelope ' + id + ' is unknown, can\'t remove it') + return + } + const mailbox = state.mailboxes[envelope.mailboxId] + for (const listId in mailbox.envelopeLists) { + if (!Object.hasOwnProperty.call(mailbox.envelopeLists, listId)) { continue } - const list = folder.envelopeLists[listId] - const idx = list.indexOf(normalizedMessageId(accountId, folderId, uid)) + const list = mailbox.envelopeLists[listId] + const idx = list.indexOf(id) if (idx < 0) { continue } - console.debug('envelope removed from mailbox', accountId, folder.id, uid, listId) + console.debug('envelope ' + id + ' removed from mailbox list ' + listId) list.splice(idx, 1) } - state.accounts[UNIFIED_ACCOUNT_ID].folders - .map((fId) => state.folders[fId]) - .filter((f) => f.specialRole && f.specialRole === folder.specialRole) - .forEach((folder) => { - for (const listId in folder.envelopeLists) { - if (!Object.hasOwnProperty.call(folder.envelopeLists, listId)) { + state.accounts[UNIFIED_ACCOUNT_ID].mailboxes + .map((mailboxId) => state.mailboxes[mailboxId]) + .filter((mb) => mb.specialRole && mb.specialRole === mailbox.specialRole) + .forEach((mailbox) => { + for (const listId in mailbox.envelopeLists) { + if (!Object.hasOwnProperty.call(mailbox.envelopeLists, listId)) { continue } - const list = folder.envelopeLists[listId] - const idx = list.indexOf(normalizedMessageId(accountId, folderId, uid)) + const list = mailbox.envelopeLists[listId] + const idx = list.indexOf(id) if (idx < 0) { console.warn( 'envelope does not exist in unified mailbox', - accountId, - folder.id, - uid, + mailbox.databaseId, + id, listId, list ) continue } - console.debug('envelope removed from unified mailbox', accountId, folder.id, uid, listId) + console.debug('envelope removed from unified mailbox', mailbox.databaseId, id) list.splice(idx, 1) } }) }, - addMessage(state, { accountId, folderId, message }) { - const uuid = normalizedMessageId(accountId, folderId, message.uid) - message.accountId = accountId - message.folderId = folderId - message.uuid = uuid - Vue.set(state.messages, uuid, message) - }, - updateDraft(state, { draft, data, newUid }) { - // Update draft's UID - const oldUid = draft.uid - const uid = normalizedMessageId(draft.accountId, draft.folderId, newUid) - console.debug('saving draft as UID ' + uid) - draft.uid = uid - - // TODO: strategy to keep the full draft object in sync, not just the visible - // changes - draft.subject = data.subject - - // Update ref in folder's envelope list - const envs = state.folders[normalizedFolderId(draft.accountId, draft.folderId)].envelopes - const idx = envs.indexOf(oldUid) - if (idx < 0) { - console.warn('not replacing draft ' + oldUid + ' in envelope list because it did not exist') - } else { - envs[idx] = uid - } - - // Move message/envelope objects to new keys - Vue.delete(state.envelopes, oldUid) - Vue.delete(state.messages, oldUid) - Vue.set(state.envelopes, uid, draft) - Vue.set(state.messages, uid, draft) - }, - removeMessage(state, { accountId, folderId, uid }) { - Vue.delete(state.messages, normalizedMessageId(accountId, folderId, uid)) + addMessage(state, { message }) { + Vue.set(state.messages, message.databaseId, message) + }, + removeMessage(state, { id }) { + Vue.delete(state.messages, id) }, createAlias(state, { account, alias }) { account.aliases.push(alias) diff --git a/src/store/normalization.js b/src/store/normalization.js index 1da1a671c..6a5ab0439 100644 --- a/src/store/normalization.js +++ b/src/store/normalization.js @@ -19,14 +19,6 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -import { curry, defaultTo } from 'ramda' - -export const normalizedFolderId = curry((accountId, folderId) => { - return `${accountId}-${folderId}` -}) - -export const normalizedMessageId = curry((accountId, folderId, uid) => { - return `${accountId}-${folderId}-${uid}` -}) +import { defaultTo } from 'ramda' export const normalizedEnvelopeListId = defaultTo('') diff --git a/src/tests/setup.js b/src/tests/setup.js index 9245898f7..1862490f1 100644 --- a/src/tests/setup.js +++ b/src/tests/setup.js @@ -45,4 +45,5 @@ global.OC = { return string }, }, + isUserAdmin: () => false, } diff --git a/src/tests/unit/components/Address.spec.js b/src/tests/unit/components/Address.spec.js index 9fbcb4010..2ea954636 100644 --- a/src/tests/unit/components/Address.spec.js +++ b/src/tests/unit/components/Address.spec.js @@ -18,8 +18,7 @@ describe('Address', () => { it('renders', () => { $route.params = { - accountId: 1, - folderId: 'folder1', + mailboxId: 12, } const addr = shallowMount(Address, { localVue, @@ -33,8 +32,7 @@ describe('Address', () => { }) expect(addr.vm.newMessageRoute.name).to.equal('message') - expect(addr.vm.newMessageRoute.params.accountId).to.equal(1) - expect(addr.vm.newMessageRoute.params.folderId).to.equal('folder1') + expect(addr.vm.newMessageRoute.params.mailboxId).to.equal(12) expect(addr.vm.newMessageRoute.query.to).to.equal('user@domain.com') }) }) diff --git a/src/tests/unit/i18n/MailboxTranslator.spec.js b/src/tests/unit/i18n/MailboxTranslator.spec.js index 6f2182d06..450592411 100644 --- a/src/tests/unit/i18n/MailboxTranslator.spec.js +++ b/src/tests/unit/i18n/MailboxTranslator.spec.js @@ -23,24 +23,24 @@ import { translate } from '../../../i18n/MailboxTranslator' describe('MailboxTranslator', () => { it('translates the inbox', () => { - const folder = { + const mailbox = { id: btoa('INBOX'), specialUse: ['inbox'], } - const name = translate(folder) + const name = translate(mailbox) expect(name).to.equal('Inbox') }) it('does not translate an arbitrary mailbox', () => { - const folder = { + const mailbox = { id: btoa('Newsletters'), displayName: 'Newsletters', specialUse: [], } - const name = translate(folder) + const name = translate(mailbox) expect(name).to.equal('Newsletters') }) diff --git a/src/tests/unit/imap/MailboxHierarchy.spec.js b/src/tests/unit/imap/MailboxHierarchy.spec.js index 8fea48911..29b35381f 100644 --- a/src/tests/unit/imap/MailboxHierarchy.spec.js +++ b/src/tests/unit/imap/MailboxHierarchy.spec.js @@ -47,12 +47,12 @@ describe('mailboxHierarchyBuilder', () => { { id: btoa('INBOX'), delimiter: '.', - folders: [], + mailboxes: [], }, { id: btoa('Sent'), delimiter: '.', - folders: [], + mailboxes: [], }, ]) }) @@ -74,11 +74,11 @@ describe('mailboxHierarchyBuilder', () => { { id: btoa('Archive'), delimiter: '.', - folders: [ + mailboxes: [ { id: btoa('Archive.Sent'), delimiter: '.', - folders: [], + mailboxes: [], }, ], }, @@ -106,23 +106,23 @@ describe('mailboxHierarchyBuilder', () => { { id: btoa('Archive'), delimiter: '.', - folders: [ + mailboxes: [ { id: btoa('Archive.Sent'), delimiter: '.', - folders: [], + mailboxes: [], }, { id: btoa('Archive.Sent.Old'), delimiter: '.', - folders: [], + mailboxes: [], }, ], }, ]) }) - it('does not use the flagged inbox as subfolder of inbox', () => { + it('does not use the flagged inbox as submailbox of inbox', () => { const mb1 = { id: btoa('INBOX'), delimiter: '/', @@ -143,17 +143,17 @@ describe('mailboxHierarchyBuilder', () => { { id: btoa('INBOX'), delimiter: '/', - folders: [], + mailboxes: [], }, { id: btoa('INBOX/FLAGGED'), delimiter: '/', - folders: [], + mailboxes: [], }, { id: btoa('Archive'), delimiter: '/', - folders: [], + mailboxes: [], }, ]) }) @@ -175,11 +175,11 @@ describe('mailboxHierarchyBuilder', () => { { id: btoa('INBOX.Archive'), delimiter: '.', - folders: [ + mailboxes: [ { id: btoa('INBOX.Archive.Sent'), delimiter: '.', - folders: [], + mailboxes: [], }, ], }, diff --git a/src/tests/unit/store/actions.spec.js b/src/tests/unit/store/actions.spec.js index 4e00fdc9f..9764c8c30 100644 --- a/src/tests/unit/store/actions.spec.js +++ b/src/tests/unit/store/actions.spec.js @@ -26,14 +26,11 @@ import orderBy from 'lodash/fp/orderBy' import actions from '../../../store/actions' import * as MessageService from '../../../service/MessageService' import * as NotificationService from '../../../service/NotificationService' -import { normalizedMessageId } from '../../../store/normalization' import { UNIFIED_ACCOUNT_ID, UNIFIED_INBOX_ID } from '../../../store/constants' -const mockEnvelope = curry((accountId, folderId, uid) => ({ - accountId, - folderId, +const mockEnvelope = curry((mailboxId, uid) => ({ + mailboxId, uid, - uuid: normalizedMessageId(accountId, folderId, uid), dateInt: uid * 10000, })) @@ -46,9 +43,9 @@ describe('Vuex store actions', () => { dispatch: sinon.stub(), getters: { accounts: [], - getFolder: sinon.stub(), - getFolders: sinon.stub(), - getEnvelopeById: sinon.stub(), + getMailbox: sinon.stub(), + getMailboxes: sinon.stub(), + getEnvelope: sinon.stub(), getEnvelopes: sinon.stub(), }, } @@ -59,13 +56,12 @@ describe('Vuex store actions', () => { }) it('combines unified inbox even if no inboxes are present', () => { - context.getters.getFolder.returns({ + context.getters.getMailbox.returns({ isUnified: true, }) const envelopes = actions.fetchEnvelopes(context, { - accountId: UNIFIED_ACCOUNT_ID, - folderId: UNIFIED_INBOX_ID, + mailboxId: UNIFIED_INBOX_ID, }) expect(envelopes).to.be.empty @@ -74,60 +70,58 @@ describe('Vuex store actions', () => { it('creates a unified page from one mailbox', async() => { context.getters.accounts.push({ id: 13, - accountId: 13, }) - context.getters.getFolder.withArgs(UNIFIED_ACCOUNT_ID, UNIFIED_INBOX_ID).returns({ + context.getters.getMailbox.withArgs(UNIFIED_INBOX_ID).returns({ isUnified: true, specialRole: 'inbox', + databaseId: UNIFIED_INBOX_ID, }) - context.getters.getFolders.withArgs(13).returns([ + context.getters.getMailboxes.withArgs(13).returns([ { id: 'INBOX', + databaseId: 21, accountId: 13, specialRole: 'inbox', }, { id: 'Drafts', + databaseId: 22, accountId: 13, specialRole: 'draft', }, ]) context.dispatch .withArgs('fetchEnvelopes', { - accountId: 13, - folderId: 'INBOX', + mailboxId: 21, query: undefined, }) .returns([ { - accountId: 13, - folderId: 'INBOX', - uid: '13-INBOX-123', + databaseId: 123, + mailboxId: 21, + uid: 321, subject: 'msg1', }, ]) const envelopes = await actions.fetchEnvelopes(context, { - accountId: UNIFIED_ACCOUNT_ID, - folderId: UNIFIED_INBOX_ID, + mailboxId: UNIFIED_INBOX_ID, }) expect(envelopes).to.deep.equal([ { - accountId: 13, - folderId: 'INBOX', - uid: '13-INBOX-123', + databaseId: 123, + mailboxId: 21, + uid: 321, subject: 'msg1', }, ]) expect(context.dispatch).to.have.been.calledOnce expect(context.commit).to.have.been.calledWith('addEnvelope', { - accountId: UNIFIED_ACCOUNT_ID, - folderId: UNIFIED_INBOX_ID, envelope: { - accountId: 13, - folderId: 'INBOX', - uid: '13-INBOX-123', + databaseId: 123, + mailboxId: 21, + uid: 321, subject: 'msg1', }, query: undefined, @@ -136,26 +130,25 @@ describe('Vuex store actions', () => { it('fetches the next individual page', async() => { context.getters.accounts.push({ - id: 13, accountId: 13, }) - context.getters.getFolder.withArgs(13, 'INBOX').returns({ - id: 'INBOX', + context.getters.getMailbox.withArgs(13).returns({ + name: 'INBOX', + databaseId: 11, accountId: 13, specialRole: 'inbox', envelopeLists: { - '': reverse(range(21, 40).map(normalizedMessageId(13, 'INBOX'))), + '': reverse(range(21, 40)), }, }) - context.getters.getEnvelopeById - .withArgs(normalizedMessageId(13, 'INBOX', 21)) - .returns(mockEnvelope(13, 'INBOX', 1)) + context.getters.getEnvelope + .withArgs(21) + .returns(mockEnvelope(11, 1)) sinon.stub(MessageService, 'fetchEnvelopes').returns( Promise.resolve( reverse( range(1, 21).map((n) => ({ uid: n, - uuid: normalizedMessageId(13, 'INBOX', n), dateInt: n * 10000, })) ) @@ -163,15 +156,13 @@ describe('Vuex store actions', () => { ) const page = await actions.fetchNextEnvelopePage(context, { - accountId: 13, - folderId: 'INBOX', + mailboxId: 13, }) expect(page).to.deep.equal( reverse( range(1, 21).map((n) => ({ uid: n, - uuid: normalizedMessageId(13, 'INBOX', n), dateInt: n * 10000, })) ) @@ -186,57 +177,56 @@ describe('Vuex store actions', () => { const msgs2 = reverse(range(5, 35)) context.getters.accounts.push({ id: 13, - accountId: 13, }) context.getters.accounts.push({ id: 26, - accountId: 26, }) - context.getters.getFolder.withArgs(UNIFIED_ACCOUNT_ID, UNIFIED_INBOX_ID).returns({ + context.getters.getMailbox.withArgs(UNIFIED_INBOX_ID).returns({ isUnified: true, specialRole: 'inbox', accountId: UNIFIED_ACCOUNT_ID, - id: UNIFIED_INBOX_ID, + databaseId: UNIFIED_INBOX_ID, }) - context.getters.getFolders.withArgs(13).returns([ + context.getters.getMailboxes.withArgs(13).returns([ { - id: 'INBOX', - accountId: 13, + name: 'INBOX', + databaseId: 11, specialRole: 'inbox', }, { - id: 'Drafts', - accountId: 13, + name: 'Drafts', + databaseId: 12, specialRole: 'draft', }, ]) - context.getters.getFolders.withArgs(26).returns([ + context.getters.getMailboxes.withArgs(26).returns([ { - id: 'INBOX', + name: 'INBOX', + databaseId: 21, accountId: 26, specialRole: 'inbox', }, { - id: 'Drafts', + name: 'Drafts', + databaseId: 22, accountId: 26, specialRole: 'draft', }, ]) context.getters.getEnvelopes - .withArgs(UNIFIED_ACCOUNT_ID, UNIFIED_INBOX_ID, undefined) + .withArgs(UNIFIED_INBOX_ID, undefined) .returns( orderBy( prop('dateInt'), 'desc', - page1.map(mockEnvelope(13, 'INBOX')).concat(page2.map(mockEnvelope(26, 'INBOX'))) + page1.map(mockEnvelope(11)).concat(page2.map(mockEnvelope(21))) ) ) - context.getters.getEnvelopes.withArgs(13, 'INBOX', undefined).returns(msgs1.map(mockEnvelope(13, 'INBOX'))) - context.getters.getEnvelopes.withArgs(26, 'INBOX', undefined).returns(msgs2.map(mockEnvelope(26, 'INBOX'))) + context.getters.getEnvelopes.withArgs(11, undefined).returns(msgs1.map(mockEnvelope(11))) + context.getters.getEnvelopes.withArgs(21, undefined).returns(msgs2.map(mockEnvelope(21))) const page = await actions.fetchNextEnvelopePage(context, { - accountId: UNIFIED_ACCOUNT_ID, - folderId: UNIFIED_INBOX_ID, + mailboxId: UNIFIED_INBOX_ID, }) expect(context.dispatch).not.have.been.called @@ -250,69 +240,68 @@ describe('Vuex store actions', () => { const msgs2 = reverse(range(5, 35)) context.getters.accounts.push({ id: 13, - accountId: 13, }) context.getters.accounts.push({ id: 26, - accountId: 26, }) - context.getters.getFolder.withArgs(UNIFIED_ACCOUNT_ID, UNIFIED_INBOX_ID).returns({ + context.getters.getMailbox.withArgs(UNIFIED_INBOX_ID).returns({ isUnified: true, + databaseId: UNIFIED_INBOX_ID, specialRole: 'inbox', accountId: UNIFIED_ACCOUNT_ID, id: UNIFIED_INBOX_ID, }) - context.getters.getFolders.withArgs(13).returns([ + context.getters.getMailboxes.withArgs(13).returns([ { - id: 'INBOX', - accountId: 13, + name: 'INBOX', + databaseId: 11, specialRole: 'inbox', }, { - id: 'Drafts', - accountId: 13, + name: 'Drafts', + databaseId: 12, specialRole: 'draft', }, ]) - context.getters.getFolders.withArgs(26).returns([ + context.getters.getMailboxes.withArgs(26).returns([ { - id: 'INBOX', + name: 'INBOX', + databaseId: 21, accountId: 26, specialRole: 'inbox', }, { - id: 'Drafts', + name: 'Drafts', + databaseId: 22, accountId: 26, specialRole: 'draft', }, ]) context.getters.getEnvelopes - .withArgs(UNIFIED_ACCOUNT_ID, UNIFIED_INBOX_ID, undefined) + .withArgs(UNIFIED_INBOX_ID, undefined) .returns( orderBy( prop('dateInt'), 'desc', - page1.map(mockEnvelope(13, 'INBOX')).concat(page2.map(mockEnvelope(26, 'INBOX'))) + page1.map(mockEnvelope(11)).concat(page2.map(mockEnvelope(12))) ) ) - context.getters.getEnvelopes.withArgs(13, 'INBOX', undefined).returns(msgs1.map(mockEnvelope(13, 'INBOX'))) - context.getters.getEnvelopes.withArgs(26, 'INBOX', undefined).returns(msgs2.map(mockEnvelope(26, 'INBOX'))) + context.getters.getEnvelopes.withArgs(11, undefined).returns(msgs1.map(mockEnvelope(11))) + context.getters.getEnvelopes.withArgs(21, undefined).returns(msgs2.map(mockEnvelope(21))) await actions.fetchNextEnvelopePage(context, { - accountId: UNIFIED_ACCOUNT_ID, - folderId: UNIFIED_INBOX_ID, + mailboxId: UNIFIED_INBOX_ID, }) expect(context.dispatch).have.been.calledTwice expect(context.dispatch).have.been.calledWith('fetchNextEnvelopePage', { - accountId: 26, - folderId: 'INBOX', + mailboxId: 21, query: undefined, }) expect(context.dispatch).have.been.calledWith('fetchNextEnvelopePage', { - accountId: UNIFIED_ACCOUNT_ID, - folderId: UNIFIED_INBOX_ID, + mailboxId: UNIFIED_INBOX_ID, query: undefined, + rec: false, }) }) @@ -328,41 +317,41 @@ describe('Vuex store actions', () => { it('fetches the inbox first', async() => { context.getters.accounts.push({ id: 13, - accountId: 13, }) context.getters.accounts.push({ id: 26, - accountId: 26, }) - context.getters.getFolder.withArgs(UNIFIED_ACCOUNT_ID, UNIFIED_INBOX_ID).returns({ + context.getters.getMailbox.withArgs(UNIFIED_INBOX_ID).returns({ isUnified: true, specialRole: 'inbox', accountId: UNIFIED_ACCOUNT_ID, id: UNIFIED_INBOX_ID, }) - context.getters.getFolders.withArgs(13).returns([ + context.getters.getMailboxes.withArgs(13).returns([ { - id: 'INBOX', - accountId: 13, + name: 'INBOX', + databaseId: 11, specialRole: 'inbox', envelopeLists: {}, }, { - id: 'Drafts', - accountId: 13, + name: 'Drafts', + databaseId: 12, specialRole: 'draft', envelopeLists: {}, }, ]) - context.getters.getFolders.withArgs(26).returns([ + context.getters.getMailboxes.withArgs(26).returns([ { - id: 'INBOX', + name: 'INBOX', + databaseId: 21, accountId: 26, specialRole: 'inbox', envelopeLists: {}, }, { - id: 'Drafts', + name: 'Drafts', + databaseId: 22, accountId: 26, specialRole: 'draft', envelopeLists: {}, @@ -373,20 +362,16 @@ describe('Vuex store actions', () => { expect(context.dispatch).have.callCount(4) // 2 fetch + 2 sync expect(context.dispatch).have.been.calledWith('fetchEnvelopes', { - accountId: 13, - folderId: 'INBOX', + mailboxId: 11, }) expect(context.dispatch).have.been.calledWith('syncEnvelopes', { - accountId: 13, - folderId: 'INBOX', + mailboxId: 11, }) expect(context.dispatch).have.been.calledWith('fetchEnvelopes', { - accountId: 26, - folderId: 'INBOX', + mailboxId: 21, }) expect(context.dispatch).have.been.calledWith('syncEnvelopes', { - accountId: 26, - folderId: 'INBOX', + mailboxId: 21, }) // We can't detect new messages here expect(NotificationService.showNewMessagesNotification).not.have.been.called @@ -395,39 +380,41 @@ describe('Vuex store actions', () => { it('syncs each individual mailbox', async() => { context.getters.accounts.push({ id: 13, - accountId: 13, }) context.getters.accounts.push({ id: 26, - accountId: 26, }) - context.getters.getFolder.withArgs(UNIFIED_ACCOUNT_ID, UNIFIED_INBOX_ID).returns({ + context.getters.getMailbox.withArgs(UNIFIED_INBOX_ID).returns({ isUnified: true, specialRole: 'inbox', accountId: UNIFIED_ACCOUNT_ID, id: UNIFIED_INBOX_ID, + envelopeLists: { + '': [], + }, }) - context.getters.getFolders.withArgs(13).returns([ + context.getters.getMailboxes.withArgs(13).returns([ { - id: 'INBOX', - accountId: 13, + name: 'INBOX', + databaseId: 11, specialRole: 'inbox', envelopeLists: { '': [], }, }, { - id: 'Drafts', - accountId: 13, + name: 'Drafts', + databaseId: 12, specialRole: 'draft', envelopeLists: { '': [], }, }, ]) - context.getters.getFolders.withArgs(26).returns([ + context.getters.getMailboxes.withArgs(26).returns([ { - id: 'INBOX', + name: 'INBOX', + databaseId: 21, accountId: 26, specialRole: 'inbox', envelopeLists: { @@ -435,7 +422,8 @@ describe('Vuex store actions', () => { }, }, { - id: 'Drafts', + name: 'Drafts', + databaseId: 22, accountId: 26, specialRole: 'draft', envelopeLists: { @@ -445,21 +433,18 @@ describe('Vuex store actions', () => { ]) context.dispatch .withArgs('syncEnvelopes', { - accountId: 13, - folderId: 'INBOX', + mailboxId: 11, }) .returns(Promise.resolve([{ id: 123 }, { id: 321 }])) await actions.syncInboxes(context) - expect(context.dispatch).have.been.calledTwice + //expect(context.dispatch).have.been expect(context.dispatch).have.been.calledWith('syncEnvelopes', { - accountId: 13, - folderId: 'INBOX', + mailboxId: 11, }) expect(context.dispatch).have.been.calledWith('syncEnvelopes', { - accountId: 26, - folderId: 'INBOX', + mailboxId: 21, }) // Here we expect notifications expect(NotificationService.showNewMessagesNotification).have.been.called diff --git a/src/tests/unit/store/getters.spec.js b/src/tests/unit/store/getters.spec.js index fe5a3f962..b6e438f81 100644 --- a/src/tests/unit/store/getters.spec.js +++ b/src/tests/unit/store/getters.spec.js @@ -33,7 +33,7 @@ describe('Vuex store getters', () => { state = { accountList: [], accounts: {}, - folders: {}, + mailboxes: {}, envelopes: {}, messages: {}, } @@ -68,5 +68,4 @@ describe('Vuex store getters', () => { accountId: 13, }) }) - it('gets account folders', () => {}) }) diff --git a/src/tests/unit/store/mutations.spec.js b/src/tests/unit/store/mutations.spec.js index e2131064d..6099c9cac 100644 --- a/src/tests/unit/store/mutations.spec.js +++ b/src/tests/unit/store/mutations.spec.js @@ -22,10 +22,8 @@ import mutations from '../../../store/mutations' import { PRIORITY_INBOX_ID, - PRIORITY_INBOX_UID, UNIFIED_ACCOUNT_ID, UNIFIED_INBOX_ID, - UNIFIED_INBOX_UID, } from '../../../store/constants' describe('Vuex store mutations', () => { @@ -35,28 +33,27 @@ describe('Vuex store mutations', () => { [UNIFIED_ACCOUNT_ID]: { accountId: UNIFIED_ACCOUNT_ID, id: UNIFIED_ACCOUNT_ID, - folders: [], + mailboxes: [], }, }, envelopes: {}, - folders: { - '13-INBOX': { - id: 'INBOX', + mailboxes: { + 27: { + name: 'INBOX', + accountId: 13, envelopeLists: {}, }, }, } mutations.addEnvelope(state, { - accountId: 13, - folderId: 'INBOX', query: undefined, envelope: { - accountId: 13, - folderId: 'INBOX', + mailboxId: 27, + databaseId: 12345, id: 123, subject: 'henlo', - uid: '13-INBOX-123', + uid: 321, }, }) @@ -65,23 +62,25 @@ describe('Vuex store mutations', () => { [UNIFIED_ACCOUNT_ID]: { accountId: UNIFIED_ACCOUNT_ID, id: UNIFIED_ACCOUNT_ID, - folders: [], + mailboxes: [], }, }, envelopes: { - '13-INBOX-123': { + 12345: { accountId: 13, - folderId: 'INBOX', - uid: '13-INBOX-123', + mailboxId: 27, + databaseId: 12345, + uid: 321, id: 123, subject: 'henlo', }, }, - folders: { - '13-INBOX': { - id: 'INBOX', + mailboxes: { + 27: { + name: 'INBOX', + accountId: 13, envelopeLists: { - '': ['13-INBOX-123'], + '': [12345], }, }, }, @@ -94,17 +93,19 @@ describe('Vuex store mutations', () => { [UNIFIED_ACCOUNT_ID]: { accountId: UNIFIED_ACCOUNT_ID, id: UNIFIED_ACCOUNT_ID, - folders: [UNIFIED_INBOX_UID], + mailboxes: [UNIFIED_INBOX_ID], }, }, envelopes: {}, - folders: { - '13-INBOX': { - id: 'INBOX', + mailboxes: { + 27: { + name: 'INBOX', + databaseId: 27, + accountId: 2, envelopeLists: {}, specialRole: 'inbox', }, - [UNIFIED_INBOX_UID]: { + [UNIFIED_INBOX_ID]: { specialRole: 'inbox', envelopeLists: {}, }, @@ -112,15 +113,12 @@ describe('Vuex store mutations', () => { } mutations.addEnvelope(state, { - accountId: 13, - folderId: 'INBOX', query: undefined, envelope: { - accountId: 13, - folderId: 'INBOX', - id: 123, + mailboxId: 27, + databaseId: 12345, subject: 'henlo', - uid: '13-INBOX-123', + uid: 321, }, }) @@ -129,30 +127,32 @@ describe('Vuex store mutations', () => { [UNIFIED_ACCOUNT_ID]: { accountId: UNIFIED_ACCOUNT_ID, id: UNIFIED_ACCOUNT_ID, - folders: [UNIFIED_INBOX_UID], + mailboxes: [UNIFIED_INBOX_ID], }, }, envelopes: { - '13-INBOX-123': { - accountId: 13, - folderId: 'INBOX', - uid: '13-INBOX-123', - id: 123, + 12345: { + databaseId: 12345, + mailboxId: 27, + accountId: 2, + uid: 321, subject: 'henlo', }, }, - folders: { - '13-INBOX': { - id: 'INBOX', + mailboxes: { + 27: { + name: 'INBOX', + databaseId: 27, + accountId: 2, specialRole: 'inbox', envelopeLists: { - '': ['13-INBOX-123'], + '': [12345], }, }, - [UNIFIED_INBOX_UID]: { + [UNIFIED_INBOX_ID]: { specialRole: 'inbox', envelopeLists: { - '': ['13-INBOX-123'], + '': [12345], }, }, }, @@ -165,49 +165,46 @@ describe('Vuex store mutations', () => { [UNIFIED_ACCOUNT_ID]: { accountId: UNIFIED_ACCOUNT_ID, id: UNIFIED_ACCOUNT_ID, - folders: [UNIFIED_INBOX_UID, PRIORITY_INBOX_UID], + mailboxes: [UNIFIED_INBOX_ID, PRIORITY_INBOX_ID], }, }, envelopes: { - '13-INBOX-123': { - accountId: 13, - folderId: 'INBOX', + 12345: { + mailboxId: 27, id: 123, - uid: '13-INBOX-123', + uid: 12345, }, }, - folders: { - '13-INBOX': { + mailboxes: { + 27: { id: 'INBOX', specialUse: ['inbox'], specialRole: 'inbox', envelopeLists: { - '': ['13-INBOX-123'], + '': [12345], }, }, - [UNIFIED_INBOX_UID]: { + [UNIFIED_INBOX_ID]: { id: UNIFIED_INBOX_ID, specialUse: ['inbox'], specialRole: 'inbox', envelopeLists: { - '': ['13-INBOX-123'], + '': [12345], }, }, - [PRIORITY_INBOX_UID]: { + [PRIORITY_INBOX_ID]: { id: PRIORITY_INBOX_ID, specialUse: ['inbox'], specialRole: 'inbox', envelopeLists: { - 'is:starred not:important': ['13-INBOX-123'], + 'is:starred not:important': [12345], }, }, }, } mutations.removeEnvelope(state, { - accountId: 13, - folderId: 'INBOX', - id: 123, + id: 12345, }) expect(state).to.deep.equal({ @@ -215,19 +212,18 @@ describe('Vuex store mutations', () => { [UNIFIED_ACCOUNT_ID]: { accountId: UNIFIED_ACCOUNT_ID, id: UNIFIED_ACCOUNT_ID, - folders: [UNIFIED_INBOX_UID, PRIORITY_INBOX_UID], + mailboxes: [UNIFIED_INBOX_ID, PRIORITY_INBOX_ID], }, }, envelopes: { - '13-INBOX-123': { - accountId: 13, - folderId: 'INBOX', + 12345: { + mailboxId: 27, id: 123, - uid: '13-INBOX-123', + uid: 12345, }, }, - folders: { - '13-INBOX': { + mailboxes: { + 27: { id: 'INBOX', specialUse: ['inbox'], specialRole: 'inbox', @@ -235,7 +231,7 @@ describe('Vuex store mutations', () => { '': [], }, }, - [UNIFIED_INBOX_UID]: { + [UNIFIED_INBOX_ID]: { id: UNIFIED_INBOX_ID, specialUse: ['inbox'], specialRole: 'inbox', @@ -243,7 +239,7 @@ describe('Vuex store mutations', () => { '': [], }, }, - [PRIORITY_INBOX_UID]: { + [PRIORITY_INBOX_ID]: { id: PRIORITY_INBOX_ID, specialUse: ['inbox'], specialRole: 'inbox', @@ -255,27 +251,27 @@ describe('Vuex store mutations', () => { }) }) - it('removes a folder', () => { + it('removes a mailbox', () => { const state = { accounts: { 13: { accountId: 13, id: 13, - folders: ['13-INBOX'], + mailboxes: [27], }, }, - folders: { - '13-INBOX': { + mailboxes: { + 27: { id: 'INBOX', + accountId: 13, specialUse: ['inbox'], specialRole: 'inbox', }, }, } - mutations.removeFolder(state, { - accountId: 13, - folderId: 'INBOX', + mutations.removeMailbox(state, { + id: 27, }) expect(state).to.deep.equal({ @@ -283,41 +279,42 @@ describe('Vuex store mutations', () => { 13: { accountId: 13, id: 13, - folders: [], + mailboxes: [], }, }, - folders: {}, + mailboxes: {}, }) }) - it('removes a subfolder', () => { + it('removes a sub-mailbox', () => { const state = { accounts: { 13: { accountId: 13, id: 13, - folders: ['13-INBOX'], + mailboxes: [27], }, }, - folders: { - '13-INBOX': { + mailboxes: { + 27: { id: 'INBOX', + accountId: 13, specialUse: ['inbox'], specialRole: 'inbox', - folders: ['13-INBOX.sub'], + mailboxes: [28], }, - '13-INBOX.sub': { + 28: { id: 'INBOX.sub', + accountId: 13, specialUse: ['inbox'], specialRole: 'inbox', - folders: [], + mailboxes: [], }, }, } - mutations.removeFolder(state, { - accountId: 13, - folderId: 'INBOX.sub', + mutations.removeMailbox(state, { + id: 28, }) expect(state).to.deep.equal({ @@ -325,15 +322,16 @@ describe('Vuex store mutations', () => { 13: { accountId: 13, id: 13, - folders: ['13-INBOX'], + mailboxes: [27], }, }, - folders: { - '13-INBOX': { + mailboxes: { + 27: { id: 'INBOX', + accountId: 13, specialUse: ['inbox'], specialRole: 'inbox', - folders: [], + mailboxes: [], }, }, }) diff --git a/src/tests/unit/store/normalization.spec.js b/src/tests/unit/store/normalization.spec.js deleted file mode 100644 index 29ea16bf0..000000000 --- a/src/tests/unit/store/normalization.spec.js +++ /dev/null @@ -1,43 +0,0 @@ -/* - * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at> - * - * @author 2020 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/>. - */ - -import { normalizedFolderId, normalizedMessageId } from '../../../store/normalization' - -describe('Vuex store normalization', () => { - it('creates a unique folder ID', () => { - const accountId = 13 - const folderId = 'INBOX' - - const id = normalizedFolderId(accountId, folderId) - - expect(id).to.equal('13-INBOX') - }) - - it('creates a unique message ID', () => { - const accountId = 13 - const folderId = 'INBOX' - const messageId = 123 - - const id = normalizedMessageId(accountId, folderId, messageId) - - expect(id).to.equal('13-INBOX-123') - }) -}) diff --git a/src/tests/unit/util/EnvelopeUidParser.spec.js b/src/tests/unit/util/EnvelopeUidParser.spec.js deleted file mode 100644 index d35fbe5fe..000000000 --- a/src/tests/unit/util/EnvelopeUidParser.spec.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at> - * - * @author 2019 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/>. - */ - -import { parseUuid } from '../../../util/EnvelopeUidParser' - -describe('EnvelopeUidParser', () => { - it('parses a simple UID', () => { - const uuid = '1-SU5CT1g=-123' - - const parsed = parseUuid(uuid) - - expect(parsed.accountId).to.equal(1) - expect(parsed.folderId).to.equal('SU5CT1g=') - expect(parsed.uid).to.equal(123) - }) - - it('parses the default account UID', () => { - const uuid = '-2-SU5CT1g=-123' - - const parsed = parseUuid(uuid) - - expect(parsed.accountId).to.equal(-2) - expect(parsed.folderId).to.equal('SU5CT1g=') - expect(parsed.uid).to.equal(123) - }) -}) diff --git a/src/util/EnvelopeUidParser.js b/src/util/EnvelopeUidParser.js deleted file mode 100644 index 29741f454..000000000 --- a/src/util/EnvelopeUidParser.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at> - * - * @author 2018 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/>. - */ - -const reg = /^(-?\d+)-((?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4}))-(\d+)$/ - -export const parseUuid = (str) => { - const match = reg.exec(str) - - if (match === null) { - console.error(`UID ${str} is invalid`) - throw new Error(`UID ${str} is invalid`) - } - - return { - accountId: parseInt(match[1], 10), - folderId: match[2], - uid: parseInt(match[3], 10), - } -} diff --git a/src/views/Dashboard.vue b/src/views/Dashboard.vue index d4516ede1..e6afd2c2d 100644 --- a/src/views/Dashboard.vue +++ b/src/views/Dashboard.vue @@ -50,14 +50,17 @@ </template> <script> -import { fetchEnvelopes } from '../service/MessageService' import { loadState } from '@nextcloud/initial-state' import { generateUrl, imagePath } from '@nextcloud/router' -import Avatar from '../components/Avatar' import { DashboardWidget, DashboardWidgetItem } from '@nextcloud/vue-dashboard' import orderBy from 'lodash/fp/orderBy' import prop from 'lodash/fp/prop' +import Avatar from '../components/Avatar' +import { fetchEnvelopes } from '../service/MessageService' +import logger from '../logger' +import { fetchAll } from '../service/MailboxService' + const accounts = loadState('mail', 'mail-accounts') const orderByDateInt = orderBy(prop('dateInt'), 'desc') @@ -72,15 +75,13 @@ export default { return { messages: [], accounts, + loading: true, fetchedAccounts: 0, emptyImage: imagePath('mail', 'newsletter.svg'), accountSetupUrl: generateUrl('/apps/mail/#/setup'), } }, computed: { - loading() { - return this.fetchedAccounts < this.accounts.length - }, importantMessages() { if (!this.messages) { return [] @@ -89,9 +90,8 @@ export default { }, getWidgetItem() { return (item) => { - const { uid, accountId, mailbox } = item return { - targetUrl: generateUrl(`/apps/mail/#/accounts/${accountId}/folders/${mailbox}/message/${accountId}-${mailbox}-${uid}`), + targetUrl: generateUrl(`/apps/mail/box/priority/thread/${item.databaseId}`), mainText: item.from ? item.from[0].label : '', subText: item.subject, message: item, @@ -99,15 +99,33 @@ export default { } }, }, - mounted() { - // TODO: check if there is a more sane way to query this and if other mailboxes should be fetched as well - this.accounts.forEach((account) => { - fetchEnvelopes(account.accountId, btoa('INBOX'), 'is:important', undefined, 10).then((messages) => { - messages = messages.map((message) => ({ ...message, accountId: account.accountId, mailbox: btoa('INBOX') })) - this.messages = this.messages !== null ? [...this.messages, ...messages] : messages - this.fetchedAccounts++ + async mounted() { + const accountInboxes = await Promise.all(this.accounts.map(async(account) => { + logger.debug('account', { + account, }) + + const mailboxes = await fetchAll(account.accountId) + + logger.debug('mailboxes', { + mailboxes, + }) + + return mailboxes.filter(mb => mb.specialRole === 'inbox') + })) + const inboxes = accountInboxes.flat() + + logger.debug(`found ${inboxes.length} inboxes`, { + inboxes, }) + + await Promise.all(inboxes.map(async(mailbox) => { + const messages = await fetchEnvelopes(mailbox.databaseId, 'is:important', undefined, 10) + this.messages = this.messages !== null ? [...this.messages, ...messages] : messages + this.fetchedAccounts++ + })) + + this.loading = false }, } </script> diff --git a/src/views/Home.vue b/src/views/Home.vue index f5b30c047..37292b331 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -1,7 +1,7 @@ <template> <Content v-shortkey.once="['c']" app-name="mail" @shortkey.native="onNewMessage"> <Navigation /> - <MailboxMessage v-if="activeAccount" :account="activeAccount" :folder="activeFolder" /> + <MailboxMessage v-if="activeAccount" :account="activeAccount" :mailbox="activeMailbox" /> </Content> </template> @@ -23,10 +23,10 @@ export default { mixins: [isMobile], computed: { activeAccount() { - return this.$store.getters.getAccount(this.$route.params.accountId) + return this.$store.getters.getAccount(this.activeMailbox?.accountId) }, - activeFolder() { - return this.$store.getters.getFolder(this.$route.params.accountId, this.$route.params.folderId) + activeMailbox() { + return this.$store.getters.getMailbox(this.$route.params.mailboxId) }, menu() { return this.buildMenu() @@ -36,13 +36,12 @@ export default { $route(to, from) { if ( from.name === 'message' - && to.name === 'folder' + && to.name === 'mailbox' && !this.isMobile - && Number.parseInt(from.params.accountId, 10) === Number.parseInt(to.params.accountId, 10) - && from.params.folderId === to.params.folderId + && from.params.mailboxId === to.params.mailboxId && from.params.filter === to.params.filter ) { - logger.warn("navigation from a message to just the folder. we don't want that, do we? let's go back", { + logger.warn("navigation from a message to just the mailbox. we don't want that, do we? let's go back", { to, from, }) @@ -56,16 +55,15 @@ export default { if (this.$route.name === 'home' && accounts.length > 1) { // Show first account const firstAccount = accounts[0] - // FIXME: this assumes that there's at least one folder - const firstFolder = this.$store.getters.getFolders(firstAccount.id)[0] + // FIXME: this assumes that there's at least one mailbox + const firstMailbox = this.$store.getters.getMailboxes(firstAccount.id)[0] - console.debug('loading first folder of first account', firstAccount.id, firstFolder.id) + console.debug('loading first mailbox of first account', firstAccount.id, firstMailbox.databaseId) this.$router.replace({ - name: 'folder', + name: 'mailbox', params: { - accountId: firstAccount.id, - folderId: firstFolder.id, + mailboxId: firstMailbox.databaseId, }, }) } else if (this.$route.name === 'home' && accounts.length === 1) { @@ -81,17 +79,16 @@ export default { // Show first account const firstAccount = accounts[0] - // FIXME: this assumes that there's at least one folder - const firstFolder = this.$store.getters.getFolders(firstAccount.id)[0] + // FIXME: this assumes that there's at least one mailbox + const firstMailbox = this.$store.getters.getMailboxes(firstAccount.id)[0] - console.debug('loading composer with first account and folder', firstAccount.id, firstFolder.id) + console.debug('loading composer with first account and mailbox', firstAccount.id, firstMailbox.id) this.$router.replace({ name: 'message', params: { - accountId: firstAccount.id, - folderId: firstFolder.id, - messageUuid: 'new', + mailboxId: firstMailbox.databaseId, + threadId: 'new', }, query: { to: this.$route.query.to, @@ -109,9 +106,8 @@ export default { this.$router.push({ name: 'message', params: { - accountId: this.$route.params.accountId, - folderId: this.$route.params.folderId, - messageUuid: 'new', + mailboxId: this.$route.params.mailboxId, + threadId: 'new', }, }) }, diff --git a/tests/Integration/Db/MailAccountTest.php b/tests/Integration/Db/MailAccountTest.php index bc4140010..595868754 100644 --- a/tests/Integration/Db/MailAccountTest.php +++ b/tests/Integration/Db/MailAccountTest.php @@ -48,6 +48,7 @@ class MailAccountTest extends TestCase { $a->setOrder(13); $this->assertEquals([ + 'id' => 12345, 'accountId' => 12345, 'name' => 'Peter Parker', 'emailAddress' => 'peter.parker@marvel.com', @@ -69,6 +70,7 @@ class MailAccountTest extends TestCase { public function testMailAccountConstruct() { $expected = [ + 'id' => 12345, 'accountId' => 12345, 'accountName' => 'Peter Parker', 'emailAddress' => 'peter.parker@marvel.com', diff --git a/tests/Integration/Framework/ImapTest.php b/tests/Integration/Framework/ImapTest.php index 7e9f93cd2..4b9288f72 100644 --- a/tests/Integration/Framework/ImapTest.php +++ b/tests/Integration/Framework/ImapTest.php @@ -123,7 +123,7 @@ trait ImapTest { * * @return int id of the new message */ - public function saveMessage($mailbox, SimpleMessage $message, MailAccount $account = null) { + public function saveMessage(string $mailbox, SimpleMessage $message, MailAccount $account = null) { $client = $this->getClient($account); $headers = [ @@ -201,7 +201,7 @@ trait ImapTest { * @param int $number * @param string $mailbox */ - public function assertMessageCount($number, $mailbox) { + public function assertMessageCount(int $number, string $mailbox) { $client = $this->getTestClient(); $query = new Horde_Imap_Client_Fetch_Query(); diff --git a/tests/Integration/MailboxSynchronizationTest.php b/tests/Integration/MailboxSynchronizationTest.php index fe54bdab7..c79f3e474 100644 --- a/tests/Integration/MailboxSynchronizationTest.php +++ b/tests/Integration/MailboxSynchronizationTest.php @@ -27,7 +27,7 @@ use Horde_Imap_Client; use OC; use OCA\Mail\Account; use OCA\Mail\Contracts\IMailManager; -use OCA\Mail\Controller\FoldersController; +use OCA\Mail\Controller\MailboxesController; use OCA\Mail\Service\AccountService; use OCA\Mail\Service\Sync\SyncService; use OCA\Mail\Tests\Integration\Framework\ImapTest; @@ -37,13 +37,13 @@ class MailboxSynchronizationTest extends TestCase { use ImapTest, ImapTestAccount; - /** @var FoldersController */ + /** @var MailboxesController */ private $foldersController; protected function setUp(): void { parent::setUp(); - $this->foldersController = new FoldersController( + $this->foldersController = new MailboxesController( 'mail', OC::$server->getRequest(), OC::$server->query(AccountService::class), @@ -55,21 +55,28 @@ class MailboxSynchronizationTest extends TestCase { public function testSyncEmptyMailbox() { $account = $this->createTestAccount(); - $mailbox = 'INBOX'; + /** @var IMailManager $mailManager */ + $mailManager = OC::$server->query(IMailManager::class); + $mailBoxes = $mailManager->getMailboxes(new Account($account)); + $inbox = null; + foreach ($mailBoxes as $mailBox) { + if ($mailBox->getName() === 'INBOX') { + $inbox = $mailBox; + break; + } + } /** @var SyncService $syncService */ $syncService = OC::$server->query(SyncService::class); $syncService->syncMailbox( new Account($account), - $mailbox, + $inbox, Horde_Imap_Client::SYNC_NEWMSGSUIDS | Horde_Imap_Client::SYNC_FLAGSUIDS | Horde_Imap_Client::SYNC_VANISHEDUIDS, [], false ); - $mailbox = 'INBOX'; $jsonResponse = $this->foldersController->sync( - $account->getId(), - base64_encode($mailbox), + $inbox->getId(), [] ); @@ -87,10 +94,19 @@ class MailboxSynchronizationTest extends TestCase { $account = $this->createTestAccount(); /** @var SyncService $syncService */ $syncService = OC::$server->query(SyncService::class); - $mailbox = 'INBOX'; + /** @var IMailManager $mailManager */ + $mailManager = OC::$server->query(IMailManager::class); + $mailBoxes = $mailManager->getMailboxes(new Account($account)); + $inbox = null; + foreach ($mailBoxes as $mailBox) { + if ($mailBox->getName() === 'INBOX') { + $inbox = $mailBox; + break; + } + } $syncService->syncMailbox( new Account($account), - $mailbox, + $inbox, Horde_Imap_Client::SYNC_NEWMSGSUIDS | Horde_Imap_Client::SYNC_FLAGSUIDS | Horde_Imap_Client::SYNC_VANISHEDUIDS, [], false @@ -100,11 +116,10 @@ class MailboxSynchronizationTest extends TestCase { ->from('ralph@buffington@domain.tld') ->to('user@domain.tld') ->finish(); - $newUid = $this->saveMessage($mailbox, $message, $account); + $newUid = $this->saveMessage($inbox->getName(), $message, $account); $jsonResponse = $this->foldersController->sync( - $account->getId(), - base64_encode($mailbox), + $inbox->getId(), [] ); $syncJson = $jsonResponse->getData()->jsonSerialize(); @@ -124,19 +139,29 @@ class MailboxSynchronizationTest extends TestCase { ->from('ralph@buffington@domain.tld') ->to('user@domain.tld') ->finish(); - $id = $this->saveMessage($mailbox, $message, $account); + $uid = $this->saveMessage($mailbox, $message, $account); + /** @var IMailManager $mailManager */ + $mailManager = OC::$server->query(IMailManager::class); + $mailBoxes = $mailManager->getMailboxes(new Account($account)); + $inbox = null; + foreach ($mailBoxes as $mailBox) { + if ($mailBox->getName() === 'INBOX') { + $inbox = $mailBox; + break; + } + } $syncService->syncMailbox( new Account($account), - $mailbox, + $inbox, Horde_Imap_Client::SYNC_NEWMSGSUIDS | Horde_Imap_Client::SYNC_FLAGSUIDS | Horde_Imap_Client::SYNC_VANISHEDUIDS, [], false ); - $this->flagMessage($mailbox, $id, $account); + $this->flagMessage($mailbox, $uid, $account); + $id = $mailManager->getMessageIdForUid($inbox, $uid); $jsonResponse = $this->foldersController->sync( - $account->getId(), - base64_encode($mailbox), + $inbox->getId(), [ $id ]); @@ -156,11 +181,21 @@ class MailboxSynchronizationTest extends TestCase { ->to('user@domain.tld') ->finish(); $id = $this->saveMessage($mailbox, $message, $account); + /** @var IMailManager $mailManager */ + $mailManager = OC::$server->query(IMailManager::class); + $mailBoxes = $mailManager->getMailboxes(new Account($account)); + $inbox = null; + foreach ($mailBoxes as $mailBox) { + if ($mailBox->getName() === 'INBOX') { + $inbox = $mailBox; + break; + } + } /** @var SyncService $syncService */ $syncService = OC::$server->query(SyncService::class); $syncService->syncMailbox( new Account($account), - $mailbox, + $inbox, Horde_Imap_Client::SYNC_NEWMSGSUIDS | Horde_Imap_Client::SYNC_FLAGSUIDS | Horde_Imap_Client::SYNC_VANISHEDUIDS, [], false @@ -168,8 +203,7 @@ class MailboxSynchronizationTest extends TestCase { $this->deleteMessage($mailbox, $id, $account); $jsonResponse = $this->foldersController->sync( - $account->getId(), - base64_encode($mailbox), + $inbox->getId(), [ $id ]); diff --git a/tests/Integration/Service/MailTransmissionIntegrationTest.php b/tests/Integration/Service/MailTransmissionIntegrationTest.php index fcfed63f8..95813954b 100644 --- a/tests/Integration/Service/MailTransmissionIntegrationTest.php +++ b/tests/Integration/Service/MailTransmissionIntegrationTest.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + /** * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @@ -29,6 +31,7 @@ use OCA\Mail\Contracts\IMailTransmission; use OCA\Mail\Db\MailAccount; use OCA\Mail\Db\MailAccountMapper; use OCA\Mail\Db\MailboxMapper; +use OCA\Mail\Db\Message; use OCA\Mail\IMAP\IMAPClientFactory; use OCA\Mail\IMAP\MessageMapper; use OCA\Mail\Model\NewMessageData; @@ -169,7 +172,7 @@ class MailTransmissionIntegrationTest extends TestCase { $message = NewMessageData::fromRequest($this->account, 'recipient@domain.com', null, null, '', 'hello there', []); $reply = new RepliedMessageData($this->account, $inbox, $originalUID); - $uid = $this->transmission->sendMessage($message, $reply); + $this->transmission->sendMessage($message, $reply); $this->assertMailboxExists('Sent'); $this->assertMessageCount(1, 'Sent'); @@ -194,7 +197,7 @@ class MailTransmissionIntegrationTest extends TestCase { public function testSaveNewDraft() { $message = NewMessageData::fromRequest($this->account, 'recipient@domain.com', null, null, 'greetings', 'hello there', [], false); - $uid = $this->transmission->saveDraft($message); + [,,$uid] = $this->transmission->saveDraft($message); // There should be a new mailbox … $this->assertMailboxExists('Drafts'); // … and it should have exactly one message … @@ -205,9 +208,11 @@ class MailTransmissionIntegrationTest extends TestCase { public function testReplaceDraft() { $message1 = NewMessageData::fromRequest($this->account, 'recipient@domain.com', null, null, 'greetings', 'hello t', []); - $uid = $this->transmission->saveDraft($message1); + [,,$uid] = $this->transmission->saveDraft($message1); $message2 = NewMessageData::fromRequest($this->account, 'recipient@domain.com', null, null, 'greetings', 'hello there', []); - $this->transmission->saveDraft($message2, $uid); + $previous = new Message(); + $previous->setUid($uid); + $this->transmission->saveDraft($message2, $previous); $this->assertMessageCount(1, 'Drafts'); } diff --git a/tests/Unit/Controller/AccountsControllerTest.php b/tests/Unit/Controller/AccountsControllerTest.php index 106444440..bfd50a799 100644 --- a/tests/Unit/Controller/AccountsControllerTest.php +++ b/tests/Unit/Controller/AccountsControllerTest.php @@ -31,6 +31,7 @@ use OCA\Mail\Account; use OCA\Mail\Contracts\IMailManager; use OCA\Mail\Contracts\IMailTransmission; use OCA\Mail\Controller\AccountsController; +use OCA\Mail\Db\Mailbox; use OCA\Mail\Exception\ClientException; use OCA\Mail\Model\NewMessageData; use OCA\Mail\Model\RepliedMessageData; @@ -39,6 +40,7 @@ use OCA\Mail\Service\AliasesService; use OCA\Mail\Service\AutoConfig\AutoConfig; use OCA\Mail\Service\SetupService; use OCA\Mail\Service\GroupsIntegration; +use OCA\Mail\Service\Sync\SyncService; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Http; use OCP\AppFramework\Http\JSONResponse; @@ -98,6 +100,9 @@ class AccountsControllerTest extends TestCase { /** @var IMailManager|MockObject */ private $mailManager; + /** @var SyncService|MockObject */ + private $syncService; + protected function setUp(): void { parent::setUp(); @@ -117,6 +122,7 @@ class AccountsControllerTest extends TestCase { $this->transmission = $this->createMock(IMailTransmission::class); $this->setupService = $this->createMock(SetupService::class); $this->mailManager = $this->createMock(IMailManager::class); + $this->syncService = $this->createMock(SyncService::class); $this->controller = new AccountsController( $this->appName, @@ -129,7 +135,8 @@ class AccountsControllerTest extends TestCase { $this->aliasesService, $this->transmission, $this->setupService, - $this->mailManager + $this->mailManager, + $this->syncService ); $this->account = $this->createMock(Account::class); $this->accountId = 123; @@ -461,7 +468,7 @@ class AccountsControllerTest extends TestCase { $this->assertEquals($expected, $resp); } - public function draftDataProvider() { + public function draftDataProvider(): array { return [ [false, false], [true, true], @@ -470,28 +477,33 @@ class AccountsControllerTest extends TestCase { ]; } - public function testDraft() { + public function testDraft(): void { $subject = 'Hello'; $body = 'Hi!'; $to = 'user1@example.com'; $cc = '"user2" <user2@example.com>, user3@example.com'; $bcc = 'user4@example.com'; - $uid = 123; - $newUID = 124; - + $id = 123; + $newId = 1245; + $newUid = 124; + $account = $this->createMock(Account::class); + $mailbox = new Mailbox(); $this->accountService->expects($this->once()) ->method('find') ->with($this->userId, $this->accountId) ->will($this->returnValue($this->account)); $this->transmission->expects($this->once()) ->method('saveDraft') - ->willReturn($newUID); + ->willReturn([$account, $mailbox, $newUid]); + $this->mailManager->expects($this->once()) + ->method('getMessageIdForUid') + ->willReturn($newId); + + $actual = $this->controller->draft($this->accountId, $subject, $body, $to, $cc, $bcc, true, $id); $expected = new JSONResponse([ - 'uid' => $newUID, + 'id' => $newId, ]); - $actual = $this->controller->draft($this->accountId, $subject, $body, $to, $cc, $bcc, true, $uid); - $this->assertEquals($expected, $actual); } } diff --git a/tests/Unit/Controller/FoldersControllerTest.php b/tests/Unit/Controller/MailboxesControllerTest.php index 3ef4fd653..30499a6fd 100644 --- a/tests/Unit/Controller/FoldersControllerTest.php +++ b/tests/Unit/Controller/MailboxesControllerTest.php @@ -23,12 +23,12 @@ declare(strict_types=1); namespace OCA\Mail\Tests\Unit\Controller; +use OCA\Mail\Db\Mailbox; use OCA\Mail\Service\Sync\SyncService; use PHPUnit\Framework\MockObject\MockObject; -use function base64_encode; use OCA\Mail\Account; use OCA\Mail\Contracts\IMailManager; -use OCA\Mail\Controller\FoldersController; +use OCA\Mail\Controller\MailboxesController; use OCA\Mail\Exception\NotImplemented; use OCA\Mail\Folder; use OCA\Mail\IMAP\FolderStats; @@ -37,7 +37,7 @@ use ChristophWurst\Nextcloud\Testing\TestCase; use OCP\AppFramework\Http\JSONResponse; use OCP\IRequest; -class FoldersControllerTest extends TestCase { +class MailboxesControllerTest extends TestCase { /** @var string */ private $appName = 'mail'; @@ -54,7 +54,7 @@ class FoldersControllerTest extends TestCase { /** @var IMailManager|MockObject */ private $mailManager; - /** @var FoldersController */ + /** @var MailboxesController */ private $controller; /** @var SyncService|MockObject */ @@ -67,7 +67,7 @@ class FoldersControllerTest extends TestCase { $this->accountService = $this->createMock(AccountService::class); $this->mailManager = $this->createMock(IMailManager::class); $this->syncService = $this->createMock(SyncService::class); - $this->controller = new FoldersController( + $this->controller = new MailboxesController( $this->appName, $this->request, $this->accountService, @@ -103,7 +103,7 @@ class FoldersControllerTest extends TestCase { $expected = new JSONResponse([ 'id' => 28, 'email' => 'user@example.com', - 'folders' => [ + 'mailboxes' => [ $folder, ], 'delimiter' => '.', @@ -119,20 +119,20 @@ class FoldersControllerTest extends TestCase { public function testCreate() { $account = $this->createMock(Account::class); - $folder = $this->createMock(Folder::class); + $mailbox = new Mailbox(); $accountId = 28; $this->accountService->expects($this->once()) ->method('find') ->with($this->equalTo($this->userId), $this->equalTo($accountId)) ->willReturn($account); $this->mailManager->expects($this->once()) - ->method('createFolder') + ->method('createMailbox') ->with($this->equalTo($account), $this->equalTo('new')) - ->willReturn($folder); + ->willReturn($mailbox); $response = $this->controller->create($accountId, 'new'); - $expected = new JSONResponse($folder); + $expected = new JSONResponse($mailbox); $this->assertEquals($expected, $response); } @@ -140,16 +140,22 @@ class FoldersControllerTest extends TestCase { $account = $this->createMock(Account::class); $stats = $this->createMock(FolderStats::class); $accountId = 28; + $mailbox = new Mailbox(); + $mailbox->setAccountId($accountId); + $this->mailManager->expects($this->once()) + ->method('getMailbox') + ->with('john', 13) + ->willReturn($mailbox); $this->accountService->expects($this->once()) ->method('find') ->with($this->equalTo($this->userId), $this->equalTo($accountId)) ->willReturn($account); $this->mailManager->expects($this->once()) - ->method('getFolderStats') - ->with($this->equalTo($account), $this->equalTo('INBOX')) + ->method('getMailboxStats') + ->with($this->equalTo($account), $mailbox) ->willReturn($stats); - $response = $this->controller->stats($accountId, base64_encode('INBOX')); + $response = $this->controller->stats(13); $expected = new JSONResponse($stats); $this->assertEquals($expected, $response); diff --git a/tests/Unit/Controller/MessagesControllerTest.php b/tests/Unit/Controller/MessagesControllerTest.php index 4683f827c..6b4f724df 100644 --- a/tests/Unit/Controller/MessagesControllerTest.php +++ b/tests/Unit/Controller/MessagesControllerTest.php @@ -178,18 +178,35 @@ class MessagesControllerTest extends TestCase { public function testGetHtmlBody() { $accountId = 17; + $mailboxId = 13; $folderId = 'testfolder'; $messageId = 4321; - $message = $this->createMock(IMAPMessage::class); - + $this->account + ->method('getId') + ->willReturn($accountId); + $mailbox = new \OCA\Mail\Db\Mailbox(); + $message = new \OCA\Mail\Db\Message(); + $message->setMailboxId($mailboxId); + $message->setUid(123); + $mailbox->setAccountId($accountId); + $mailbox->setName($folderId); + $this->mailManager->expects($this->once()) + ->method('getMessage') + ->with($this->userId, $messageId) + ->willReturn($message); + $this->mailManager->expects($this->once()) + ->method('getMailbox') + ->with($this->userId, $mailboxId) + ->willReturn($mailbox); $this->accountService->expects($this->once()) ->method('find') ->with($this->equalTo($this->userId), $this->equalTo($accountId)) ->will($this->returnValue($this->account)); + $imapMessage = $this->createMock(IMAPMessage::class); $this->mailManager->expects($this->once()) - ->method('getMessage') - ->with($this->account, $folderId, $messageId, true) - ->willReturn($message); + ->method('getImapMessage') + ->with($this->account, $mailbox, 123, true) + ->willReturn($imapMessage); $expectedResponse = new HtmlResponse(''); $expectedResponse->cacheFor(3600); @@ -203,34 +220,46 @@ class MessagesControllerTest extends TestCase { $expectedResponse->setContentSecurityPolicy($policy); } - $actualResponse = $this->controller->getHtmlBody($accountId, - base64_encode($folderId), $messageId); + $actualResponse = $this->controller->getHtmlBody($messageId); $this->assertEquals($expectedResponse, $actualResponse); } public function testDownloadAttachment() { $accountId = 17; - $folderId = base64_encode('my folder'); - $messageId = 123; + $mailboxId = 987; + $id = 123; + $uid = 321; $attachmentId = 3; // Attachment data $contents = 'abcdef'; $name = 'cat.jpg'; $type = 'image/jpg'; - + $message = new \OCA\Mail\Db\Message(); + $message->setMailboxId($mailboxId); + $message->setUid($uid); + $mailbox = new \OCA\Mail\Db\Mailbox(); + $mailbox->setName('INBOX'); + $mailbox->setAccountId($accountId); + $this->mailManager->expects($this->once()) + ->method('getMessage') + ->with($this->userId, $id) + ->willReturn($message); + $this->mailManager->expects($this->once()) + ->method('getMailbox') + ->with($this->userId, $mailboxId) + ->willReturn($mailbox); $this->accountService->expects($this->once()) ->method('find') ->with($this->equalTo($this->userId), $this->equalTo($accountId)) ->will($this->returnValue($this->account)); $this->account->expects($this->once()) ->method('getMailbox') - ->with(base64_decode($folderId)) - ->will($this->returnValue($this->mailbox)); + ->willReturn($this->mailbox); $this->mailbox->expects($this->once()) ->method('getAttachment') - ->with($messageId, $attachmentId) + ->with($uid, $attachmentId) ->will($this->returnValue($this->attachment)); $this->attachment->expects($this->once()) ->method('getContents') @@ -243,30 +272,46 @@ class MessagesControllerTest extends TestCase { ->will($this->returnValue($type)); $expected = new AttachmentDownloadResponse($contents, $name, $type); - $response = $this->controller->downloadAttachment($accountId, $folderId, - $messageId, $attachmentId); + $response = $this->controller->downloadAttachment( + $id, + $attachmentId + ); $this->assertEquals($expected, $response); } public function testSaveSingleAttachment() { $accountId = 17; - $folderId = base64_encode('my folder'); - $messageId = 123; + $mailboxId = 987; + $id = 123; + $uid = 321; $attachmentId = '2.2'; $targetPath = 'Downloads'; - + $message = new \OCA\Mail\Db\Message(); + $message->setMailboxId($mailboxId); + $message->setUid($uid); + $mailbox = new \OCA\Mail\Db\Mailbox(); + $mailbox->setName('INBOX'); + $mailbox->setAccountId($accountId); + $this->mailManager->expects($this->once()) + ->method('getMessage') + ->with($this->userId, $id) + ->willReturn($message); + $this->mailManager->expects($this->once()) + ->method('getMailbox') + ->with($this->userId, $mailboxId) + ->willReturn($mailbox); $this->accountService->expects($this->once()) ->method('find') ->with($this->equalTo($this->userId), $this->equalTo($accountId)) ->will($this->returnValue($this->account)); $this->account->expects($this->once()) ->method('getMailbox') - ->with(base64_decode($folderId)) + ->with('INBOX') ->will($this->returnValue($this->mailbox)); $this->mailbox->expects($this->once()) ->method('getAttachment') - ->with($messageId, $attachmentId) + ->with($uid, $attachmentId) ->will($this->returnValue($this->attachment)); $this->attachment->expects($this->once()) ->method('getName') @@ -291,30 +336,47 @@ class MessagesControllerTest extends TestCase { ->will($this->returnValue('abcdefg')); $expected = new JSONResponse(); - $response = $this->controller->saveAttachment($accountId, $folderId, - $messageId, $attachmentId, $targetPath); + $response = $this->controller->saveAttachment( + $id, + $attachmentId, + $targetPath + ); $this->assertEquals($expected, $response); } public function testSaveAllAttachments() { $accountId = 17; - $folderId = base64_encode('my folder'); - $messageId = 123; + $mailboxId = 987; + $id = 123; + $uid = 321; $attachmentId = '0'; $targetPath = 'Downloads'; - + $message = new \OCA\Mail\Db\Message(); + $message->setMailboxId($mailboxId); + $message->setUid($uid); + $mailbox = new \OCA\Mail\Db\Mailbox(); + $mailbox->setName('INBOX'); + $mailbox->setAccountId($accountId); + $this->mailManager->expects($this->once()) + ->method('getMessage') + ->with($this->userId, $id) + ->willReturn($message); + $this->mailManager->expects($this->once()) + ->method('getMailbox') + ->with($this->userId, $mailboxId) + ->willReturn($mailbox); $this->accountService->expects($this->once()) ->method('find') ->with($this->equalTo($this->userId), $this->equalTo($accountId)) ->will($this->returnValue($this->account)); $this->account->expects($this->once()) ->method('getMailbox') - ->with(base64_decode($folderId)) + ->with('INBOX') ->will($this->returnValue($this->mailbox)); $this->mailbox->expects($this->once()) ->method('getMessage') - ->with($messageId) + ->with($id) ->will($this->returnValue($this->message)); $this->message->attachments = [ [ @@ -324,7 +386,7 @@ class MessagesControllerTest extends TestCase { $this->mailbox->expects($this->once()) ->method('getAttachment') - ->with($messageId, $attachmentId) + ->with($uid, $attachmentId) ->will($this->returnValue($this->attachment)); $this->attachment->expects($this->once()) ->method('getName') @@ -350,9 +412,7 @@ class MessagesControllerTest extends TestCase { $expected = new JSONResponse(); $response = $this->controller->saveAttachment( - $accountId, - $folderId, - $messageId, + $id, $attachmentId, $targetPath ); @@ -362,25 +422,36 @@ class MessagesControllerTest extends TestCase { public function testSetFlagsUnseen() { $accountId = 17; - $folderId = base64_encode('my folder'); - $messageId = 123; + $mailboxId = 987; + $id = 123; $flags = [ 'unseen' => false ]; - + $message = new \OCA\Mail\Db\Message(); + $message->setUid(444); + $message->setMailboxId($mailboxId); + $mailbox = new \OCA\Mail\Db\Mailbox(); + $mailbox->setName('INBOX'); + $mailbox->setAccountId($accountId); + $this->mailManager->expects($this->once()) + ->method('getMessage') + ->with($this->userId, $id) + ->willReturn($message); + $this->mailManager->expects($this->once()) + ->method('getMailbox') + ->with($this->userId, $mailboxId) + ->willReturn($mailbox); $this->accountService->expects($this->once()) ->method('find') ->with($this->equalTo($this->userId), $this->equalTo($accountId)) ->will($this->returnValue($this->account)); $this->mailManager->expects($this->once()) ->method('flagMessage') - ->with($this->account, 'my folder', $messageId, 'unseen', false); + ->with($this->account, 'INBOX', 444, 'unseen', false); $expected = new JSONResponse(); $response = $this->controller->setFlags( - $accountId, - $folderId, - $messageId, + $id, $flags ); @@ -389,25 +460,36 @@ class MessagesControllerTest extends TestCase { public function testSetFlagsFlagged() { $accountId = 17; - $folderId = base64_encode('my folder'); - $messageId = 123; + $mailboxId = 987; + $id = 123; $flags = [ 'flagged' => true ]; - + $message = new \OCA\Mail\Db\Message(); + $message->setUid(444); + $message->setMailboxId($mailboxId); + $mailbox = new \OCA\Mail\Db\Mailbox(); + $mailbox->setName('INBOX'); + $mailbox->setAccountId($accountId); + $this->mailManager->expects($this->once()) + ->method('getMessage') + ->with($this->userId, $id) + ->willReturn($message); + $this->mailManager->expects($this->once()) + ->method('getMailbox') + ->with($this->userId, $mailboxId) + ->willReturn($mailbox); $this->accountService->expects($this->once()) ->method('find') ->with($this->equalTo($this->userId), $this->equalTo($accountId)) ->will($this->returnValue($this->account)); $this->mailManager->expects($this->once()) ->method('flagMessage') - ->with($this->account, 'my folder', $messageId, 'flagged', true); + ->with($this->account, 'INBOX', 444, 'flagged', true); $expected = new JSONResponse(); $response = $this->controller->setFlags( - $accountId, - $folderId, - $messageId, + $id, $flags ); @@ -416,27 +498,54 @@ class MessagesControllerTest extends TestCase { public function testDestroy() { $accountId = 17; - $folderId = base64_encode('my folder'); - $messageId = 123; - + $mailboxId = 987; + $id = 123; + $message = new \OCA\Mail\Db\Message(); + $message->setUid(444); + $message->setMailboxId($mailboxId); + $mailbox = new \OCA\Mail\Db\Mailbox(); + $mailbox->setName('INBOX'); + $mailbox->setAccountId($accountId); + $this->mailManager->expects($this->once()) + ->method('getMessage') + ->with($this->userId, $id) + ->willReturn($message); + $this->mailManager->expects($this->once()) + ->method('getMailbox') + ->with($this->userId, $mailboxId) + ->willReturn($mailbox); $this->accountService->expects($this->once()) ->method('find') ->with($this->equalTo($this->userId), $this->equalTo($accountId)) ->will($this->returnValue($this->account)); $this->mailManager->expects($this->once()) ->method('deleteMessage') - ->with($this->account, base64_decode($folderId), $messageId); + ->with($this->account, 'INBOX', 444); $expected = new JSONResponse(); - $result = $this->controller->destroy($accountId, $folderId, $messageId); + $result = $this->controller->destroy($id); $this->assertEquals($expected, $result); } public function testDestroyWithAccountNotFound() { $accountId = 17; - $folderId = base64_encode('my folder'); - $messageId = 123; + $mailboxId = 987; + $id = 123; + $message = new \OCA\Mail\Db\Message(); + $message->setUid(444); + $message->setMailboxId($mailboxId); + $mailbox = new \OCA\Mail\Db\Mailbox(); + $mailbox->setName('INBOX'); + $mailbox->setAccountId($accountId); + $this->mailManager->expects($this->once()) + ->method('getMessage') + ->with($this->userId, $id) + ->willReturn($message); + $this->mailManager->expects($this->once()) + ->method('getMailbox') + ->with($this->userId, $mailboxId) + ->willReturn($mailbox); $this->accountService->expects($this->once()) ->method('find') ->with($this->equalTo($this->userId), $this->equalTo($accountId)) @@ -444,23 +553,37 @@ class MessagesControllerTest extends TestCase { $expected = new JSONResponse(null, Http::STATUS_FORBIDDEN); - $this->assertEquals($expected, $this->controller->destroy($accountId, $folderId, $messageId)); + $this->assertEquals($expected, $this->controller->destroy($id)); } public function testDestroyWithFolderOrMessageNotFound() { $accountId = 17; - $folderId = base64_encode('my folder'); - $messageId = 123; + $mailboxId = 987; + $id = 123; + $message = new \OCA\Mail\Db\Message(); + $message->setUid(444); + $message->setMailboxId($mailboxId); + $mailbox = new \OCA\Mail\Db\Mailbox(); + $mailbox->setName('INBOX'); + $mailbox->setAccountId($accountId); + $this->mailManager->expects($this->once()) + ->method('getMessage') + ->with($this->userId, $id) + ->willReturn($message); + $this->mailManager->expects($this->once()) + ->method('getMailbox') + ->with($this->userId, $mailboxId) + ->willReturn($mailbox); $this->accountService->expects($this->once()) ->method('find') ->with($this->equalTo($this->userId), $this->equalTo($accountId)) ->will($this->returnValue($this->account)); $this->mailManager->expects($this->once()) ->method('deleteMessage') - ->with($this->account, base64_decode($folderId), $messageId) + ->with($this->account, 'INBOX', 444) ->willThrowException(new ServiceException()); $this->expectException(ServiceException::class); - $this->controller->destroy($accountId, $folderId, $messageId); + $this->controller->destroy($id); } } diff --git a/tests/Unit/Controller/PageControllerTest.php b/tests/Unit/Controller/PageControllerTest.php index b2ee973bb..cad8c87a3 100644 --- a/tests/Unit/Controller/PageControllerTest.php +++ b/tests/Unit/Controller/PageControllerTest.php @@ -173,7 +173,7 @@ class PageControllerTest extends TestCase { 'a11', 'a12', ], - 'folders' => [ + 'mailboxes' => [ [ 'id' => 'inbox', ], @@ -185,7 +185,7 @@ class PageControllerTest extends TestCase { 'a21', 'a22', ], - 'folders' => [], + 'mailboxes' => [], ], ]; diff --git a/tests/Unit/Listener/DeleteDraftListenerTest.php b/tests/Unit/Listener/DeleteDraftListenerTest.php index bafe771db..045214368 100644 --- a/tests/Unit/Listener/DeleteDraftListenerTest.php +++ b/tests/Unit/Listener/DeleteDraftListenerTest.php @@ -26,6 +26,7 @@ use ChristophWurst\Nextcloud\Testing\TestCase; use OCA\Mail\Account; use OCA\Mail\Db\Mailbox; use OCA\Mail\Db\MailboxMapper; +use OCA\Mail\Db\Message; use OCA\Mail\Events\DraftSavedEvent; use OCA\Mail\Events\MessageSentEvent; use OCA\Mail\IMAP\IMAPClientFactory; @@ -110,10 +111,13 @@ class DeleteDraftListenerTest extends TestCase { $account = $this->createMock(Account::class); /** @var NewMessageData|MockObject $newMessageData */ $newMessageData = $this->createMock(NewMessageData::class); + $draft = new Message(); + $uid = 123; + $draft->setUid($uid); $event = new DraftSavedEvent( $account, $newMessageData, - 123 + $draft ); /** @var \Horde_Imap_Client_Socket|MockObject $client */ $client = $this->createMock(\Horde_Imap_Client_Socket::class); @@ -149,7 +153,7 @@ class DeleteDraftListenerTest extends TestCase { ->with( $client, $mailbox, - 123, + $uid, \Horde_Imap_Client::FLAG_DELETED ); $client->expects($this->once()) @@ -190,11 +194,14 @@ class DeleteDraftListenerTest extends TestCase { $message = $this->createMock(IMessage::class); /** @var \Horde_Mime_Mail|MockObject $mail */ $mail = $this->createMock(\Horde_Mime_Mail::class); + $draft = new Message(); + $uid = 123; + $draft->setUid($uid); $event = new MessageSentEvent( $account, $newMessageData, $repliedMessageData, - 123, + $draft, $message, $mail ); @@ -215,7 +222,7 @@ class DeleteDraftListenerTest extends TestCase { ->with( $client, $mailbox, - 123, + $uid, \Horde_Imap_Client::FLAG_DELETED ); $client->expects($this->once()) diff --git a/tests/Unit/Listener/DraftMailboxCreatorListenerTest.php b/tests/Unit/Listener/DraftMailboxCreatorListenerTest.php index 885e6596c..b06063908 100644 --- a/tests/Unit/Listener/DraftMailboxCreatorListenerTest.php +++ b/tests/Unit/Listener/DraftMailboxCreatorListenerTest.php @@ -29,6 +29,7 @@ use ChristophWurst\Nextcloud\Testing\TestCase; use OCA\Mail\Account; use OCA\Mail\Db\Mailbox; use OCA\Mail\Db\MailboxMapper; +use OCA\Mail\Db\Message; use OCA\Mail\Events\SaveDraftEvent; use OCA\Mail\IMAP\IMAPClientFactory; use OCA\Mail\IMAP\MailboxSync; @@ -85,10 +86,12 @@ class DraftMailboxCreatorListenerTest extends TestCase { $account = $this->createMock(Account::class); /** @var NewMessageData|MockObject $newMessageData */ $newMessageData = $this->createMock(NewMessageData::class); + $draft = new Message(); + $draft->setUid(123); $event = new SaveDraftEvent( $account, $newMessageData, - 123 + $draft ); /** @var \Horde_Imap_Client_Socket|MockObject $client */ $client = $this->createMock(\Horde_Imap_Client_Socket::class); @@ -113,10 +116,12 @@ class DraftMailboxCreatorListenerTest extends TestCase { $account = $this->createMock(Account::class); /** @var NewMessageData|MockObject $newMessageData */ $newMessageData = $this->createMock(NewMessageData::class); + $draft = new Message(); + $draft->setUid(123); $event = new SaveDraftEvent( $account, $newMessageData, - 123 + $draft ); /** @var \Horde_Imap_Client_Socket|MockObject $client */ $client = $this->createMock(\Horde_Imap_Client_Socket::class); diff --git a/tests/Unit/Listener/FlagRepliedMessageListenerTest.php b/tests/Unit/Listener/FlagRepliedMessageListenerTest.php index fae3b3705..151965aa7 100644 --- a/tests/Unit/Listener/FlagRepliedMessageListenerTest.php +++ b/tests/Unit/Listener/FlagRepliedMessageListenerTest.php @@ -29,6 +29,7 @@ use ChristophWurst\Nextcloud\Testing\TestCase; use OCA\Mail\Account; use OCA\Mail\Db\Mailbox; use OCA\Mail\Db\MailboxMapper; +use OCA\Mail\Db\Message; use OCA\Mail\Events\MessageSentEvent; use OCA\Mail\IMAP\IMAPClientFactory; use OCA\Mail\IMAP\MessageMapper; @@ -91,11 +92,13 @@ class FlagRepliedMessageListenerTest extends TestCase { $message = $this->createMock(IMessage::class); /** @var \Horde_Mime_Mail|MockObject $mail */ $mail = $this->createMock(\Horde_Mime_Mail::class); + $draft = new Message(); + $draft->setUid(123); $event = new MessageSentEvent( $account, $newMessageData, null, - 123, + $draft, $message, $mail ); @@ -118,11 +121,13 @@ class FlagRepliedMessageListenerTest extends TestCase { $message = $this->createMock(IMessage::class); /** @var \Horde_Mime_Mail|MockObject $mail */ $mail = $this->createMock(\Horde_Mime_Mail::class); + $draft = new Message(); + $draft->setUid(123); $event = new MessageSentEvent( $account, $newMessageData, $repliedMessageData, - 123, + $draft, $message, $mail ); @@ -152,11 +157,13 @@ class FlagRepliedMessageListenerTest extends TestCase { $message = $this->createMock(IMessage::class); /** @var \Horde_Mime_Mail|MockObject $mail */ $mail = $this->createMock(\Horde_Mime_Mail::class); + $draft = new Message(); + $draft->setUid(123); $event = new MessageSentEvent( $account, $newMessageData, $repliedMessageData, - 123, + $draft, $message, $mail ); diff --git a/tests/Unit/Listener/SaveSentMessageListenerTest.php b/tests/Unit/Listener/SaveSentMessageListenerTest.php index b5a443c03..85da65b6f 100644 --- a/tests/Unit/Listener/SaveSentMessageListenerTest.php +++ b/tests/Unit/Listener/SaveSentMessageListenerTest.php @@ -29,6 +29,7 @@ use ChristophWurst\Nextcloud\Testing\TestCase; use OCA\Mail\Account; use OCA\Mail\Db\Mailbox; use OCA\Mail\Db\MailboxMapper; +use OCA\Mail\Db\Message; use OCA\Mail\Events\MessageSentEvent; use OCA\Mail\Exception\ServiceException; use OCA\Mail\IMAP\IMAPClientFactory; @@ -101,11 +102,13 @@ class SaveSentMessageListenerTest extends TestCase { $message = $this->createMock(IMessage::class); /** @var \Horde_Mime_Mail|MockObject $mail */ $mail = $this->createMock(\Horde_Mime_Mail::class); + $draft = new Message(); + $draft->setUid(123); $event = new MessageSentEvent( $account, $newMessageData, $repliedMessageData, - 123, + $draft, $message, $mail ); @@ -153,11 +156,13 @@ class SaveSentMessageListenerTest extends TestCase { $message = $this->createMock(IMessage::class); /** @var \Horde_Mime_Mail|MockObject $mail */ $mail = $this->createMock(\Horde_Mime_Mail::class); + $draft = new Message(); + $draft->setUid(123); $event = new MessageSentEvent( $account, $newMessageData, $repliedMessageData, - 123, + $draft, $message, $mail ); @@ -207,11 +212,13 @@ class SaveSentMessageListenerTest extends TestCase { $message = $this->createMock(IMessage::class); /** @var \Horde_Mime_Mail|MockObject $mail */ $mail = $this->createMock(\Horde_Mime_Mail::class); + $draft = new Message(); + $draft->setUid(123); $event = new MessageSentEvent( $account, $newMessageData, $repliedMessageData, - 123, + $draft, $message, $mail ); @@ -244,11 +251,13 @@ class SaveSentMessageListenerTest extends TestCase { $message = $this->createMock(IMessage::class); /** @var \Horde_Mime_Mail|MockObject $mail */ $mail = $this->createMock(\Horde_Mime_Mail::class); + $draft = new Message(); + $draft->setUid(123); $event = new MessageSentEvent( $account, $newMessageData, $repliedMessageData, - 123, + $draft, $message, $mail ); diff --git a/tests/Unit/Model/IMAPMessageTest.php b/tests/Unit/Model/IMAPMessageTest.php index fb0a7c5b4..657b997f8 100644 --- a/tests/Unit/Model/IMAPMessageTest.php +++ b/tests/Unit/Model/IMAPMessageTest.php @@ -74,7 +74,7 @@ class IMAPMessageTest extends TestCase { $message = new IMAPMessage($conn, 'INBOX', 123, null, true, $htmlService); - $htmlBody = $message->getHtmlBody(0, 0, 123); + $htmlBody = $message->getHtmlBody(123); $this->assertTrue(strlen($htmlBody) > 1000); $plainTextBody = $message->getPlainBody(); diff --git a/tests/Unit/Service/MailManagerTest.php b/tests/Unit/Service/MailManagerTest.php index 521726575..eaac0a94e 100644 --- a/tests/Unit/Service/MailManagerTest.php +++ b/tests/Unit/Service/MailManagerTest.php @@ -127,10 +127,15 @@ class MailManagerTest extends TestCase { $this->folderMapper->expects($this->once()) ->method('detectFolderSpecialUse') ->with($this->equalTo([$folder])); + $mailbox = new Mailbox(); + $this->mailboxMapper->expects($this->once()) + ->method('find') + ->with($account, 'new') + ->willReturn($mailbox); - $created = $this->manager->createFolder($account, 'new'); + $created = $this->manager->createMailbox($account, 'new'); - $this->assertEquals($folder, $created); + $this->assertEquals($mailbox, $created); } public function testGetFolderStats() { @@ -144,8 +149,13 @@ class MailManagerTest extends TestCase { ->method('getFoldersStatusAsObject') ->with($this->equalTo($client), $this->equalTo('INBOX')) ->willReturn($stats); + $mailbox = new Mailbox(); + $mailbox->setName('INBOX'); - $actual = $this->manager->getFolderStats($account, 'INBOX'); + $actual = $this->manager->getMailboxStats( + $account, + $mailbox + ); $this->assertEquals($stats, $actual); } diff --git a/tests/Unit/Service/MailTransmissionTest.php b/tests/Unit/Service/MailTransmissionTest.php index b50f32930..796dd7f63 100644 --- a/tests/Unit/Service/MailTransmissionTest.php +++ b/tests/Unit/Service/MailTransmissionTest.php @@ -245,7 +245,7 @@ class MailTransmissionTest extends TestCase { ->with($client, $draftsMailbox, $this->anything()) ->willReturn(13); - $newId = $this->transmission->saveDraft($messageData); + [,,$newId] = $this->transmission->saveDraft($messageData); $this->assertEquals(13, $newId); } diff --git a/tests/Unit/Service/Search/MailSearchTest.php b/tests/Unit/Service/Search/MailSearchTest.php index 107a0ef32..453685146 100644 --- a/tests/Unit/Service/Search/MailSearchTest.php +++ b/tests/Unit/Service/Search/MailSearchTest.php @@ -89,14 +89,11 @@ class MailSearchTest extends TestCase { $mailbox = new Mailbox(); $mailbox->setSyncNewToken('abc'); $mailbox->setSyncChangedToken('def'); - $this->mailboxMapper->expects($this->once()) - ->method('find') - ->willReturn($mailbox); $this->expectException(MailboxNotCachedException::class); $this->search->findMessages( $account, - 'INBOX', + $mailbox, null, null, null @@ -107,14 +104,11 @@ class MailSearchTest extends TestCase { $account = $this->createMock(Account::class); $mailbox = new Mailbox(); $mailbox->setSyncNewLock(123); - $this->mailboxMapper->expects($this->once()) - ->method('find') - ->willReturn($mailbox); $this->expectException(MailboxLockedException::class); $this->search->findMessages( $account, - 'INBOX', + $mailbox, null, null, null @@ -127,13 +121,10 @@ class MailSearchTest extends TestCase { $mailbox->setSyncNewToken('abc'); $mailbox->setSyncChangedToken('def'); $mailbox->setSyncVanishedToken('ghi'); - $this->mailboxMapper->expects($this->once()) - ->method('find') - ->willReturn($mailbox); $messages = $this->search->findMessages( $account, - 'INBOX', + $mailbox, null, null, null @@ -148,9 +139,6 @@ class MailSearchTest extends TestCase { $mailbox->setSyncNewToken('abc'); $mailbox->setSyncChangedToken('def'); $mailbox->setSyncVanishedToken('ghi'); - $this->mailboxMapper->expects($this->once()) - ->method('find') - ->willReturn($mailbox); $query = new SearchQuery(); $query->addFlag('seen'); $this->filterStringParser->expects($this->once()) @@ -158,11 +146,7 @@ class MailSearchTest extends TestCase { ->with('my search') ->willReturn($query); $this->messageMapper->expects($this->once()) - ->method('findUidsByQuery') - ->with($mailbox, $query, null) - ->willReturn([1, 2]); - $this->messageMapper->expects($this->once()) - ->method('findByUids') + ->method('findByIds') ->willReturn([ $this->createMock(Message::class), $this->createMock(Message::class), @@ -175,7 +159,7 @@ class MailSearchTest extends TestCase { $messages = $this->search->findMessages( $account, - 'INBOX', + $mailbox, 'my search', null, null @@ -190,9 +174,6 @@ class MailSearchTest extends TestCase { $mailbox->setSyncNewToken('abc'); $mailbox->setSyncChangedToken('def'); $mailbox->setSyncVanishedToken('ghi'); - $this->mailboxMapper->expects($this->once()) - ->method('find') - ->willReturn($mailbox); $query = new SearchQuery(); $query->addTextToken('my'); $query->addTextToken('search'); @@ -205,11 +186,7 @@ class MailSearchTest extends TestCase { ->with($account, $mailbox, $query) ->willReturn([2, 3]); $this->messageMapper->expects($this->once()) - ->method('findUidsByQuery') - ->with($mailbox, $query, null, [2, 3]) - ->willReturn([1, 2]); - $this->messageMapper->expects($this->once()) - ->method('findByUids') + ->method('findByIds') ->willReturn([ $this->createMock(Message::class), $this->createMock(Message::class), @@ -220,7 +197,7 @@ class MailSearchTest extends TestCase { $messages = $this->search->findMessages( $account, - 'INBOX', + $mailbox, 'my search', null, null |