File indexing completed on 2025-01-19 04:51:57

0001 /*
0002     Copyright (c) 2016 Michael Bohlender <michael.bohlender@kdemail.net>
0003     Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com>
0004 
0005     This library is free software; you can redistribute it and/or modify it
0006     under the terms of the GNU Library General Public License as published by
0007     the Free Software Foundation; either version 2 of the License, or (at your
0008     option) any later version.
0009 
0010     This library is distributed in the hope that it will be useful, but WITHOUT
0011     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0012     FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
0013     License for more details.
0014 
0015     You should have received a copy of the GNU Library General Public License
0016     along with this library; see the file COPYING.LIB.  If not, write to the
0017     Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
0018     02110-1301, USA.
0019 */
0020 #include "composercontroller.h"
0021 #include <settings/settings.h>
0022 #include <KMime/Message>
0023 #include <QVariant>
0024 #include <QList>
0025 #include <QDebug>
0026 #include <QMimeDatabase>
0027 #include <QUrlQuery>
0028 #include <QFileInfo>
0029 #include <QFile>
0030 #include <QTemporaryFile>
0031 #include <KEmailAddress>
0032 #include <sink/store.h>
0033 #include <sink/log.h>
0034 
0035 #include "identitiesmodel.h"
0036 #include "recepientautocompletionmodel.h"
0037 #include "mime/mailtemplates.h"
0038 #include "mime/mailcrypto.h"
0039 #include "async.h"
0040 #include "sinkutils.h"
0041 
0042 std::vector<Crypto::Key> &operator+=(std::vector<Crypto::Key> &list, const std::vector<Crypto::Key> &add)
0043 {
0044     list.insert(std::end(list), std::begin(add), std::end(add));
0045     return list;
0046 }
0047 
0048 class IdentitySelector : public Selector {
0049     Q_OBJECT
0050     Q_PROPERTY (QString currentAccountId WRITE setCurrentAccountId)
0051 
0052 public:
0053     IdentitySelector(ComposerController &controller) : Selector(new IdentitiesModel), mController(controller)
0054     {
0055     }
0056 
0057     void setCurrent(const QModelIndex &index) Q_DECL_OVERRIDE
0058     {
0059         if (index.isValid()) {
0060             auto currentAccountId = index.data(IdentitiesModel::AccountId).toByteArray();
0061 
0062             KMime::Types::Mailbox mb;
0063             mb.setName(index.data(IdentitiesModel::Username).toString());
0064             mb.setAddress(index.data(IdentitiesModel::Address).toString().toUtf8());
0065             SinkLog() << "Setting current identity: " << mb.prettyAddress() << "Account: " << currentAccountId;
0066 
0067             mController.setIdentity(mb);
0068             mController.setAccountId(currentAccountId);
0069         } else {
0070             SinkWarning() << "No valid identity for index: " << index;
0071             mController.clearIdentity();
0072             mController.clearAccountId();
0073         }
0074 
0075     }
0076 
0077     void setCurrentAccountId(const QString &accountId)
0078     {
0079         for (int i = 0; i < model()->rowCount(); i++) {
0080             if (model()->index(i, 0).data(IdentitiesModel::AccountId).toString() == accountId) {
0081                 setCurrentIndex(i);
0082                 return;
0083             }
0084         }
0085     }
0086 
0087     void setCurrentIdentity(const QString &emailAddress)
0088     {
0089         for (int i = 0; i < model()->rowCount(); i++) {
0090             if (model()->index(i, 0).data(IdentitiesModel::Address).toString() == emailAddress) {
0091                 setCurrentIndex(i);
0092                 return;
0093             }
0094         }
0095     }
0096 
0097     QVector<QByteArray> getAllAddresses()
0098     {
0099         QVector<QByteArray> list;
0100         for (int i = 0; i < model()->rowCount(); i++) {
0101             list << model()->data(model()->index(i, 0), IdentitiesModel::Address).toString().toUtf8();
0102         }
0103         return list;
0104     }
0105 private:
0106     ComposerController &mController;
0107 };
0108 
0109 class RecipientCompleter : public Completer {
0110 public:
0111     RecipientCompleter() : Completer(new RecipientAutocompletionModel)
0112     {
0113     }
0114 
0115     void setSearchString(const QString &s) {
0116         static_cast<RecipientAutocompletionModel*>(model())->setFilter(s);
0117         Completer::setSearchString(s);
0118     }
0119 };
0120 
0121 class AddresseeController : public Kube::ListPropertyController
0122 {
0123     Q_OBJECT
0124     Q_PROPERTY(bool foundAllKeys READ foundAllKeys NOTIFY foundAllKeysChanged)
0125 public:
0126 
0127     bool mFoundAllKeys = true;
0128 
0129     QSet<QByteArray> mMissingKeys;
0130     AddresseeController() : Kube::ListPropertyController{{"name", "keyFound", "key", "fetching"}}
0131     {
0132         QObject::connect(
0133             this, &Kube::ListPropertyController::added, this, [this](const QByteArray &id, const QVariantMap &map) {
0134                 findKey(id, map.value("name").toString(), false);
0135             });
0136 
0137         QObject::connect(this, &Kube::ListPropertyController::removed, this, [this] (const QByteArray &id) {
0138             mMissingKeys.remove(id);
0139             setFoundAllKeys(mMissingKeys.isEmpty());
0140         });
0141     }
0142 
0143     bool foundAllKeys()
0144     {
0145         return mFoundAllKeys;
0146     }
0147 
0148     void setFoundAllKeys(bool found)
0149     {
0150         mFoundAllKeys = found;
0151         emit foundAllKeysChanged();
0152     }
0153 
0154     void findKey(const QByteArray &id, const QString &addressee, bool fetchRemote)
0155     {
0156         KMime::Types::Mailbox mb;
0157         mb.fromUnicodeString(addressee);
0158         const auto mbAddress = mb.address();
0159 
0160         if (mbAddress.isEmpty()) {
0161             return;
0162         }
0163 
0164         SinkLog() << "Searching key for: " << mbAddress;
0165 
0166         mMissingKeys << id;
0167         setFoundAllKeys(false);
0168 
0169         setValue(id, "fetching", fetchRemote);
0170 
0171         asyncRun<std::vector<Crypto::Key>>(this,
0172             [=] {
0173                 return Crypto::findKeys({mbAddress}, false, fetchRemote);
0174             },
0175             [this, addressee, id](const std::vector<Crypto::Key> &keys) {
0176                 setValue(id, "fetching", false);
0177                 if (!keys.empty()) {
0178                     if (keys.size() > 1) {
0179                         SinkWarning() << "Found more than one key, encrypting to all of them.";
0180                     }
0181                     for (const auto &key : keys) {
0182                         SinkLog() << "Found key: " << key;
0183                     }
0184                     setValue(id, "keyFound", true);
0185                     setValue(id, "key", QVariant::fromValue(keys));
0186                     mMissingKeys.remove(id);
0187                     setFoundAllKeys(mMissingKeys.isEmpty());
0188                 } else {
0189                     SinkWarning() << "Failed to find key for recipient.";
0190                 }
0191             });
0192     }
0193 
0194     Q_INVOKABLE void fetchKeys(const QByteArray &id, const QString &addressee)
0195     {
0196         findKey(id, addressee, true);
0197     }
0198 
0199     void set(const QStringList &list)
0200     {
0201         for (const auto &email : list) {
0202             Kube::ListPropertyController::add({{"name", email}});
0203         }
0204     }
0205 
0206     Q_INVOKABLE virtual void add(const QVariantMap &map)
0207     {
0208         // Support adding multiple addresses separated by comma
0209         for (const auto &part : KEmailAddress::splitAddressList(map.value("name").toString())) {
0210             const auto address = part.trimmed();
0211 
0212             //Validation
0213             KMime::Types::Mailbox mb;
0214             mb.fromUnicodeString(address);
0215             if (mb.address().isEmpty()) {
0216                 SinkTrace() << "Ignoring invalid address " << address;
0217                 continue;
0218             }
0219 
0220             Kube::ListPropertyController::add({{"name", address}});
0221         }
0222     }
0223 signals:
0224     void foundAllKeysChanged();
0225 };
0226 
0227 class AttachmentController : public Kube::ListPropertyController
0228 {
0229 public:
0230 
0231     AttachmentController()
0232         : Kube::ListPropertyController{{"name", "filename", "content", "mimetype", "description", "iconname", "url", "inline"}}
0233     {
0234         QObject::connect(this, &Kube::ListPropertyController::added, this, [this] (const QByteArray &id, const QVariantMap &map) {
0235             auto url = map.value("url").toUrl();
0236             setAttachmentProperties(id, url);
0237         });
0238     }
0239 
0240     void setAttachmentProperties(const QByteArray &id, const QUrl &url)
0241     {
0242         QMimeDatabase db;
0243         auto mimeType = db.mimeTypeForUrl(url);
0244         if (mimeType.name() == QLatin1String("inode/directory")) {
0245             qWarning() << "Can't deal with directories yet.";
0246         } else {
0247             if (!url.isLocalFile()) {
0248                 qWarning() << "Cannot attach remote file: " << url;
0249                 return;
0250             }
0251 
0252             QFileInfo fileInfo(url.toLocalFile());
0253             if (!fileInfo.exists()) {
0254                 qWarning() << "The file doesn't exist: " << url;
0255             }
0256 
0257             QFile file{fileInfo.filePath()};
0258             file.open(QIODevice::ReadOnly);
0259             const auto data = file.readAll();
0260             QVariantMap map;
0261             map.insert("filename", fileInfo.fileName());
0262             map.insert("mimetype", mimeType.name().toLatin1());
0263             map.insert("filename", fileInfo.fileName().toLatin1());
0264             map.insert("inline", false);
0265             map.insert("iconname", mimeType.iconName());
0266             map.insert("url", url);
0267             map.insert("content", data);
0268             setValues(id, map);
0269         }
0270     }
0271 };
0272 
0273 ComposerController::ComposerController()
0274     : Kube::Controller(),
0275     controller_to{new AddresseeController},
0276     controller_cc{new AddresseeController},
0277     controller_bcc{new AddresseeController},
0278     controller_attachments{new AttachmentController},
0279     action_send{new Kube::ControllerAction{this, &ComposerController::send}},
0280     action_saveAsDraft{new Kube::ControllerAction{this, &ComposerController::saveAsDraft}},
0281     mRecipientCompleter{new RecipientCompleter},
0282     mIdentitySelector{new IdentitySelector{*this}}
0283 {
0284     QObject::connect(this, &ComposerController::identityChanged, &ComposerController::findPersonalKey);
0285 }
0286 
0287 void ComposerController::findPersonalKey()
0288 {
0289     const auto identityAddress = getIdentity().address();
0290     if (identityAddress.isEmpty()) {
0291         SinkTrace() << "Not looking for personal key because of empty identity.";
0292         return;
0293     }
0294     SinkLog() << "Looking for personal key for: " << identityAddress;
0295     asyncRun<std::vector<Crypto::Key>>(this, [=] {
0296             return Crypto::findKeys({identityAddress}, true);
0297         },
0298         [this](const std::vector<Crypto::Key> &keys) {
0299             if (keys.empty()) {
0300                 SinkWarning() << "Failed to find a personal key.";
0301             } else if (keys.size() > 1) {
0302                 SinkWarning() << "Found multiple keys, using all of them.";
0303             }
0304             setPersonalKeys(QVariant::fromValue(keys));
0305             setFoundPersonalKeys(!keys.empty());
0306         });
0307 }
0308 
0309 void ComposerController::clear()
0310 {
0311     Controller::clear();
0312     //Reapply account and identity from selection
0313     mIdentitySelector->reapplyCurrentIndex();
0314     //FIXME implement in Controller::clear instead
0315     toController()->clear();
0316     ccController()->clear();
0317     bccController()->clear();
0318 }
0319 
0320 Completer *ComposerController::recipientCompleter() const
0321 {
0322     return mRecipientCompleter.data();
0323 }
0324 
0325 Selector *ComposerController::identitySelector() const
0326 {
0327     return mIdentitySelector.data();
0328 }
0329 
0330 static void applyAddresses(const KMime::Types::Mailbox::List &list, std::function<void(const QByteArray &, const QByteArray &)> callback)
0331 {
0332     for (const auto &to : list) {
0333         callback(to.address(), to.name().toUtf8());
0334     }
0335 }
0336 
0337 static void applyAddresses(const QStringList &list, std::function<void(const QByteArray &, const QByteArray &)> callback)
0338 {
0339     KMime::Types::Mailbox::List mailboxes;
0340     for (const auto &s : list) {
0341         KMime::Types::Mailbox mb;
0342         mb.fromUnicodeString(s);
0343         mailboxes << mb;
0344     }
0345     applyAddresses(mailboxes, callback);
0346 }
0347 
0348 static QStringList getStringListFromAddresses(const KMime::Types::Mailbox::List &mailboxes)
0349 {
0350     QStringList list;
0351     for (const auto &mb : mailboxes) {
0352         list << mb.prettyAddress(KMime::Types::Mailbox::QuoteWhenNecessary);
0353     }
0354     return list;
0355 }
0356 
0357 void ComposerController::addAttachmentPart(KMime::Content *partToAttach)
0358 {
0359     QVariantMap map;
0360     // May need special care for the multipart/digest MIME type
0361     map.insert("content", partToAttach->decodedContent());
0362     map.insert("mimetype", partToAttach->contentType()->mimeType());
0363 
0364     QMimeDatabase db;
0365     auto mimeType = db.mimeTypeForName(partToAttach->contentType()->mimeType());
0366     map.insert("iconname", mimeType.iconName());
0367 
0368     if (partToAttach->contentDescription(false)) {
0369         map.insert("description", partToAttach->contentDescription()->asUnicodeString());
0370     }
0371     QString name;
0372     QString filename;
0373     if (partToAttach->contentType(false)) {
0374         if (partToAttach->contentType()->hasParameter(QStringLiteral("name"))) {
0375             name = partToAttach->contentType()->parameter(QStringLiteral("name"));
0376         }
0377     }
0378     if (partToAttach->contentDisposition(false)) {
0379         filename = partToAttach->contentDisposition()->filename();
0380         map.insert("inline", partToAttach->contentDisposition()->disposition() == KMime::Headers::CDinline);
0381     }
0382 
0383     if (name.isEmpty() && !filename.isEmpty()) {
0384         name = filename;
0385     }
0386     if (filename.isEmpty() && !name.isEmpty()) {
0387         filename = name;
0388     }
0389 
0390     if (!filename.isEmpty()) {
0391         map.insert("filename", filename);
0392     }
0393     if (!name.isEmpty()) {
0394         map.insert("name", name);
0395     }
0396     attachmentsController()->add(map);
0397 }
0398 
0399 void ComposerController::setMessage(const KMime::Message::Ptr &msg)
0400 {
0401     static_cast<AddresseeController*>(toController())->set(getStringListFromAddresses(msg->to(true)->mailboxes()));
0402     static_cast<AddresseeController*>(ccController())->set(getStringListFromAddresses(msg->cc(true)->mailboxes()));
0403     static_cast<AddresseeController*>(bccController())->set(getStringListFromAddresses(msg->bcc(true)->mailboxes()));
0404 
0405     setSubject(msg->subject(true)->asUnicodeString());
0406     bool isHtml = false;
0407     const auto body = MailTemplates::body(msg, isHtml);
0408     setHtmlBody(isHtml);
0409     setBody(body);
0410 
0411     //TODO use ObjecTreeParser to get encrypted attachments as well
0412     foreach (const auto &att, msg->attachments()) {
0413         addAttachmentPart(att);
0414     }
0415 
0416     setExistingMessage(msg);
0417     emit messageLoaded(body);
0418 }
0419 
0420 void ComposerController::loadDraft(const QVariant &message) {
0421     clear();
0422     loadMessage(message, [this] (const KMime::Message::Ptr &mail) {
0423         setEncrypt(KMime::isEncrypted(mail.data()));
0424         setSign(KMime::isSigned(mail.data()));
0425         mRemoveDraft = true;
0426         setMessage(mail);
0427     });
0428 }
0429 
0430 void ComposerController::selectIdentityFromMailboxes(const KMime::Types::Mailbox::List &mailboxes, const QVector<QString> &meStrings)
0431 {
0432     for (const auto &mb : mailboxes) {
0433         const auto address = mb.addrSpec().asString();
0434         if (meStrings.contains(address)) {
0435             static_cast<IdentitySelector*>(mIdentitySelector.data())->setCurrentIdentity(address);
0436             return;
0437         }
0438     }
0439 }
0440 
0441 void ComposerController::loadReply(const QVariant &message) {
0442     clear();
0443     auto guard = QPointer<QObject>{this};
0444     loadMessage(message, [this, guard] (const KMime::Message::Ptr &mail) {
0445         Q_ASSERT(guard);
0446         //Find all personal email addresses to exclude from reply
0447         KMime::Types::AddrSpecList me;
0448         QVector<QString> meStrings;
0449         auto list = static_cast<IdentitySelector*>(mIdentitySelector.data())->getAllAddresses();
0450         for (const auto &a : list) {
0451             KMime::Types::Mailbox mb;
0452             mb.setAddress(a);
0453             me << mb.addrSpec();
0454             meStrings << a;
0455         }
0456 
0457         selectIdentityFromMailboxes(mail->to()->mailboxes() + mail->cc()->mailboxes() + mail->bcc()->mailboxes(), meStrings);
0458 
0459         setEncrypt(KMime::isEncrypted(mail.data()));
0460         setSign(KMime::isSigned(mail.data()));
0461         MailTemplates::reply(mail, [this, guard] (const auto &msg) {
0462             Q_ASSERT(guard);
0463             setMessage(msg);
0464         }, me);
0465     });
0466 }
0467 
0468 void ComposerController::loadForward(const QVariant &message) {
0469     clear();
0470     loadMessage(message, [this] (const KMime::Message::Ptr &mail) {
0471         setEncrypt(KMime::isEncrypted(mail.data()));
0472         setSign(KMime::isSigned(mail.data()));
0473         MailTemplates::forward(mail, [this] (const auto &msg) {
0474             setMessage(msg);
0475         });
0476     });
0477 }
0478 
0479 void ComposerController::loadMessage(const QVariant &message, std::function<void(const KMime::Message::Ptr&)> callback)
0480 {
0481     using namespace Sink;
0482     using namespace Sink::ApplicationDomain;
0483 
0484     auto msg = message.value<Mail::Ptr>();
0485     Q_ASSERT(msg);
0486     Query query(*msg);
0487     query.request<Mail::MimeMessage>();
0488     query.request<Mail::Draft>();
0489     setLoading(true);
0490     Store::fetchOne<Mail>(query).then([this, callback](const Mail &mail) {
0491         setExistingMail(mail);
0492         setLoading(false);
0493 
0494         const auto mailData = KMime::CRLFtoLF(mail.getMimeMessage());
0495         if (!mailData.isEmpty()) {
0496             KMime::Message::Ptr mail(new KMime::Message);
0497             mail->setContent(mailData);
0498             mail->parse();
0499             callback(mail);
0500         } else {
0501             qWarning() << "Retrieved empty message";
0502         }
0503     }).exec();
0504 }
0505 
0506 void ComposerController::recordForAutocompletion(const QByteArray &addrSpec, const QByteArray &displayName)
0507 {
0508     if (auto model = static_cast<RecipientAutocompletionModel*>(recipientCompleter()->model())) {
0509         model->addEntry(addrSpec, displayName);
0510     }
0511 }
0512 
0513 std::vector<Crypto::Key> ComposerController::getRecipientKeys()
0514 {
0515     std::vector<Crypto::Key> keys;
0516     {
0517         const auto list = toController()->getList<std::vector<Crypto::Key>>("key");
0518         for (const auto &l: list) {
0519             keys.insert(std::end(keys), std::begin(l), std::end(l));
0520         }
0521     }
0522     {
0523         const auto list = ccController()->getList<std::vector<Crypto::Key>>("key");
0524         for (const auto &l: list) {
0525             keys.insert(std::end(keys), std::begin(l), std::end(l));
0526         }
0527     }
0528     {
0529         const auto list = bccController()->getList<std::vector<Crypto::Key>>("key");
0530         for (const auto &l: list) {
0531             keys.insert(std::end(keys), std::begin(l), std::end(l));
0532         }
0533     }
0534     return keys;
0535 }
0536 
0537 KMime::Message::Ptr ComposerController::assembleMessage()
0538 {
0539     auto toAddresses = toController()->getList<QString>("name");
0540     auto ccAddresses = ccController()->getList<QString>("name");
0541     auto bccAddresses = bccController()->getList<QString>("name");
0542     applyAddresses(toAddresses + ccAddresses + bccAddresses, [&](const QByteArray &addrSpec, const QByteArray &displayName) {
0543         recordForAutocompletion(addrSpec, displayName);
0544     });
0545 
0546     QList<Attachment> attachments;
0547     attachmentsController()->traverse([&](const QVariantMap &value) {
0548         attachments << Attachment{
0549             value["name"].toString(),
0550             value["filename"].toString(),
0551             value["mimetype"].toByteArray(),
0552             value["inline"].toBool(),
0553             value["content"].toByteArray()
0554         };
0555     });
0556 
0557     Crypto::Key attachedKey;
0558     std::vector<Crypto::Key> signingKeys;
0559     if (getSign()) {
0560         signingKeys = getPersonalKeys().value<std::vector<Crypto::Key>>();
0561         Q_ASSERT(!signingKeys.empty());
0562         attachedKey = signingKeys[0];
0563     }
0564     std::vector<Crypto::Key> encryptionKeys;
0565     if (getEncrypt()) {
0566         //Encrypt to self so we can read the sent message
0567         auto personalKeys = getPersonalKeys().value<std::vector<Crypto::Key>>();
0568 
0569         attachedKey = personalKeys[0];
0570 
0571         encryptionKeys += personalKeys;
0572         encryptionKeys += getRecipientKeys();
0573     }
0574 
0575     return MailTemplates::createMessage(mExistingMessage, toAddresses, ccAddresses, bccAddresses, getIdentity(), getSubject(), getBody(), getHtmlBody(), attachments, signingKeys, encryptionKeys, attachedKey);
0576 }
0577 
0578 void ComposerController::send()
0579 {
0580     auto message = assembleMessage();
0581     if (!message) {
0582         SinkWarning() << "Failed to assemble the message.";
0583         return;
0584     }
0585 
0586     auto accountId = getAccountId();
0587     Q_ASSERT(!accountId.isEmpty());
0588     if (accountId.isEmpty()) {
0589         SinkWarning() << "No account id.";
0590         return;
0591     }
0592     auto job = SinkUtils::sendMail(message->encodedContent(true), accountId.toUtf8())
0593         .then([&] (const KAsync::Error &error) {
0594             if (!error) {
0595                 if (mRemoveDraft) {
0596                     SinkLog() << "Removing draft message.";
0597                     Sink::Store::remove(getExistingMail()).exec();
0598                 }
0599             }
0600             emit done();
0601         });
0602 
0603     run(job);
0604 }
0605 
0606 void ComposerController::saveAsDraft()
0607 {
0608     SinkLog() << "Save as draft";
0609     const auto accountId = getAccountId();
0610     Q_ASSERT(!accountId.isEmpty());
0611     if (accountId.isEmpty()) {
0612         SinkWarning() << "No account id.";
0613         return;
0614     }
0615     auto existingMail = getExistingMail();
0616 
0617     auto message = assembleMessage();
0618     if (!message) {
0619         SinkWarning() << "Failed to assemble the message.";
0620         return;
0621     }
0622 
0623     using namespace Sink;
0624     using namespace Sink::ApplicationDomain;
0625 
0626     auto job = [&] {
0627         if (existingMail.identifier().isEmpty() || !existingMail.getDraft()) {
0628             SinkLog() << "Creating a new draft" << existingMail.identifier() << "in account" << accountId;
0629             Query query;
0630             query.containsFilter<SinkResource::Capabilities>(ResourceCapabilities::Mail::drafts);
0631             query.filter<SinkResource::Account>(accountId.toLatin1());
0632             return Store::fetchOne<SinkResource>(query)
0633                 .then([=](const SinkResource &resource) {
0634                     Mail mail(resource.identifier());
0635                     mail.setDraft(true);
0636                     mail.setMimeMessage(message->encodedContent(true));
0637                     return Store::create(mail);
0638                 })
0639                 .onError([] (const KAsync::Error &error) {
0640                     SinkWarning() << "Error while creating draft: " << error.errorMessage;
0641                 });
0642         } else {
0643             SinkLog() << "Modifying an existing mail" << existingMail.identifier();
0644             existingMail.setDraft(true);
0645             existingMail.setMimeMessage(message->encodedContent(true));
0646             return Store::modify(existingMail);
0647         }
0648     }();
0649     job = job.then([&] (const KAsync::Error &) {
0650         emit done();
0651     });
0652     run(job);
0653 }
0654 
0655 #include "composercontroller.moc"