SVN_SILENT made messages (after extraction)
[trojita.git] / src / Composer / Submission.cpp
blob904ff8d79c6df66f7786d0dc8a6506e4a2f624ff
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("STATE_INIT");
54 case Submission::STATE_BUILDING_MESSAGE:
55 return QStringLiteral("STATE_BUILDING_MESSAGE");
56 case Submission::STATE_SAVING:
57 return QStringLiteral("STATE_SAVING");
58 case Submission::STATE_PREPARING_URLAUTH:
59 return QStringLiteral("STATE_PREPARING_URLAUTH");
60 case Submission::STATE_SUBMITTING:
61 return QStringLiteral("STATE_SUBMITTING");
62 case Submission::STATE_UPDATING_FLAGS:
63 return QStringLiteral("STATE_UPDATING_FLAGS");
64 case Submission::STATE_SENT:
65 return QStringLiteral("STATE_SENT");
66 case Submission::STATE_FAILED:
67 return QStringLiteral("STATE_FAILED");
69 return QStringLiteral("[unknown: %1]").arg(QString::number(static_cast<int>(progress)));
72 Submission::Submission(QObject *parent, Imap::Mailbox::Model *model, MSA::MSAFactory *msaFactory, const QString &accountId) :
73 QObject(parent),
74 m_appendUidReceived(false), m_appendUidValidity(0), m_appendUid(0), m_genUrlAuthReceived(false),
75 m_saveToSentFolder(false), m_useBurl(false), m_useImapSubmit(false), m_state(STATE_INIT),
76 m_msaMaximalProgress(0),
77 m_composer(0), m_model(model), m_msaFactory(msaFactory),
78 m_accountId(accountId),
79 m_updateReplyingToMessageFlagsTask(0),
80 m_updateForwardingMessageFlagsTask(0)
82 m_composer = new Composer::MessageComposer(model, this);
83 m_composer->setPreloadEnabled(shouldBuildMessageLocally());
86 MessageComposer *Submission::composer()
88 return m_composer;
91 Submission::~Submission()
95 QString Submission::accountId() const
97 return m_accountId;
100 void Submission::changeConnectionState(const SubmissionProgress state)
102 m_state = state;
103 if (m_model)
104 m_model->logTrace(0, Common::LOG_OTHER, QStringLiteral("Submission"), submissionProgressToString(m_state));
106 // Now broadcast a human-readable message and update the progress dialog
107 switch (state) {
108 case STATE_INIT:
109 emit progressMin(0);
110 emit progressMax(0);
111 emit progress(0);
112 emit updateStatusMessage(tr("Preparing to send"));
113 break;
114 case STATE_BUILDING_MESSAGE:
115 emit progressMax(0);
116 emit progress(0);
117 emit updateStatusMessage(tr("Creating message"));
118 break;
119 case STATE_SAVING:
120 emit progressMax(0);
121 emit progress(0);
122 emit updateStatusMessage(tr("Saving to the sent folder"));
123 break;
124 case STATE_PREPARING_URLAUTH:
125 emit progressMax(PROGRESS_MAX);
126 emit progress(PROGRESS_SAVING_DONE);
127 emit updateStatusMessage(tr("Preparing message for delivery"));
128 break;
129 case STATE_SUBMITTING:
130 emit progressMax(PROGRESS_MAX);
131 emit progress(m_saveToSentFolder ? PROGRESS_DELIVERY_START_WITH_SAVING : PROGRESS_DELIVERY_START_WITHOUT_SAVING);
132 emit updateStatusMessage(tr("Submitting message"));
133 break;
134 case STATE_UPDATING_FLAGS:
135 emit progressMax(PROGRESS_MAX);
136 emit progress(PROGRESS_DELIVERY_DONE);
137 emit updateStatusMessage(tr("Updating message keywords"));
138 break;
139 case STATE_SENT:
140 emit progressMax(PROGRESS_MAX);
141 emit progress(PROGRESS_MAX);
142 emit updateStatusMessage(tr("Message sent"));
143 break;
144 case STATE_FAILED:
145 // revert to the busy indicator
146 emit progressMin(0);
147 emit progressMax(0);
148 emit progress(0);
149 emit updateStatusMessage(tr("Sending failed"));
150 break;
154 void Submission::setImapOptions(const bool saveToSentFolder, const QString &sentFolderName,
155 const QString &hostname, const QString &username, const bool useImapSubmit)
157 m_saveToSentFolder = saveToSentFolder;
158 m_sentFolderName = sentFolderName;
159 m_imapHostname = hostname;
160 m_imapUsername = username;
161 m_useImapSubmit = useImapSubmit;
162 m_composer->setPreloadEnabled(shouldBuildMessageLocally());
165 void Submission::setSmtpOptions(const bool useBurl, const QString &smtpUsername)
167 m_useBurl = useBurl;
168 if (m_useBurl && !m_model->isGenUrlAuthSupported()) {
169 m_model->logTrace(0, Common::LOG_OTHER, QStringLiteral("Submission"), QStringLiteral("Cannot BURL without the URLAUTH extension"));
170 m_useBurl = false;
172 m_smtpUsername = smtpUsername;
173 m_composer->setPreloadEnabled(shouldBuildMessageLocally());
176 void Submission::send()
178 if (!m_model) {
179 gotError(tr("The IMAP connection has disappeared. "
180 "You'll have close the composer, save the draft and re-open it later. "
181 "The attachments will have to be added later. Sorry for the trouble, "
182 "please see <a href=\"https://projects.flaska.net/issues/640\">https://projects.flaska.net/issues/640</a> "
183 "for details."));
184 return;
187 // this double-updating is needed in case the same Submission attempts to send a message more than once
188 changeConnectionState(STATE_INIT);
189 changeConnectionState(STATE_BUILDING_MESSAGE);
191 if (shouldBuildMessageLocally() && !m_composer->isReadyForSerialization()) {
192 // we have to wait until the data arrive
193 // FIXME: relax this to wait here
194 gotError(tr("Some data are not available yet"));
195 } else {
196 slotMessageDataAvailable();
201 void Submission::slotMessageDataAvailable()
203 m_rawMessageData.clear();
204 QBuffer buf(&m_rawMessageData);
205 buf.open(QIODevice::WriteOnly);
206 QString errorMessage;
207 QList<Imap::Mailbox::CatenatePair> catenateable;
209 if (shouldBuildMessageLocally() && !m_composer->asRawMessage(&buf, &errorMessage)) {
210 gotError(tr("Cannot send right now -- saving failed:\n %1").arg(errorMessage));
211 return;
213 if (m_model->isCatenateSupported() && !m_composer->asCatenateData(catenateable, &errorMessage)) {
214 gotError(tr("Cannot send right now -- saving (CATENATE) failed:\n %1").arg(errorMessage));
215 return;
218 if (m_saveToSentFolder) {
219 Q_ASSERT(m_model);
220 m_appendUidReceived = false;
221 m_genUrlAuthReceived = false;
223 changeConnectionState(STATE_SAVING);
224 QPointer<Imap::Mailbox::AppendTask> appendTask = 0;
226 if (m_model->isCatenateSupported()) {
227 // FIXME: without UIDPLUS, there isn't much point in $SubmitPending...
228 appendTask = QPointer<Imap::Mailbox::AppendTask>(
229 m_model->appendIntoMailbox(
230 m_sentFolderName,
231 catenateable,
232 QStringList() << QStringLiteral("\\Seen"),
233 m_composer->timestamp()));
234 } else {
235 // FIXME: without UIDPLUS, there isn't much point in $SubmitPending...
236 appendTask = QPointer<Imap::Mailbox::AppendTask>(
237 m_model->appendIntoMailbox(
238 m_sentFolderName,
239 m_rawMessageData,
240 QStringList() << QStringLiteral("\\Seen"),
241 m_composer->timestamp()));
244 Q_ASSERT(appendTask);
245 connect(appendTask.data(), &Imap::Mailbox::AppendTask::appendUid, this, &Submission::slotAppendUidKnown);
246 connect(appendTask.data(), &Imap::Mailbox::ImapTask::completed, this, &Submission::slotAppendSucceeded);
247 connect(appendTask.data(), &Imap::Mailbox::ImapTask::failed, this, &Submission::slotAppendFailed);
248 } else {
249 slotInvokeMsaNow();
253 void Submission::slotAskForUrl()
255 Q_ASSERT(m_appendUidReceived && m_useBurl);
256 changeConnectionState(STATE_PREPARING_URLAUTH);
257 Imap::Mailbox::GenUrlAuthTask *genUrlAuthTask = QPointer<Imap::Mailbox::GenUrlAuthTask>(
258 m_model->generateUrlAuthForMessage(m_imapHostname,
259 killDomainPartFromString(m_imapUsername),
260 m_sentFolderName,
261 m_appendUidValidity, m_appendUid, QString(),
262 QStringLiteral("submit+%1").arg(
263 killDomainPartFromString(m_smtpUsername))
265 connect(genUrlAuthTask, &Imap::Mailbox::GenUrlAuthTask::gotAuth, this, &Submission::slotGenUrlAuthReceived);
266 connect(genUrlAuthTask, &Imap::Mailbox::ImapTask::failed, this, &Submission::gotError);
269 void Submission::slotInvokeMsaNow()
271 changeConnectionState(STATE_SUBMITTING);
272 MSA::AbstractMSA *msa = m_msaFactory->create(this);
273 connect(msa, &MSA::AbstractMSA::progressMax, this, &Submission::onMsaProgressMaxChanged);
274 connect(msa, &MSA::AbstractMSA::progress, this, &Submission::onMsaProgressCurrentChanged);
275 connect(msa, &MSA::AbstractMSA::sent, this, &Submission::sent);
276 connect(msa, &MSA::AbstractMSA::error, this, &Submission::gotError);
277 connect(msa, &MSA::AbstractMSA::passwordRequested, this, &Submission::passwordRequested);
278 connect(this, &Submission::gotPassword, msa, &MSA::AbstractMSA::setPassword);
279 connect(this, &Submission::canceled, msa, &MSA::AbstractMSA::cancel);
281 if (m_useImapSubmit && msa->supportsImapSending() && m_appendUidReceived) {
282 Imap::Mailbox::UidSubmitOptionsList options;
283 options.append(qMakePair<QByteArray,QVariant>("FROM", m_composer->rawFromAddress()));
284 Q_FOREACH(const QByteArray &recipient, m_composer->rawRecipientAddresses()) {
285 options.append(qMakePair<QByteArray,QVariant>("RECIPIENT", recipient));
287 msa->sendImap(m_sentFolderName, m_appendUidValidity, m_appendUid, options);
288 } else if (m_genUrlAuthReceived && m_useBurl) {
289 msa->sendBurl(m_composer->rawFromAddress(), m_composer->rawRecipientAddresses(), m_urlauth.toUtf8());
290 } else {
291 msa->sendMail(m_composer->rawFromAddress(), m_composer->rawRecipientAddresses(), m_rawMessageData);
295 void Submission::setPassword(const QString &password)
297 emit gotPassword(password);
300 void Submission::cancelPassword()
302 emit canceled();
305 void Submission::gotError(const QString &error)
307 if (m_model)
308 m_model->logTrace(0, Common::LOG_OTHER, QStringLiteral("Submission"), QStringLiteral("gotError: %1").arg(error));
309 changeConnectionState(STATE_FAILED);
310 emit failed(error);
313 void Submission::sent()
315 if (m_composer->replyingToMessage().isValid()) {
316 m_updateReplyingToMessageFlagsTask = m_model->setMessageFlags(QModelIndexList() << m_composer->replyingToMessage(),
317 QStringLiteral("\\Answered"), Imap::Mailbox::FLAG_ADD);
318 connect(m_updateReplyingToMessageFlagsTask, &Imap::Mailbox::ImapTask::completed,
319 this, &Submission::onUpdatingFlagsOfReplyingToSucceded);
320 connect(m_updateReplyingToMessageFlagsTask, &Imap::Mailbox::ImapTask::failed,
321 this, &Submission::onUpdatingFlagsOfReplyingToFailed);
322 changeConnectionState(STATE_UPDATING_FLAGS);
323 } else if (m_composer->forwardingMessage().isValid()) {
324 m_updateForwardingMessageFlagsTask = m_model->setMessageFlags(QModelIndexList() << m_composer->forwardingMessage(),
325 QStringLiteral("$Forwarded"), Imap::Mailbox::FLAG_ADD);
326 connect(m_updateForwardingMessageFlagsTask, &Imap::Mailbox::ImapTask::completed,
327 this, &Submission::onUpdatingFlagsOfForwardingSucceeded);
328 connect(m_updateForwardingMessageFlagsTask, &Imap::Mailbox::ImapTask::failed,
329 this, &Submission::onUpdatingFlagsOfForwardingFailed);
330 changeConnectionState(STATE_UPDATING_FLAGS);
331 } else {
332 changeConnectionState(STATE_SENT);
333 emit succeeded();
335 #if 0
336 if (m_appendUidReceived) {
337 // FIXME: check the UIDVALIDITY!!!
338 // FIXME: doesn't work at all; the messageIndexByUid() only works on already selected mailboxes
339 QModelIndex message = m_mainWindow->imapModel()->
340 messageIndexByUid(QSettings().value(Common::SettingsNames::composerImapSentKey, tr("Sent")).toString(), m_appendUid);
341 if (message.isValid()) {
342 m_mainWindow->imapModel()->setMessageFlags(QModelIndexList() << message,
343 QLatin1String("\\Seen $Submitted"), Imap::Mailbox::FLAG_USE_THESE);
346 #endif
348 // FIXME: move back to the currently selected mailbox
351 /** @short Remember the APPENDUID as reported by the APPEND operation */
352 void Submission::slotAppendUidKnown(const uint uidValidity, const uint uid)
354 m_appendUidValidity = uidValidity;
355 m_appendUid = uid;
358 void Submission::slotAppendFailed(const QString &error)
360 gotError(tr("APPEND failed: %1").arg(error));
363 void Submission::slotAppendSucceeded()
365 if (m_appendUid && m_appendUidValidity) {
366 // Only ever consider valid UIDVALIDITY/UID pair
367 m_appendUidReceived = true;
369 if (m_useBurl) {
370 slotAskForUrl();
371 } else {
372 slotInvokeMsaNow();
374 } else {
375 m_useBurl = false;
376 m_model->logTrace(0, Common::LOG_OTHER, QStringLiteral("Submission"),
377 QStringLiteral("APPEND does not contain APPENDUID or UIDVALIDITY, cannot use BURL or the SUBMIT command"));
378 slotInvokeMsaNow();
382 /** @short Remember the GENURLAUTH response */
383 void Submission::slotGenUrlAuthReceived(const QString &url)
385 m_urlauth = url;
386 if (!m_urlauth.isEmpty()) {
387 m_genUrlAuthReceived = true;
388 slotInvokeMsaNow();
389 } else {
390 gotError(tr("The URLAUTH response does not contain a proper URL"));
394 /** @short Remove the "@domain" from a string */
395 QString Submission::killDomainPartFromString(const QString &s)
397 return s.split(QLatin1Char('@'))[0];
400 /** @short Return true if the message payload shall be built locally */
401 bool Submission::shouldBuildMessageLocally() const
403 if (!m_useImapSubmit) {
404 // sending via SMTP or Sendmail
405 // Unless all of URLAUTH, CATENATE and BURL is present and enabled, we will still have to download the data in the end
406 return ! (m_useBurl && m_model->isCatenateSupported() && m_model->isGenUrlAuthSupported());
407 } else {
408 return ! m_model->isCatenateSupported();
412 void Submission::onUpdatingFlagsOfReplyingToSucceded()
414 m_updateReplyingToMessageFlagsTask = 0;
415 changeConnectionState(STATE_SENT);
416 emit succeeded();
419 void Submission::onUpdatingFlagsOfReplyingToFailed()
421 m_updateReplyingToMessageFlagsTask = 0;
422 m_model->logTrace(0, Common::LOG_OTHER, QStringLiteral("Submission"),
423 QStringLiteral("Cannot update flags of the message we replied to -- interesting, but we cannot do anything at this point anyway"));
424 changeConnectionState(STATE_SENT);
425 emit succeeded();
428 void Submission::onUpdatingFlagsOfForwardingSucceeded()
430 m_updateForwardingMessageFlagsTask = 0;
431 changeConnectionState(STATE_SENT);
432 emit succeeded();
435 void Submission::onUpdatingFlagsOfForwardingFailed()
437 m_updateForwardingMessageFlagsTask = 0;
438 m_model->logTrace(0, Common::LOG_OTHER, QStringLiteral("Submission"),
439 QStringLiteral("Cannot update flags of the message we forwarded -- interesting, but we cannot do anything at this point anyway"));
440 changeConnectionState(STATE_SENT);
441 emit succeeded();
444 void Submission::onMsaProgressCurrentChanged(const int value)
446 if (m_msaMaximalProgress > 0) {
447 // prevent division by zero or performing operations which do not make any sense
448 int low = m_saveToSentFolder ? PROGRESS_DELIVERY_START_WITH_SAVING : PROGRESS_DELIVERY_START_WITHOUT_SAVING;
449 int high = PROGRESS_DELIVERY_DONE;
450 emit progress(1.0 * value / m_msaMaximalProgress * (high - low) + low);
454 void Submission::onMsaProgressMaxChanged(const int max)
456 m_msaMaximalProgress = max;