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

github.com/nextcloud/mail.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristoph Wurst <christoph@winzerhof-wurst.at>2020-10-15 12:24:38 +0300
committerChristoph Wurst <christoph@winzerhof-wurst.at>2020-10-30 19:16:24 +0300
commit15867528b25cad3e1262014d0fa4f967a73d64be (patch)
tree6dd7c46876325acf8aad147b9ef791a73376c102
parentfe074001833362fa1eb4d18576131b0678907fb5 (diff)
Store special mailboxes as preference of the account
… instead of using a fragile autodetection every time we need one of those. We will still try to auto-detect the mailboxes but the users will have to option to change the destinations. Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
-rw-r--r--appinfo/info.xml2
-rw-r--r--lib/AppInfo/Application.php9
-rw-r--r--lib/Contracts/IMailManager.php1
-rw-r--r--lib/Contracts/IMailTransmission.php4
-rw-r--r--lib/Controller/AccountsController.php16
-rw-r--r--lib/Db/MailAccount.php21
-rw-r--r--lib/Db/MailboxMapper.php26
-rw-r--r--lib/Events/MailboxesSynchronizedEvent.php47
-rw-r--r--lib/Events/MessageSentEvent.php3
-rw-r--r--lib/Exception/DraftsMailboxNotSetException.php32
-rw-r--r--lib/Exception/SentMailboxNotSetException.php32
-rw-r--r--lib/Exception/TrashMailboxNotSetException.php32
-rw-r--r--lib/IMAP/MailboxSync.php15
-rw-r--r--lib/Listener/DeleteDraftListener.php51
-rw-r--r--lib/Listener/DraftMailboxCreatorListener.php99
-rw-r--r--lib/Listener/MailboxesSynchronizedSpecialMailboxesUpdater.php122
-rw-r--r--lib/Listener/SaveSentMessageListener.php56
-rw-r--r--lib/Listener/TrashMailboxCreatorListener.php102
-rw-r--r--lib/Migration/Version1060Date20201015084952.php59
-rw-r--r--lib/Service/Classification/ImportanceClassifier.php7
-rw-r--r--lib/Service/MailManager.php9
-rw-r--r--lib/Service/MailTransmission.php12
-rw-r--r--package-lock.json42
-rw-r--r--package.json1
-rw-r--r--src/components/AccountDefaultsSettings.vue176
-rw-r--r--src/components/Composer.vue45
-rw-r--r--src/components/Envelope.vue25
-rw-r--r--src/components/EnvelopeList.vue24
-rw-r--r--src/components/Mailbox.vue30
-rw-r--r--src/components/MailboxInlinePicker.vue85
-rw-r--r--src/components/Navigation.vue32
-rw-r--r--src/components/ThreadEnvelope.vue24
-rw-r--r--src/errors/NoDraftsMailboxConfiguredError.js34
-rw-r--r--src/errors/NoSentMailboxConfiguredError.js34
-rw-r--r--src/errors/NoTrashMailboxConfiguredError.js34
-rw-r--r--src/errors/convert.js6
-rw-r--r--src/main.js1
-rw-r--r--src/service/MessageService.js23
-rw-r--r--src/views/AccountSettings.vue5
-rw-r--r--tests/Integration/Db/MailAccountTest.php6
-rw-r--r--tests/Integration/Db/MailboxMapperTest.php34
-rw-r--r--tests/Integration/Service/MailTransmissionIntegrationTest.php5
-rw-r--r--tests/Unit/IMAP/MailboxSyncTest.php14
-rw-r--r--tests/Unit/Listener/DeleteDraftListenerTest.php87
-rw-r--r--tests/Unit/Listener/DraftMailboxCreatorListenerTest.php156
-rw-r--r--tests/Unit/Listener/SaveSentMessageListenerTest.php109
-rw-r--r--tests/Unit/Listener/TrashMailboxCreatorListenerTest.php168
-rw-r--r--tests/Unit/Service/MailManagerTest.php24
-rw-r--r--tests/Unit/Service/MailTransmissionTest.php9
49 files changed, 1133 insertions, 857 deletions
diff --git a/appinfo/info.xml b/appinfo/info.xml
index 74906b801..d5beca95c 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -12,7 +12,7 @@
- **🙈 We’re not reinventing the wheel!** Based on the great [Horde](http://horde.org) libraries.
- **📬 Want to host your own mail server?** We don’t have to reimplement this as you could set up [Mail-in-a-Box](https://mailinabox.email)!
]]></description>
- <version>1.5.0</version>
+ <version>1.6.0-alpha.1</version>
<licence>agpl</licence>
<author>Christoph Wurst</author>
<author>Greta Doçi</author>
diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php
index a9eca0249..bf6edfdb7 100644
--- a/lib/AppInfo/Application.php
+++ b/lib/AppInfo/Application.php
@@ -33,27 +33,25 @@ use OCA\Mail\Contracts\IMailSearch;
use OCA\Mail\Contracts\IMailTransmission;
use OCA\Mail\Contracts\IUserPreferences;
use OCA\Mail\Dashboard\MailWidget;
-use OCA\Mail\Events\BeforeMessageDeletedEvent;
use OCA\Mail\Events\DraftSavedEvent;
+use OCA\Mail\Events\MailboxesSynchronizedEvent;
use OCA\Mail\Events\SynchronizationEvent;
use OCA\Mail\Events\MessageDeletedEvent;
use OCA\Mail\Events\MessageFlaggedEvent;
use OCA\Mail\Events\MessageSentEvent;
use OCA\Mail\Events\NewMessagesSynchronized;
-use OCA\Mail\Events\SaveDraftEvent;
use OCA\Mail\HordeTranslationHandler;
use OCA\Mail\Http\Middleware\ErrorMiddleware;
use OCA\Mail\Http\Middleware\ProvisioningMiddleware;
use OCA\Mail\Listener\AddressCollectionListener;
use OCA\Mail\Listener\DeleteDraftListener;
-use OCA\Mail\Listener\DraftMailboxCreatorListener;
use OCA\Mail\Listener\FlagRepliedMessageListener;
use OCA\Mail\Listener\InteractionListener;
use OCA\Mail\Listener\AccountSynchronizedThreadUpdaterListener;
+use OCA\Mail\Listener\MailboxesSynchronizedSpecialMailboxesUpdater;
use OCA\Mail\Listener\MessageCacheUpdaterListener;
use OCA\Mail\Listener\NewMessageClassificationListener;
use OCA\Mail\Listener\SaveSentMessageListener;
-use OCA\Mail\Listener\TrashMailboxCreatorListener;
use OCA\Mail\Listener\UserDeletedListener;
use OCA\Mail\Search\Provider;
use OCA\Mail\Service\Attachment\AttachmentService;
@@ -99,8 +97,8 @@ class Application extends App implements IBootstrap {
$context->registerServiceAlias(IMailTransmission::class, MailTransmission::class);
$context->registerServiceAlias(IUserPreferences::class, UserPreferenceSevice::class);
- $context->registerEventListener(BeforeMessageDeletedEvent::class, TrashMailboxCreatorListener::class);
$context->registerEventListener(DraftSavedEvent::class, DeleteDraftListener::class);
+ $context->registerEventListener(MailboxesSynchronizedEvent::class, MailboxesSynchronizedSpecialMailboxesUpdater::class);
$context->registerEventListener(MessageFlaggedEvent::class, MessageCacheUpdaterListener::class);
$context->registerEventListener(MessageDeletedEvent::class, MessageCacheUpdaterListener::class);
$context->registerEventListener(MessageSentEvent::class, AddressCollectionListener::class);
@@ -109,7 +107,6 @@ class Application extends App implements IBootstrap {
$context->registerEventListener(MessageSentEvent::class, InteractionListener::class);
$context->registerEventListener(MessageSentEvent::class, SaveSentMessageListener::class);
$context->registerEventListener(NewMessagesSynchronized::class, NewMessageClassificationListener::class);
- $context->registerEventListener(SaveDraftEvent::class, DraftMailboxCreatorListener::class);
$context->registerEventListener(SynchronizationEvent::class, AccountSynchronizedThreadUpdaterListener::class);
$context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class);
diff --git a/lib/Contracts/IMailManager.php b/lib/Contracts/IMailManager.php
index 02c3254cc..ca17c0edf 100644
--- a/lib/Contracts/IMailManager.php
+++ b/lib/Contracts/IMailManager.php
@@ -145,6 +145,7 @@ interface IMailManager {
* @param string $mailboxId
* @param int $messageId
*
+ * @throws ClientException
* @throws ServiceException
*/
public function deleteMessage(Account $account, string $mailboxId, int $messageId): void;
diff --git a/lib/Contracts/IMailTransmission.php b/lib/Contracts/IMailTransmission.php
index f58ae37d6..918556435 100644
--- a/lib/Contracts/IMailTransmission.php
+++ b/lib/Contracts/IMailTransmission.php
@@ -25,6 +25,8 @@ namespace OCA\Mail\Contracts;
use OCA\Mail\Db\Alias;
use OCA\Mail\Db\Message;
+use OCA\Mail\Exception\ClientException;
+use OCA\Mail\Exception\SentMailboxNotSetException;
use OCA\Mail\Exception\ServiceException;
use OCA\Mail\Model\NewMessageData;
use OCA\Mail\Model\RepliedMessageData;
@@ -39,6 +41,7 @@ interface IMailTransmission {
* @param Alias|null $alias
* @param Message|null $draft
*
+ * @throws SentMailboxNotSetException
* @throws ServiceException
*/
public function sendMessage(NewMessageData $messageData,
@@ -54,6 +57,7 @@ interface IMailTransmission {
*
* @return array
*
+ * @throws ClientException if no drafts mailbox is configured
* @throws ServiceException
*/
public function saveDraft(NewMessageData $message, Message $previousDraft = null): array;
diff --git a/lib/Controller/AccountsController.php b/lib/Controller/AccountsController.php
index 04e910743..3909c4ca5 100644
--- a/lib/Controller/AccountsController.php
+++ b/lib/Controller/AccountsController.php
@@ -234,7 +234,10 @@ class AccountsController extends Controller {
public function patchAccount(int $id,
string $editorMode = null,
int $order = null,
- bool $showSubscribedOnly = null): JSONResponse {
+ bool $showSubscribedOnly = null,
+ int $draftsMailboxId = null,
+ int $sentMailboxId = null,
+ int $trashMailboxId = null): JSONResponse {
$account = $this->accountService->find($this->currentUserId, $id);
$dbAccount = $account->getMailAccount();
@@ -247,6 +250,15 @@ class AccountsController extends Controller {
if ($showSubscribedOnly !== null) {
$dbAccount->setShowSubscribedOnly($showSubscribedOnly);
}
+ if ($draftsMailboxId !== null) {
+ $dbAccount->setDraftsMailboxId($draftsMailboxId);
+ }
+ if ($sentMailboxId !== null) {
+ $dbAccount->setSentMailboxId($sentMailboxId);
+ }
+ if ($trashMailboxId !== null) {
+ $dbAccount->setTrashMailboxId($trashMailboxId);
+ }
return new JSONResponse(
$this->accountService->save($dbAccount)->toJson()
);
@@ -452,7 +464,7 @@ class AccountsController extends Controller {
return new JSONResponse([
'id' => $this->mailManager->getMessageIdForUid($draftsMailbox, $newUID)
]);
- } catch (ServiceException $ex) {
+ } catch (ClientException|ServiceException $ex) {
$this->logger->error('Saving draft failed: ' . $ex->getMessage());
throw $ex;
}
diff --git a/lib/Db/MailAccount.php b/lib/Db/MailAccount.php
index 80b554c46..f5b6a8a27 100644
--- a/lib/Db/MailAccount.php
+++ b/lib/Db/MailAccount.php
@@ -71,6 +71,12 @@ use OCP\AppFramework\Db\Entity;
* @method void setShowSubscribedOnly(bool $showSubscribedOnly)
* @method string|null getPersonalNamespace()
* @method void setPersonalNamespace(string|null $personalNamespace)
+ * @method void setDraftsMailboxId(?int $id)
+ * @method int|null getDraftsMailboxId()
+ * @method void setSentMailboxId(?int $id)
+ * @method int|null getSentMailboxId()
+ * @method void setTrashMailboxId(?int $id)
+ * @method int|null getTrashMailboxId()
*/
class MailAccount extends Entity {
protected $userId;
@@ -94,6 +100,15 @@ class MailAccount extends Entity {
protected $showSubscribedOnly;
protected $personalNamespace;
+ /** @var int|null */
+ protected $draftsMailboxId;
+
+ /** @var int|null */
+ protected $sentMailboxId;
+
+ /** @var int|null */
+ protected $trashMailboxId;
+
/**
* @param array $params
*/
@@ -150,6 +165,9 @@ class MailAccount extends Entity {
$this->addType('order', 'integer');
$this->addType('showSubscribedOnly', 'boolean');
$this->addType('personalNamespace', 'string');
+ $this->addType('draftsMailboxId', 'integer');
+ $this->addType('sentMailboxId', 'integer');
+ $this->addType('trashMailboxId', 'integer');
}
/**
@@ -171,6 +189,9 @@ class MailAccount extends Entity {
'provisioned' => $this->getProvisioned(),
'showSubscribedOnly' => $this->getShowSubscribedOnly(),
'personalNamespace' => $this->getPersonalNamespace(),
+ 'draftsMailboxId' => $this->getDraftsMailboxId(),
+ 'sentMailboxId' => $this->getSentMailboxId(),
+ 'trashMailboxId' => $this->getTrashMailboxId(),
];
if (!is_null($this->getOutboundHost())) {
diff --git a/lib/Db/MailboxMapper.php b/lib/Db/MailboxMapper.php
index 13dd0a445..4d5ee15c2 100644
--- a/lib/Db/MailboxMapper.php
+++ b/lib/Db/MailboxMapper.php
@@ -142,32 +142,6 @@ class MailboxMapper extends QBMapper {
}
/**
- * @throws DoesNotExistException
- */
- public function findSpecial(Account $account, string $specialUse): Mailbox {
- $mailboxes = $this->findAll($account);
-
- // First, let's try to detect by special use attribute
- foreach ($mailboxes as $mailbox) {
- $specialUses = json_decode($mailbox->getSpecialUse(), true) ?? [];
- if (in_array($specialUse, $specialUses, true)) {
- return $mailbox;
- }
- }
-
- // No luck so far, let's do another round and just guess
- foreach ($mailboxes as $mailbox) {
- // TODO: also check localized name
- if (strtolower($mailbox->getName()) === strtolower($specialUse)) {
- return $mailbox;
- }
- }
-
- // Give up
- throw new DoesNotExistException("Special mailbox $specialUse does not exist");
- }
-
- /**
* @throws MailboxLockedException
*/
private function lockForSync(Mailbox $mailbox, string $attr, ?int $lock): int {
diff --git a/lib/Events/MailboxesSynchronizedEvent.php b/lib/Events/MailboxesSynchronizedEvent.php
new file mode 100644
index 000000000..d6e971ad1
--- /dev/null
+++ b/lib/Events/MailboxesSynchronizedEvent.php
@@ -0,0 +1,47 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @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/>.
+ */
+
+namespace OCA\Mail\Events;
+
+use OCA\Mail\Account;
+use OCP\EventDispatcher\Event;
+
+/**
+ * @psalm-immutable
+ */
+class MailboxesSynchronizedEvent extends Event {
+
+ /** @var Account */
+ private $account;
+
+ public function __construct(Account $account) {
+ parent::__construct();
+ $this->account = $account;
+ }
+
+ public function getAccount(): Account {
+ return $this->account;
+ }
+}
diff --git a/lib/Events/MessageSentEvent.php b/lib/Events/MessageSentEvent.php
index a2973cb8c..07bb965ff 100644
--- a/lib/Events/MessageSentEvent.php
+++ b/lib/Events/MessageSentEvent.php
@@ -33,6 +33,9 @@ use OCA\Mail\Model\NewMessageData;
use OCA\Mail\Model\RepliedMessageData;
use OCP\EventDispatcher\Event;
+/**
+ * @psalm-immutable
+ */
class MessageSentEvent extends Event {
/** @var Account */
diff --git a/lib/Exception/DraftsMailboxNotSetException.php b/lib/Exception/DraftsMailboxNotSetException.php
new file mode 100644
index 000000000..209190b3d
--- /dev/null
+++ b/lib/Exception/DraftsMailboxNotSetException.php
@@ -0,0 +1,32 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @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/>.
+ */
+
+namespace OCA\Mail\Exception;
+
+class DraftsMailboxNotSetException extends ClientException {
+ public function __construct() {
+ parent::__construct("No drafts mailbox configured");
+ }
+}
diff --git a/lib/Exception/SentMailboxNotSetException.php b/lib/Exception/SentMailboxNotSetException.php
new file mode 100644
index 000000000..8c572a59f
--- /dev/null
+++ b/lib/Exception/SentMailboxNotSetException.php
@@ -0,0 +1,32 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @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/>.
+ */
+
+namespace OCA\Mail\Exception;
+
+class SentMailboxNotSetException extends ClientException {
+ public function __construct() {
+ parent::__construct("No sent mailbox configured");
+ }
+}
diff --git a/lib/Exception/TrashMailboxNotSetException.php b/lib/Exception/TrashMailboxNotSetException.php
new file mode 100644
index 000000000..db49c84e8
--- /dev/null
+++ b/lib/Exception/TrashMailboxNotSetException.php
@@ -0,0 +1,32 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @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/>.
+ */
+
+namespace OCA\Mail\Exception;
+
+class TrashMailboxNotSetException extends ClientException {
+ public function __construct() {
+ parent::__construct("No trash mailbox configured");
+ }
+}
diff --git a/lib/IMAP/MailboxSync.php b/lib/IMAP/MailboxSync.php
index 15df59453..343abfa56 100644
--- a/lib/IMAP/MailboxSync.php
+++ b/lib/IMAP/MailboxSync.php
@@ -29,7 +29,9 @@ use Horde_Imap_Client;
use Horde_Imap_Client_Data_Namespace;
use Horde_Imap_Client_Exception;
use Horde_Imap_Client_Namespace_List;
+use OCA\Mail\Events\MailboxesSynchronizedEvent;
use OCA\Mail\Exception\ServiceException;
+use OCP\EventDispatcher\IEventDispatcher;
use Psr\Log\LoggerInterface;
use function in_array;
use function json_encode;
@@ -57,16 +59,21 @@ class MailboxSync {
/** @var ITimeFactory */
private $timeFactory;
+ /** @var IEventDispatcher */
+ private $dispatcher;
+
public function __construct(MailboxMapper $mailboxMapper,
FolderMapper $folderMapper,
MailAccountMapper $mailAccountMapper,
IMAPClientFactory $imapClientFactory,
- ITimeFactory $timeFactory) {
+ ITimeFactory $timeFactory,
+ IEventDispatcher $dispatcher) {
$this->mailboxMapper = $mailboxMapper;
$this->folderMapper = $folderMapper;
$this->mailAccountMapper = $mailAccountMapper;
$this->imapClientFactory = $imapClientFactory;
$this->timeFactory = $timeFactory;
+ $this->dispatcher = $dispatcher;
}
/**
@@ -98,7 +105,7 @@ class MailboxSync {
} catch (Horde_Imap_Client_Exception $e) {
throw new ServiceException(
"IMAP error: " . $e->getMessage(),
- (int) $e->getCode(),
+ (int)$e->getCode(),
$e
);
}
@@ -113,6 +120,10 @@ class MailboxSync {
);
$this->persist($account, $folders, $indexedOld);
+
+ $this->dispatcher->dispatchTyped(
+ new MailboxesSynchronizedEvent($account)
+ );
}
private function persist(Account $account, array $folders, array $existing): void {
diff --git a/lib/Listener/DeleteDraftListener.php b/lib/Listener/DeleteDraftListener.php
index 3f701aa8f..87f46b47a 100644
--- a/lib/Listener/DeleteDraftListener.php
+++ b/lib/Listener/DeleteDraftListener.php
@@ -34,7 +34,6 @@ use OCA\Mail\Db\Message;
use OCA\Mail\Events\DraftSavedEvent;
use OCA\Mail\Events\MessageSentEvent;
use OCA\Mail\IMAP\IMAPClientFactory;
-use OCA\Mail\IMAP\MailboxSync;
use OCA\Mail\IMAP\MessageMapper;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\EventDispatcher\Event;
@@ -52,21 +51,16 @@ class DeleteDraftListener implements IEventListener {
/** @var MessageMapper */
private $messageMapper;
- /** @var MailboxSync */
- private $mailboxSync;
-
/** @var LoggerInterface */
private $logger;
public function __construct(IMAPClientFactory $imapClientFactory,
MailboxMapper $mailboxMapper,
MessageMapper $messageMapper,
- MailboxSync $mailboxSync,
LoggerInterface $logger) {
$this->imapClientFactory = $imapClientFactory;
$this->mailboxMapper = $mailboxMapper;
$this->messageMapper = $messageMapper;
- $this->mailboxSync = $mailboxSync;
$this->logger = $logger;
}
@@ -84,7 +78,12 @@ class DeleteDraftListener implements IEventListener {
*/
private function deleteDraft(Account $account, Message $draft): void {
$client = $this->imapClientFactory->getClient($account);
- $draftsMailbox = $this->getDraftsMailbox($account);
+ try {
+ $draftsMailbox = $this->getDraftsMailbox($account);
+ } catch (DoesNotExistException $e) {
+ $this->logger->warning("Account has no draft mailbox set, can't delete the draft");
+ return;
+ }
try {
$this->messageMapper->addFlag(
@@ -108,38 +107,14 @@ class DeleteDraftListener implements IEventListener {
}
}
+ /**
+ * @throws DoesNotExistException
+ */
private function getDraftsMailbox(Account $account): Mailbox {
- try {
- return $this->mailboxMapper->findSpecial($account, 'drafts');
- } catch (DoesNotExistException $e) {
- $this->logger->debug('Creating drafts mailbox');
- $this->createDraftsMailbox($account);
- $this->logger->debug('Drafts mailbox created');
- }
-
- return $this->mailboxMapper->findSpecial($account, 'drafts');
- }
-
- private function createDraftsMailbox(Account $account): void {
- $client = $this->imapClientFactory->getClient($account);
-
- try {
- // TODO: localize mailbox name
- $client->createMailbox(
- 'Drafts',
- [
- 'special_use' => [
- \Horde_Imap_Client::SPECIALUSE_DRAFTS,
- ],
- ]
- );
- } catch (Horde_Imap_Client_Exception $e) {
- $this->logger->error('Could not create drafts mailbox', [
- 'exception' => $e,
- ]);
+ $draftsMailboxId = $account->getMailAccount()->getDraftsMailboxId();
+ if ($draftsMailboxId === null) {
+ throw new DoesNotExistException("No drafts mailbox ID set");
}
-
- // TODO: find a more elegant solution for updating the mailbox cache
- $this->mailboxSync->sync($account, $this->logger,true);
+ return $this->mailboxMapper->findById($draftsMailboxId);
}
}
diff --git a/lib/Listener/DraftMailboxCreatorListener.php b/lib/Listener/DraftMailboxCreatorListener.php
deleted file mode 100644
index f4d915e81..000000000
--- a/lib/Listener/DraftMailboxCreatorListener.php
+++ /dev/null
@@ -1,99 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-/**
- * @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/>.
- */
-
-namespace OCA\Mail\Listener;
-
-use Horde_Imap_Client_Exception;
-use OCA\Mail\Account;
-use OCA\Mail\Db\MailboxMapper;
-use OCA\Mail\Events\SaveDraftEvent;
-use OCA\Mail\IMAP\IMAPClientFactory;
-use OCA\Mail\IMAP\MailboxSync;
-use OCP\AppFramework\Db\DoesNotExistException;
-use OCP\EventDispatcher\Event;
-use OCP\EventDispatcher\IEventListener;
-use Psr\Log\LoggerInterface;
-
-class DraftMailboxCreatorListener implements IEventListener {
-
- /** @var MailboxMapper */
- private $mailboxMapper;
-
- /** @var IMAPClientFactory */
- private $imapClientFactory;
-
- /** @var MailboxSync */
- private $mailboxSync;
-
- /** @var LoggerInterface */
- private $logger;
-
- public function __construct(MailboxMapper $mailboxMapper,
- IMAPClientFactory $imapClientFactory,
- MailboxSync $mailboxSync,
- LoggerInterface $logger) {
- $this->mailboxMapper = $mailboxMapper;
- $this->imapClientFactory = $imapClientFactory;
- $this->mailboxSync = $mailboxSync;
- $this->logger = $logger;
- }
-
- public function handle(Event $event): void {
- if (!($event instanceof SaveDraftEvent)) {
- return;
- }
-
- try {
- $this->mailboxMapper->findSpecial($event->getAccount(), 'drafts');
- } catch (DoesNotExistException $e) {
- $this->logger->debug('Creating drafts mailbox');
- $this->createDraftsMailbox($event->getAccount());
- $this->logger->debug('Drafts mailbox created');
- }
- }
-
- private function createDraftsMailbox(Account $account): void {
- $client = $this->imapClientFactory->getClient($account);
-
- try {
- // TODO: localize mailbox name
- $client->createMailbox(
- 'Drafts',
- [
- 'special_use' => [
- \Horde_Imap_Client::SPECIALUSE_DRAFTS,
- ],
- ]
- );
- } catch (Horde_Imap_Client_Exception $e) {
- $this->logger->error('Could not create drafts mailbox', [
- 'exception' => $e,
- ]);
- }
-
- // TODO: find a more elegant solution for updating the mailbox cache
- $this->mailboxSync->sync($account, $this->logger,true);
- }
-}
diff --git a/lib/Listener/MailboxesSynchronizedSpecialMailboxesUpdater.php b/lib/Listener/MailboxesSynchronizedSpecialMailboxesUpdater.php
new file mode 100644
index 000000000..32c009061
--- /dev/null
+++ b/lib/Listener/MailboxesSynchronizedSpecialMailboxesUpdater.php
@@ -0,0 +1,122 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @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/>.
+ */
+
+namespace OCA\Mail\Listener;
+
+use OCA\Mail\Db\MailAccountMapper;
+use OCA\Mail\Db\Mailbox;
+use OCA\Mail\Db\MailboxMapper;
+use OCA\Mail\Events\MailboxesSynchronizedEvent;
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+use Psr\Log\LoggerInterface;
+use function array_combine;
+use function array_key_exists;
+use function array_map;
+use function in_array;
+use function json_decode;
+use function strtolower;
+
+class MailboxesSynchronizedSpecialMailboxesUpdater implements IEventListener {
+
+ /** @var MailAccountMapper */
+ private $mailAccountMapper;
+
+ /** @var MailboxMapper */
+ private $mailboxMapper;
+
+ /** @var LoggerInterface */
+ private $logger;
+
+ public function __construct(MailAccountMapper $mailAccountMapper,
+ MailboxMapper $mailboxMapper,
+ LoggerInterface $logger) {
+ $this->mailAccountMapper = $mailAccountMapper;
+ $this->mailboxMapper = $mailboxMapper;
+ $this->logger = $logger;
+ }
+
+ /**
+ * @param Event $event
+ */
+ public function handle(Event $event): void {
+ /** @var MailboxesSynchronizedEvent $event */
+ $account = $event->getAccount();
+ $mailAccount = $account->getMailAccount();
+ $mailboxes = $this->indexMailboxes(
+ $this->mailboxMapper->findAll($account)
+ );
+
+ if ($mailAccount->getDraftsMailboxId() === null || !array_key_exists($mailAccount->getDraftsMailboxId(), $mailboxes)) {
+ $draftsMailbox = $this->findSpecial($mailboxes, 'drafts');
+ $mailAccount->setDraftsMailboxId($draftsMailbox->getId());
+ }
+ if ($mailAccount->getSentMailboxId() === null || !array_key_exists($mailAccount->getSentMailboxId(), $mailboxes)) {
+ $sentMailbox = $this->findSpecial($mailboxes, 'sent');
+ $mailAccount->setSentMailboxId($sentMailbox->getId());
+ }
+ if ($mailAccount->getTrashMailboxId() === null || !array_key_exists($mailAccount->getTrashMailboxId(), $mailboxes)) {
+ $trashMailbox = $this->findSpecial($mailboxes, 'trash');
+ $mailAccount->setTrashMailboxId($trashMailbox->getId());
+ }
+
+ $this->mailAccountMapper->update($mailAccount);
+ }
+
+ private function indexMailboxes(array $mailboxes): array {
+ return array_combine(
+ array_map(function (Mailbox $mb): int {
+ return $mb->getId();
+ }, $mailboxes),
+ $mailboxes
+ );
+ }
+
+ /**
+ * @param Mailbox[] $mailboxes
+ * @throws DoesNotExistException
+ */
+ private function findSpecial(array $mailboxes, string $specialUse): Mailbox {
+ // First, let's try to detect by special use attribute
+ foreach ($mailboxes as $mailbox) {
+ $specialUses = json_decode($mailbox->getSpecialUse(), true) ?? [];
+ if (in_array($specialUse, $specialUses, true)) {
+ return $mailbox;
+ }
+ }
+
+ // No luck so far, let's do another round and just guess
+ foreach ($mailboxes as $mailbox) {
+ // TODO: also check localized name
+ if (strtolower($mailbox->getName()) === strtolower($specialUse)) {
+ return $mailbox;
+ }
+ }
+
+ // Give up
+ throw new DoesNotExistException("Special mailbox $specialUse does not exist");
+ }
+}
diff --git a/lib/Listener/SaveSentMessageListener.php b/lib/Listener/SaveSentMessageListener.php
index aa43eb6fc..37eedd93c 100644
--- a/lib/Listener/SaveSentMessageListener.php
+++ b/lib/Listener/SaveSentMessageListener.php
@@ -26,13 +26,10 @@ declare(strict_types=1);
namespace OCA\Mail\Listener;
use Horde_Imap_Client_Exception;
-use OCA\Mail\Account;
-use OCA\Mail\Db\Mailbox;
use OCA\Mail\Db\MailboxMapper;
use OCA\Mail\Events\MessageSentEvent;
use OCA\Mail\Exception\ServiceException;
use OCA\Mail\IMAP\IMAPClientFactory;
-use OCA\Mail\IMAP\MailboxSync;
use OCA\Mail\IMAP\MessageMapper;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\EventDispatcher\Event;
@@ -50,21 +47,16 @@ class SaveSentMessageListener implements IEventListener {
/** @var MessageMapper */
private $messageMapper;
- /** @var MailboxSync */
- private $mailboxSync;
-
/** @var LoggerInterface */
private $logger;
public function __construct(MailboxMapper $mailboxMapper,
IMAPClientFactory $imapClientFactory,
MessageMapper $messageMapper,
- MailboxSync $mailboxSync,
LoggerInterface $logger) {
$this->mailboxMapper = $mailboxMapper;
$this->imapClientFactory = $imapClientFactory;
$this->messageMapper = $messageMapper;
- $this->mailboxSync = $mailboxSync;
$this->logger = $logger;
}
@@ -73,13 +65,22 @@ class SaveSentMessageListener implements IEventListener {
return;
}
+ $sentMailboxId = $event->getAccount()->getMailAccount()->getSentMailboxId();
+ if ($sentMailboxId === null) {
+ $this->logger->warning("No sent mailbox exists, can't save sent message");
+ return;
+ }
+
// Save the message in the sent mailbox
try {
- $sentMailbox = $this->mailboxMapper->findSpecial($event->getAccount(), 'sent');
+ $sentMailbox = $this->mailboxMapper->findById(
+ $sentMailboxId
+ );
} catch (DoesNotExistException $e) {
- $this->logger->debug('creating sent mailbox');
- $sentMailbox = $this->createSentMailbox($event->getAccount());
- $this->logger->debug('sent mailbox created');
+ $this->logger->error("Sent mailbox could not be found", [
+ 'exception' => $e,
+ ]);
+ return;
}
try {
@@ -92,35 +93,4 @@ class SaveSentMessageListener implements IEventListener {
throw new ServiceException('Could not save sent message on IMAP', 0, $e);
}
}
-
- /**
- * @throws DoesNotExistException
- * @throws ServiceException
- */
- private function createSentMailbox(Account $account): Mailbox {
- $client = $this->imapClientFactory->getClient($account);
-
- try {
- // TODO: localize mailbox name
- $client->createMailbox(
- 'Sent',
- [
- 'special_use' => [
- \Horde_Imap_Client::SPECIALUSE_SENT,
- ],
- ]
- );
- } catch (Horde_Imap_Client_Exception $e) {
- // Let's assume this error is caused because the mailbox already exists,
- // caused by concurrent requests or out-of-sync mailbox cache
- $this->logger->warning('Could not create sent mailbox: ' . $e->getMessage(), [
- 'exception' => $e,
- ]);
- }
-
- // TODO: find a more elegant solution for updating the mailbox cache
- $this->mailboxSync->sync($account, $this->logger,true);
-
- return $this->mailboxMapper->findSpecial($account, 'sent');
- }
}
diff --git a/lib/Listener/TrashMailboxCreatorListener.php b/lib/Listener/TrashMailboxCreatorListener.php
deleted file mode 100644
index 3a57e2b4d..000000000
--- a/lib/Listener/TrashMailboxCreatorListener.php
+++ /dev/null
@@ -1,102 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-/**
- * @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/>.
- */
-
-namespace OCA\Mail\Listener;
-
-use Horde_Imap_Client_Exception;
-use OCA\Mail\Account;
-use OCA\Mail\Db\MailboxMapper;
-use OCA\Mail\Events\BeforeMessageDeletedEvent;
-use OCA\Mail\IMAP\IMAPClientFactory;
-use OCA\Mail\IMAP\MailboxSync;
-use OCP\AppFramework\Db\DoesNotExistException;
-use OCP\EventDispatcher\Event;
-use OCP\EventDispatcher\IEventListener;
-use Psr\Log\LoggerInterface;
-
-class TrashMailboxCreatorListener implements IEventListener {
-
- /** @var MailboxMapper */
- private $mailboxMapper;
-
- /** @var IMAPClientFactory */
- private $imapClientFactory;
-
- /** @var MailboxSync */
- private $mailboxSync;
-
- /** @var LoggerInterface */
- private $logger;
-
- public function __construct(MailboxMapper $mailboxMapper,
- IMAPClientFactory $imapClientFactory,
- MailboxSync $mailboxSync,
- LoggerInterface $logger) {
- $this->mailboxMapper = $mailboxMapper;
- $this->imapClientFactory = $imapClientFactory;
- $this->mailboxSync = $mailboxSync;
- $this->logger = $logger;
- }
-
- public function handle(Event $event): void {
- if (!($event instanceof BeforeMessageDeletedEvent)) {
- return;
- }
-
- try {
- $this->mailboxMapper->findSpecial(
- $event->getAccount(),
- 'trash'
- );
- } catch (DoesNotExistException $e) {
- $this->logger->debug("Creating trash mailbox");
- $this->createTrash($event->getAccount());
- $this->logger->debug("Trash mailbox created");
- }
- }
-
- private function createTrash(Account $account): void {
- $client = $this->imapClientFactory->getClient($account);
-
- try {
- // TODO: localize mailbox name
- $client->createMailbox(
- 'Trash',
- [
- 'special_use' => [
- \Horde_Imap_Client::SPECIALUSE_TRASH,
- ],
- ]
- );
-
- // TODO: find a more elegant solution for updating the mailbox cache
- $this->mailboxSync->sync($account, $this->logger,true);
- } catch (Horde_Imap_Client_Exception $e) {
- $this->logger->error('Could not creat trash mailbox', [
- 'exception' => $e,
- ]);
- }
- }
-}
diff --git a/lib/Migration/Version1060Date20201015084952.php b/lib/Migration/Version1060Date20201015084952.php
new file mode 100644
index 000000000..222ab62e5
--- /dev/null
+++ b/lib/Migration/Version1060Date20201015084952.php
@@ -0,0 +1,59 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OCA\Mail\Migration;
+
+use Closure;
+use OCP\DB\ISchemaWrapper;
+use OCP\IDBConnection;
+use OCP\Migration\IOutput;
+use OCP\Migration\SimpleMigrationStep;
+
+class Version1060Date20201015084952 extends SimpleMigrationStep {
+
+ /** @var IDBConnection */
+ protected $connection;
+
+ public function __construct(IDBConnection $connection) {
+ $this->connection = $connection;
+ }
+
+ /**
+ * @param IOutput $output
+ * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
+ * @param array $options
+ * @return ISchemaWrapper
+ */
+ public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ISchemaWrapper {
+ /** @var ISchemaWrapper $schema */
+ $schema = $schemaClosure();
+
+ $accountsTable = $schema->getTable('mail_accounts');
+ $accountsTable->addColumn('drafts_mailbox_id', 'integer', [
+ 'notnull' => false,
+ 'default' => null,
+ 'length' => 20,
+ ]);
+ $accountsTable->addColumn('sent_mailbox_id', 'integer', [
+ 'notnull' => false,
+ 'default' => null,
+ 'length' => 20,
+ ]);
+ $accountsTable->addColumn('trash_mailbox_id', 'integer', [
+ 'notnull' => false,
+ 'default' => null,
+ 'length' => 20,
+ ]);
+
+ return $schema;
+ }
+
+ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options) {
+ // Force a re-sync, so the values are propagated ASAP
+ $update = $this->connection->getQueryBuilder();
+ $update->update('mail_accounts')
+ ->set('last_mailbox_sync', $update->createNamedParameter(0));
+ $update->execute();
+ }
+}
diff --git a/lib/Service/Classification/ImportanceClassifier.php b/lib/Service/Classification/ImportanceClassifier.php
index f237e1f42..c414476e1 100644
--- a/lib/Service/Classification/ImportanceClassifier.php
+++ b/lib/Service/Classification/ImportanceClassifier.php
@@ -246,8 +246,13 @@ class ImportanceClassifier {
*/
private function getOutgoingMailboxes(Account $account): array {
try {
+ $sentMailboxId = $account->getMailAccount()->getSentMailboxId();
+ if ($sentMailboxId === null) {
+ return [];
+ }
+
return [
- $this->mailboxMapper->findSpecial($account, 'sent')
+ $this->mailboxMapper->findById($sentMailboxId)
];
} catch (DoesNotExistException $e) {
return [];
diff --git a/lib/Service/MailManager.php b/lib/Service/MailManager.php
index 777347a70..10e7cfc8e 100644
--- a/lib/Service/MailManager.php
+++ b/lib/Service/MailManager.php
@@ -37,6 +37,7 @@ use OCA\Mail\Events\MessageDeletedEvent;
use OCA\Mail\Events\MessageFlaggedEvent;
use OCA\Mail\Exception\ClientException;
use OCA\Mail\Exception\ServiceException;
+use OCA\Mail\Exception\TrashMailboxNotSetException;
use OCA\Mail\Folder;
use OCA\Mail\IMAP\FolderMapper;
use OCA\Mail\IMAP\FolderStats;
@@ -268,9 +269,9 @@ class MailManager implements IMailManager {
}
/**
+ * @throws ClientException
* @throws ServiceException
* @todo evaluate if we should sync mailboxes first
- *
*/
public function deleteMessage(Account $account,
string $mailboxId,
@@ -286,7 +287,11 @@ class MailManager implements IMailManager {
throw new ServiceException("Source mailbox $mailboxId does not exist", 0, $e);
}
try {
- $trashMailbox = $this->mailboxMapper->findSpecial($account, 'trash');
+ $trashMailboxId = $account->getMailAccount()->getTrashMailboxId();
+ if ($trashMailboxId === null) {
+ throw new TrashMailboxNotSetException();
+ }
+ $trashMailbox = $this->mailboxMapper->findById($trashMailboxId);
} catch (DoesNotExistException $e) {
throw new ServiceException("No trash folder", 0, $e);
}
diff --git a/lib/Service/MailTransmission.php b/lib/Service/MailTransmission.php
index d2bb318a1..8a82de861 100644
--- a/lib/Service/MailTransmission.php
+++ b/lib/Service/MailTransmission.php
@@ -42,6 +42,8 @@ use OCA\Mail\Events\DraftSavedEvent;
use OCA\Mail\Events\MessageSentEvent;
use OCA\Mail\Events\SaveDraftEvent;
use OCA\Mail\Exception\AttachmentNotFoundException;
+use OCA\Mail\Exception\ClientException;
+use OCA\Mail\Exception\SentMailboxNotSetException;
use OCA\Mail\Exception\ServiceException;
use OCA\Mail\IMAP\IMAPClientFactory;
use OCA\Mail\IMAP\MessageMapper;
@@ -107,6 +109,9 @@ class MailTransmission implements IMailTransmission {
Alias $alias = null,
Message $draft = null): void {
$account = $messageData->getAccount();
+ if ($account->getMailAccount()->getSentMailboxId() === null) {
+ throw new SentMailboxNotSetException();
+ }
if ($replyData !== null) {
$message = $this->buildReplyMessage($account, $messageData, $replyData);
@@ -180,6 +185,7 @@ class MailTransmission implements IMailTransmission {
*
* @return array
*
+ * @throws ClientException
* @throws ServiceException
*/
public function saveDraft(NewMessageData $message, Message $previousDraft = null): array {
@@ -225,7 +231,11 @@ class MailTransmission implements IMailTransmission {
$mail->send($transport, false, false);
// save the message in the drafts folder
$client = $this->imapClientFactory->getClient($account);
- $draftsMailbox = $this->mailboxMapper->findSpecial($account, 'drafts');
+ $draftsMailboxId = $account->getMailAccount()->getDraftsMailboxId();
+ if ($draftsMailboxId === null) {
+ throw new ClientException("No drafts mailbox configured");
+ }
+ $draftsMailbox = $this->mailboxMapper->findById($draftsMailboxId);
$newUid = $this->messageMapper->save(
$client,
$draftsMailbox,
diff --git a/package-lock.json b/package-lock.json
index 882c9c046..615e3e541 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3014,6 +3014,21 @@
"fastq": "^1.6.0"
}
},
+ "@riophae/vue-treeselect": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@riophae/vue-treeselect/-/vue-treeselect-0.4.0.tgz",
+ "integrity": "sha512-J4atYmBqXQmiPFK/0B5sXKjtnGc21mBJEiyKIDZwk0Q9XuynVFX6IJ4EpaLmUgL5Tve7HAS7wkiGGSti6Uaxcg==",
+ "requires": {
+ "@babel/runtime": "^7.3.1",
+ "babel-helper-vue-jsx-merge-props": "^2.0.3",
+ "easings-css": "^1.0.0",
+ "fuzzysearch": "^1.0.3",
+ "is-promise": "^2.1.0",
+ "lodash": "^4.0.0",
+ "material-colors": "^1.2.6",
+ "watch-size": "^2.0.0"
+ }
+ },
"@sinonjs/commons": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz",
@@ -4071,6 +4086,11 @@
"babel-types": "^6.24.1"
}
},
+ "babel-helper-vue-jsx-merge-props": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-2.0.3.tgz",
+ "integrity": "sha512-gsLiKK7Qrb7zYJNgiXKpXblxbV5ffSwR0f5whkPAaBAR4fhi6bwRZxX9wBlIc5M/v8CCkXUbXZL4N/nSE97cqg=="
+ },
"babel-loader": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.1.0.tgz",
@@ -6018,7 +6038,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": {
@@ -6080,6 +6100,11 @@
"stream-shift": "^1.0.0"
}
},
+ "easings-css": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/easings-css/-/easings-css-1.0.0.tgz",
+ "integrity": "sha512-7Uq7NdazNfVtr0RNmPAys8it0zKCuaqxJStYKEl72D3j4gbvXhhaM7iWNbqhA4C94ygCye6VuyhzBRQC4szeBg=="
+ },
"ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
@@ -7496,6 +7521,11 @@
"integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
"dev": true
},
+ "fuzzysearch": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/fuzzysearch/-/fuzzysearch-1.0.3.tgz",
+ "integrity": "sha1-3/yA9tawQiPyImqnndGUIxCW0Ag="
+ },
"gauge": {
"version": "2.7.4",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
@@ -8356,6 +8386,11 @@
"integrity": "sha1-DFLlS8yjkbssSUsh6GJtczbG45c=",
"dev": true
},
+ "is-promise": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
+ "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ=="
+ },
"is-regex": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz",
@@ -14497,6 +14532,11 @@
"xml-name-validator": "^3.0.0"
}
},
+ "watch-size": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/watch-size/-/watch-size-2.0.0.tgz",
+ "integrity": "sha512-M92R89dNoTPWyCD+HuUEDdhaDnh9jxPGOwlDc0u51jAgmjUvzqaEMynXSr3BaWs+QdHYk4KzibPy1TFtjLmOZQ=="
+ },
"watchpack": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.4.tgz",
diff --git a/package.json b/package.json
index c6a7a29a6..6d727db31 100644
--- a/package.json
+++ b/package.json
@@ -40,6 +40,7 @@
"@nextcloud/router": "^1.2.0",
"@nextcloud/vue": "^2.8.1",
"@nextcloud/vue-dashboard": "^1.0.1",
+ "@riophae/vue-treeselect": "^0.4.0",
"@vue/babel-preset-app": "^4.5.8",
"color-convert": "^2.0.1",
"core-js": "^3.6.5",
diff --git a/src/components/AccountDefaultsSettings.vue b/src/components/AccountDefaultsSettings.vue
new file mode 100644
index 000000000..df69c9d23
--- /dev/null
+++ b/src/components/AccountDefaultsSettings.vue
@@ -0,0 +1,176 @@
+<!--
+ - @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/>.
+ -->
+
+<template>
+ <div class="section">
+ <h2>{{ t('mail', 'Defaults') }}</h2>
+ <p class="settings-hint">
+ {{
+ t('mail', 'Here you can select where Nextcloud Mail stores your drafts as well as sent and deleted messages.')
+ }}
+ </p>
+ <table>
+ <tr>
+ <td>
+ {{ t('mail', 'Draft messages are saved to:') }}
+ </td>
+ <td>
+ <MailboxInlinePicker v-model="draftsMailbox" :account="account" :disabled="saving" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ {{ t('mail', 'Sent messages are saved to:') }}
+ </td>
+ <td>
+ <MailboxInlinePicker v-model="sentMailbox" :account="account" :disabled="saving" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ {{ t('mail', 'Deleted messages are moved to:') }}
+ </td>
+ <td>
+ <MailboxInlinePicker v-model="trashMailbox" :account="account" :disabled="saving" />
+ </td>
+ </tr>
+ </table>
+ </div>
+</template>
+
+<script>
+import logger from '../logger'
+import MailboxInlinePicker from './MailboxInlinePicker'
+
+export default {
+ name: 'AccountDefaultsSettings',
+ components: {
+ MailboxInlinePicker,
+ },
+ props: {
+ account: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ saving: false,
+ }
+ },
+ computed: {
+ draftsMailbox: {
+ get() {
+ const mb = this.$store.getters.getMailbox(this.account.draftsMailboxId)
+ if (!mb) {
+ return
+ }
+ return mb.databaseId
+ },
+ async set(draftsMailboxId) {
+ logger.debug('setting drafts mailbox to ' + draftsMailboxId)
+ this.saving = true
+ try {
+ await this.$store.dispatch('patchAccount', {
+ account: this.account,
+ data: {
+ draftsMailboxId,
+ },
+ })
+ } catch (error) {
+ logger.error('could not set drafts mailbox', {
+ error,
+ })
+ } finally {
+ this.saving = false
+ }
+ },
+ },
+ sentMailbox: {
+ get() {
+ const mb = this.$store.getters.getMailbox(this.account.sentMailboxId)
+ if (!mb) {
+ return
+ }
+ return mb.databaseId
+ },
+ async set(sentMailboxId) {
+ logger.debug('setting sent mailbox to ' + sentMailboxId)
+ this.saving = true
+ try {
+ await this.$store.dispatch('patchAccount', {
+ account: this.account,
+ data: {
+ sentMailboxId,
+ },
+ })
+ } catch (error) {
+ logger.error('could not set sent mailbox', {
+ error,
+ })
+ } finally {
+ this.saving = false
+ }
+ },
+ },
+ trashMailbox: {
+ get() {
+ const mb = this.$store.getters.getMailbox(this.account.trashMailboxId)
+ if (!mb) {
+ return
+ }
+ return mb.databaseId
+ },
+ async set(trashMailboxId) {
+ logger.debug('setting trash mailbox to ' + trashMailboxId)
+ this.saving = true
+ try {
+ await this.$store.dispatch('patchAccount', {
+ account: this.account,
+ data: {
+ trashMailboxId,
+ },
+ })
+ } catch (error) {
+ logger.error('could not set trash mailbox', {
+ error,
+ })
+ } finally {
+ this.saving = false
+ }
+ },
+ },
+ },
+}
+</script>
+
+<style lang="scss" scoped>
+.button.icon-rename {
+ background-color: transparent;
+ border: none;
+ opacity: 0.3;
+
+ &:hover,
+ &:focus {
+ opacity: 1;
+ }
+}
+</style>
diff --git a/src/components/Composer.vue b/src/components/Composer.vue
index d2b84b376..6736e7415 100644
--- a/src/components/Composer.vue
+++ b/src/components/Composer.vue
@@ -146,7 +146,8 @@
<ComposerAttachments v-model="attachments" :bus="bus" @upload="onAttachmentsUploading" />
<div class="composer-actions-right">
<p class="composer-actions-draft">
- <span v-if="savingDraft === true" id="draft-status">{{ t('mail', 'Saving draft …') }}</span>
+ <span v-if="!canSaveDraft" id="draft-status">{{ t('mail', 'Can not save draft because this account does not have a drafts mailbox configured.') }}</span>
+ <span v-else-if="savingDraft === true" id="draft-status">{{ t('mail', 'Saving draft …') }}</span>
<span v-else-if="savingDraft === false" id="draft-status">{{ t('mail', 'Draft saved') }}</span>
</p>
<Actions>
@@ -244,6 +245,11 @@ import { buildReplyBody } from '../ReplyBuilder'
import MailvelopeEditor from './MailvelopeEditor'
import { getMailvelope } from '../crypto/mailvelope'
import { isPgpgMessage } from '../crypto/pgp'
+import { matchError } from '../errors/match'
+import NoSentMailboxConfiguredError
+ from '../errors/NoSentMailboxConfiguredError'
+import NoDraftsMailboxConfiguredError
+ from '../errors/NoDraftsMailboxConfiguredError'
const debouncedSearch = debouncePromise(findRecipient, 500)
@@ -326,6 +332,7 @@ export default {
noReply: this.to.some((to) => to.email.startsWith('noreply@') || to.email.startsWith('no-reply@')),
draftsPromise: Promise.resolve(),
attachmentsPromise: Promise.resolve(),
+ canSaveDraft: true,
savingDraft: undefined,
saveDraftDebounced: debounce(700, this.saveDraft),
state: STATES.EDITING,
@@ -534,7 +541,26 @@ export default {
}
return this.draft(draftData)
})
- .catch(logger.error.bind(logger))
+ .then((uid) => {
+ // It works (again)
+ this.canSaveDraft = true
+
+ return uid
+ })
+ .catch(async(error) => {
+ console.error('could not save draft', error)
+ const canSave = await matchError(error, {
+ [NoDraftsMailboxConfiguredError.getName()]() {
+ return false
+ },
+ default() {
+ return true
+ },
+ })
+ if (!canSave) {
+ this.canSaveDraft = false
+ }
+ })
.then((uid) => {
this.savingDraft = false
return uid
@@ -610,11 +636,18 @@ export default {
.then((data) => this.send(data))
.then(() => logger.info('message sent'))
.then(() => (this.state = STATES.FINISHED))
- .catch((error) => {
+ .catch(async(error) => {
logger.error('could not send message', { error })
- if (error && error.toString) {
- this.errorText = error.toString()
- }
+ this.errorText = await matchError(error, {
+ [NoSentMailboxConfiguredError.getName()]() {
+ return t('mail', 'No sent mailbox configured. Please pick one in the account settings.')
+ },
+ default(error) {
+ if (error && error.toString) {
+ return error.toString()
+ }
+ },
+ })
this.state = STATES.ERROR
})
},
diff --git a/src/components/Envelope.vue b/src/components/Envelope.vue
index c3325c904..a8c5c4024 100644
--- a/src/components/Envelope.vue
+++ b/src/components/Envelope.vue
@@ -107,9 +107,14 @@ import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
import Moment from './Moment'
import MoveModal from './MoveModal'
import importantSvg from '../../img/important.svg'
+import { showError } from '@nextcloud/dialogs'
import Avatar from './Avatar'
import { calculateAccountColor } from '../util/AccountColor'
+import { matchError } from '../errors/match'
+import NoTrashMailboxConfiguredError
+ from '../errors/NoTrashMailboxConfiguredError'
+import logger from '../logger'
export default {
name: 'Envelope',
@@ -230,15 +235,27 @@ export default {
onToggleJunk() {
this.$store.dispatch('toggleEnvelopeJunk', this.data)
},
- onDelete() {
+ async onDelete() {
// Remove from selection first
this.setSelected(false)
// Delete
this.$emit('delete')
- this.$store.dispatch('deleteMessage', {
- id: this.data.databaseId,
- })
+ try {
+ await this.$store.dispatch('deleteMessage', {
+ id: this.data.databaseId,
+ })
+ } catch (error) {
+ showError(await matchError(error, {
+ [NoTrashMailboxConfiguredError.getName()]() {
+ return t('mail', 'No trash mailbox configured')
+ },
+ default(error) {
+ logger.error('could not delete message', error)
+ return t('mail', 'Could not delete message')
+ },
+ }))
+ }
},
onOpenMoveModal() {
this.setSelected(false)
diff --git a/src/components/EnvelopeList.vue b/src/components/EnvelopeList.vue
index 6235b49b7..068524dda 100644
--- a/src/components/EnvelopeList.vue
+++ b/src/components/EnvelopeList.vue
@@ -115,10 +115,14 @@
<script>
import Actions from '@nextcloud/vue/dist/Components/Actions'
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
+import { showError } from '@nextcloud/dialogs'
import Envelope from './Envelope'
import logger from '../logger'
import MoveModal from './MoveModal'
+import { matchError } from '../errors/match'
+import NoTrashMailboxConfiguredError
+ from '../errors/NoTrashMailboxConfiguredError'
export default {
name: 'EnvelopeList',
@@ -213,7 +217,7 @@ export default {
this.unselectAll()
},
deleteAllSelected() {
- this.selection.forEach((envelopeId) => {
+ this.selection.forEach(async(envelopeId) => {
// Navigate if the message being deleted is the one currently viewed
if (this.envelopes[envelopeId].databaseId === this.$route.params.threadId) {
let next
@@ -234,9 +238,21 @@ export default {
}
}
logger.info(`deleting message ${this.envelopes[envelopeId].databaseId}`)
- this.$store.dispatch('deleteMessage', {
- id: this.envelopes[envelopeId].databaseId,
- })
+ try {
+ await this.$store.dispatch('deleteMessage', {
+ id: this.envelopes[envelopeId].databaseId,
+ })
+ } catch (error) {
+ showError(await matchError(error, {
+ [NoTrashMailboxConfiguredError.getName()]() {
+ return t('mail', 'No trash mailbox configured')
+ },
+ default(error) {
+ logger.error('could not delete message', error)
+ return t('mail', 'Could not delete message')
+ },
+ }))
+ }
})
this.unselectAll()
},
diff --git a/src/components/Mailbox.vue b/src/components/Mailbox.vue
index 042f6a3f6..2202bc076 100644
--- a/src/components/Mailbox.vue
+++ b/src/components/Mailbox.vue
@@ -54,6 +54,9 @@ import MailboxNotCachedError from '../errors/MailboxNotCachedError'
import { matchError } from '../errors/match'
import { wait } from '../util/wait'
import EmptyMailboxSection from './EmptyMailboxSection'
+import { showError } from '@nextcloud/dialogs'
+import NoTrashMailboxConfiguredError
+ from '../errors/NoTrashMailboxConfiguredError'
export default {
name: 'Mailbox',
@@ -272,7 +275,7 @@ export default {
this.loadingMore = false
}
},
- handleShortcut(e) {
+ async handleShortcut(e) {
const envelopes = this.envelopes
const currentId = parseInt(this.$route.params.threadId, 10)
@@ -318,16 +321,25 @@ export default {
case 'del':
logger.debug('deleting', { env })
this.onDelete(env.databaseId)
- this.$store
- .dispatch('deleteMessage', {
+ try {
+ await this.$store.dispatch('deleteMessage', {
id: env.databaseId,
})
- .catch((error) =>
- logger.error('could not delete envelope', {
- env,
- error,
- })
- )
+ } catch (error) {
+ logger.error('could not delete envelope', {
+ env,
+ error,
+ })
+
+ showError(await matchError(error, {
+ [NoTrashMailboxConfiguredError.getName()]() {
+ return t('mail', 'No trash mailbox configured')
+ },
+ default() {
+ return t('mail', 'Could not delete message')
+ },
+ }))
+ }
break
case 'flag':
diff --git a/src/components/MailboxInlinePicker.vue b/src/components/MailboxInlinePicker.vue
new file mode 100644
index 000000000..18ff042e7
--- /dev/null
+++ b/src/components/MailboxInlinePicker.vue
@@ -0,0 +1,85 @@
+<template>
+ <Treeselect
+ ref="Treeselect"
+ v-model="selected"
+ :options="mailboxes"
+ :multiple="false"
+ :clearable="false"
+ :disabled="disabled" />
+</template>
+<script>
+import Treeselect from '@riophae/vue-treeselect'
+import '@riophae/vue-treeselect/dist/vue-treeselect.css'
+
+export default {
+ name: 'MailboxInlinePicker',
+ components: {
+ Treeselect,
+ },
+ props: {
+ account: {
+ type: Object,
+ required: true,
+ },
+ disabled: {
+ type: Boolean,
+ default: false,
+ },
+ value: {
+ type: Number,
+ required: false,
+ },
+ },
+ data() {
+ return {
+ selected: this.value,
+ }
+ },
+ computed: {
+ mailboxes() {
+ return this.getMailboxes()
+ },
+ },
+ watch: {
+ selected(val) {
+ if (val !== this.value) {
+ this.$emit('input', val)
+ this.selected = val
+ }
+ },
+ },
+ methods: {
+ getMailboxes(mailboxId) {
+ let mailboxes = []
+ if (!mailboxId) {
+ mailboxes = this.$store.getters.getMailboxes(this.account.accountId)
+ } else {
+ mailboxes = this.$store.getters.getSubMailboxes(mailboxId)
+ }
+ return mailboxes.map((mailbox) => {
+ return {
+ id: mailbox.databaseId,
+ label: mailbox.displayName,
+ children: mailbox.mailboxes.length > 0 ? this.getMailboxes(mailbox.databaseId) : '',
+ }
+ })
+ },
+ },
+}
+</script>
+<style>
+.vue-treeselect__control {
+ padding: 0;
+ border: 0;
+ width: 300px;
+}
+.vue-treeselect__control-arrow-container {
+ display: none;
+}
+.vue-treeselect--searchable .vue-treeselect__input-container {
+ padding-left: 0;
+}
+input.vue-treeselect__input {
+ margin: 0;
+}
+</style>
diff --git a/src/components/Navigation.vue b/src/components/Navigation.vue
index 61318f895..b8d1a98fb 100644
--- a/src/components/Navigation.vue
+++ b/src/components/Navigation.vue
@@ -41,7 +41,7 @@
v-show="
!group.isCollapsible ||
!group.account.collapsed ||
- SHOW_COLLAPSED.indexOf(item.specialRole) !== -1
+ !isCollapsed(group.account, item)
"
:key="item.databaseId"
:account="group.account"
@@ -72,8 +72,10 @@
<script>
import AppNavigation from '@nextcloud/vue/dist/Components/AppNavigation'
import AppNavigationNew from '@nextcloud/vue/dist/Components/AppNavigationNew'
-import AppNavigationSettings from '@nextcloud/vue/dist/Components/AppNavigationSettings'
-import AppNavigationSpacer from '@nextcloud/vue/dist/Components/AppNavigationSpacer'
+import AppNavigationSettings
+ from '@nextcloud/vue/dist/Components/AppNavigationSettings'
+import AppNavigationSpacer
+ from '@nextcloud/vue/dist/Components/AppNavigationSpacer'
import logger from '../logger'
import NavigationAccount from './NavigationAccount'
@@ -82,8 +84,6 @@ import NavigationMailbox from './NavigationMailbox'
import AppSettingsMenu from '../components/AppSettingsMenu'
-const SHOW_COLLAPSED = Object.seal(['inbox', 'flagged', 'drafts', 'sent'])
-
export default {
name: 'Navigation',
components: {
@@ -96,17 +96,12 @@ export default {
NavigationAccountExpandCollapse,
NavigationMailbox,
},
- data() {
- return {
- SHOW_COLLAPSED,
- }
- },
computed: {
menu() {
return this.$store.getters.accounts.map((account) => {
const mailboxes = this.$store.getters.getMailboxes(account.id)
const nonSpecialRoleMailboxes = mailboxes.filter(
- (mailbox) => SHOW_COLLAPSED.indexOf(mailbox.specialRole) === -1
+ (mailbox) => this.isCollapsed(account, mailbox)
)
const isCollapsible = nonSpecialRoleMailboxes.length > 1
@@ -120,6 +115,21 @@ export default {
},
},
methods: {
+ isCollapsed(account, mailbox) {
+ if (mailbox.specialRole === 'inbox') {
+ // INBOX is always visible
+ return false
+ }
+
+ if (mailbox.databaseId === account.draftsMailboxId
+ || mailbox.databaseId === account.sentMailboxId
+ || mailbox.databaseId === account.trashMailboxId) {
+ // Special folders are always visible
+ return false
+ }
+
+ return true
+ },
onNewMessage() {
const accountId = this.$route.params.accountId || this.$store.getters.accounts[0].id
diff --git a/src/components/ThreadEnvelope.vue b/src/components/ThreadEnvelope.vue
index aca573447..f7c10e9f3 100644
--- a/src/components/ThreadEnvelope.vue
+++ b/src/components/ThreadEnvelope.vue
@@ -164,6 +164,10 @@ import { generateUrl } from '@nextcloud/router'
import Modal from '@nextcloud/vue/dist/Components/Modal'
import { Base64 } from 'js-base64'
import importantSvg from '../../img/important.svg'
+import { matchError } from '../errors/match'
+import { showError } from '@nextcloud/dialogs'
+import NoTrashMailboxConfiguredError
+ from '../errors/NoTrashMailboxConfiguredError'
export default {
name: 'ThreadEnvelope',
@@ -327,11 +331,23 @@ export default {
onToggleJunk() {
this.$store.dispatch('toggleEnvelopeJunk', this.envelope)
},
- onDelete() {
+ async onDelete() {
this.$emit('delete', this.envelope.databaseId)
- this.$store.dispatch('deleteMessage', {
- id: this.envelope.databaseId,
- })
+ try {
+ await this.$store.dispatch('deleteMessage', {
+ id: this.envelope.databaseId,
+ })
+ } catch (error) {
+ showError(await matchError(error, {
+ [NoTrashMailboxConfiguredError.getName()]() {
+ return t('mail', 'No trash mailbox configured')
+ },
+ default(error) {
+ logger.error('could not delete message', error)
+ return t('mail', 'Could not delete message')
+ },
+ }))
+ }
},
async onShowSource() {
this.sourceLoading = true
diff --git a/src/errors/NoDraftsMailboxConfiguredError.js b/src/errors/NoDraftsMailboxConfiguredError.js
new file mode 100644
index 000000000..97e99a2eb
--- /dev/null
+++ b/src/errors/NoDraftsMailboxConfiguredError.js
@@ -0,0 +1,34 @@
+/**
+ * @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/>.
+ */
+
+export default class NoDraftsMailboxConfiguredError extends Error {
+
+ constructor(message) {
+ super(message)
+ this.name = NoDraftsMailboxConfiguredError.getName()
+ this.message = message
+ }
+
+ static getName() {
+ return 'NoDraftsMailboxConfiguredError'
+ }
+
+}
diff --git a/src/errors/NoSentMailboxConfiguredError.js b/src/errors/NoSentMailboxConfiguredError.js
new file mode 100644
index 000000000..c6d1cc38b
--- /dev/null
+++ b/src/errors/NoSentMailboxConfiguredError.js
@@ -0,0 +1,34 @@
+/**
+ * @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/>.
+ */
+
+export default class NoSentMailboxConfiguredError extends Error {
+
+ constructor(message) {
+ super(message)
+ this.name = NoSentMailboxConfiguredError.getName()
+ this.message = message
+ }
+
+ static getName() {
+ return 'NoSentMailboxConfiguredError'
+ }
+
+}
diff --git a/src/errors/NoTrashMailboxConfiguredError.js b/src/errors/NoTrashMailboxConfiguredError.js
new file mode 100644
index 000000000..fd81c23cb
--- /dev/null
+++ b/src/errors/NoTrashMailboxConfiguredError.js
@@ -0,0 +1,34 @@
+/**
+ * @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/>.
+ */
+
+export default class NoTrashMailboxConfiguredError extends Error {
+
+ constructor(message) {
+ super(message)
+ this.name = NoTrashMailboxConfiguredError.getName()
+ this.message = message
+ }
+
+ static getName() {
+ return 'NoTrashMailboxConfiguredError'
+ }
+
+}
diff --git a/src/errors/convert.js b/src/errors/convert.js
index e8bb40190..a860999a3 100644
--- a/src/errors/convert.js
+++ b/src/errors/convert.js
@@ -21,10 +21,16 @@
import MailboxLockedError from './MailboxLockedError'
import MailboxNotCachedError from './MailboxNotCachedError'
+import NoDraftsMailboxConfiguredError from './NoDraftsMailboxConfiguredError'
+import NoSentMailboxConfiguredError from './NoSentMailboxConfiguredError'
+import NoTrashMailboxConfiguredError from './NoTrashMailboxConfiguredError'
const map = {
+ 'OCA\\Mail\\Exception\\DraftsMailboxNotSetException': NoDraftsMailboxConfiguredError,
'OCA\\Mail\\Exception\\MailboxLockedException': MailboxLockedError,
'OCA\\Mail\\Exception\\MailboxNotCachedException': MailboxNotCachedError,
+ 'OCA\\Mail\\Exception\\SentMailboxNotSetException': NoSentMailboxConfiguredError,
+ 'OCA\\Mail\\Exception\\TrashMailboxNotSetException': NoTrashMailboxConfiguredError,
}
/**
diff --git a/src/main.js b/src/main.js
index 5db7da7ce..005c2e161 100644
--- a/src/main.js
+++ b/src/main.js
@@ -24,6 +24,7 @@ import Vue from 'vue'
import { getRequestToken } from '@nextcloud/auth'
import { sync } from 'vuex-router-sync'
import { generateFilePath } from '@nextcloud/router'
+import '@nextcloud/dialogs/styles/toast.scss'
import VueShortKey from 'vue-shortkey'
import VTooltip from 'v-tooltip'
diff --git a/src/service/MessageService.js b/src/service/MessageService.js
index 541f30462..d1fc0f964 100644
--- a/src/service/MessageService.js
+++ b/src/service/MessageService.js
@@ -138,23 +138,36 @@ export async function saveDraft(accountId, data) {
accountId,
})
- return (await axios.post(url, data)).data
+ try {
+ return (await axios.post(url, data)).data
+ } catch (e) {
+ throw convertAxiosError(e)
+ }
}
-export function sendMessage(accountId, data) {
+export async function sendMessage(accountId, data) {
const url = generateUrl('/apps/mail/api/accounts/{accountId}/send', {
accountId,
})
- return axios.post(url, data).then((resp) => resp.data)
+ try {
+ const resp = await axios.post(url, data)
+ return resp.data
+ } catch (e) {
+ throw convertAxiosError(e)
+ }
}
-export function deleteMessage(id) {
+export async function deleteMessage(id) {
const url = generateUrl('/apps/mail/api/messages/{id}', {
id,
})
- return axios.delete(url).then((resp) => resp.data)
+ try {
+ return (await axios.delete(url)).data
+ } catch (e) {
+ throw convertAxiosError(e)
+ }
}
export function moveMessage(id, destFolderId) {
diff --git a/src/views/AccountSettings.vue b/src/views/AccountSettings.vue
index b51a31bc6..2b9e815ff 100644
--- a/src/views/AccountSettings.vue
+++ b/src/views/AccountSettings.vue
@@ -16,6 +16,7 @@
</div>
<SignatureSettings :account="account" />
<EditorSettings :account="account" />
+ <AccountDefaultsSettings :account="account" />
<div v-if="!account.provisioned" class="section">
<h2>{{ t('mail', 'Mail server') }}</h2>
<div id="mail-settings">
@@ -35,16 +36,18 @@
import AppContent from '@nextcloud/vue/dist/Components/AppContent'
import Content from '@nextcloud/vue/dist/Components/Content'
+import AccountDefaultsSettings from '../components/AccountDefaultsSettings'
import AccountForm from '../components/AccountForm'
+import AliasSettings from '../components/AliasSettings'
import EditorSettings from '../components/EditorSettings'
import Logger from '../logger'
import Navigation from '../components/Navigation'
import SignatureSettings from '../components/SignatureSettings'
-import AliasSettings from '../components/AliasSettings'
export default {
name: 'AccountSettings',
components: {
+ AccountDefaultsSettings,
AccountForm,
AliasSettings,
AppContent,
diff --git a/tests/Integration/Db/MailAccountTest.php b/tests/Integration/Db/MailAccountTest.php
index c05f4c342..8bf2af949 100644
--- a/tests/Integration/Db/MailAccountTest.php
+++ b/tests/Integration/Db/MailAccountTest.php
@@ -66,6 +66,9 @@ class MailAccountTest extends TestCase {
'order' => 13,
'showSubscribedOnly' => null,
'personalNamespace' => null,
+ 'draftsMailboxId' => null,
+ 'sentMailboxId' => null,
+ 'trashMailboxId' => null,
], $a->toJson());
}
@@ -89,6 +92,9 @@ class MailAccountTest extends TestCase {
'order' => null,
'showSubscribedOnly' => null,
'personalNamespace' => null,
+ 'draftsMailboxId' => null,
+ 'sentMailboxId' => null,
+ 'trashMailboxId' => null,
];
$a = new MailAccount($expected);
// TODO: fix inconsistency
diff --git a/tests/Integration/Db/MailboxMapperTest.php b/tests/Integration/Db/MailboxMapperTest.php
index d086d993a..9c67c0664 100644
--- a/tests/Integration/Db/MailboxMapperTest.php
+++ b/tests/Integration/Db/MailboxMapperTest.php
@@ -129,38 +129,4 @@ class MailboxMapperTest extends TestCase {
$this->assertSame('INBOX', $result->getName());
}
-
- public function testNoTrashFound() {
- /** @var Account|MockObject $account */
- $account = $this->createMock(Account::class);
- $account->method('getId')->willReturn(13);
- $this->expectException(DoesNotExistException::class);
-
- $this->mapper->findSpecial($account, 'trash');
- }
-
- public function testFindTrash() {
- /** @var Account|MockObject $account */
- $account = $this->createMock(Account::class);
- $account->method('getId')->willReturn(13);
- $qb = $this->db->getQueryBuilder();
- $insert = $qb->insert($this->mapper->getTableName())
- ->values([
- 'name' => $qb->createNamedParameter('Trash'),
- 'account_id' => $qb->createNamedParameter(13, IQueryBuilder::PARAM_INT),
- 'sync_new_token' => $qb->createNamedParameter('VTEsVjE0Mjg1OTkxNDk='),
- 'sync_changed_token' => $qb->createNamedParameter('VTEsVjE0Mjg1OTkxNDk='),
- 'sync_vanished_token' => $qb->createNamedParameter('VTEsVjE0Mjg1OTkxNDk='),
- 'delimiter' => $qb->createNamedParameter('.'),
- 'messages' => $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT),
- 'unseen' => $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT),
- 'selectable' => $qb->createNamedParameter(true, IQueryBuilder::PARAM_BOOL),
- 'special_use' => $qb->createNamedParameter(json_encode(['trash'])),
- ]);
- $insert->execute();
-
- $result = $this->mapper->findSpecial($account, 'trash');
-
- $this->assertSame('Trash', $result->getName());
- }
}
diff --git a/tests/Integration/Service/MailTransmissionIntegrationTest.php b/tests/Integration/Service/MailTransmissionIntegrationTest.php
index 097b20313..8540465dc 100644
--- a/tests/Integration/Service/MailTransmissionIntegrationTest.php
+++ b/tests/Integration/Service/MailTransmissionIntegrationTest.php
@@ -95,6 +95,11 @@ class MailTransmissionIntegrationTest extends TestCase {
$this->attachmentService = OC::$server->query(IAttachmentService::class);
$userFolder = OC::$server->getUserFolder($this->user->getUID());
+ // Make sure the mailbox preferences are set
+ /** @var MailboxSync $mbSync */
+ $mbSync = OC::$server->query(MailboxSync::class);
+ $mbSync->sync($this->account, new NullLogger(), true);
+
$this->transmission = new MailTransmission(
$userFolder,
$this->attachmentService,
diff --git a/tests/Unit/IMAP/MailboxSyncTest.php b/tests/Unit/IMAP/MailboxSyncTest.php
index 645c9b628..159edcee1 100644
--- a/tests/Unit/IMAP/MailboxSyncTest.php
+++ b/tests/Unit/IMAP/MailboxSyncTest.php
@@ -33,11 +33,13 @@ use OCA\Mail\Account;
use OCA\Mail\Db\MailAccount;
use OCA\Mail\Db\MailAccountMapper;
use OCA\Mail\Db\MailboxMapper;
+use OCA\Mail\Events\MailboxesSynchronizedEvent;
use OCA\Mail\Folder;
use OCA\Mail\IMAP\FolderMapper;
use OCA\Mail\IMAP\IMAPClientFactory;
use OCA\Mail\IMAP\MailboxSync;
use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\EventDispatcher\IEventDispatcher;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\NullLogger;
@@ -61,6 +63,9 @@ class MailboxSyncTest extends TestCase {
/** @var MailboxSync */
private $sync;
+ /** @var IEventDispatcher|MockObject */
+ private $dispatcher;
+
protected function setUp(): void {
parent::setUp();
@@ -69,13 +74,15 @@ class MailboxSyncTest extends TestCase {
$this->mailAccountMapper = $this->createMock(MailAccountMapper::class);
$this->imapClientFactory = $this->createMock(IMAPClientFactory::class);
$this->timeFactory = $this->createMock(ITimeFactory::class);
+ $this->dispatcher = $this->createMock(IEventDispatcher::class);
$this->sync = new MailboxSync(
$this->mailboxMapper,
$this->folderMapper,
$this->mailAccountMapper,
$this->imapClientFactory,
- $this->timeFactory
+ $this->timeFactory,
+ $this->dispatcher
);
}
@@ -87,6 +94,7 @@ class MailboxSyncTest extends TestCase {
$this->timeFactory->method('getTime')->willReturn(100000);
$this->imapClientFactory->expects($this->never())
->method('getClient');
+ $this->dispatcher->expects($this->never())->method('dispatchTyped');
$this->sync->sync($account, new NullLogger());
}
@@ -116,6 +124,10 @@ class MailboxSyncTest extends TestCase {
$this->folderMapper->expects($this->once())
->method('detectFolderSpecialUse')
->with($folders);
+ $this->dispatcher
+ ->expects($this->once())
+ ->method('dispatchTyped')
+ ->with($this->equalTo(new MailboxesSynchronizedEvent($account)));
$this->sync->sync($account, new NullLogger());
}
diff --git a/tests/Unit/Listener/DeleteDraftListenerTest.php b/tests/Unit/Listener/DeleteDraftListenerTest.php
index f262120a2..774c42588 100644
--- a/tests/Unit/Listener/DeleteDraftListenerTest.php
+++ b/tests/Unit/Listener/DeleteDraftListenerTest.php
@@ -27,13 +27,13 @@ namespace OCA\Mail\Tests\Unit\Listener;
use ChristophWurst\Nextcloud\Testing\TestCase;
use OCA\Mail\Account;
+use OCA\Mail\Db\MailAccount;
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;
-use OCA\Mail\IMAP\MailboxSync;
use OCA\Mail\IMAP\MessageMapper;
use OCA\Mail\Listener\DeleteDraftListener;
use OCA\Mail\Model\IMessage;
@@ -56,9 +56,6 @@ class DeleteDraftListenerTest extends TestCase {
/** @var MessageMapper|MockObject */
private $messageMapper;
- /** @var MailboxSync|MockObject */
- private $mailboxSync;
-
/** @var LoggerInterface|MockObject */
private $logger;
@@ -71,14 +68,12 @@ class DeleteDraftListenerTest extends TestCase {
$this->imapClientFactory = $this->createMock(IMAPClientFactory::class);
$this->mailboxMapper = $this->createMock(MailboxMapper::class);
$this->messageMapper = $this->createMock(MessageMapper::class);
- $this->mailboxSync = $this->createMock(MailboxSync::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->listener = new DeleteDraftListener(
$this->imapClientFactory,
$this->mailboxMapper,
$this->messageMapper,
- $this->mailboxSync,
$this->logger
);
}
@@ -109,9 +104,11 @@ class DeleteDraftListenerTest extends TestCase {
$this->listener->handle($event);
}
- public function testHandleDraftSavedEventCreatesDraftsMailbox(): void {
+ public function testHandleDraftSavedEventNoDraftMailboxSet(): void {
/** @var Account|MockObject $account */
$account = $this->createMock(Account::class);
+ $mailAccount = new MailAccount();
+ $account->method('getMailAccount')->willReturn($mailAccount);
/** @var NewMessageData|MockObject $newMessageData */
$newMessageData = $this->createMock(NewMessageData::class);
$draft = new Message();
@@ -130,39 +127,42 @@ class DeleteDraftListenerTest extends TestCase {
->willReturn($client);
$mailbox = new Mailbox();
$mailbox->setName('Drafts');
- $this->mailboxMapper->expects($this->exactly(2))
- ->method('findSpecial')
- ->with($account, 'drafts')
- ->willReturnOnConsecutiveCalls(
- $this->throwException(new DoesNotExistException('')),
- $mailbox
- );
- $client->expects($this->once())
- ->method('createMailbox')
- ->with(
- 'Drafts',
- [
- 'special_use' => [
- \Horde_Imap_Client::SPECIALUSE_DRAFTS,
- ],
- ]
- );
- $this->mailboxSync->expects($this->once())
- ->method('sync')
- ->with($account, $this->logger, true);
- $this->messageMapper->expects($this->once())
- ->method('addFlag')
- ->with(
- $client,
- $mailbox,
- $uid,
- \Horde_Imap_Client::FLAG_DELETED
- );
- $client->expects($this->once())
- ->method('expunge')
- ->with('Drafts');
- $this->logger->expects($this->never())
- ->method('error');
+ $this->mailboxMapper->expects($this->never())
+ ->method('findById');
+ $this->logger->expects($this->once())->method('warning');
+
+ $this->listener->handle($event);
+ }
+
+ public function testHandleDraftSavedEventDraftMailboxNotFound(): void {
+ /** @var Account|MockObject $account */
+ $account = $this->createMock(Account::class);
+ $mailAccount = new MailAccount();
+ $mailAccount->setDraftsMailboxId(123);
+ $account->method('getMailAccount')->willReturn($mailAccount);
+ /** @var NewMessageData|MockObject $newMessageData */
+ $newMessageData = $this->createMock(NewMessageData::class);
+ $draft = new Message();
+ $uid = 123;
+ $draft->setUid($uid);
+ $event = new DraftSavedEvent(
+ $account,
+ $newMessageData,
+ $draft
+ );
+ /** @var \Horde_Imap_Client_Socket|MockObject $client */
+ $client = $this->createMock(\Horde_Imap_Client_Socket::class);
+ $this->imapClientFactory
+ ->method('getClient')
+ ->with($account)
+ ->willReturn($client);
+ $mailbox = new Mailbox();
+ $mailbox->setName('Drafts');
+ $this->mailboxMapper->expects($this->once())
+ ->method('findById')
+ ->with(123)
+ ->willThrowException(new DoesNotExistException(""));
+ $this->logger->expects($this->once())->method('warning');
$this->listener->handle($event);
}
@@ -188,6 +188,9 @@ class DeleteDraftListenerTest extends TestCase {
public function testHandleMessageSentEvent(): void {
/** @var Account|MockObject $account */
$account = $this->createMock(Account::class);
+ $mailAccount = new MailAccount();
+ $mailAccount->setDraftsMailboxId(123);
+ $account->method('getMailAccount')->willReturn($mailAccount);
/** @var NewMessageData|MockObject $newMessageData */
$newMessageData = $this->createMock(NewMessageData::class);
/** @var RepliedMessageData|MockObject $repliedMessageData */
@@ -216,8 +219,8 @@ class DeleteDraftListenerTest extends TestCase {
$mailbox = new Mailbox();
$mailbox->setName('Drafts');
$this->mailboxMapper->expects($this->once())
- ->method('findSpecial')
- ->with($account, 'drafts')
+ ->method('findById')
+ ->with(123)
->willReturn($mailbox);
$this->messageMapper->expects($this->once())
->method('addFlag')
diff --git a/tests/Unit/Listener/DraftMailboxCreatorListenerTest.php b/tests/Unit/Listener/DraftMailboxCreatorListenerTest.php
deleted file mode 100644
index a7c1bb1dd..000000000
--- a/tests/Unit/Listener/DraftMailboxCreatorListenerTest.php
+++ /dev/null
@@ -1,156 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-/**
- * @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/>.
- */
-
-namespace OCA\Mail\Tests\Unit\Listener;
-
-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;
-use OCA\Mail\Listener\DraftMailboxCreatorListener;
-use OCA\Mail\Model\NewMessageData;
-use OCP\AppFramework\Db\DoesNotExistException;
-use OCP\EventDispatcher\Event;
-use PHPUnit\Framework\MockObject\MockObject;
-use Psr\Log\LoggerInterface;
-
-class DraftMailboxCreatorListenerTest extends TestCase {
-
- /** @var MailboxMapper|MockObject */
- private $mailboxMapper;
-
- /** @var IMAPClientFactory|MockObject */
- private $imapClientFactory;
-
- /** @var MailboxSync|MockObject */
- private $mailboxSync;
-
- /** @var LoggerInterface|MockObject */
- private $logger;
-
- /** @var DraftMailboxCreatorListener */
- private $listener;
-
- protected function setUp(): void {
- parent::setUp();
-
- $this->mailboxMapper = $this->createMock(MailboxMapper::class);
- $this->imapClientFactory = $this->createMock(IMAPClientFactory::class);
- $this->mailboxSync = $this->createMock(MailboxSync::class);
- $this->logger = $this->createMock(LoggerInterface::class);
-
- $this->listener = new DraftMailboxCreatorListener(
- $this->mailboxMapper,
- $this->imapClientFactory,
- $this->mailboxSync,
- $this->logger
- );
- }
-
- public function testHandleUnrelated() {
- $event = new Event();
-
- $this->listener->handle($event);
-
- $this->addToAssertionCount(1);
- }
-
- public function testHandleSaveDraftEventMailboxExists(): void {
- /** @var Account|MockObject $account */
- $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,
- $draft
- );
- /** @var \Horde_Imap_Client_Socket|MockObject $client */
- $client = $this->createMock(\Horde_Imap_Client_Socket::class);
- $this->imapClientFactory
- ->method('getClient')
- ->with($account)
- ->willReturn($client);
- $mailbox = new Mailbox();
- $mailbox->setName('Drafts');
- $this->mailboxMapper->expects($this->once())
- ->method('findSpecial')
- ->with($account, 'drafts')
- ->willReturn($mailbox);
- $this->logger->expects($this->never())
- ->method('error');
-
- $this->listener->handle($event);
- }
-
- public function testHandleSaveDraftEventCreatesDraftsMailbox(): void {
- /** @var Account|MockObject $account */
- $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,
- $draft
- );
- /** @var \Horde_Imap_Client_Socket|MockObject $client */
- $client = $this->createMock(\Horde_Imap_Client_Socket::class);
- $this->imapClientFactory
- ->method('getClient')
- ->with($account)
- ->willReturn($client);
- $mailbox = new Mailbox();
- $mailbox->setName('Drafts');
- $this->mailboxMapper->expects($this->once())
- ->method('findSpecial')
- ->with($account, 'drafts')
- ->willThrowException(new DoesNotExistException(''));
- $client->expects($this->once())
- ->method('createMailbox')
- ->with(
- 'Drafts',
- [
- 'special_use' => [
- \Horde_Imap_Client::SPECIALUSE_DRAFTS,
- ],
- ]
- );
- $this->mailboxSync->expects($this->once())
- ->method('sync')
- ->with($account, $this->logger, true);
- $this->logger->expects($this->never())
- ->method('error');
-
- $this->listener->handle($event);
- }
-}
diff --git a/tests/Unit/Listener/SaveSentMessageListenerTest.php b/tests/Unit/Listener/SaveSentMessageListenerTest.php
index 35b627d43..e8c46e213 100644
--- a/tests/Unit/Listener/SaveSentMessageListenerTest.php
+++ b/tests/Unit/Listener/SaveSentMessageListenerTest.php
@@ -27,13 +27,13 @@ namespace OCA\Mail\Tests\Unit\Listener;
use ChristophWurst\Nextcloud\Testing\TestCase;
use OCA\Mail\Account;
+use OCA\Mail\Db\MailAccount;
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;
-use OCA\Mail\IMAP\MailboxSync;
use OCA\Mail\IMAP\MessageMapper;
use OCA\Mail\Listener\SaveSentMessageListener;
use OCA\Mail\Model\IMessage;
@@ -56,9 +56,6 @@ class SaveSentMessageListenerTest extends TestCase {
/** @var MessageMapper|MockObject */
private $messageMapper;
- /** @var MailboxSync|MockObject */
- private $mailboxSync;
-
/** @var LoggerInterface|MockObject */
private $logger;
@@ -71,14 +68,12 @@ class SaveSentMessageListenerTest extends TestCase {
$this->mailboxMapper = $this->createMock(MailboxMapper::class);
$this->imapClientFactory = $this->createMock(IMAPClientFactory::class);
$this->messageMapper = $this->createMock(MessageMapper::class);
- $this->mailboxSync = $this->createMock(MailboxSync::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->listener = new SaveSentMessageListener(
$this->mailboxMapper,
$this->imapClientFactory,
$this->messageMapper,
- $this->mailboxSync,
$this->logger
);
}
@@ -91,9 +86,11 @@ class SaveSentMessageListenerTest extends TestCase {
$this->addToAssertionCount(1);
}
- public function testHandleMessageSentMailboxDoesNotExistCantCreate(): void {
+ public function testHandleMessageSentMailboxNotSet(): void {
/** @var Account|MockObject $account */
$account = $this->createMock(Account::class);
+ $mailAccount = new MailAccount();
+ $account->method('getMailAccount')->willReturn($mailAccount);
/** @var NewMessageData|MockObject $newMessageData */
$newMessageData = $this->createMock(NewMessageData::class);
/** @var RepliedMessageData|MockObject $repliedMessageData */
@@ -112,42 +109,10 @@ class SaveSentMessageListenerTest extends TestCase {
$message,
$mail
);
- $mailbox = new Mailbox();
- $this->mailboxMapper->expects($this->exactly(2))
- ->method('findSpecial')
- ->withConsecutive(
- [$account, 'sent'],
- [$account, 'sent']
- )
- ->willReturnOnConsecutiveCalls(
- $this->throwException(new DoesNotExistException('')),
- $mailbox
- );
- $client = $this->createMock(\Horde_Imap_Client_Socket::class);
- $this->imapClientFactory
- ->method('getClient')
- ->with($account)
- ->willReturn($client);
- $exception = new \Horde_Imap_Client_Exception();
- $client->expects($this->once())
- ->method('createMailbox')
- ->with(
- 'Sent',
- [
- 'special_use' => [
- \Horde_Imap_Client::SPECIALUSE_SENT,
- ],
- ]
- )
- ->willThrowException($exception);
+ $this->mailboxMapper->expects($this->never())
+ ->method('findById');
$this->logger->expects($this->once())
- ->method('warning')
- ->with(
- 'Could not create sent mailbox: ',
- $this->equalTo([
- 'exception' => $exception,
- ])
- );
+ ->method('warning');
$this->listener->handle($event);
}
@@ -155,6 +120,9 @@ class SaveSentMessageListenerTest extends TestCase {
public function testHandleMessageSentMailboxDoesNotExist(): void {
/** @var Account|MockObject $account */
$account = $this->createMock(Account::class);
+ $mailAccount = new MailAccount();
+ $mailAccount->setSentMailboxId(123);
+ $account->method('getMailAccount')->willReturn($mailAccount);
/** @var NewMessageData|MockObject $newMessageData */
$newMessageData = $this->createMock(NewMessageData::class);
/** @var RepliedMessageData|MockObject $repliedMessageData */
@@ -173,39 +141,14 @@ class SaveSentMessageListenerTest extends TestCase {
$message,
$mail
);
- $mailbox = new Mailbox();
- $this->mailboxMapper->expects($this->exactly(2))
- ->method('findSpecial')
- ->withConsecutive(
- [$account, 'sent'],
- [$account, 'sent']
- )
- ->willReturnOnConsecutiveCalls(
- $this->throwException(new DoesNotExistException('')),
- $mailbox
- );
- $client = $this->createMock(\Horde_Imap_Client_Socket::class);
- $this->imapClientFactory
- ->method('getClient')
- ->with($account)
- ->willReturn($client);
- $client->expects($this->once())
- ->method('createMailbox')
- ->with(
- 'Sent',
- [
- 'special_use' => [
- \Horde_Imap_Client::SPECIALUSE_SENT,
- ],
- ]
- );
- $this->messageMapper->expects($this->once())
- ->method('save')
- ->with(
- $this->anything(),
- $mailbox,
- $mail
- );
+ $this->mailboxMapper->expects($this->once())
+ ->method('findById')
+ ->with(123)
+ ->willThrowException(new DoesNotExistException(''));
+ $this->messageMapper->expects($this->never())
+ ->method('save');
+ $this->logger->expects($this->once())
+ ->method('error');
$this->listener->handle($event);
}
@@ -213,6 +156,9 @@ class SaveSentMessageListenerTest extends TestCase {
public function testHandleMessageSentSavingError(): void {
/** @var Account|MockObject $account */
$account = $this->createMock(Account::class);
+ $mailAccount = new MailAccount();
+ $mailAccount->setSentMailboxId(123);
+ $account->method('getMailAccount')->willReturn($mailAccount);
/** @var NewMessageData|MockObject $newMessageData */
$newMessageData = $this->createMock(NewMessageData::class);
/** @var RepliedMessageData|MockObject $repliedMessageData */
@@ -233,8 +179,8 @@ class SaveSentMessageListenerTest extends TestCase {
);
$mailbox = new Mailbox();
$this->mailboxMapper->expects($this->once())
- ->method('findSpecial')
- ->with($account, 'sent')
+ ->method('findById')
+ ->with(123)
->willReturn($mailbox);
$this->messageMapper->expects($this->once())
->method('save')
@@ -252,6 +198,9 @@ class SaveSentMessageListenerTest extends TestCase {
public function testHandleMessageSent(): void {
/** @var Account|MockObject $account */
$account = $this->createMock(Account::class);
+ $mailAccount = new MailAccount();
+ $mailAccount->setSentMailboxId(123);
+ $account->method('getMailAccount')->willReturn($mailAccount);
/** @var NewMessageData|MockObject $newMessageData */
$newMessageData = $this->createMock(NewMessageData::class);
/** @var RepliedMessageData|MockObject $repliedMessageData */
@@ -272,8 +221,8 @@ class SaveSentMessageListenerTest extends TestCase {
);
$mailbox = new Mailbox();
$this->mailboxMapper->expects($this->once())
- ->method('findSpecial')
- ->with($account, 'sent')
+ ->method('findById')
+ ->with(123)
->willReturn($mailbox);
$this->messageMapper->expects($this->once())
->method('save')
@@ -282,6 +231,8 @@ class SaveSentMessageListenerTest extends TestCase {
$mailbox,
$mail
);
+ $this->logger->expects($this->never())->method('warning');
+ $this->logger->expects($this->never())->method('error');
$this->listener->handle($event);
}
diff --git a/tests/Unit/Listener/TrashMailboxCreatorListenerTest.php b/tests/Unit/Listener/TrashMailboxCreatorListenerTest.php
deleted file mode 100644
index b54d93fb4..000000000
--- a/tests/Unit/Listener/TrashMailboxCreatorListenerTest.php
+++ /dev/null
@@ -1,168 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-/**
- * @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/>.
- */
-
-namespace OCA\Mail\Tests\Unit\Listener;
-
-use ChristophWurst\Nextcloud\Testing\TestCase;
-use OCA\Mail\Account;
-use OCA\Mail\Db\Mailbox;
-use OCA\Mail\Db\MailboxMapper;
-use OCA\Mail\Events\BeforeMessageDeletedEvent;
-use OCA\Mail\IMAP\IMAPClientFactory;
-use OCA\Mail\IMAP\MailboxSync;
-use OCA\Mail\Listener\TrashMailboxCreatorListener;
-use OCP\AppFramework\Db\DoesNotExistException;
-use OCP\EventDispatcher\Event;
-use PHPUnit\Framework\MockObject\MockObject;
-use Psr\Log\LoggerInterface;
-
-class TrashMailboxCreatorListenerTest extends TestCase {
-
- /** @var MailboxMapper|MockObject */
- private $mailboxMapper;
-
- /** @var IMAPClientFactory|MockObject */
- private $imapClientFactory;
-
- /** @var MailboxSync|MockObject */
- private $mailboxSync;
-
- /** @var LoggerInterface|MockObject */
- private $logger;
-
- /** @var TrashMailboxCreatorListener */
- private $listener;
-
- protected function setUp(): void {
- parent::setUp();
-
- $this->mailboxMapper = $this->createMock(MailboxMapper::class);
- $this->imapClientFactory = $this->createMock(IMAPClientFactory::class);
- $this->mailboxSync = $this->createMock(MailboxSync::class);
- $this->logger = $this->createMock(LoggerInterface::class);
-
- $this->listener = new TrashMailboxCreatorListener(
- $this->mailboxMapper,
- $this->imapClientFactory,
- $this->mailboxSync,
- $this->logger
- );
- }
-
- public function testHandleUnrelated(): void {
- $event = new Event();
-
- $this->listener->handle($event);
-
- $this->addToAssertionCount(1);
- }
-
- public function testHandleDoesAlreadyExist(): void {
- /** @var Account|MockObject $account */
- $account = $this->createMock(Account::class);
- $event = new BeforeMessageDeletedEvent(
- $account,
- 'INBOX',
- 123
- );
- $mailbox = new Mailbox();
- $this->mailboxMapper->expects($this->once())
- ->method('findSpecial')
- ->with($account, 'trash')
- ->willReturn($mailbox);
-
- $this->listener->handle($event);
- }
-
- public function testHandleDoesNotExistCantCreate(): void {
- /** @var Account|MockObject $account */
- $account = $this->createMock(Account::class);
- $event = new BeforeMessageDeletedEvent(
- $account,
- 'INBOX',
- 123
- );
- $this->mailboxMapper->expects($this->once())
- ->method('findSpecial')
- ->with($account, 'trash')
- ->willThrowException(new DoesNotExistException(''));
- $client = $this->createMock(\Horde_Imap_Client_Socket::class);
- $this->imapClientFactory->expects($this->once())
- ->method('getClient')
- ->with($account)
- ->willReturn($client);
- $client->expects($this->once())
- ->method('createMailbox')
- ->with(
- 'Trash',
- [
- 'special_use' => [
- \Horde_Imap_Client::SPECIALUSE_TRASH,
- ],
- ]
- )
- ->willThrowException(new \Horde_Imap_Client_Exception());
- $this->logger->expects($this->once())
- ->method('error');
-
- $this->listener->handle($event);
- }
-
- public function testHandleDoesNotExist(): void {
- /** @var Account|MockObject $account */
- $account = $this->createMock(Account::class);
- $event = new BeforeMessageDeletedEvent(
- $account,
- 'INBOX',
- 123
- );
- $this->mailboxMapper->expects($this->once())
- ->method('findSpecial')
- ->with($account, 'trash')
- ->willThrowException(new DoesNotExistException(''));
- $client = $this->createMock(\Horde_Imap_Client_Socket::class);
- $this->imapClientFactory->expects($this->once())
- ->method('getClient')
- ->with($account)
- ->willReturn($client);
- $client->expects($this->once())
- ->method('createMailbox')
- ->with(
- 'Trash',
- [
- 'special_use' => [
- \Horde_Imap_Client::SPECIALUSE_TRASH,
- ],
- ]
- );
- $this->mailboxSync->expects($this->once())
- ->method('sync')
- ->with($account, $this->logger, true);
- $this->logger->expects($this->never())
- ->method('error');
-
- $this->listener->handle($event);
- }
-}
diff --git a/tests/Unit/Service/MailManagerTest.php b/tests/Unit/Service/MailManagerTest.php
index d5cece922..e553abb31 100644
--- a/tests/Unit/Service/MailManagerTest.php
+++ b/tests/Unit/Service/MailManagerTest.php
@@ -26,6 +26,7 @@ namespace OCA\Mail\Tests\Unit\Service;
use ChristophWurst\Nextcloud\Testing\TestCase;
use Horde_Imap_Client_Socket;
use OCA\Mail\Account;
+use OCA\Mail\Db\MailAccount;
use OCA\Mail\Db\Mailbox;
use OCA\Mail\Db\MailboxMapper;
use OCA\Mail\Db\MessageMapper as DbMessageMapper;
@@ -188,9 +189,12 @@ class MailManagerTest extends TestCase {
);
}
- public function testDeleteMessageTrashFolderNotFound(): void {
+ public function testDeleteMessageTrashMailboxNotFound(): void {
/** @var Account|MockObject $account */
$account = $this->createMock(Account::class);
+ $mailAccount = new MailAccount();
+ $mailAccount->setTrashMailboxId(123);
+ $account->method('getMailAccount')->willReturn($mailAccount);
$this->eventDispatcher->expects($this->once())
->method('dispatch')
->with(
@@ -202,8 +206,8 @@ class MailManagerTest extends TestCase {
->with($account, 'INBOX')
->willReturn($this->createMock(Mailbox::class));
$this->mailboxMapper->expects($this->once())
- ->method('findSpecial')
- ->with($account, 'trash')
+ ->method('findById')
+ ->with(123)
->willThrowException(new DoesNotExistException(""));
$this->expectException(ServiceException::class);
@@ -217,6 +221,9 @@ class MailManagerTest extends TestCase {
public function testDeleteMessage(): void {
/** @var Account|MockObject $account */
$account = $this->createMock(Account::class);
+ $mailAccount = new MailAccount();
+ $mailAccount->setTrashMailboxId(123);
+ $account->method('getMailAccount')->willReturn($mailAccount);
$inbox = new Mailbox();
$inbox->setName('INBOX');
$trash = new Mailbox();
@@ -228,8 +235,8 @@ class MailManagerTest extends TestCase {
->with($account, 'INBOX')
->willReturn($inbox);
$this->mailboxMapper->expects($this->once())
- ->method('findSpecial')
- ->with($account, 'trash')
+ ->method('findById')
+ ->with(123)
->willReturn($trash);
$client = $this->createMock(Horde_Imap_Client_Socket::class);
$this->imapClientFactory->expects($this->once())
@@ -254,6 +261,9 @@ class MailManagerTest extends TestCase {
public function testExpungeMessage(): void {
/** @var Account|MockObject $account */
$account = $this->createMock(Account::class);
+ $mailAccount = new MailAccount();
+ $mailAccount->setTrashMailboxId(123);
+ $account->method('getMailAccount')->willReturn($mailAccount);
$source = new Mailbox();
$source->setName('Trash');
$trash = new Mailbox();
@@ -265,8 +275,8 @@ class MailManagerTest extends TestCase {
->with($account, 'Trash')
->willReturn($source);
$this->mailboxMapper->expects($this->once())
- ->method('findSpecial')
- ->with($account, 'trash')
+ ->method('findById')
+ ->with(123)
->willReturn($trash);
$client = $this->createMock(Horde_Imap_Client_Socket::class);
$this->imapClientFactory->expects($this->once())
diff --git a/tests/Unit/Service/MailTransmissionTest.php b/tests/Unit/Service/MailTransmissionTest.php
index 4aeaf2cda..63cf1587d 100644
--- a/tests/Unit/Service/MailTransmissionTest.php
+++ b/tests/Unit/Service/MailTransmissionTest.php
@@ -100,6 +100,7 @@ class MailTransmissionTest extends TestCase {
public function testSendNewMessage() {
$mailAccount = new MailAccount();
$mailAccount->setUserId('testuser');
+ $mailAccount->setSentMailboxId(123);
/** @var Account|MockObject $account */
$account = $this->createMock(Account::class);
$account->method('getMailAccount')->willReturn($mailAccount);
@@ -122,6 +123,7 @@ class MailTransmissionTest extends TestCase {
public function testSendMessageFromAlias() {
$mailAccount = new MailAccount();
$mailAccount->setUserId('testuser');
+ $mailAccount->setSentMailboxId(123);
/** @var Account|MockObject $account */
$account = $this->createMock(Account::class);
$account->method('getMailAccount')->willReturn($mailAccount);
@@ -152,6 +154,7 @@ class MailTransmissionTest extends TestCase {
public function testSendNewMessageWithCloudAttachments() {
$mailAccount = new MailAccount();
$mailAccount->setUserId('testuser');
+ $mailAccount->setSentMailboxId(123);
/** @var Account|MockObject $account */
$account = $this->createMock(Account::class);
$account->method('getMailAccount')->willReturn($mailAccount);
@@ -194,6 +197,7 @@ class MailTransmissionTest extends TestCase {
public function testReplyToAnExistingMessage() {
$mailAccount = new MailAccount();
$mailAccount->setUserId('testuser');
+ $mailAccount->setSentMailboxId(123);
/** @var Account|MockObject $account */
$account = $this->createMock(Account::class);
$account->method('getMailAccount')->willReturn($mailAccount);
@@ -222,6 +226,7 @@ class MailTransmissionTest extends TestCase {
public function testSaveDraft() {
$mailAccount = new MailAccount();
$mailAccount->setUserId('testuser');
+ $mailAccount->setDraftsMailboxId(123);
/** @var Account|MockObject $account */
$account = $this->createMock(Account::class);
$account->method('getMailAccount')->willReturn($mailAccount);
@@ -239,8 +244,8 @@ class MailTransmissionTest extends TestCase {
->willReturn($client);
$draftsMailbox = new \OCA\Mail\Db\Mailbox();
$this->mailboxMapper->expects($this->once())
- ->method('findSpecial')
- ->with($account, 'drafts')
+ ->method('findById')
+ ->with(123)
->willReturn($draftsMailbox);
$this->messageMapper->expects($this->once())
->method('save')