presentation/MailOutboxSender.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include "data/AccountConfig.h" | ||
| 4 | #include "data/MailOutboxStore.h" | ||
| 5 | |||
| 6 | #include <QObject> | ||
| 7 | #include <QSet> | ||
| 8 | #include <QTimer> | ||
| 9 | #include <functional> | ||
| 10 | #include <optional> | ||
| 11 | |||
| 12 | // Sprint 108 S108.7.3 (D-20): the only way a composed message leaves MailJD. | ||
| 13 | // Entries of the outbox (MailOutboxStore) are sent when due through the SMTP | ||
| 14 | // account of their identity; the result is classified: | ||
| 15 | // | ||
| 16 | // network no connection, timeout, 4xx → retry with backoff | ||
| 17 | // account authentication refused, TLS failure → waits until the account | ||
| 18 | // changes, then retries | ||
| 19 | // recipients some RCPT refused permanently → sent to the others; the | ||
| 20 | // entry names the refused | ||
| 21 | // message 552/554 and other permanent refusals → failed; back to a draft | ||
| 22 | // | ||
| 23 | // The Date header is set when the message actually leaves. An entry found | ||
| 24 | // in "sending" at start (MailJD stopped after handing it over) is looked up | ||
| 25 | // in the Sent folder by Message-ID before anything is sent again. | ||
| 26 | class MailOutboxSender : public QObject { | ||
| 27 | 25 | Q_OBJECT | |
| 28 | public: | ||
| 29 | struct TransmitResult { | ||
| 30 | bool ok = false; | ||
| 31 | int code = 0; // the server's reply code (0: local failure) | ||
| 32 | QString error; // the server's words | ||
| 33 | QStringList refused; // partly refused recipients | ||
| 34 | QString refusalReply; | ||
| 35 | }; | ||
| 36 | using TransmitDone = std::function<void(const TransmitResult &)>; | ||
| 37 | struct Deps { | ||
| 38 | // The account with its SMTP password resolved (asynchronous). | ||
| 39 | std::function<void(const QString &accountId, std::function<void(std::optional<AccountConfig>)>)> resolveAccount; | ||
| 40 | // Sends bytes through SMTP (production: one SmtpService per message | ||
| 41 | // with partial recipients). | ||
| 42 | std::function<void(const AccountConfig &, const QString &from, const QStringList &recipients, | ||
| 43 | const QByteArray &message, TransmitDone)> transmit; | ||
| 44 | // Stores the private Sent copy; false = not now (another account), retried later. | ||
| 45 | std::function<bool(const OutboxEntry &, const QByteArray &sentCopy)> storeSentCopy; | ||
| 46 | // Flags the original (\Answered / $Forwarded) after the transmission. | ||
| 47 | std::function<void(const OutboxEntry &)> flagOriginal; | ||
| 48 | // After a transmission (D-23: an invitation writes its event now). | ||
| 49 | std::function<void(const OutboxEntry &, const QByteArray &sentBytes)> afterTransmit; | ||
| 50 | // Whether a Message-ID is already in the account's Sent folder (restart). | ||
| 51 | std::function<void(const OutboxEntry &, std::function<void(bool found)>)> sentContains; | ||
| 52 | // S108.8.4 (D-24): an invitation the calendar server delivers — no | ||
| 53 | // SMTP and no Sent copy; deliverByServer writes the event (the server | ||
| 54 | // then sends the invitations) and answers whether that worked. | ||
| 55 | std::function<bool(const OutboxEntry &)> serverDelivers; | ||
| 56 | std::function<bool(const OutboxEntry &)> deliverByServer; | ||
| 57 | std::function<bool()> online; | ||
| 58 | std::function<void(const QString &title, const QString &body)> notify; | ||
| 59 | std::function<QDateTime()> now; // tests drive time | ||
| 60 | }; | ||
| 61 | |||
| 62 | MailOutboxSender(MailOutboxStore &store, Deps deps, QObject *parent = nullptr); | ||
| 63 | |||
| 64 | // Startup: recovers entries left in "sending" and schedules the next run. | ||
| 65 | void start(); | ||
| 66 | qint64 enqueue(const OutboxEntry &entry); | ||
| 67 | // *Undo* within the undo window: the entry leaves the outbox; returns it | ||
| 68 | // (its draft session reopens). Empty when it is already on its way. | ||
| 69 | std::optional<OutboxEntry> takeBack(qint64 id); | ||
| 70 | bool sendNow(qint64 id); | ||
| 71 | bool reschedule(qint64 id, const QDateTime &due); | ||
| 72 | // D-20 "Send again only to these": a new attempt for the refused recipients. | ||
| 73 | bool resendToRefused(qint64 id); | ||
| 74 | // The account's settings changed: waiting entries try again. | ||
| 75 | void accountChanged(const QString &accountId); | ||
| 76 | // Network came back. | ||
| 77 | void networkAvailable(); | ||
| 78 | // Quit: messages in their undo window go now (the user pressed *Send*); | ||
| 79 | // waits up to `timeoutMs` for them. | ||
| 80 | void flushUndoWindow(int timeoutMs = 15000); | ||
| 81 | // Runs every due entry once (the timer calls it; tests too). | ||
| 82 | void processDue(); | ||
| 83 | bool busy() const { return !m_inFlight.isEmpty(); } | ||
| 84 | // RFC 5322 Date header replaced by the transmission time. | ||
| 85 | static QByteArray withDate(const QByteArray &message, const QDateTime &when); | ||
| 86 | // The error class of a failed transmission. | ||
| 87 | static QString classify(const TransmitResult &result); | ||
| 88 | static QDateTime nextRetry(const QDateTime &now, int attempts); | ||
| 89 | |||
| 90 | signals: | ||
| 91 | void changed(); | ||
| 92 | // A message left (to the accepted recipients); `late` when its scheduled | ||
| 93 | // time passed while MailJD was not running. | ||
| 94 | void transmitted(qint64 id, const QString &summaryTo, bool late); | ||
| 95 | void failedEntry(qint64 id, const QString &errorClass, const QString &error); | ||
| 96 | |||
| 97 | private: | ||
| 98 | MailOutboxStore &m_store; | ||
| 99 | Deps m_deps; | ||
| 100 | QTimer m_timer; | ||
| 101 | QSet<qint64> m_inFlight; | ||
| 102 | QDateTime now() const; | ||
| 103 | void scheduleNext(); | ||
| 104 | void transmit(const OutboxEntry &entry); | ||
| 105 | void deliverByServer(const OutboxEntry &entry); | ||
| 106 | void finish(const OutboxEntry &entry, const TransmitResult &result, const QByteArray &sentBytes); | ||
| 107 | }; | ||
| 108 |