Skip to content

Commit

Permalink
feat: move messages to junk folder
Browse files Browse the repository at this point in the history
Signed-off-by: Daniel Kesselberg <[email protected]>
  • Loading branch information
kesselb committed Jul 7, 2023
1 parent c3066c3 commit 45d1f06
Show file tree
Hide file tree
Showing 17 changed files with 761 additions and 7 deletions.
2 changes: 1 addition & 1 deletion appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Positive:
Learn more about the Nextcloud Ethical AI Rating [in our blog](https://nextcloud.com/blog/nextcloud-ethical-ai-rating/).
]]></description>
<version>3.3.0-alpha.1</version>
<version>3.3.0-alpha.2</version>
<licence>agpl</licence>
<author>Greta Doçi</author>
<author homepage="https://github.com/nextcloud/groupware">Nextcloud Groupware Team</author>
Expand Down
2 changes: 2 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
use OCA\Mail\Listener\InteractionListener;
use OCA\Mail\Listener\MailboxesSynchronizedSpecialMailboxesUpdater;
use OCA\Mail\Listener\MessageCacheUpdaterListener;
use OCA\Mail\Listener\MoveJunkListener;
use OCA\Mail\Listener\NewMessageClassificationListener;
use OCA\Mail\Listener\OauthTokenRefreshListener;
use OCA\Mail\Listener\SaveSentMessageListener;
Expand Down Expand Up @@ -121,6 +122,7 @@ public function register(IRegistrationContext $context): void {
$context->registerEventListener(MessageFlaggedEvent::class, MessageCacheUpdaterListener::class);
$context->registerEventListener(MessageFlaggedEvent::class, SpamReportListener::class);
$context->registerEventListener(MessageFlaggedEvent::class, HamReportListener::class);
$context->registerEventListener(MessageFlaggedEvent::class, MoveJunkListener::class);
$context->registerEventListener(MessageDeletedEvent::class, MessageCacheUpdaterListener::class);
$context->registerEventListener(MessageSentEvent::class, AddressCollectionListener::class);
$context->registerEventListener(MessageSentEvent::class, FlagRepliedMessageListener::class);
Expand Down
11 changes: 10 additions & 1 deletion lib/Controller/AccountsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,9 @@ public function patchAccount(int $id,
int $sentMailboxId = null,
int $trashMailboxId = null,
int $archiveMailboxId = null,
bool $signatureAboveQuote = null): JSONResponse {
bool $signatureAboveQuote = null,
int $junkMailboxId = null,
bool $moveJunk = null): JSONResponse {
$account = $this->accountService->find($this->currentUserId, $id);

$dbAccount = $account->getMailAccount();
Expand Down Expand Up @@ -274,6 +276,13 @@ public function patchAccount(int $id,
if ($signatureAboveQuote !== null) {
$dbAccount->setSignatureAboveQuote($signatureAboveQuote);
}
if ($junkMailboxId !== null) {
$this->mailManager->getMailbox($this->currentUserId, $junkMailboxId);
$dbAccount->setJunkMailboxId($junkMailboxId);
}
if ($moveJunk !== null) {
$dbAccount->setMoveJunk($moveJunk);
}
return new JSONResponse(
$this->accountService->save($dbAccount)
);
Expand Down
11 changes: 11 additions & 0 deletions lib/Db/MailAccount.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@
* @method void setOauthTokenTtl(int $ttl)
* @method int|null getSmimeCertificateId()
* @method void setSmimeCertificateId(int|null $smimeCertificateId)
* @method int|null getJunkMailboxId()
* @method void setJunkMailboxId(?int $id)
* @method bool isMoveJunk()
* @method void setMoveJunk(bool $moveJunk)
*/
class MailAccount extends Entity {
public const SIGNATURE_MODE_PLAIN = 0;
Expand Down Expand Up @@ -171,6 +175,9 @@ class MailAccount extends Entity {
/** @var int|null */
protected $smimeCertificateId;

protected ?int $junkMailboxId = null;
protected bool $moveJunk = false;

/**
* @param array $params
*/
Expand Down Expand Up @@ -239,6 +246,8 @@ public function __construct(array $params = []) {
$this->addType('signatureAboveQuote', 'boolean');
$this->addType('signatureMode', 'int');
$this->addType('smimeCertificateId', 'integer');
$this->addType('junkMailboxId', 'integer');
$this->addType('moveJunk', 'boolean');
}

/**
Expand Down Expand Up @@ -268,6 +277,8 @@ public function toJson() {
'signatureAboveQuote' => ($this->isSignatureAboveQuote() === true),
'signatureMode' => $this->getSignatureMode(),
'smimeCertificateId' => $this->getSmimeCertificateId(),
'junkMailboxId' => $this->getJunkMailboxId(),
'moveJunk' => ($this->isMoveJunk() === true),
];

if (!is_null($this->getOutboundHost())) {
Expand Down
9 changes: 9 additions & 0 deletions lib/Listener/MailboxesSynchronizedSpecialMailboxesUpdater.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,15 @@ public function handle(Event $event): void {
$mailAccount->setArchiveMailboxId(null);
}
}
if ($mailAccount->getJunkMailboxId() === null || !array_key_exists($mailAccount->getJunkMailboxId(), $mailboxes)) {
try {
$junkMailbox = $this->findSpecial($mailboxes, 'junk');
$mailAccount->setJunkMailboxId($junkMailbox->getId());
} catch (DoesNotExistException) {
$this->logger->info("Account " . $account->getId() . " does not have an junk mailbox");
$mailAccount->setJunkMailboxId(null);
}
}

$this->mailAccountMapper->update($mailAccount);
}
Expand Down
102 changes: 102 additions & 0 deletions lib/Listener/MoveJunkListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

declare(strict_types=1);

/**
* @copyright Copyright (c) 2023 Daniel Kesselberg <[email protected]>
*
* @author Daniel Kesselberg <[email protected]>
*
* @license AGPL-3.0-or-later
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/

namespace OCA\Mail\Listener;

use OCA\Mail\Contracts\IMailManager;
use OCA\Mail\Events\MessageFlaggedEvent;
use OCA\Mail\Exception\ClientException;
use OCA\Mail\Exception\ServiceException;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use Psr\Log\LoggerInterface;

/**
* @template-implements IEventListener<Event|MessageFlaggedEvent>
*/
class MoveJunkListener implements IEventListener {
public function __construct(
private IMailManager $mailManager,
private LoggerInterface $logger,
) {
}

public function handle(Event $event): void {
if (!$event instanceof MessageFlaggedEvent || $event->getFlag() !== '$junk') {
return;
}

$account = $event->getAccount();
$mailAccount = $account->getMailAccount();

if (!$mailAccount->isMoveJunk()) {
return;
}

$mailbox = $event->getMailbox();

if ($event->isSet() && $mailAccount->getJunkMailboxId() !== $mailbox->getId()) {
try {
$junkMailbox = $this->mailManager->getMailbox($account->getUserId(), $mailAccount->getJunkMailboxId());
} catch (ClientException) {
$this->logger->debug('move to junk enabled, but junk mailbox does not exist. account_id: {account_id}, junk_mailbox_id: {junk_mailbox_id}', [
'account_id' => $account->getId(),
'junk_mailbox_id' => $mailAccount->getJunkMailboxId(),
]);
return;
}

try {
$this->mailManager->moveMessage(
$account,
$mailbox->getName(),
$event->getUid(),
$account,
$junkMailbox->getName(),
);
} catch (ServiceException $e) {
$this->logger->error('move message to junk mailbox failed. account_id: {account_id}', [
'exception' => $e,
'account_id' => $account->getId(),
]);
}
} elseif (!$event->isSet() && 'INBOX' !== $mailbox->getName()) {
try {
$this->mailManager->moveMessage(
$account,
$mailbox->getName(),
$event->getUid(),
$account,
'INBOX',
);
} catch (ServiceException $e) {
$this->logger->error('move message to inbox failed. account_id: {account_id}', [
'exception' => $e,
'account_id' => $account->getId(),
]);
}
}
}
}
52 changes: 52 additions & 0 deletions lib/Migration/Version3001Date20230307113544.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

declare(strict_types=1);

/**
* @copyright Copyright (c) 2023 Daniel Kesselberg <[email protected]>
*
* @author Daniel Kesselberg <[email protected]>
*
* @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\Migration;

use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;

class Version3001Date20230307113544 extends SimpleMigrationStep {
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();

$accountsTable = $schema->getTable('mail_accounts');
$accountsTable->addColumn('junk_mailbox_id', 'integer', [
'notnull' => false,
'default' => null,
'length' => 20,
]);
$accountsTable->addColumn('move_junk', 'boolean', [
'notnull' => false,
'default' => false,
]);

return $schema;
}
}
5 changes: 5 additions & 0 deletions src/components/AccountSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@
:account="account" />
</div>
</AppSettingsSection>
<AppSettingsSection id="junk-settings" :title="t('mail', 'Junk settings')">
<JunkSettings :account="account" />
</AppSettingsSection>
<AppSettingsSection id="trusted-sender" :title="t('mail', 'Trusted senders')">
<TrustedSenders />
</AppSettingsSection>
Expand Down Expand Up @@ -119,6 +122,7 @@ import SieveAccountForm from './SieveAccountForm'
import SieveFilterForm from './SieveFilterForm'
import OutOfOfficeForm from './OutOfOfficeForm'
import CertificateSettings from './CertificateSettings'
import JunkSettings from './JunkSettings'
export default {
name: 'AccountSettings',
Expand All @@ -135,6 +139,7 @@ export default {
AccountDefaultsSettings,
OutOfOfficeForm,
CertificateSettings,
JunkSettings,
},
props: {
account: {
Expand Down
26 changes: 24 additions & 2 deletions src/components/Envelope.vue
Original file line number Diff line number Diff line change
Expand Up @@ -585,8 +585,30 @@ export default {
onToggleSeen() {
this.$store.dispatch('toggleEnvelopeSeen', { envelope: this.data })
},
onToggleJunk() {
this.$store.dispatch('toggleEnvelopeJunk', this.data)
async onToggleJunk() {
const removeEnvelope = await this.$store.dispatch('moveEnvelopeToJunk', this.data)
/**
* moveEnvelopeToJunk returns true if the envelope should be moved to a different mailbox.
*
* Our backend (MessageMapper.move) implemented move as copy and delete.
* The message is copied to another mailbox and gets a new UID; the message in the current folder is deleted.
*
* Trigger the delete event here to open the next envelope and remove the current envelope from the list.
* The delete event bubbles up to Mailbox.onDelete to the actual implementation.
*
* In Mailbox.onDelete, fetchNextEnvelopes requires the current envelope to find the next envelope.
* Therefore, it must run before removing the envelope.
*/
if (removeEnvelope) {
await this.$emit('delete', this.data.databaseId)
}
await this.$store.dispatch('toggleEnvelopeJunk', {
envelope: this.data,
removeEnvelope,
})
},
async onDelete() {
// Remove from selection first
Expand Down
Loading

0 comments on commit 45d1f06

Please sign in to comment.