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>
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"
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;
49 QString
submissionProgressToString(const Submission::SubmissionProgress 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
) :
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()
91 Submission::~Submission()
95 QString
Submission::accountId() const
100 void Submission::changeConnectionState(const SubmissionProgress state
)
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
112 emit
updateStatusMessage(tr("Preparing to send"));
114 case STATE_BUILDING_MESSAGE
:
117 emit
updateStatusMessage(tr("Creating message"));
122 emit
updateStatusMessage(tr("Saving to the sent folder"));
124 case STATE_PREPARING_URLAUTH
:
125 emit
progressMax(PROGRESS_MAX
);
126 emit
progress(PROGRESS_SAVING_DONE
);
127 emit
updateStatusMessage(tr("Preparing message for delivery"));
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"));
134 case STATE_UPDATING_FLAGS
:
135 emit
progressMax(PROGRESS_MAX
);
136 emit
progress(PROGRESS_DELIVERY_DONE
);
137 emit
updateStatusMessage(tr("Updating message keywords"));
140 emit
progressMax(PROGRESS_MAX
);
141 emit
progress(PROGRESS_MAX
);
142 emit
updateStatusMessage(tr("Message sent"));
145 // revert to the busy indicator
149 emit
updateStatusMessage(tr("Sending failed"));
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
)
168 if (m_useBurl
&& !m_model
->isGenUrlAuthSupported()) {
169 m_model
->logTrace(0, Common::LOG_OTHER
, QStringLiteral("Submission"), QStringLiteral("Cannot BURL without the URLAUTH extension"));
172 m_smtpUsername
= smtpUsername
;
173 m_composer
->setPreloadEnabled(shouldBuildMessageLocally());
176 void Submission::send()
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> "
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"));
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
));
213 if (m_model
->isCatenateSupported() && !m_composer
->asCatenateData(catenateable
, &errorMessage
)) {
214 gotError(tr("Cannot send right now -- saving (CATENATE) failed:\n %1").arg(errorMessage
));
218 if (m_saveToSentFolder
) {
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(
232 QStringList() << QStringLiteral("\\Seen"),
233 m_composer
->timestamp()));
235 // FIXME: without UIDPLUS, there isn't much point in $SubmitPending...
236 appendTask
= QPointer
<Imap::Mailbox::AppendTask
>(
237 m_model
->appendIntoMailbox(
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
);
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
),
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());
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()
305 void Submission::gotError(const QString
&error
)
308 m_model
->logTrace(0, Common::LOG_OTHER
, QStringLiteral("Submission"), QStringLiteral("gotError: %1").arg(error
));
309 changeConnectionState(STATE_FAILED
);
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
);
332 changeConnectionState(STATE_SENT
);
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
);
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
;
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;
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"));
382 /** @short Remember the GENURLAUTH response */
383 void Submission::slotGenUrlAuthReceived(const QString
&url
)
386 if (!m_urlauth
.isEmpty()) {
387 m_genUrlAuthReceived
= true;
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());
408 return ! m_model
->isCatenateSupported();
412 void Submission::onUpdatingFlagsOfReplyingToSucceded()
414 m_updateReplyingToMessageFlagsTask
= 0;
415 changeConnectionState(STATE_SENT
);
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
);
428 void Submission::onUpdatingFlagsOfForwardingSucceeded()
430 m_updateForwardingMessageFlagsTask
= 0;
431 changeConnectionState(STATE_SENT
);
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
);
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
;