/*
  SPDX-FileCopyrightText: 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
  SPDX-FileCopyrightText: 2010 Leo Franchi <lfranchi@kde.org>

  SPDX-License-Identifier: LGPL-2.0-or-later
*/

#include "composerviewbase.h"

#include "attachment/attachmentcontrollerbase.h"
#include "attachment/attachmentmodel.h"
#include "job/composerjob.h"
#include "mailtemplates.h"
#include "messagedispatcher.h"
#include "nodehelper.h"
#include "part/globalpart.h"
#include "part/infopart.h"
#include "richtextcomposerng.h"
#include "richtextcomposersignatures.h"
#include "signaturecontroller.h"
#include "util.h"
#include "util_p.h"

#include "messagecomposersettings.h"
#include "recipientseditor.h"

#include "identity/identity.h"
#include <KCursorSaver>
#include <KEmailAddress>
#include <KPIMTextEdit/RichTextComposerControler>
#include <KPIMTextEdit/RichTextComposerImages>
#include <MimeTreeParserCore/ObjectTreeParser>
#include <Sonnet/DictionaryComboBox>

#include "editor_debug.h"

#include <Libkleo/ExpiryChecker>
#include <Libkleo/ExpiryCheckerSettings>
#include <Libkleo/KeyCache>
#include <Libkleo/KeyResolverCore>
#include <libkleo/enum.h>

#include <QGpgME/ExportJob>
#include <QGpgME/ImportJob>
#include <QGpgME/Protocol>
#include <global.h>
#include <gpgme++/context.h>
#include <gpgme++/importresult.h>

#include <KLocalizedString>
#include <KMessageBox>

#include <QDir>
#include <QSaveFile>
#include <QStandardPaths>
#include <QTemporaryDir>
#include <QTimer>
#include <QUuid>

#include "draft/draftmanager.h"

using namespace MessageComposer;
using namespace Qt::Literals::StringLiterals;

ComposerViewBase::ComposerViewBase(QObject *parent, QWidget *parentGui)
    : QObject(parent)
    , m_msg(KMime::Message::Ptr(new KMime::Message))
    , m_parentWidget(parentGui)
    , m_cryptoMessageFormat(Kleo::AutoFormat)
    , m_autoSaveInterval(60000) // default of 1 min
{
    initAutoSave();

    connect(this, &ComposerViewBase::composerCreated, this, &ComposerViewBase::slotComposerCreated);
}

ComposerViewBase::~ComposerViewBase() = default;

bool ComposerViewBase::isComposing() const
{
    return !m_composers.isEmpty();
}

void ComposerViewBase::setMessage(const KMime::Message::Ptr &msg)
{
    if (m_attachmentModel) {
        const auto attachments{m_attachmentModel->attachments()};
        for (const MessageCore::AttachmentPart::Ptr &attachment : attachments) {
            if (!m_attachmentModel->removeAttachment(attachment)) {
                qCWarning(EDITOR_LOG) << "Attachment not found.";
            }
        }
    }
    m_msg = msg;
    if (m_recipientsEditor) {
        m_recipientsEditor->clear();
        bool resultTooManyRecipients = m_recipientsEditor->setRecipientString(m_msg->to()->mailboxes(), Recipient::To);
        if (!resultTooManyRecipients) {
            resultTooManyRecipients = m_recipientsEditor->setRecipientString(m_msg->cc()->mailboxes(), Recipient::Cc);
        }
        if (!resultTooManyRecipients) {
            resultTooManyRecipients = m_recipientsEditor->setRecipientString(m_msg->bcc()->mailboxes(), Recipient::Bcc);
        }
        if (!resultTooManyRecipients) {
            resultTooManyRecipients = m_recipientsEditor->setRecipientString(m_msg->replyTo()->mailboxes(), Recipient::ReplyTo);
        }
        m_recipientsEditor->setFocusBottom();
        Q_EMIT tooManyRecipient(resultTooManyRecipients);
    }
    m_subject = m_msg->subject()->asUnicodeString();
    // First, we copy the message and then parse it to the object tree parser.
    // The otp gets the message text out of it, in textualContent(), and also decrypts
    // the message if necessary.
    KMime::Content msgContent;
    msgContent.setContent(m_msg->encodedContent());
    msgContent.parse();

    // Load the attachments
    const auto attachments{msgContent.attachments()};
    for (const auto &att : attachments) {
        addAttachmentPart(att);
    }

    // Set the HTML text and collect HTML images
    bool isHtml = false;
    const auto body = MailTemplates::body(msg, isHtml);

    if (isHtml) {
        Q_EMIT enableHtml();
        editor()->activateRichText();
    } else {
        editor()->switchToPlainText();
        Q_EMIT disableHtml(LetUserConfirm);
    }
    editor()->setText(body);

    if (auto hdr = m_msg->headerByType("X-KMail-CursorPos")) {
        m_editor->setCursorPositionFromStart(hdr->asUnicodeString().toUInt());
    }
}

void ComposerViewBase::saveMailSettings()
{
    auto header = new KMime::Headers::Generic("X-KMail-Identity");
    header->fromUnicodeString(QString::number(m_identity.uoid()));
    m_msg->setHeader(header);

    header = new KMime::Headers::Generic("X-KMail-Identity-Name");
    header->fromUnicodeString(m_identity.identityName());
    m_msg->setHeader(header);

    header = new KMime::Headers::Generic("X-KMail-Dictionary");
    header->fromUnicodeString(m_dictionary->currentDictionary());
    m_msg->setHeader(header);

    // Save the quote prefix which is used for this message. Each message can have
    // a different quote prefix, for example depending on the original sender.
    if (m_editor->quotePrefixName().isEmpty()) {
        m_msg->removeHeader("X-KMail-QuotePrefix");
    } else {
        header = new KMime::Headers::Generic("X-KMail-QuotePrefix");
        header->fromUnicodeString(m_editor->quotePrefixName());
        m_msg->setHeader(header);
    }

    if (m_editor->composerControler()->isFormattingUsed()) {
        qCDebug(EDITOR_LOG) << "HTML mode";
        header = new KMime::Headers::Generic("X-KMail-Markup");
        header->fromUnicodeString(QStringLiteral("true"));
        m_msg->setHeader(header);
    } else {
        m_msg->removeHeader("X-KMail-Markup");
        qCDebug(EDITOR_LOG) << "Plain text";
    }
}

void ComposerViewBase::clearFollowUp()
{
    mFollowUpDate = QDate();
}

void ComposerViewBase::send()
{
    KCursorSaver saver(Qt::WaitCursor);
    saveMailSettings();

    if (m_editor->composerControler()->isFormattingUsed() && inlineSigningEncryptionSelected()) {
        const QString keepBtnText =
            m_encrypt ? m_sign ? i18n("&Keep markup, do not sign/encrypt") : i18n("&Keep markup, do not encrypt") : i18n("&Keep markup, do not sign");
        const QString yesBtnText = m_encrypt ? m_sign ? i18n("Sign/Encrypt (delete markup)") : i18n("Encrypt (delete markup)") : i18n("Sign (delete markup)");
        int ret = KMessageBox::warningTwoActionsCancel(m_parentWidget,
                                                       i18n("<qt><p>Inline signing/encrypting of HTML messages is not possible;</p>"
                                                            "<p>do you want to delete your markup?</p></qt>"),
                                                       i18nc("@title:window", "Sign/Encrypt Message?"),
                                                       KGuiItem(yesBtnText),
                                                       KGuiItem(keepBtnText));
        if (KMessageBox::Cancel == ret) {
            return;
        }
        if (KMessageBox::ButtonCode::SecondaryAction == ret) {
            m_encrypt = false;
            m_sign = false;
        } else {
            Q_EMIT disableHtml(NoConfirmationNeeded);
        }
    }

    readyForSending();
}

void ComposerViewBase::setCustomHeader(const QMap<QByteArray, QString> &customHeader)
{
    m_customHeader = customHeader;
}

void ComposerViewBase::readyForSending()
{
    qCDebug(EDITOR_LOG) << "Entering readyForSending";
    if (!m_msg) {
        qCDebug(EDITOR_LOG) << "m_msg == 0!";
        return;
    }

    if (!m_composers.isEmpty()) {
        // This may happen if e.g. the autosave timer calls applyChanges.
        qCDebug(EDITOR_LOG) << "ready for sending: Called while composer active; ignoring. Number of composer " << m_composers.count();
        return;
    }

    mExpandedFrom = from();
    mExpandedTo = m_recipientsEditor->recipientStringList(Recipient::To);
    mExpandedCc = m_recipientsEditor->recipientStringList(Recipient::Cc);
    mExpandedBcc = m_recipientsEditor->recipientStringList(Recipient::Bcc);
    mExpandedReplyTo = m_recipientsEditor->recipientStringList(Recipient::ReplyTo);

    Q_ASSERT(m_composers.isEmpty()); // composers should be empty. The caller of this function
    // checks for emptiness before calling it
    // so just ensure it actually is empty
    // and document it
    // we first figure out if we need to create multiple messages with different crypto formats
    // if so, we create a composer per format
    // if we aren't signing or encrypting, this just returns a single empty message
    if (m_neverEncrypt) {
        auto composer = new MessageComposer::ComposerJob;
        composer->setNoCrypto(true);
        m_composers.append(composer);
        slotComposerCreated();
    } else {
        generateCryptoMessages();
    }
}

void ComposerViewBase::slotComposerCreated()
{
    if (m_composers.isEmpty()) {
        Q_EMIT failed(i18n("It was not possible to create a message composer."));
        return;
    }

    // Compose each message and prepare it for queueing, sending, or storing

    // working copy in case composers instantly emit result
    const auto composers = m_composers;
    for (MessageComposer::ComposerJob *composer : composers) {
        fillComposer(composer, UseExpandedRecipients, false);
        connect(composer, &MessageComposer::ComposerJob::result, this, &ComposerViewBase::slotSendComposeResult);
        composer->start();
        qCDebug(EDITOR_LOG) << "Started a composer for sending!";
    }
}

namespace
{
// helper methods for reading encryption settings

inline Kleo::chrono::days encryptOwnKeyNearExpiryWarningThresholdInDays()
{
    if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
        return Kleo::chrono::days{-1};
    }
    const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnOwnEncrKeyNearExpiryThresholdDays();
    return Kleo::chrono::days{qMax(1, num)};
}

inline Kleo::chrono::days encryptKeyNearExpiryWarningThresholdInDays()
{
    if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
        return Kleo::chrono::days{-1};
    }
    const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrKeyNearExpiryThresholdDays();
    return Kleo::chrono::days{qMax(1, num)};
}

inline Kleo::chrono::days encryptRootCertNearExpiryWarningThresholdInDays()
{
    if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
        return Kleo::chrono::days{-1};
    }
    const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrRootNearExpiryThresholdDays();
    return Kleo::chrono::days{qMax(1, num)};
}

inline Kleo::chrono::days encryptChainCertNearExpiryWarningThresholdInDays()
{
    if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
        return Kleo::chrono::days{-1};
    }
    const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrChaincertNearExpiryThresholdDays();
    return Kleo::chrono::days{qMax(1, num)};
}

} // nameless namespace

Kleo::KeyResolver *ComposerViewBase::fillKeyResolver(bool encryptSomething)
{
    auto keyResolverCore = new Kleo::KeyResolver(m_encrypt, m_sign, GpgME::UnknownProtocol, false);
    keyResolverCore->setMinimumValidity(GpgME::UserID::Unknown);

    QStringList signingKeys, encryptionKeys;

    if (m_cryptoMessageFormat & Kleo::AnyOpenPGP) {
        if (!m_identity.pgpSigningKey().isEmpty()) {
            signingKeys.push_back(QLatin1String(m_identity.pgpSigningKey()));
        }
        if (!m_identity.pgpEncryptionKey().isEmpty()) {
            encryptionKeys.push_back(QLatin1String(m_identity.pgpEncryptionKey()));
        }
    }

    if (m_cryptoMessageFormat & Kleo::AnySMIME) {
        if (!m_identity.smimeSigningKey().isEmpty()) {
            signingKeys.push_back(QLatin1String(m_identity.smimeSigningKey()));
        }
        if (!m_identity.smimeEncryptionKey().isEmpty()) {
            encryptionKeys.push_back(QLatin1String(m_identity.smimeEncryptionKey()));
        }
    }

    keyResolverCore->setSender(m_identity.fullEmailAddr());

    const auto normalized = GpgME::UserID::addrSpecFromString(m_identity.fullEmailAddr().toUtf8().constData());
    const auto normalizedSender = QString::fromUtf8(normalized.c_str());
    keyResolverCore->setSigningKeys(signingKeys);
    keyResolverCore->setOverrideKeys({{GpgME::UnknownProtocol, {{normalizedSender, encryptionKeys}}}});

    if (encryptSomething) {
        QStringList recipients;
        const auto lst = m_recipientsEditor->lines();
        for (auto line : lst) {
            auto recipient = line->data().dynamicCast<Recipient>();
            recipients.push_back(recipient->email());
        }

        keyResolverCore->setRecipients(recipients);
    }
    return keyResolverCore;
}

void ComposerViewBase::generateCryptoMessages()
{
    bool canceled = false;

    qCDebug(EDITOR_LOG) << "filling crypto info";
    connect(expiryChecker().get(),
            &Kleo::ExpiryChecker::expiryMessage,
            this,
            [&canceled](const GpgME::Key &key, QString msg, Kleo::ExpiryChecker::ExpiryInformation info, bool isNewMessage) {
                if (!isNewMessage) {
                    return;
                }

                if (canceled) {
                    return;
                }
                QString title;
                QString dontAskAgainName;
                if (info == Kleo::ExpiryChecker::OwnKeyExpired || info == Kleo::ExpiryChecker::OwnKeyNearExpiry) {
                    dontAskAgainName = QStringLiteral("own key expires soon warning");
                } else {
                    dontAskAgainName = QStringLiteral("other encryption key near expiry warning");
                }
                if (info == Kleo::ExpiryChecker::OwnKeyExpired || info == Kleo::ExpiryChecker::OtherKeyExpired) {
                    title = key.protocol() == GpgME::OpenPGP ? i18n("OpenPGP Key Expired") : i18n("S/MIME Certificate Expired");
                } else {
                    title = key.protocol() == GpgME::OpenPGP ? i18n("OpenPGP Key Expires Soon") : i18n("S/MIME Certificate Expires Soon");
                }
                if (KMessageBox::warningContinueCancel(nullptr, msg, title, KStandardGuiItem::cont(), KStandardGuiItem::cancel(), dontAskAgainName)
                    == KMessageBox::Cancel) {
                    canceled = true;
                }
            });

    bool signSomething = m_sign;
    bool doSignCompletely = m_sign;
    bool encryptSomething = m_encrypt;
    bool doEncryptCompletely = m_encrypt;

    if (m_attachmentModel) {
        const auto attachments = m_attachmentModel->attachments();
        for (const MessageCore::AttachmentPart::Ptr &attachment : attachments) {
            if (attachment->isSigned()) {
                signSomething = true;
            } else {
                doEncryptCompletely = false;
            }
            if (attachment->isEncrypted()) {
                encryptSomething = true;
            } else {
                doSignCompletely = false;
            }
        }
    }

    qCInfo(EDITOR_LOG) << "Encrypting completely:" << doEncryptCompletely;
    qCInfo(EDITOR_LOG) << "Signing completely:" << doSignCompletely;

    // No encryption or signing is needed
    if (!signSomething && !encryptSomething) {
        m_composers = {new MessageComposer::ComposerJob};
        Q_EMIT composerCreated();
        return;
    }

    auto keyResolver = fillKeyResolver(encryptSomething);

    connect(keyResolver, &Kleo::KeyResolver::keysResolved, this, [this, encryptSomething, signSomething, keyResolver](bool success, bool sendUnencrypted) {
        if (!success) {
            qCDebug(EDITOR_LOG) << "resolveAllKeys: failed to resolve keys! oh noes";
            Q_EMIT failed(i18n("Failed to resolve keys. Please report a bug."));
            return;
        }

        qCDebug(EDITOR_LOG) << "done resolving keys.";

        if (sendUnencrypted) {
            m_composers = {new MessageComposer::ComposerJob};
            Q_EMIT composerCreated();
            return;
        }

        const auto result = keyResolver->result();
        QList<MessageComposer::ComposerJob *> composers;

        auto signingKeyFinder = [&result](const GpgME::Protocol protocol) -> std::optional<GpgME::Key> { // clazy:excludeall=lambda-in-connect
            for (const auto &key : result.signingKeys) {
                if (key.protocol() == protocol) {
                    return key;
                }
            }
            return std::nullopt;
        };

        if (encryptSomething || signSomething) {
            if (encryptSomething) {
                std::vector<GpgME::Key> pgpKeys;
                QStringList pgpRecipients;
                std::vector<GpgME::Key> smimeKeys;
                QStringList smimeRecipients;

                for (const auto &[recipient, keys] : result.encryptionKeys.asKeyValueRange()) {
                    const auto recipientKeys = result.encryptionKeys[recipient];
                    if (recipientKeys.size() > 1) {
                        // TODO Carl group handling
                    } else {
                        const auto &key = recipientKeys[0];
                        if (key.protocol() == GpgME::CMS) {
                            smimeRecipients.append(recipient);
                            smimeKeys.push_back(recipientKeys[0]);
                        } else {
                            pgpRecipients.append(recipient);
                            pgpKeys.push_back(recipientKeys[0]);
                        }
                    }
                }

                Q_ASSERT(smimeRecipients.count() == (int)smimeKeys.size());
                Q_ASSERT(pgpRecipients.count() == (int)pgpKeys.size());

                if (pgpRecipients.count() > 0) {
                    auto composer = new MessageComposer::ComposerJob;
                    composer->setEncryptionKeys({QPair<QStringList, std::vector<GpgME::Key>>(pgpRecipients, pgpKeys)});
                    auto pgpSigningKey = signingKeyFinder(GpgME::OpenPGP);
                    if (signSomething && pgpSigningKey) {
                        composer->setSigningKeys({*pgpSigningKey});
                    }
                    composer->setCryptoMessageFormat(Kleo::OpenPGPMIMEFormat);
                    composer->setSignAndEncrypt(signSomething, encryptSomething);
                    composers << composer;
                }

                if (smimeRecipients.count() > 0) {
                    auto composer = new MessageComposer::ComposerJob;
                    composer->setEncryptionKeys({QPair<QStringList, std::vector<GpgME::Key>>(smimeRecipients, smimeKeys)});

                    auto smimeSigningKey = signingKeyFinder(GpgME::CMS);
                    if (signSomething && smimeSigningKey) {
                        composer->setSigningKeys({*smimeSigningKey});
                    }
                    composer->setCryptoMessageFormat(Kleo::SMIMEFormat);
                    composer->setSignAndEncrypt(signSomething, encryptSomething);
                    composers << composer;
                }
            } else {
                // signing only
                Q_ASSERT(signSomething);
                Q_ASSERT(!encryptSomething);

                auto composer = new MessageComposer::ComposerJob;
                composer->setSignAndEncrypt(signSomething, encryptSomething);

                Q_ASSERT(result.protocol != GpgME::UnknownProtocol); // No mixed protocol allowed here

                const auto signingKey = signingKeyFinder(result.protocol);
                Q_ASSERT(signingKey);

                composer->setSigningKeys({*signingKey});
                composer->setCryptoMessageFormat(result.protocol == GpgME::OpenPGP ? Kleo::OpenPGPMIMEFormat : Kleo::SMIMEFormat);

                composers << composer;
            }
        } else {
            auto composer = new MessageComposer::ComposerJob;
            composers.append(composer);
            // If we canceled sign or encrypt be sure to change status in attachment.
            markAllAttachmentsForSigning(false);
            markAllAttachmentsForEncryption(false);
        }

        if (composers.isEmpty() && (signSomething || encryptSomething)) {
            Q_ASSERT_X(false, "ComposerViewBase::generateCryptoMessages", "No concrete sign or encrypt method selected");
        }

        m_composers = composers;
        Q_EMIT composerCreated();
        qWarning() << "composer created" << composers;
    });
    keyResolver->start(false, m_parentWidget);
}

void ComposerViewBase::fillGlobalPart(MessageComposer::GlobalPart *globalPart)
{
    globalPart->setParentWidgetForGui(m_parentWidget);
    globalPart->setMDNRequested(m_mdnRequested);
    globalPart->setRequestDeleveryConfirmation(m_requestDeleveryConfirmation);
}

void ComposerViewBase::fillInfoPart(MessageComposer::InfoPart *infoPart, ComposerViewBase::RecipientExpansion expansion)
{
    // TODO splitAddressList and expandAliases ugliness should be handled by a
    // special AddressListEdit widget... (later: see RecipientsEditor)

    if (expansion == UseExpandedRecipients) {
        infoPart->setFrom(mExpandedFrom);
        infoPart->setTo(mExpandedTo);
        infoPart->setCc(mExpandedCc);
        infoPart->setBcc(mExpandedBcc);
        infoPart->setReplyTo(mExpandedReplyTo);
    } else {
        infoPart->setFrom(from());
        infoPart->setTo(m_recipientsEditor->recipientStringList(Recipient::To));
        infoPart->setCc(m_recipientsEditor->recipientStringList(Recipient::Cc));
        infoPart->setBcc(m_recipientsEditor->recipientStringList(Recipient::Bcc));
        infoPart->setReplyTo(m_recipientsEditor->recipientStringList(Recipient::ReplyTo));
    }
    infoPart->setSubject(subject());
    infoPart->setUserAgent(QStringLiteral("KMail"));
    infoPart->setUrgent(m_urgent);

    if (auto inReplyTo = m_msg->inReplyTo(false)) {
        infoPart->setInReplyTo(inReplyTo->asUnicodeString());
    }

    if (auto references = m_msg->references(false)) {
        infoPart->setReferences(references->asUnicodeString());
    }

    KMime::Headers::Base::List extras;
    if (auto hdr = m_msg->headerByType("X-KMail-SignatureActionEnabled")) {
        extras << hdr;
    }
    if (auto hdr = m_msg->headerByType("X-KMail-EncryptActionEnabled")) {
        extras << hdr;
    }
    if (auto hdr = m_msg->headerByType("X-KMail-CryptoMessageFormat")) {
        extras << hdr;
    }
    if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-To")) {
        extras << hdr;
    }
    if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-CC")) {
        extras << hdr;
    }
    if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-BCC")) {
        extras << hdr;
    }
    if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-Reply-To")) {
        extras << hdr;
    }
    if (auto hdr = m_msg->organization(false)) {
        extras << hdr;
    }
    if (auto hdr = m_msg->headerByType("X-KMail-Fcc")) {
        extras << hdr;
    }
    if (auto hdr = m_msg->headerByType("X-KMail-Drafts")) {
        extras << hdr;
    }
    if (auto hdr = m_msg->headerByType("X-KMail-Templates")) {
        extras << hdr;
    }
    if (auto hdr = m_msg->headerByType("X-KMail-Link-Message")) {
        extras << hdr;
    }
    if (auto hdr = m_msg->headerByType("X-KMail-Link-Type")) {
        extras << hdr;
    }
    if (auto hdr = m_msg->headerByType("X-Face")) {
        extras << hdr;
    }
    if (auto hdr = m_msg->headerByType("Face")) {
        extras << hdr;
    }
    if (auto hdr = m_msg->headerByType("X-KMail-FccDisabled")) {
        extras << hdr;
    }

    infoPart->setExtraHeaders(extras);
}

void ComposerViewBase::slotSendComposeResult(KJob *job)
{
    Q_ASSERT(dynamic_cast<MessageComposer::ComposerJob *>(job));
    auto composer = static_cast<MessageComposer::ComposerJob *>(job);

    if (composer->error() != MessageComposer::ComposerJob::NoError) {
        qCDebug(EDITOR_LOG) << "compose job might have error: " << job->error() << " errorString: " << job->errorString();
    }

    if (composer->error() == MessageComposer::ComposerJob::NoError) {
        Q_ASSERT(m_composers.contains(composer));
        // The messages were composed successfully.
        qCDebug(EDITOR_LOG) << "NoError.";
        const int numberOfMessage(composer->resultMessages().size());
        for (int i = 0; i < numberOfMessage; ++i) {
            queueMessage(composer->resultMessages().at(i));
        }
    } else if (composer->error() == MessageComposer::ComposerJob::UserCancelledError) {
        // The job warned the user about something, and the user chose to return
        // to the message.  Nothing to do.
        qCDebug(EDITOR_LOG) << "UserCancelledError.";
        Q_EMIT failed(i18n("Job cancelled by the user"));
    } else {
        qCDebug(EDITOR_LOG) << "other Error." << composer->error();
        QString msg;
        if (composer->error() == MessageComposer::ComposerJob::BugError) {
            msg = i18n("Could not compose message: %1 \n Please report this bug.", job->errorString());
        } else {
            msg = i18n("Could not compose message: %1", job->errorString());
        }
        Q_EMIT failed(msg);
    }

    if (!composer->gnupgHome().isEmpty()) {
        QDir dir(composer->gnupgHome());
        dir.removeRecursively();
    }

    m_composers.removeAll(composer);
}

void ComposerViewBase::initAutoSave()
{
    qCDebug(EDITOR_LOG) << "initialising autosave";

    // Ensure that the autosave directory exists.
    QDir dataDirectory(DraftManager::autosaveDirectory());
    if (!dataDirectory.exists(QStringLiteral("autosave"))) {
        qCDebug(EDITOR_LOG) << "Creating autosave directory.";
        dataDirectory.mkdir(QStringLiteral("autosave"));
    }

    updateAutoSave();
}

QDate ComposerViewBase::followUpDate() const
{
    return mFollowUpDate;
}

void ComposerViewBase::setFollowUpDate(const QDate &followUpDate)
{
    mFollowUpDate = followUpDate;
}

Sonnet::DictionaryComboBox *ComposerViewBase::dictionary() const
{
    return m_dictionary;
}

void ComposerViewBase::setDictionary(Sonnet::DictionaryComboBox *dictionary)
{
    m_dictionary = dictionary;
}

void ComposerViewBase::updateAutoSave()
{
    if (m_autoSaveInterval == 0) {
        delete m_autoSaveTimer;
        m_autoSaveTimer = nullptr;
    } else {
        if (!m_autoSaveTimer) {
            m_autoSaveTimer = new QTimer(this);
            connect(m_autoSaveTimer, &QTimer::timeout, this, &ComposerViewBase::autoSaveMessage);
        }
        m_autoSaveTimer->start(m_autoSaveInterval);
    }
}

void ComposerViewBase::cleanupAutoSave()
{
    delete m_autoSaveTimer;
    m_autoSaveTimer = nullptr;
    if (!mailIdIsEmpty()) {
        qCDebug(EDITOR_LOG) << "deleting autosave files" << mailId();

        // Delete the autosave files
        QDir autoSaveDir(DraftManager::autosaveDirectory());

        // Filter out only this composer window's autosave files
        const QStringList autoSaveFilter{mailId() + QLatin1String("*")};
        autoSaveDir.setNameFilters(autoSaveFilter);

        // Return the files to be removed
        const QStringList autoSaveFiles = autoSaveDir.entryList();
        qCDebug(EDITOR_LOG) << "There are" << autoSaveFiles.count() << "to be deleted.";

        // Delete each file
        for (const QString &file : autoSaveFiles) {
            autoSaveDir.remove(file);
        }
        m_autoSaveUUID.clear();
    }
}

void ComposerViewBase::generateMessage(std::function<void(QList<KMime::Message::Ptr>)> callback)
{
    auto composer = new ComposerJob;
    fillComposer(composer);
    composer->setAutoSave(true);
    m_composers.append(composer);
    connect(composer, &MessageComposer::ComposerJob::result, this, [composer, callback]() {
        callback(composer->resultMessages());
    });
    composer->start();
}

void ComposerViewBase::autoSaveMessage()
{
    qCDebug(EDITOR_LOG) << "Autosaving message";

    if (m_autoSaveTimer) {
        m_autoSaveTimer->stop();
    }

    if (!m_composers.isEmpty()) {
        // This may happen if e.g. the autosave timer calls applyChanges.
        qCDebug(EDITOR_LOG) << "Autosave: Called while composer active; ignoring. Number of composer " << m_composers.count();
        return;
    }

    auto composer = new ComposerJob;
    fillComposer(composer);
    composer->setAutoSave(true);
    m_composers.append(composer);
    connect(composer, &MessageComposer::ComposerJob::result, this, &ComposerViewBase::slotAutoSaveComposeResult);
    composer->start();
}

void ComposerViewBase::slotSaveDraft()
{
    qCDebug(EDITOR_LOG) << "Saving draft";

    m_composers.clear();

    auto composer = new ComposerJob;
    fillComposer(composer);
    composer->setDraft(true);
    composer->setSignAndEncrypt(false, true);
    const auto normalized = GpgME::UserID::addrSpecFromString(m_identity.fullEmailAddr().toUtf8().constData());
    const auto normalizedSender = QString::fromUtf8(normalized.c_str());

    auto encryptionKeys = Kleo::KeyCache::instance()->findByEMailAddress(normalizedSender.toStdString());
    std::vector<GpgME::Key> filteredEncryptionKeys;

    // ensure all keys are valid
    for (const auto &key : encryptionKeys) {
        if (strlen(key.userID(0).email()) > 0) {
            filteredEncryptionKeys.push_back(key);
        }
    }
    Q_ASSERT(!filteredEncryptionKeys.empty());

    composer->setEncryptionKeys({QPair<QStringList, std::vector<GpgME::Key>>{{normalizedSender}, filteredEncryptionKeys}});
    composer->setCryptoMessageFormat(m_cryptoMessageFormat);
    m_composers.append(composer);
    connect(composer, &MessageComposer::ComposerJob::result, this, &ComposerViewBase::slotSaveDraftComposeResult);
    composer->start();
}

void ComposerViewBase::setAutoSaveFileName(const QString &fileName)
{
    m_autoSaveUUID = fileName;

    Q_EMIT modified(true);
}

void ComposerViewBase::slotAutoSaveComposeResult(KJob *job)
{
    using MessageComposer::ComposerJob;

    Q_ASSERT(dynamic_cast<ComposerJob *>(job));
    auto composer = static_cast<ComposerJob *>(job);

    if (composer->error() == ComposerJob::NoError) {
        Q_ASSERT(m_composers.contains(composer));

        // The messages were composed successfully. Only save the first message, there should
        // only be one anyway, since crypto is disabled.
        qCDebug(EDITOR_LOG) << "NoError.";
        writeAutoSaveToDisk(composer->resultMessages().constFirst());
        Q_ASSERT(composer->resultMessages().size() == 1);

        if (m_autoSaveInterval > 0) {
            updateAutoSave();
        }
    } else if (composer->error() == MessageComposer::ComposerJob::UserCancelledError) {
        // The job warned the user about something, and the user chose to return
        // to the message.  Nothing to do.
        qCDebug(EDITOR_LOG) << "UserCancelledError.";
        Q_EMIT failed(i18n("Job cancelled by the user"), AutoSave);
    } else {
        qCDebug(EDITOR_LOG) << "other Error.";
        Q_EMIT failed(i18n("Could not autosave message: %1", job->errorString()), AutoSave);
    }

    m_composers.removeAll(composer);
}

void ComposerViewBase::slotSaveDraftComposeResult(KJob *job)
{
    using MessageComposer::ComposerJob;

    Q_ASSERT(dynamic_cast<ComposerJob *>(job));
    auto composer = static_cast<ComposerJob *>(job);

    if (composer->error() == ComposerJob::NoError) {
        Q_ASSERT(m_composers.contains(composer));

        // The messages were composed successfully. Only save the first message, there should
        // only be one anyway, since crypto is disabled.
        qCDebug(EDITOR_LOG) << "NoError.";
        writeDraftToDisk(composer->resultMessages().constFirst());
        Q_ASSERT(composer->resultMessages().size() == 1);

        Q_EMIT closeWindow();
    } else if (composer->error() == MessageComposer::ComposerJob::UserCancelledError) {
        // The job warned the user about something, and the user chose to return
        // to the message.  Nothing to do.
        qCDebug(EDITOR_LOG) << "UserCancelledError.";
        Q_EMIT failed(i18n("Job cancelled by the user"), Draft);
    } else {
        qCDebug(EDITOR_LOG) << "other Error." << composer->error();
        Q_EMIT failed(i18n("Could not save message as draft: %1", job->errorString()), Draft);
    }

    m_composers.removeAll(composer);
}

void ComposerViewBase::writeDraftToDisk(const KMime::Message::Ptr &message)
{
    QDir().mkpath(DraftManager::draftDirectory());

    const QString filename = DraftManager::draftDirectory() + mailId();
    QSaveFile file(filename);
    QString errorMessage;
    qCDebug(EDITOR_LOG) << "Writing message to disk as" << filename;

    if (file.open(QIODevice::WriteOnly)) {
        file.setPermissions(QFile::ReadUser | QFile::WriteUser);

        if (file.write(message->encodedContent()) != static_cast<qint64>(message->encodedContent().size())) {
            errorMessage = i18n("Could not write all data to file.");
        } else {
            if (!file.commit()) {
                errorMessage = i18n("Could not finalize the file.");
            }
        }
    } else {
        errorMessage = i18n("Could not open file.");
    }

    if (!errorMessage.isEmpty()) {
        qCWarning(EDITOR_LOG) << "Saving draft failed:" << errorMessage << file.errorString() << "mailId" << mailId();
        if (!m_autoSaveErrorShown) {
            KMessageBox::error(m_parentWidget,
                               i18n("Saving the draft message as %1 failed.\n"
                                    "%2\n"
                                    "Reason: %3",
                                    filename,
                                    errorMessage,
                                    file.errorString()),
                               i18nc("@title:window", "Autosaving Message Failed"));

            // Error dialog shown, hide the errors the next time
            m_autoSaveErrorShown = true;
        }
    } else {
        // No error occurred, the next error should be shown again
        m_autoSaveErrorShown = false;
    }
    file.commit();
    message->clear();
}

void ComposerViewBase::writeAutoSaveToDisk(const KMime::Message::Ptr &message)
{
    QDir().mkpath(DraftManager::autosaveDirectory());
    const QString filename = DraftManager::autosaveDirectory() + mailId();
    QSaveFile file(filename);
    QString errorMessage;
    qCDebug(EDITOR_LOG) << "Writing message to disk as" << filename;

    if (file.open(QIODevice::WriteOnly)) {
        file.setPermissions(QFile::ReadUser | QFile::WriteUser);

        if (file.write(message->encodedContent()) != static_cast<qint64>(message->encodedContent().size())) {
            errorMessage = i18n("Could not write all data to file.");
        } else {
            if (!file.commit()) {
                errorMessage = i18n("Could not finalize the file.");
            }
        }
    } else {
        errorMessage = i18n("Could not open file.");
    }

    if (!errorMessage.isEmpty()) {
        qCWarning(EDITOR_LOG) << "Auto saving failed:" << errorMessage << file.errorString() << "mailId=" << mailId();
        if (!m_autoSaveErrorShown) {
            KMessageBox::error(m_parentWidget,
                               i18n("Autosaving the message as %1 failed.\n"
                                    "%2\n"
                                    "Reason: %3",
                                    filename,
                                    errorMessage,
                                    file.errorString()),
                               i18nc("@title:window", "Autosaving Message Failed"));

            // Error dialog shown, hide the errors the next time
            m_autoSaveErrorShown = true;
        }
    } else {
        // No error occurred, the next error should be shown again
        m_autoSaveErrorShown = false;
    }
    file.commit();
    message->clear();
}

void ComposerViewBase::addAttachment(const QUrl &url, const QString &comment, bool sync)
{
    Q_UNUSED(comment)
    qCDebug(EDITOR_LOG) << "adding attachment with url:" << url;
    if (sync) {
        m_attachmentController->addAttachmentUrlSync(url);
    } else {
        m_attachmentController->addAttachment(url);
    }
}

void ComposerViewBase::addAttachment(const QString &name, const QString &filename, const QByteArray &data, const QByteArray &mimeType)
{
    MessageCore::AttachmentPart::Ptr attachment = MessageCore::AttachmentPart::Ptr(new MessageCore::AttachmentPart());
    if (!data.isEmpty()) {
        attachment->setName(name);
        attachment->setFileName(filename);
        attachment->setData(data);
        attachment->setMimeType(mimeType);
        // TODO what about the other fields?

        m_attachmentController->addAttachment(attachment);
    }
}

void ComposerViewBase::addAttachmentPart(KMime::Content *partToAttach)
{
    MessageCore::AttachmentPart::Ptr part(new MessageCore::AttachmentPart);
    if (partToAttach->contentType()->mimeType() == "multipart/digest" || partToAttach->contentType(false)->mimeType() == "message/rfc822") {
        // if it is a digest or a full message, use the encodedContent() of the attachment,
        // which already has the proper headers
        part->setData(partToAttach->encodedContent());
    } else {
        part->setData(partToAttach->decodedContent());
    }
    part->setMimeType(partToAttach->contentType(false)->mimeType());
    if (auto cd = partToAttach->contentDescription(false)) {
        part->setDescription(cd->asUnicodeString());
    }
    if (auto ct = partToAttach->contentType(false)) {
        if (ct->hasParameter(QByteArrayView("name"))) {
            part->setName(ct->parameter(QByteArrayView("name")));
        }
    }
    if (auto cd = partToAttach->contentDisposition(false)) {
        part->setFileName(cd->filename());
        part->setInline(cd->disposition() == KMime::Headers::CDinline);
    }
    if (part->name().isEmpty() && !part->fileName().isEmpty()) {
        part->setName(part->fileName());
    }
    if (part->fileName().isEmpty() && !part->name().isEmpty()) {
        part->setFileName(part->name());
    }
    m_attachmentController->addAttachment(part);
}

void ComposerViewBase::queueMessage(const KMime::Message::Ptr &message)
{
    Q_ASSERT(m_messageDispatcher);
    m_messageDispatcher->dispatch(message, from(), mailId());
}

void ComposerViewBase::fillComposer(MessageComposer::ComposerJob *composer)
{
    fillComposer(composer, UseUnExpandedRecipients, false);
}

void ComposerViewBase::fillComposer(MessageComposer::ComposerJob *composer, ComposerViewBase::RecipientExpansion expansion, bool autoresize)
{
    fillInfoPart(composer->infoPart(), expansion);
    fillGlobalPart(composer->globalPart());
    m_editor->fillComposerTextPart(composer->textPart());
    fillInfoPart(composer->infoPart(), expansion);
    if (m_attachmentModel) {
        composer->addAttachmentParts(m_attachmentModel->attachments(), autoresize);
    } else {
        qCDebug(EDITOR_LOG) << "fillComposer has no attachment model";
    }
}

//-----------------------------------------------------------------------------
QString ComposerViewBase::to() const
{
    if (m_recipientsEditor) {
        return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(Recipient::To));
    }
    return {};
}

//-----------------------------------------------------------------------------
QString ComposerViewBase::cc() const
{
    if (m_recipientsEditor) {
        return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(Recipient::Cc));
    }
    return {};
}

//-----------------------------------------------------------------------------
QString ComposerViewBase::bcc() const
{
    if (m_recipientsEditor) {
        return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(Recipient::Bcc));
    }
    return {};
}

QString ComposerViewBase::from() const
{
    return MessageComposer::Util::cleanedUpHeaderString(m_from);
}

QString ComposerViewBase::replyTo() const
{
    if (m_recipientsEditor) {
        return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(Recipient::ReplyTo));
    }
    return {};
}

QString ComposerViewBase::subject() const
{
    return MessageComposer::Util::cleanedUpHeaderString(m_subject);
}

void ComposerViewBase::setIdentity(const KIdentityManagementCore::Identity &identity)
{
    m_identity = identity;
}

KIdentityManagementCore::Identity ComposerViewBase::identity() const
{
    return m_identity;
}

void ComposerViewBase::setParentWidgetForGui(QWidget *w)
{
    m_parentWidget = w;
}

void ComposerViewBase::setAttachmentController(MessageComposer::AttachmentControllerBase *controller)
{
    m_attachmentController = controller;
}

MessageComposer::AttachmentControllerBase *ComposerViewBase::attachmentController()
{
    return m_attachmentController;
}

void ComposerViewBase::setAttachmentModel(MessageComposer::AttachmentModel *model)
{
    m_attachmentModel = model;
}

MessageComposer::AttachmentModel *ComposerViewBase::attachmentModel()
{
    return m_attachmentModel;
}

void ComposerViewBase::setRecipientsEditor(RecipientsEditor *recEditor)
{
    m_recipientsEditor = recEditor;
}

RecipientsEditor *ComposerViewBase::recipientsEditor()
{
    return m_recipientsEditor;
}

void ComposerViewBase::setSignatureController(MessageComposer::SignatureController *sigController)
{
    m_signatureController = sigController;
}

MessageComposer::SignatureController *ComposerViewBase::signatureController()
{
    return m_signatureController;
}

void ComposerViewBase::updateRecipients(const KIdentityManagementCore::Identity &ident, const KIdentityManagementCore::Identity &oldIdent, Recipient::Type type)
{
    QString oldIdentList;
    QString newIdentList;
    if (type == Recipient::Bcc) {
        oldIdentList = oldIdent.bcc();
        newIdentList = ident.bcc();
    } else if (type == Recipient::Cc) {
        oldIdentList = oldIdent.cc();
        newIdentList = ident.cc();
    } else if (type == Recipient::ReplyTo) {
        oldIdentList = oldIdent.replyToAddr();
        newIdentList = ident.replyToAddr();
    } else {
        return;
    }

    if (oldIdentList != newIdentList) {
        const auto oldRecipients = KMime::Types::Mailbox::listFromUnicodeString(oldIdentList);
        for (const KMime::Types::Mailbox &recipient : oldRecipients) {
            m_recipientsEditor->removeRecipient(recipient.prettyAddress(), type);
        }

        const auto newRecipients = KMime::Types::Mailbox::listFromUnicodeString(newIdentList);
        for (const KMime::Types::Mailbox &recipient : newRecipients) {
            m_recipientsEditor->addRecipient(recipient.prettyAddress(), type);
        }
        m_recipientsEditor->setFocusBottom();
    }
}

void ComposerViewBase::identityChanged(const KIdentityManagementCore::Identity &ident, const KIdentityManagementCore::Identity &oldIdent, bool msgCleared)
{
    updateRecipients(ident, oldIdent, Recipient::Bcc);
    updateRecipients(ident, oldIdent, Recipient::Cc);
    updateRecipients(ident, oldIdent, Recipient::ReplyTo);

    KIdentityManagementCore::Signature oldSig = const_cast<KIdentityManagementCore::Identity &>(oldIdent).signature();
    KIdentityManagementCore::Signature newSig = const_cast<KIdentityManagementCore::Identity &>(ident).signature();
    // replace existing signatures
    const bool replaced = editor()->composerSignature()->replaceSignature(oldSig, newSig);
    // Just append the signature if there was no old signature
    if (!replaced && (msgCleared || oldSig.rawText().isEmpty())) {
        signatureController()->applySignature(newSig);
    }

    m_editor->setAutocorrectionLanguage(ident.autocorrectionLanguage());
}

void ComposerViewBase::setEditor(MessageComposer::RichTextComposerNg *editor)
{
    m_editor = editor;
    m_editor->document()->setModified(false);
}

MessageComposer::RichTextComposerNg *ComposerViewBase::editor() const
{
    return m_editor;
}

void ComposerViewBase::setFrom(const QString &from)
{
    m_from = from;
}

void ComposerViewBase::setSubject(const QString &subject)
{
    m_subject = subject;
}

void ComposerViewBase::setAutoSaveInterval(int interval)
{
    m_autoSaveInterval = interval;
}

void ComposerViewBase::setCryptoOptions(bool sign, bool encrypt, Kleo::CryptoMessageFormat format, bool neverEncryptDrafts)
{
    m_sign = sign;
    m_encrypt = encrypt;
    m_cryptoMessageFormat = format;
    m_neverEncrypt = neverEncryptDrafts;
}

void ComposerViewBase::setMDNRequested(bool mdnRequested)
{
    m_mdnRequested = mdnRequested;
}

void ComposerViewBase::setUrgent(bool urgent)
{
    m_urgent = urgent;
}

int ComposerViewBase::autoSaveInterval() const
{
    return m_autoSaveInterval;
}

//-----------------------------------------------------------------------------
void ComposerViewBase::collectImages(KMime::Content *root)
{
    if (KMime::Content *n = Util::findTypeInMessage(root, "multipart", "alternative")) {
        KMime::Content *parentnode = n->parent();
        if (parentnode && parentnode->contentType()->isMultipart() && parentnode->contentType()->subType() == "related") {
            KMime::Content *node = MessageCore::NodeHelper::nextSibling(n);
            while (node) {
                if (node->contentType()->isImage()) {
                    qCDebug(EDITOR_LOG) << "found image in multipart/related : " << node->contentType()->name();
                    QImage img;
                    img.loadFromData(node->decodedContent());
                    m_editor->composerControler()->composerImages()->loadImage(
                        img,
                        QString::fromLatin1(QByteArray(QByteArrayLiteral("cid:") + node->contentID()->identifier())),
                        node->contentType()->name());
                }
                node = MessageCore::NodeHelper::nextSibling(node);
            }
        }
    }
}

//-----------------------------------------------------------------------------
bool ComposerViewBase::inlineSigningEncryptionSelected() const
{
    if (!m_sign && !m_encrypt) {
        return false;
    }
    return m_cryptoMessageFormat == Kleo::InlineOpenPGPFormat;
}

bool ComposerViewBase::hasMissingAttachments(const QStringList &attachmentKeywords)
{
    if (attachmentKeywords.isEmpty()) {
        return false;
    }
    if (m_attachmentModel && m_attachmentModel->rowCount() > 0) {
        return false;
    }

    return MessageComposer::Util::hasMissingAttachments(attachmentKeywords, m_editor->document(), subject());
}

ComposerViewBase::MissingAttachment ComposerViewBase::checkForMissingAttachments(const QStringList &attachmentKeywords)
{
    if (!hasMissingAttachments(attachmentKeywords)) {
        return NoMissingAttachmentFound;
    }
    const int rc = KMessageBox::warningTwoActionsCancel(m_editor,

                                                        i18n("The message you have composed seems to refer to an "
                                                             "attached file but you have not attached anything.\n"
                                                             "Do you want to attach a file to your message?"),
                                                        i18nc("@title:window", "File Attachment Reminder"),
                                                        KGuiItem(i18n("&Attach File..."), QLatin1String("mail-attachment")),
                                                        KGuiItem(i18n("&Send as Is"), QLatin1String("mail-send")));
    if (rc == KMessageBox::Cancel) {
        return FoundMissingAttachmentAndCancel;
    }
    if (rc == KMessageBox::ButtonCode::PrimaryAction) {
        m_attachmentController->showAddAttachmentFileDialog();
        return FoundMissingAttachmentAndAddedAttachment;
    }

    return FoundMissingAttachmentAndSending;
}

void ComposerViewBase::markAllAttachmentsForSigning(bool sign)
{
    if (m_attachmentModel) {
        const auto attachments = m_attachmentModel->attachments();
        for (MessageCore::AttachmentPart::Ptr attachment : attachments) {
            attachment->setSigned(sign);
        }
    }
}

void ComposerViewBase::markAllAttachmentsForEncryption(bool encrypt)
{
    if (m_attachmentModel) {
        const auto attachments = m_attachmentModel->attachments();
        for (MessageCore::AttachmentPart::Ptr attachment : attachments) {
            attachment->setEncrypted(encrypt);
        }
    }
}

bool ComposerViewBase::requestDeleveryConfirmation() const
{
    return m_requestDeleveryConfirmation;
}

void ComposerViewBase::setRequestDeleveryConfirmation(bool requestDeleveryConfirmation)
{
    m_requestDeleveryConfirmation = requestDeleveryConfirmation;
}

KMime::Message::Ptr ComposerViewBase::msg() const
{
    return m_msg;
}

std::shared_ptr<Kleo::ExpiryChecker> ComposerViewBase::expiryChecker()
{
    if (!mExpiryChecker) {
        mExpiryChecker.reset(new Kleo::ExpiryChecker{Kleo::ExpiryCheckerSettings{encryptOwnKeyNearExpiryWarningThresholdInDays(),
                                                                                 encryptKeyNearExpiryWarningThresholdInDays(),
                                                                                 encryptRootCertNearExpiryWarningThresholdInDays(),
                                                                                 encryptChainCertNearExpiryWarningThresholdInDays()}});
    }
    return mExpiryChecker;
}

QString ComposerViewBase::mailId() const
{
    if (m_autoSaveUUID.isEmpty()) {
        m_autoSaveUUID = QUuid::createUuid().toString(QUuid::WithoutBraces);
    }
    return m_autoSaveUUID;
}

bool ComposerViewBase::mailIdIsEmpty() const
{
    return m_autoSaveUUID.isEmpty();
}

MessageDispatcher *ComposerViewBase::messageDispatcher() const
{
    return m_messageDispatcher;
}

void ComposerViewBase::setMessageDispatcher(MessageDispatcher *messageDispatcher)
{
    m_messageDispatcher = messageDispatcher;
}

#include "moc_composerviewbase.cpp"
