Add credits for Marek
[trojita.git] / src / Composer / Submission.cpp
blob39058ddea849210d3144a36976abe8609972cd56
1 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net>
2 Copyright (C) 2012 Peter Amidon <peter@picnicpark.org>
4 This file is part of the Trojita Qt IMAP e-mail client,
5 http://trojita.flaska.net/
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of
10 the License or (at your option) version 3 or any later version
11 accepted by the membership of KDE e.V. (or its successor approved
12 by the membership of KDE e.V.), which shall act as a proxy
13 defined in Section 14 of version 3 of the license.
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
20 You should have received a copy of the GNU General Public License
21 along with this program. If not, see <http://www.gnu.org/licenses/>.
23 #include <QCoreApplication>
24 #include <QBuffer>
25 #include <QSettings>
26 #include "Composer/Submission.h"
27 #include "Composer/MessageComposer.h"
28 #include "Imap/Model/Model.h"
29 #include "Imap/Tasks/AppendTask.h"
30 #include "Imap/Tasks/GenUrlAuthTask.h"
31 #include "Imap/Tasks/UidSubmitTask.h"
32 #include "MSA/Sendmail.h"
33 #include "MSA/SMTP.h"
35 namespace {
36 const int PROGRESS_MAX = 1000;
37 // We're very likely almost half-way there -- let's call it 45%
38 const int PROGRESS_SAVING_DONE = PROGRESS_MAX * 0.45;
39 // Updating flags might take roughly as much time as the URLAUTH
40 const int PROGRESS_DELIVERY_DONE = PROGRESS_MAX * 0.95;
42 const int PROGRESS_DELIVERY_START_WITHOUT_SAVING = PROGRESS_MAX * 0.10;
43 const int PROGRESS_DELIVERY_START_WITH_SAVING = PROGRESS_MAX * 0.5;
46 namespace Composer
49 QString submissionProgressToString(const Submission::SubmissionProgress progress)
51 switch (progress) {
52 case Submission::STATE_INIT:
53 return QStringLiteral("INIT");
54 case Submission::STATE_BUILDING_MESSAGE:
55 return QStringLiteral("BUILDING_MESSAGE");
56 case Submission::STATE_SAVING:
57 return QStringLiteral("SAVING");
58 case Submission::STATE_PREPARING_URLAUTH:
59 return QStringLiteral("PREPARING_URLAUTH");
60 case Submission::STATE_SUBMITTING:
61 return QStringLiteral("SUBMITTING");
62 case Submission::STATE_UPDATING_FLAGS:
63 return QStringLiteral("UPDATING_FLAGS");
64 case Submission::STATE_SENT:
65 return QStringLiteral("SENT");
66 case Submission::STATE_FAILED:
67 return QStringLiteral("FAILED");
69 return QStringLiteral("[unknown: %1]").arg(QString::number(static_cast<int>(progress)));
72 Submission::Submission(QObject *parent, std::shared_ptr<Composer::AbstractComposer> composer,
73 Imap::Mailbox::Model *model, MSA::MSAFactory *msaFactory, const QString &accountId)
74 : QObject(parent)
75 , m_appendUidReceived(false)
76 , m_appendUidValidity(0)
77 , m_appendUid(0)
78 , m_genUrlAuthReceived(false)
79 , m_saveToSentFolder(false)
80 , m_useBurl(false)
81 , m_useImapSubmit(false)
82 , m_state(STATE_INIT)
83 , m_msaMaximalProgress(0)
84 , m_source(composer)
85 , m_model(model)
86 , m_msaFactory(msaFactory)
87 , m_accountId(accountId)
88 , m_updateReplyingToMessageFlagsTask(0)
89 , m_updateForwardingMessageFlagsTask(0)
91 m_source->setPreloadEnabled(shouldBuildMessageLocally());
94 Submission::~Submission() = default;
96 QString Submission::accountId() const
98 return m_accountId;
101 void Submission::changeConnectionState(const SubmissionProgress state)
103 emit logged(Common::LOG_SUBMISSION, QStringLiteral("Submission"), QStringLiteral("Progress: %1 -> %2").arg(
104 submissionProgressToString(m_state), submissionProgressToString(state)));
105 m_state = state;
107 // Now broadcast a human-readable message and update the progress dialog
108 switch (state) {
109 case STATE_INIT:
110 emit progressMin(0);
111 emit progressMax(0);
112 emit progress(0);
113 emit updateStatusMessage(tr("Preparing to send"));
114 break;
115 case STATE_BUILDING_MESSAGE:
116 emit progressMax(0);
117 emit progress(0);
118 emit updateStatusMessage(tr("Creating message"));
119 break;
120 case STATE_SAVING:
121 emit progressMax(0);
122 emit progress(0);
123 emit updateStatusMessage(tr("Saving to the sent folder"));
124 break;
125 case STATE_PREPARING_URLAUTH:
126 emit progressMax(PROGRESS_MAX);
127 emit progress(PROGRESS_SAVING_DONE);
128 emit updateStatusMessage(tr("Preparing message for delivery"));
129 break;
130 case STATE_SUBMITTING:
131 emit progressMax(PROGRESS_MAX);
132 emit progress(m_saveToSentFolder ? PROGRESS_DELIVERY_START_WITH_SAVING : PROGRESS_DELIVERY_START_WITHOUT_SAVING);
133 emit updateStatusMessage(tr("Submitting message"));
134 break;
135 case STATE_UPDATING_FLAGS:
136 emit progressMax(PROGRESS_MAX);
137 emit progress(PROGRESS_DELIVERY_DONE);
138 emit updateStatusMessage(tr("Updating message keywords"));
139 break;
140 case STATE_SENT:
141 emit progressMax(PROGRESS_MAX);
142 emit progress(PROGRESS_MAX);
143 emit updateStatusMessage(tr("Message sent"));
144 break;
145 case STATE_FAILED:
146 // revert to the busy indicator
147 emit progressMin(0);
148 emit progressMax(0);
149 emit progress(0);
150 emit updateStatusMessage(tr("Sending failed"));
151 break;
155 void Submission::setImapOptions(const bool saveToSentFolder, const QString &sentFolderName,
156 const QString &hostname, const QString &username, const bool useImapSubmit)
158 m_saveToSentFolder = saveToSentFolder;
159 m_sentFolderName = sentFolderName;
160 m_imapHostname = hostname;
161 m_imapUsername = username;
162 m_useImapSubmit = useImapSubmit;
163 m_source->setPreloadEnabled(shouldBuildMessageLocally());
166 void Submission::setSmtpOptions(const bool useBurl, const QString &smtpUsername)
168 m_useBurl = useBurl;
169 if (m_useBurl && !m_model->isGenUrlAuthSupported()) {
170 emit logged(Common::LOG_SUBMISSION, QStringLiteral("Submission"), QStringLiteral("Cannot BURL without the URLAUTH extension"));
171 m_useBurl = false;
173 m_smtpUsername = smtpUsername;
174 m_source->setPreloadEnabled(shouldBuildMessageLocally());
177 void Submission::send()
179 if (!m_model) {
180 gotError(tr("The IMAP connection has disappeared. "
181 "You'll have close the composer, save the draft and re-open it later. "
182 "The attachments will have to be added later. Sorry for the trouble, "
183 "please see <a href=\"https://projects.flaska.net/issues/640\">https://projects.flaska.net/issues/640</a> "
184 "for details."));
185 return;
188 // this double-updating is needed in case the same Submission attempts to send a message more than once
189 changeConnectionState(STATE_INIT);
190 changeConnectionState(STATE_BUILDING_MESSAGE);
192 if (shouldBuildMessageLocally() && !m_source->isReadyForSerialization()) {
193 // we have to wait until the data arrive
194 // FIXME: relax this to wait here
195 gotError(tr("Some data are not available yet"));
196 } else {
197 slotMessageDataAvailable();
202 void Submission::slotMessageDataAvailable()
204 m_rawMessageData.clear();
205 QBuffer buf(&m_rawMessageData);
206 buf.open(QIODevice::WriteOnly);
207 QString errorMessage;
208 QList<Imap::Mailbox::CatenatePair> catenateable;
210 if (shouldBuildMessageLocally() && !m_source->asRawMessage(&buf, &errorMessage)) {
211 gotError(tr("Cannot send right now -- saving failed:\n %1").arg(errorMessage));
212 return;
214 if (m_model->isCatenateSupported() && !m_source->asCatenateData(catenateable, &errorMessage)) {
215 gotError(tr("Cannot send right now -- saving (CATENATE) failed:\n %1").arg(errorMessage));
216 return;
219 if (m_saveToSentFolder) {
220 Q_ASSERT(m_model);
221 m_appendUidReceived = false;
222 m_genUrlAuthReceived = false;
224 changeConnectionState(STATE_SAVING);
225 QPointer<Imap::Mailbox::AppendTask> appendTask = 0;
227 if (m_model->isCatenateSupported()) {
228 // FIXME: without UIDPLUS, there isn't much point in $SubmitPending...
229 appendTask = QPointer<Imap::Mailbox::AppendTask>(
230 m_model->appendIntoMailbox(
231 m_sentFolderName,
232 catenateable,
233 QStringList() << QStringLiteral("\\Seen"),
234 m_source->timestamp()));
235 } else {
236 // FIXME: without UIDPLUS, there isn't much point in $SubmitPending...
237 appendTask = QPointer<Imap::Mailbox::AppendTask>(
238 m_model->appendIntoMailbox(
239 m_sentFolderName,
240 m_rawMessageData,
241 QStringList() << QStringLiteral("\\Seen"),
242 m_source->timestamp()));
245 Q_ASSERT(appendTask);
246 connect(appendTask.data(), &Imap::Mailbox::AppendTask::appendUid, this, &Submission::slotAppendUidKnown);
247 connect(appendTask.data(), &Imap::Mailbox::ImapTask::completed, this, &Submission::slotAppendSucceeded);
248 connect(appendTask.data(), &Imap::Mailbox::ImapTask::failed, this, &Submission::slotAppendFailed);
249 } else {
250 slotInvokeMsaNow();
254 void Submission::slotAskForUrl()
256 Q_ASSERT(m_appendUidReceived && m_useBurl);
257 changeConnectionState(STATE_PREPARING_URLAUTH);
258 Imap::Mailbox::GenUrlAuthTask *genUrlAuthTask = QPointer<Imap::Mailbox::GenUrlAuthTask>(
259 m_model->generateUrlAuthForMessage(m_imapHostname,
260 killDomainPartFromString(m_imapUsername),
261 m_sentFolderName,
262 m_appendUidValidity, m_appendUid, QString(),
263 QStringLiteral("submit+%1").arg(
264 killDomainPartFromString(m_smtpUsername))
266 connect(genUrlAuthTask, &Imap::Mailbox::GenUrlAuthTask::gotAuth, this, &Submission::slotGenUrlAuthReceived);
267 connect(genUrlAuthTask, &Imap::Mailbox::ImapTask::failed, this, &Submission::gotError);
270 void Submission::slotInvokeMsaNow()
272 changeConnectionState(STATE_SUBMITTING);
273 MSA::AbstractMSA *msa = m_msaFactory->create(this);
274 connect(msa, &MSA::AbstractMSA::progressMax, this, &Submission::onMsaProgressMaxChanged);
275 connect(msa, &MSA::AbstractMSA::progress, this, &Submission::onMsaProgressCurrentChanged);
276 connect(msa, &MSA::AbstractMSA::sent, this, &Submission::sent);
277 connect(msa, &MSA::AbstractMSA::error, this, &Submission::gotError);
278 connect(msa, &MSA::AbstractMSA::passwordRequested, this, &Submission::passwordRequested);
279 connect(msa, &MSA::AbstractMSA::logged, this, &Submission::logged);
280 connect(this, &Submission::gotPassword, msa, &MSA::AbstractMSA::setPassword);
281 connect(this, &Submission::canceled, msa, &MSA::AbstractMSA::cancel);
283 if (m_useImapSubmit && msa->supportsImapSending() && m_appendUidReceived) {
284 Imap::Mailbox::UidSubmitOptionsList options;
285 options.append(qMakePair<QByteArray,QVariant>("FROM", m_source->rawFromAddress()));
286 Q_FOREACH(const QByteArray &recipient, m_source->rawRecipientAddresses()) {
287 options.append(qMakePair<QByteArray,QVariant>("RECIPIENT", recipient));
289 msa->sendImap(m_sentFolderName, m_appendUidValidity, m_appendUid, options);
290 } else if (m_genUrlAuthReceived && m_useBurl) {
291 msa->sendBurl(m_source->rawFromAddress(), m_source->rawRecipientAddresses(), m_urlauth.toUtf8());
292 } else {
293 msa->sendMail(m_source->rawFromAddress(), m_source->rawRecipientAddresses(), m_rawMessageData);
297 void Submission::setPassword(const QString &password)
299 emit gotPassword(password);
302 void Submission::cancelPassword()
304 emit canceled();
307 void Submission::gotError(const QString &error)
309 emit logged(Common::LogKind::LOG_SUBMISSION, QStringLiteral("Submission"), QStringLiteral("Error: ") + error);
310 changeConnectionState(STATE_FAILED);
311 emit failed(error);
314 void Submission::sent()
316 if (m_source->replyingToMessage().isValid()) {
317 m_updateReplyingToMessageFlagsTask = m_model->setMessageFlags(QModelIndexList() << m_source->replyingToMessage(),
318 QStringLiteral("\\Answered"), Imap::Mailbox::FLAG_ADD);
319 connect(m_updateReplyingToMessageFlagsTask, &Imap::Mailbox::ImapTask::completed,
320 this, &Submission::onUpdatingFlagsOfReplyingToSucceded);
321 connect(m_updateReplyingToMessageFlagsTask, &Imap::Mailbox::ImapTask::failed,
322 this, &Submission::onUpdatingFlagsOfReplyingToFailed);
323 changeConnectionState(STATE_UPDATING_FLAGS);
324 } else if (m_source->forwardingMessage().isValid()) {
325 m_updateForwardingMessageFlagsTask = m_model->setMessageFlags(QModelIndexList() << m_source->forwardingMessage(),
326 QStringLiteral("$Forwarded"), Imap::Mailbox::FLAG_ADD);
327 connect(m_updateForwardingMessageFlagsTask, &Imap::Mailbox::ImapTask::completed,
328 this, &Submission::onUpdatingFlagsOfForwardingSucceeded);
329 connect(m_updateForwardingMessageFlagsTask, &Imap::Mailbox::ImapTask::failed,
330 this, &Submission::onUpdatingFlagsOfForwardingFailed);
331 changeConnectionState(STATE_UPDATING_FLAGS);
332 } else {
333 changeConnectionState(STATE_SENT);
334 emit succeeded();
336 #if 0
337 if (m_appendUidReceived) {
338 // FIXME: check the UIDVALIDITY!!!
339 // FIXME: doesn't work at all; the messageIndexByUid() only works on already selected mailboxes
340 QModelIndex message = m_mainWindow->imapModel()->
341 messageIndexByUid(QSettings().value(Common::SettingsNames::composerImapSentKey, QStringLiteral("Sent")).toString(), m_appendUid);
342 if (message.isValid()) {
343 m_mainWindow->imapModel()->setMessageFlags(QModelIndexList() << message,
344 QLatin1String("\\Seen $Submitted"), Imap::Mailbox::FLAG_USE_THESE);
347 #endif
349 // FIXME: move back to the currently selected mailbox
352 /** @short Remember the APPENDUID as reported by the APPEND operation */
353 void Submission::slotAppendUidKnown(const uint uidValidity, const uint uid)
355 m_appendUidValidity = uidValidity;
356 m_appendUid = uid;
359 void Submission::slotAppendFailed(const QString &error)
361 gotError(tr("APPEND failed: %1").arg(error));
364 void Submission::slotAppendSucceeded()
366 if (m_appendUid && m_appendUidValidity) {
367 // Only ever consider valid UIDVALIDITY/UID pair
368 m_appendUidReceived = true;
370 if (m_useBurl) {
371 slotAskForUrl();
372 } else {
373 slotInvokeMsaNow();
375 } else {
376 m_useBurl = false;
377 emit logged(Common::LogKind::LOG_SUBMISSION, QStringLiteral("Submission"),
378 QStringLiteral("APPEND does not contain APPENDUID or UIDVALIDITY, cannot use BURL or the SUBMIT command"));
379 slotInvokeMsaNow();
383 /** @short Remember the GENURLAUTH response */
384 void Submission::slotGenUrlAuthReceived(const QString &url)
386 m_urlauth = url;
387 if (!m_urlauth.isEmpty()) {
388 m_genUrlAuthReceived = true;
389 slotInvokeMsaNow();
390 } else {
391 gotError(tr("The URLAUTH response does not contain a proper URL"));
395 /** @short Remove the "@domain" from a string */
396 QString Submission::killDomainPartFromString(const QString &s)
398 return s.split(QLatin1Char('@'))[0];
401 /** @short Return true if the message payload shall be built locally */
402 bool Submission::shouldBuildMessageLocally() const
404 if (!m_useImapSubmit) {
405 // sending via SMTP or Sendmail
406 // Unless all of URLAUTH, CATENATE and BURL is present and enabled, we will still have to download the data in the end
407 return ! (m_useBurl && m_model->isCatenateSupported() && m_model->isGenUrlAuthSupported());
408 } else {
409 return ! m_model->isCatenateSupported();
413 void Submission::onUpdatingFlagsOfReplyingToSucceded()
415 m_updateReplyingToMessageFlagsTask = 0;
416 changeConnectionState(STATE_SENT);
417 emit succeeded();
420 void Submission::onUpdatingFlagsOfReplyingToFailed()
422 m_updateReplyingToMessageFlagsTask = 0;
423 emit logged(Common::LogKind::LOG_OTHER, QStringLiteral("Submission"),
424 QStringLiteral("Cannot update flags of the message we replied to -- interesting, but we cannot do anything at this point anyway"));
425 changeConnectionState(STATE_SENT);
426 emit succeeded();
429 void Submission::onUpdatingFlagsOfForwardingSucceeded()
431 m_updateForwardingMessageFlagsTask = 0;
432 changeConnectionState(STATE_SENT);
433 emit succeeded();
436 void Submission::onUpdatingFlagsOfForwardingFailed()
438 m_updateForwardingMessageFlagsTask = 0;
439 emit logged(Common::LogKind::LOG_OTHER, QStringLiteral("Submission"),
440 QStringLiteral("Cannot update flags of the message we forwarded -- interesting, but we cannot do anything at this point anyway"));
441 changeConnectionState(STATE_SENT);
442 emit succeeded();
445 void Submission::onMsaProgressCurrentChanged(const int value)
447 if (m_msaMaximalProgress > 0) {
448 // prevent division by zero or performing operations which do not make any sense
449 int low = m_saveToSentFolder ? PROGRESS_DELIVERY_START_WITH_SAVING : PROGRESS_DELIVERY_START_WITHOUT_SAVING;
450 int high = PROGRESS_DELIVERY_DONE;
451 emit progress(1.0 * value / m_msaMaximalProgress * (high - low) + low);
455 void Submission::onMsaProgressMaxChanged(const int max)
457 m_msaMaximalProgress = max;