File indexing completed on 2024-05-12 05:20:38

0001 /*
0002     This file is part of KMail, the KDE mail client.
0003     SPDX-FileCopyrightText: 2002 Don Sanders <sanders@kde.org>
0004     SPDX-FileCopyrightText: 2013-2024 Laurent Montel <montel@kde.org>
0005 
0006     SPDX-License-Identifier: GPL-2.0-only
0007 */
0008 
0009 //
0010 // This file implements various "command" classes. These command classes
0011 // are based on the command design pattern.
0012 //
0013 // Historically various operations were implemented as slots of KMMainWin.
0014 // This proved inadequate as KMail has multiple top level windows
0015 // (KMMainWin, KMReaderMainWin, SearchWindow, KMComposeWin) that may
0016 // benefit from using these operations. It is desirable that these
0017 // classes can operate without depending on or altering the state of
0018 // a KMMainWin, in fact it is possible no KMMainWin object even exists.
0019 //
0020 // Now these operations have been rewritten as KMCommand based classes,
0021 // making them independent of KMMainWin.
0022 //
0023 // The base command class KMCommand is async, which is a difference
0024 // from the conventional command pattern. As normal derived classes implement
0025 // the execute method, but client classes call start() instead of
0026 // calling execute() directly. start() initiates async operations,
0027 // and on completion of these operations calls execute() and then deletes
0028 // the command. (So the client must not construct commands on the stack).
0029 //
0030 // The type of async operation supported by KMCommand is retrieval
0031 // of messages from an IMAP server.
0032 
0033 #include "kmcommands.h"
0034 
0035 #include "kmail_debug.h"
0036 #include "kmreadermainwin.h"
0037 #include "secondarywindow.h"
0038 #include "util.h"
0039 #include "widgets/collectionpane.h"
0040 
0041 #include "job/createforwardmessagejob.h"
0042 #include "job/createreplymessagejob.h"
0043 
0044 #include "editor/composer.h"
0045 #include "kmmainwidget.h"
0046 #include "undostack.h"
0047 
0048 #include <KIdentityManagementCore/IdentityManager>
0049 
0050 #include <KMime/MDN>
0051 #include <KMime/Message>
0052 
0053 #include <Akonadi/ItemCopyJob>
0054 #include <Akonadi/ItemCreateJob>
0055 #include <Akonadi/ItemDeleteJob>
0056 #include <Akonadi/ItemFetchJob>
0057 #include <Akonadi/ItemModifyJob>
0058 #include <Akonadi/ItemMoveJob>
0059 #include <Akonadi/Tag>
0060 #include <Akonadi/TagCreateJob>
0061 
0062 #include <Akonadi/MDNStateAttribute>
0063 #include <MailCommon/CryptoUtils>
0064 #include <MailCommon/FilterAction>
0065 #include <MailCommon/FilterManager>
0066 #include <MailCommon/FolderSettings>
0067 #include <MailCommon/MailFilter>
0068 #include <MailCommon/MailKernel>
0069 #include <MailCommon/MailUtil>
0070 #include <MailCommon/RedirectDialog>
0071 
0072 #include <MessageCore/MailingList>
0073 #include <MessageCore/MessageCoreSettings>
0074 #include <MessageCore/StringUtil>
0075 
0076 #include <MessageComposer/MessageComposerSettings>
0077 #include <MessageComposer/MessageHelper>
0078 #include <MessageComposer/MessageSender>
0079 #include <MessageComposer/Util>
0080 
0081 #include <MessageList/Pane>
0082 
0083 #include <MessageViewer/CSSHelper>
0084 #include <MessageViewer/HeaderStylePlugin>
0085 #include <MessageViewer/MessageViewerSettings>
0086 #include <MessageViewer/MessageViewerUtil>
0087 #include <MessageViewer/ObjectTreeEmptySource>
0088 
0089 #include <MimeTreeParser/NodeHelper>
0090 #include <MimeTreeParser/ObjectTreeParser>
0091 
0092 #include <Akonadi/SentBehaviourAttribute>
0093 #include <Akonadi/TransportAttribute>
0094 #include <MailTransport/TransportManager>
0095 
0096 #include <Libkdepim/ProgressManager>
0097 #include <PimCommon/BroadcastStatus>
0098 
0099 #include <KCursorSaver>
0100 
0101 #include <gpgme++/error.h>
0102 
0103 #include <KBookmarkManager>
0104 
0105 #include <KEmailAddress>
0106 #include <KFileWidget>
0107 #include <KLocalizedString>
0108 #include <KMessageBox>
0109 #include <KRecentDirs>
0110 
0111 // KIO headers
0112 #include <KIO/FileCopyJob>
0113 #include <KIO/JobUiDelegate>
0114 #include <KIO/StatJob>
0115 
0116 #include <QApplication>
0117 #include <QByteArray>
0118 #include <QFileDialog>
0119 #include <QFontDatabase>
0120 #include <QProgressDialog>
0121 #include <QStandardPaths>
0122 
0123 using KMail::SecondaryWindow;
0124 using MailTransport::TransportManager;
0125 using MessageComposer::MessageFactoryNG;
0126 
0127 using KPIM::ProgressItem;
0128 using KPIM::ProgressManager;
0129 using namespace KMime;
0130 
0131 using namespace MailCommon;
0132 
0133 /// Helper to sanely show an error message for a job
0134 static void showJobError(KJob *job)
0135 {
0136     assert(job);
0137     // we can be called from the KJob::kill, where we are no longer a KIO::Job
0138     // so better safe than sorry
0139     auto kiojob = qobject_cast<KIO::Job *>(job);
0140     if (kiojob && kiojob->uiDelegate()) {
0141         kiojob->uiDelegate()->showErrorMessage();
0142     } else {
0143         qCWarning(KMAIL_LOG) << "There is no GUI delegate set for a kjob, and it failed with error:" << job->errorString();
0144     }
0145 }
0146 
0147 KMCommand::KMCommand(QWidget *parent)
0148     : mDeletesItself(false)
0149     , mEmitsCompletedItself(false)
0150     , mParent(parent)
0151 {
0152 }
0153 
0154 KMCommand::KMCommand(QWidget *parent, const Akonadi::Item &msg)
0155     : KMCommand(parent)
0156 {
0157     if (msg.isValid() || msg.hasPayload<KMime::Message::Ptr>()) {
0158         mMsgList.append(msg);
0159     }
0160 }
0161 
0162 KMCommand::KMCommand(QWidget *parent, const Akonadi::Item::List &msgList)
0163     : KMCommand(parent)
0164 {
0165     mMsgList = msgList;
0166 }
0167 
0168 KMCommand::~KMCommand() = default;
0169 
0170 KMCommand::Result KMCommand::result() const
0171 {
0172     if (mResult == Undefined) {
0173         qCDebug(KMAIL_LOG) << "mResult is Undefined";
0174     }
0175     return mResult;
0176 }
0177 
0178 const Akonadi::Item::List KMCommand::retrievedMsgs() const
0179 {
0180     return mRetrievedMsgs;
0181 }
0182 
0183 Akonadi::Item KMCommand::retrievedMessage() const
0184 {
0185     if (mRetrievedMsgs.isEmpty()) {
0186         return {};
0187     }
0188     return *(mRetrievedMsgs.begin());
0189 }
0190 
0191 QWidget *KMCommand::parentWidget() const
0192 {
0193     return mParent;
0194 }
0195 
0196 bool KMCommand::deletesItself() const
0197 {
0198     return mDeletesItself;
0199 }
0200 
0201 void KMCommand::setDeletesItself(bool deletesItself)
0202 {
0203     mDeletesItself = deletesItself;
0204 }
0205 
0206 bool KMCommand::emitsCompletedItself() const
0207 {
0208     return mEmitsCompletedItself;
0209 }
0210 
0211 void KMCommand::setEmitsCompletedItself(bool emitsCompletedItself)
0212 {
0213     mEmitsCompletedItself = emitsCompletedItself;
0214 }
0215 
0216 void KMCommand::setResult(KMCommand::Result result)
0217 {
0218     mResult = result;
0219 }
0220 
0221 int KMCommand::mCountJobs = 0;
0222 
0223 void KMCommand::start()
0224 {
0225     connect(this, &KMCommand::messagesTransfered, this, &KMCommand::slotPostTransfer);
0226 
0227     if (mMsgList.isEmpty()) {
0228         Q_EMIT messagesTransfered(OK);
0229         return;
0230     }
0231 
0232     // Special case of operating on message that isn't in a folder
0233     const Akonadi::Item mb = mMsgList.constFirst();
0234     if ((mMsgList.count() == 1) && MessageComposer::Util::isStandaloneMessage(mb)) {
0235         mRetrievedMsgs.append(mMsgList.takeFirst());
0236         Q_EMIT messagesTransfered(OK);
0237         return;
0238     }
0239 
0240     // we can only retrieve items with a valid id
0241     for (const Akonadi::Item &item : std::as_const(mMsgList)) {
0242         if (!item.isValid()) {
0243             Q_EMIT messagesTransfered(Failed);
0244             return;
0245         }
0246     }
0247 
0248     // transfer the selected messages first
0249     transferSelectedMsgs();
0250 }
0251 
0252 void KMCommand::slotPostTransfer(KMCommand::Result result)
0253 {
0254     disconnect(this, &KMCommand::messagesTransfered, this, &KMCommand::slotPostTransfer);
0255     if (result == OK) {
0256         result = execute();
0257     }
0258     mResult = result;
0259     if (!emitsCompletedItself()) {
0260         Q_EMIT completed(this);
0261     }
0262     if (!deletesItself()) {
0263         deleteLater();
0264     }
0265 }
0266 
0267 Akonadi::ItemFetchJob *KMCommand::createFetchJob(const Akonadi::Item::List &items)
0268 {
0269     return new Akonadi::ItemFetchJob(items, this);
0270 }
0271 
0272 void KMCommand::fetchMessages(const Akonadi::Item::List &ids)
0273 {
0274     ++KMCommand::mCountJobs;
0275     Akonadi::ItemFetchJob *fetch = createFetchJob(ids);
0276     mFetchScope.fetchAttribute<Akonadi::MDNStateAttribute>();
0277     fetch->setFetchScope(mFetchScope);
0278     connect(fetch, &Akonadi::ItemFetchJob::itemsReceived, this, &KMCommand::slotMsgTransfered);
0279     connect(fetch, &Akonadi::ItemFetchJob::result, this, &KMCommand::slotJobFinished);
0280 }
0281 
0282 void KMCommand::transferSelectedMsgs()
0283 {
0284     // make sure no other transfer is active
0285     if (KMCommand::mCountJobs > 0) {
0286         Q_EMIT messagesTransfered(Failed);
0287         return;
0288     }
0289 
0290     bool complete = true;
0291     KMCommand::mCountJobs = 0;
0292     mCountMsgs = 0;
0293     mRetrievedMsgs.clear();
0294     mCountMsgs = mMsgList.count();
0295     uint totalSize = 0;
0296     // the QProgressDialog for the user-feedback. Only enable it if it's needed.
0297     // For some commands like KMSetStatusCommand it's not needed. Note, that
0298     // for some reason the QProgressDialog eats the MouseReleaseEvent (if a
0299     // command is executed after the MousePressEvent), cf. bug #71761.
0300     if (mCountMsgs > 0) {
0301         mProgressDialog = new QProgressDialog(mParent);
0302         mProgressDialog.data()->setWindowTitle(i18nc("@title:window", "Please wait"));
0303 
0304         mProgressDialog.data()->setLabelText(
0305             i18np("Please wait while the message is transferred", "Please wait while the %1 messages are transferred", mMsgList.count()));
0306         mProgressDialog.data()->setModal(true);
0307         mProgressDialog.data()->setMinimumDuration(1000);
0308     }
0309 
0310     // TODO once the message list is based on ETM and we get the more advanced caching we need to make that check a bit more clever
0311     if (!mFetchScope.isEmpty()) {
0312         complete = false;
0313         Akonadi::Item::List ids;
0314         ids.reserve(100);
0315         for (const Akonadi::Item &item : mMsgList) {
0316             ids.append(item);
0317             if (ids.count() >= 100) {
0318                 fetchMessages(ids);
0319                 ids.clear();
0320                 ids.reserve(100);
0321             }
0322         }
0323         if (!ids.isEmpty()) {
0324             fetchMessages(ids);
0325         }
0326     } else {
0327         // no need to fetch anything
0328         if (!mMsgList.isEmpty()) {
0329             mRetrievedMsgs = mMsgList;
0330         }
0331     }
0332 
0333     if (complete) {
0334         delete mProgressDialog.data();
0335         mProgressDialog.clear();
0336         Q_EMIT messagesTransfered(OK);
0337     } else {
0338         // wait for the transfer and tell the progressBar the necessary steps
0339         if (mProgressDialog.data()) {
0340             connect(mProgressDialog.data(), &QProgressDialog::canceled, this, &KMCommand::slotTransferCancelled);
0341             mProgressDialog.data()->setMaximum(totalSize);
0342         }
0343     }
0344 }
0345 
0346 void KMCommand::slotMsgTransfered(const Akonadi::Item::List &msgs)
0347 {
0348     if (mProgressDialog.data() && mProgressDialog.data()->wasCanceled()) {
0349         Q_EMIT messagesTransfered(Canceled);
0350         return;
0351     }
0352     // save the complete messages
0353     mRetrievedMsgs.append(msgs);
0354 }
0355 
0356 void KMCommand::slotJobFinished()
0357 {
0358     // the job is finished (with / without error)
0359     KMCommand::mCountJobs--;
0360 
0361     if (mProgressDialog.data() && mProgressDialog.data()->wasCanceled()) {
0362         return;
0363     }
0364 
0365     if (KMCommand::mCountJobs == 0 && (mCountMsgs > mRetrievedMsgs.count())) {
0366         // the message wasn't retrieved before => error
0367         if (mProgressDialog.data()) {
0368             mProgressDialog.data()->hide();
0369         }
0370         slotTransferCancelled();
0371         return;
0372     }
0373     // update the progressbar
0374     if (mProgressDialog.data()) {
0375         mProgressDialog.data()->setLabelText(
0376             i18np("Please wait while the message is transferred", "Please wait while the %1 messages are transferred", mCountMsgs));
0377     }
0378     if (KMCommand::mCountJobs == 0) {
0379         // all done
0380         delete mProgressDialog.data();
0381         mProgressDialog.clear();
0382         Q_EMIT messagesTransfered(OK);
0383     }
0384 }
0385 
0386 void KMCommand::slotTransferCancelled()
0387 {
0388     KMCommand::mCountJobs = 0;
0389     mCountMsgs = 0;
0390     mRetrievedMsgs.clear();
0391     Q_EMIT messagesTransfered(Canceled);
0392 }
0393 
0394 KMMailtoComposeCommand::KMMailtoComposeCommand(const QUrl &url, const Akonadi::Item &msg)
0395     : mUrl(url)
0396     , mMessage(msg)
0397 {
0398 }
0399 
0400 KMCommand::Result KMMailtoComposeCommand::execute()
0401 {
0402     KMime::Message::Ptr msg(new KMime::Message);
0403     uint id = 0;
0404 
0405     if (mMessage.isValid() && mMessage.parentCollection().isValid()) {
0406         QSharedPointer<FolderSettings> fd = FolderSettings::forCollection(mMessage.parentCollection(), false);
0407         id = fd->identity();
0408     }
0409 
0410     MessageHelper::initHeader(msg, KMKernel::self()->identityManager(), id);
0411     msg->contentType()->setCharset("utf-8");
0412     msg->to()->fromUnicodeString(KEmailAddress::decodeMailtoUrl(mUrl), "utf-8");
0413 
0414     KMail::Composer *win = KMail::makeComposer(msg, false, false, KMail::Composer::New, id);
0415     win->setFocusToSubject();
0416     win->show();
0417     return OK;
0418 }
0419 
0420 KMMailtoReplyCommand::KMMailtoReplyCommand(QWidget *parent, const QUrl &url, const Akonadi::Item &msg, const QString &selection)
0421     : KMCommand(parent, msg)
0422     , mUrl(url)
0423     , mSelection(selection)
0424 {
0425     fetchScope().fetchFullPayload(true);
0426     fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
0427 }
0428 
0429 KMCommand::Result KMMailtoReplyCommand::execute()
0430 {
0431     Akonadi::Item item = retrievedMessage();
0432     KMime::Message::Ptr msg = MessageComposer::Util::message(item);
0433     if (!msg) {
0434         return Failed;
0435     }
0436     CreateReplyMessageJobSettings settings;
0437     settings.item = item;
0438     settings.msg = msg;
0439     settings.selection = mSelection;
0440     settings.url = mUrl;
0441     settings.replyStrategy = MessageComposer::ReplyNone;
0442     settings.replyAsHtml = mReplyAsHtml;
0443 
0444     auto job = new CreateReplyMessageJob;
0445     job->setSettings(settings);
0446     job->start();
0447 
0448     return OK;
0449 }
0450 
0451 bool KMMailtoReplyCommand::replyAsHtml() const
0452 {
0453     return mReplyAsHtml;
0454 }
0455 
0456 void KMMailtoReplyCommand::setReplyAsHtml(bool replyAsHtml)
0457 {
0458     mReplyAsHtml = replyAsHtml;
0459 }
0460 
0461 KMMailtoForwardCommand::KMMailtoForwardCommand(QWidget *parent, const QUrl &url, const Akonadi::Item &msg)
0462     : KMCommand(parent, msg)
0463     , mUrl(url)
0464 {
0465     fetchScope().fetchFullPayload(true);
0466     fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
0467 }
0468 
0469 KMCommand::Result KMMailtoForwardCommand::execute()
0470 {
0471     // TODO : consider factoring createForward into this method.
0472     Akonadi::Item item = retrievedMessage();
0473     KMime::Message::Ptr msg = MessageComposer::Util::message(item);
0474     if (!msg) {
0475         return Failed;
0476     }
0477     CreateForwardMessageJobSettings settings;
0478     settings.item = item;
0479     settings.msg = msg;
0480     settings.url = mUrl;
0481 
0482     auto job = new CreateForwardMessageJob;
0483     job->setSettings(settings);
0484     job->start();
0485     return OK;
0486 }
0487 
0488 KMAddBookmarksCommand::KMAddBookmarksCommand(const QUrl &url, QWidget *parent)
0489     : KMCommand(parent)
0490     , mUrl(url)
0491 {
0492 }
0493 
0494 KMCommand::Result KMAddBookmarksCommand::execute()
0495 {
0496     const QString filename = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1StringView("/konqueror/bookmarks.xml");
0497     QFileInfo fileInfo(filename);
0498     QDir().mkpath(fileInfo.absolutePath());
0499     KBookmarkManager bookManager(filename);
0500     KBookmarkGroup group = bookManager.root();
0501     group.addBookmark(mUrl.path(), QUrl(mUrl), QString());
0502     if (bookManager.save()) {
0503         bookManager.emitChanged(group);
0504     }
0505 
0506     return OK;
0507 }
0508 
0509 KMUrlSaveCommand::KMUrlSaveCommand(const QUrl &url, QWidget *parent)
0510     : KMCommand(parent)
0511     , mUrl(url)
0512 {
0513 }
0514 
0515 KMCommand::Result KMUrlSaveCommand::execute()
0516 {
0517     if (mUrl.isEmpty()) {
0518         return OK;
0519     }
0520     QString recentDirClass;
0521     QUrl startUrl = KFileWidget::getStartUrl(QUrl(QStringLiteral("kfiledialog:///OpenMessage")), recentDirClass);
0522     startUrl.setPath(startUrl.path() + QLatin1Char('/') + mUrl.fileName());
0523     const QUrl saveUrl = QFileDialog::getSaveFileUrl(parentWidget(), i18n("Save To File"), startUrl);
0524     if (saveUrl.isEmpty()) {
0525         return Canceled;
0526     }
0527 
0528     if (!recentDirClass.isEmpty()) {
0529         KRecentDirs::add(recentDirClass, saveUrl.path());
0530     }
0531 
0532     KIO::Job *job = KIO::file_copy(mUrl, saveUrl, -1, KIO::Overwrite);
0533     connect(job, &KIO::Job::result, this, &KMUrlSaveCommand::slotUrlSaveResult);
0534     setEmitsCompletedItself(true);
0535     return OK;
0536 }
0537 
0538 void KMUrlSaveCommand::slotUrlSaveResult(KJob *job)
0539 {
0540     if (job->error()) {
0541         showJobError(job);
0542         setResult(Failed);
0543     } else {
0544         setResult(OK);
0545     }
0546     Q_EMIT completed(this);
0547 }
0548 
0549 KMEditMessageCommand::KMEditMessageCommand(QWidget *parent, const KMime::Message::Ptr &msg)
0550     : KMCommand(parent)
0551     , mMessage(msg)
0552 {
0553 }
0554 
0555 KMCommand::Result KMEditMessageCommand::execute()
0556 {
0557     if (!mMessage) {
0558         return Failed;
0559     }
0560 
0561     KMail::Composer *win = KMail::makeComposer();
0562     bool lastEncrypt = false;
0563     bool lastSign = false;
0564     KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, mMessage);
0565     win->setMessage(mMessage, lastSign, lastEncrypt, false, true);
0566     win->show();
0567     win->setModified(true);
0568     return OK;
0569 }
0570 
0571 KMEditItemCommand::KMEditItemCommand(QWidget *parent, const Akonadi::Item &msg, bool deleteFromSource)
0572     : KMCommand(parent, msg)
0573     , mDeleteFromSource(deleteFromSource)
0574 {
0575     fetchScope().fetchFullPayload(true);
0576     fetchScope().fetchAttribute<Akonadi::TransportAttribute>();
0577     fetchScope().fetchAttribute<Akonadi::SentBehaviourAttribute>();
0578     fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
0579 }
0580 
0581 KMEditItemCommand::~KMEditItemCommand() = default;
0582 
0583 KMCommand::Result KMEditItemCommand::execute()
0584 {
0585     Akonadi::Item item = retrievedMessage();
0586     if (!item.isValid() || !item.parentCollection().isValid()) {
0587         return Failed;
0588     }
0589     KMime::Message::Ptr msg = MessageComposer::Util::message(item);
0590     if (!msg) {
0591         return Failed;
0592     }
0593 
0594     if (mDeleteFromSource) {
0595         setDeletesItself(true);
0596         auto job = new Akonadi::ItemDeleteJob(item);
0597         connect(job, &KIO::Job::result, this, &KMEditItemCommand::slotDeleteItem);
0598     }
0599     KMail::Composer *win = KMail::makeComposer();
0600     bool lastEncrypt = false;
0601     bool lastSign = false;
0602     KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, msg);
0603     win->setMessage(msg, lastSign, lastEncrypt, false, true);
0604 
0605     win->setFolder(item.parentCollection());
0606 
0607     const auto *transportAttribute = item.attribute<Akonadi::TransportAttribute>();
0608     if (transportAttribute) {
0609         win->setCurrentTransport(transportAttribute->transportId());
0610     } else {
0611         int transportId = -1;
0612         if (auto hrd = msg->headerByType("X-KMail-Transport")) {
0613             transportId = hrd->asUnicodeString().toInt();
0614         }
0615         if (transportId != -1) {
0616             win->setCurrentTransport(transportId);
0617         }
0618     }
0619 
0620     const auto *sentAttribute = item.attribute<Akonadi::SentBehaviourAttribute>();
0621     if (sentAttribute && (sentAttribute->sentBehaviour() == Akonadi::SentBehaviourAttribute::MoveToCollection)) {
0622         win->setFcc(QString::number(sentAttribute->moveToCollection().id()));
0623     }
0624     win->show();
0625     if (mDeleteFromSource) {
0626         win->setModified(true);
0627     }
0628 
0629     return OK;
0630 }
0631 
0632 void KMEditItemCommand::slotDeleteItem(KJob *job)
0633 {
0634     if (job->error()) {
0635         showJobError(job);
0636         setResult(Failed);
0637     } else {
0638         setResult(OK);
0639     }
0640     Q_EMIT completed(this);
0641     deleteLater();
0642 }
0643 
0644 KMUseTemplateCommand::KMUseTemplateCommand(QWidget *parent, const Akonadi::Item &msg)
0645     : KMCommand(parent, msg)
0646 {
0647     fetchScope().fetchFullPayload(true);
0648     fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
0649 }
0650 
0651 KMCommand::Result KMUseTemplateCommand::execute()
0652 {
0653     Akonadi::Item item = retrievedMessage();
0654     if (!item.isValid() || !item.parentCollection().isValid() || !CommonKernel->folderIsTemplates(item.parentCollection())) {
0655         return Failed;
0656     }
0657     KMime::Message::Ptr msg = MessageComposer::Util::message(item);
0658     if (!msg) {
0659         return Failed;
0660     }
0661 
0662     KMime::Message::Ptr newMsg(new KMime::Message);
0663     newMsg->setContent(msg->encodedContent());
0664     newMsg->parse();
0665     // these fields need to be regenerated for the new message
0666     newMsg->removeHeader<KMime::Headers::Date>();
0667     newMsg->removeHeader<KMime::Headers::MessageID>();
0668 
0669     KMail::Composer *win = KMail::makeComposer();
0670 
0671     win->setMessage(newMsg, false, false, false, true);
0672     win->show();
0673     return OK;
0674 }
0675 
0676 KMSaveMsgCommand::KMSaveMsgCommand(QWidget *parent, const Akonadi::Item::List &msgList)
0677     : KMCommand(parent, msgList)
0678 {
0679     if (msgList.empty()) {
0680         return;
0681     }
0682 
0683     fetchScope().fetchFullPayload(true); // ### unless we call the corresponding KMCommand ctor, this has no effect
0684 }
0685 
0686 KMCommand::Result KMSaveMsgCommand::execute()
0687 {
0688     if (!MessageViewer::Util::saveMessageInMbox(retrievedMsgs(), parentWidget())) {
0689         return Failed;
0690     }
0691     return OK;
0692 }
0693 
0694 //-----------------------------------------------------------------------------
0695 
0696 KMOpenMsgCommand::KMOpenMsgCommand(QWidget *parent, const QUrl &url, const QString &encoding, KMMainWidget *main)
0697     : KMCommand(parent)
0698     , mUrl(url)
0699     , mEncoding(encoding)
0700     , mMainWidget(main)
0701 {
0702     qCDebug(KMAIL_LOG) << "url :" << url;
0703 }
0704 
0705 KMCommand::Result KMOpenMsgCommand::execute()
0706 {
0707     if (mUrl.isEmpty()) {
0708         mUrl = QFileDialog::getOpenFileUrl(parentWidget(), i18n("Open Message"), QUrl(), QStringLiteral("%1 (*.mbox *.eml)").arg(i18n("Message")));
0709     }
0710     if (mUrl.isEmpty()) {
0711         return Canceled;
0712     }
0713 
0714     if (mMainWidget) {
0715         mMainWidget->addRecentFile(mUrl);
0716     }
0717 
0718     setDeletesItself(true);
0719     mJob = KIO::get(mUrl, KIO::NoReload, KIO::HideProgressInfo);
0720     connect(mJob, &KIO::TransferJob::data, this, &KMOpenMsgCommand::slotDataArrived);
0721     connect(mJob, &KJob::result, this, &KMOpenMsgCommand::slotResult);
0722     setEmitsCompletedItself(true);
0723     return OK;
0724 }
0725 
0726 void KMOpenMsgCommand::slotDataArrived(KIO::Job *, const QByteArray &data)
0727 {
0728     if (data.isEmpty()) {
0729         return;
0730     }
0731 
0732     mMsgString.append(data.data());
0733 }
0734 
0735 void KMOpenMsgCommand::doesNotContainMessage()
0736 {
0737     KMessageBox::error(parentWidget(), i18n("The file does not contain a message."));
0738     setResult(Failed);
0739     Q_EMIT completed(this);
0740     // Emulate closing of a secondary window so that KMail exits in case it
0741     // was started with the --view command line option. Otherwise an
0742     // invisible KMail would keep running.
0743     auto win = new SecondaryWindow();
0744     win->close();
0745     win->deleteLater();
0746     deleteLater();
0747 }
0748 
0749 void KMOpenMsgCommand::slotResult(KJob *job)
0750 {
0751     if (job->error()) {
0752         // handle errors
0753         showJobError(job);
0754         setResult(Failed);
0755     } else {
0756         if (mMsgString.isEmpty()) {
0757             qCDebug(KMAIL_LOG) << " Message not found. There is a problem";
0758             doesNotContainMessage();
0759             return;
0760         }
0761         int startOfMessage = 0;
0762         if (mMsgString.startsWith("From ")) {
0763             startOfMessage = mMsgString.indexOf('\n');
0764             if (startOfMessage == -1) {
0765                 doesNotContainMessage();
0766                 return;
0767             }
0768             startOfMessage += 1; // the message starts after the '\n'
0769         }
0770         QList<KMime::Message::Ptr> listMessages;
0771 
0772         // check for multiple messages in the file
0773         bool multipleMessages = true;
0774         int endOfMessage = mMsgString.indexOf("\nFrom ", startOfMessage);
0775         while (endOfMessage != -1) {
0776             auto msg = new KMime::Message;
0777             msg->setContent(KMime::CRLFtoLF(mMsgString.mid(startOfMessage, endOfMessage - startOfMessage)));
0778             msg->parse();
0779             if (!msg->hasContent()) {
0780                 delete msg;
0781                 msg = nullptr;
0782                 doesNotContainMessage();
0783                 return;
0784             }
0785             KMime::Message::Ptr mMsg(msg);
0786             listMessages << mMsg;
0787             startOfMessage = endOfMessage + 1;
0788             endOfMessage = mMsgString.indexOf("\nFrom ", startOfMessage);
0789         }
0790         if (endOfMessage == -1) {
0791             endOfMessage = mMsgString.length();
0792             multipleMessages = false;
0793             auto msg = new KMime::Message;
0794             msg->setContent(KMime::CRLFtoLF(mMsgString.mid(startOfMessage, endOfMessage - startOfMessage)));
0795             msg->parse();
0796             if (!msg->hasContent()) {
0797                 delete msg;
0798                 msg = nullptr;
0799                 doesNotContainMessage();
0800                 return;
0801             }
0802             KMime::Message::Ptr mMsg(msg);
0803             listMessages << mMsg;
0804         }
0805         auto win = new KMReaderMainWin();
0806         win->showMessage(mEncoding, listMessages);
0807         win->show();
0808         if (multipleMessages) {
0809             KMessageBox::information(win,
0810                                      i18n("The file contains multiple messages. "
0811                                           "Only the first message is shown."));
0812         }
0813         setResult(OK);
0814     }
0815     Q_EMIT completed(this);
0816     deleteLater();
0817 }
0818 
0819 //-----------------------------------------------------------------------------
0820 KMReplyCommand::KMReplyCommand(QWidget *parent,
0821                                const Akonadi::Item &msg,
0822                                MessageComposer::ReplyStrategy replyStrategy,
0823                                const QString &selection,
0824                                bool noquote,
0825                                const QString &templateName)
0826     : KMCommand(parent, msg)
0827     , mSelection(selection)
0828     , mTemplate(templateName)
0829     , m_replyStrategy(replyStrategy)
0830     , mNoQuote(noquote)
0831 {
0832     fetchScope().fetchFullPayload(true);
0833     fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
0834 }
0835 
0836 KMCommand::Result KMReplyCommand::execute()
0837 {
0838     KCursorSaver saver(Qt::WaitCursor);
0839     Akonadi::Item item = retrievedMessage();
0840     KMime::Message::Ptr msg = MessageComposer::Util::message(item);
0841     if (!msg) {
0842         return Failed;
0843     }
0844 
0845     CreateReplyMessageJobSettings settings;
0846     settings.item = item;
0847     settings.msg = msg;
0848     settings.selection = mSelection;
0849     settings.replyStrategy = m_replyStrategy;
0850     settings.templateStr = mTemplate;
0851     settings.noQuote = mNoQuote;
0852     settings.replyAsHtml = mReplyAsHtml;
0853     // qDebug() << " settings " << mReplyAsHtml;
0854 
0855     auto job = new CreateReplyMessageJob;
0856     job->setSettings(settings);
0857     job->start();
0858 
0859     return OK;
0860 }
0861 
0862 bool KMReplyCommand::replyAsHtml() const
0863 {
0864     return mReplyAsHtml;
0865 }
0866 
0867 void KMReplyCommand::setReplyAsHtml(bool replyAsHtml)
0868 {
0869     mReplyAsHtml = replyAsHtml;
0870 }
0871 
0872 KMForwardCommand::KMForwardCommand(QWidget *parent, const Akonadi::Item::List &msgList, uint identity, const QString &templateName, const QString &selection)
0873     : KMCommand(parent, msgList)
0874     , mIdentity(identity)
0875     , mTemplate(templateName)
0876     , mSelection(selection)
0877 {
0878     fetchScope().fetchFullPayload(true);
0879     fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
0880 }
0881 
0882 KMForwardCommand::KMForwardCommand(QWidget *parent, const Akonadi::Item &msg, uint identity, const QString &templateName, const QString &selection)
0883     : KMCommand(parent, msg)
0884     , mIdentity(identity)
0885     , mTemplate(templateName)
0886     , mSelection(selection)
0887 {
0888     fetchScope().fetchFullPayload(true);
0889     fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
0890 }
0891 
0892 KMCommand::Result KMForwardCommand::createComposer(const Akonadi::Item &item)
0893 {
0894     KMime::Message::Ptr msg = MessageComposer::Util::message(item);
0895     if (!msg) {
0896         return Failed;
0897     }
0898     KCursorSaver saver(Qt::WaitCursor);
0899 
0900     CreateForwardMessageJobSettings settings;
0901     settings.item = item;
0902     settings.msg = msg;
0903     settings.identity = mIdentity;
0904     settings.templateStr = mTemplate;
0905     settings.selection = mSelection;
0906 
0907     auto job = new CreateForwardMessageJob;
0908     job->setSettings(settings);
0909     job->start();
0910     return OK;
0911 }
0912 
0913 KMCommand::Result KMForwardCommand::execute()
0914 {
0915     Akonadi::Item::List msgList = retrievedMsgs();
0916 
0917     if (msgList.count() >= 2) {
0918         // ask if they want a mime digest forward
0919 
0920         int answer = KMessageBox::questionTwoActionsCancel(parentWidget(),
0921                                                            i18n("Do you want to forward the selected messages as "
0922                                                                 "attachments in one message (as a MIME digest) or as "
0923                                                                 "individual messages?"),
0924                                                            QString(),
0925                                                            KGuiItem(i18n("Send As Digest")),
0926                                                            KGuiItem(i18n("Send Individually")));
0927 
0928         if (answer == KMessageBox::ButtonCode::PrimaryAction) {
0929             Akonadi::Item firstItem(msgList.first());
0930             MessageFactoryNG factory(KMime::Message::Ptr(new KMime::Message),
0931                                      firstItem.id(),
0932                                      CommonKernel->collectionFromId(firstItem.parentCollection().id()));
0933             factory.setIdentityManager(KMKernel::self()->identityManager());
0934             factory.setFolderIdentity(MailCommon::Util::folderIdentity(firstItem));
0935 
0936             QPair<KMime::Message::Ptr, KMime::Content *> fwdMsg = factory.createForwardDigestMIME(msgList);
0937             KMail::Composer *win = KMail::makeComposer(fwdMsg.first, false, false, KMail::Composer::Forward, mIdentity);
0938             win->addAttach(fwdMsg.second);
0939             win->show();
0940             delete fwdMsg.second;
0941             return OK;
0942         } else if (answer == KMessageBox::ButtonCode::SecondaryAction) { // NO MIME DIGEST, Multiple forward
0943             Akonadi::Item::List::const_iterator it;
0944             Akonadi::Item::List::const_iterator end(msgList.constEnd());
0945 
0946             for (it = msgList.constBegin(); it != end; ++it) {
0947                 if (createComposer(*it) == Failed) {
0948                     return Failed;
0949                 }
0950             }
0951             return OK;
0952         } else {
0953             // user cancelled
0954             return OK;
0955         }
0956     }
0957 
0958     // forward a single message at most.
0959     Akonadi::Item item = msgList.first();
0960     if (createComposer(item) == Failed) {
0961         return Failed;
0962     }
0963     return OK;
0964 }
0965 
0966 KMForwardAttachedCommand::KMForwardAttachedCommand(QWidget *parent, const Akonadi::Item::List &msgList, uint identity, KMail::Composer *win)
0967     : KMCommand(parent, msgList)
0968     , mIdentity(identity)
0969     , mWin(QPointer<KMail::Composer>(win))
0970 {
0971     fetchScope().fetchFullPayload(true);
0972     fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
0973 }
0974 
0975 KMForwardAttachedCommand::KMForwardAttachedCommand(QWidget *parent, const Akonadi::Item &msg, uint identity, KMail::Composer *win)
0976     : KMCommand(parent, msg)
0977     , mIdentity(identity)
0978     , mWin(QPointer<KMail::Composer>(win))
0979 {
0980     fetchScope().fetchFullPayload(true);
0981     fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
0982 }
0983 
0984 KMCommand::Result KMForwardAttachedCommand::execute()
0985 {
0986     Akonadi::Item::List msgList = retrievedMsgs();
0987     Akonadi::Item firstItem(msgList.first());
0988     MessageFactoryNG factory(KMime::Message::Ptr(new KMime::Message), firstItem.id(), CommonKernel->collectionFromId(firstItem.parentCollection().id()));
0989     factory.setIdentityManager(KMKernel::self()->identityManager());
0990     factory.setFolderIdentity(MailCommon::Util::folderIdentity(firstItem));
0991 
0992     QPair<KMime::Message::Ptr, QList<KMime::Content *>> fwdMsg = factory.createAttachedForward(msgList);
0993     if (!mWin) {
0994         mWin = KMail::makeComposer(fwdMsg.first, false, false, KMail::Composer::Forward, mIdentity);
0995     }
0996     for (KMime::Content *attach : std::as_const(fwdMsg.second)) {
0997         mWin->addAttach(attach);
0998         delete attach;
0999     }
1000     mWin->show();
1001     return OK;
1002 }
1003 
1004 KMRedirectCommand::KMRedirectCommand(QWidget *parent, const Akonadi::Item::List &msgList)
1005     : KMCommand(parent, msgList)
1006 {
1007     fetchScope().fetchFullPayload(true);
1008     fetchScope().fetchAttribute<Akonadi::SentBehaviourAttribute>();
1009     fetchScope().fetchAttribute<Akonadi::TransportAttribute>();
1010 
1011     fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
1012 }
1013 
1014 KMRedirectCommand::KMRedirectCommand(QWidget *parent, const Akonadi::Item &msg)
1015     : KMCommand(parent, msg)
1016 {
1017     fetchScope().fetchFullPayload(true);
1018     fetchScope().fetchAttribute<Akonadi::SentBehaviourAttribute>();
1019     fetchScope().fetchAttribute<Akonadi::TransportAttribute>();
1020 
1021     fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
1022 }
1023 
1024 KMCommand::Result KMRedirectCommand::execute()
1025 {
1026     const MailCommon::RedirectDialog::SendMode sendMode =
1027         MessageComposer::MessageComposerSettings::self()->sendImmediate() ? MailCommon::RedirectDialog::SendNow : MailCommon::RedirectDialog::SendLater;
1028 
1029     QScopedPointer<MailCommon::RedirectDialog> dlg(new MailCommon::RedirectDialog(sendMode, parentWidget()));
1030     dlg->setObjectName(QLatin1StringView("redirect"));
1031     if (dlg->exec() == QDialog::Rejected || !dlg) {
1032         return Failed;
1033     }
1034     if (!TransportManager::self()->showTransportCreationDialog(parentWidget(), TransportManager::IfNoTransportExists)) {
1035         return Failed;
1036     }
1037 
1038     // TODO use sendlateragent here too.
1039     const MessageComposer::MessageSender::SendMethod method =
1040         (dlg->sendMode() == MailCommon::RedirectDialog::SendNow) ? MessageComposer::MessageSender::SendImmediate : MessageComposer::MessageSender::SendLater;
1041 
1042     const int identity = dlg->identity();
1043     int transportId = dlg->transportId();
1044     const QString to = dlg->to();
1045     const QString cc = dlg->cc();
1046     const QString bcc = dlg->bcc();
1047     const Akonadi::Item::List lstItems = retrievedMsgs();
1048     for (const Akonadi::Item &item : lstItems) {
1049         const KMime::Message::Ptr msg = MessageComposer::Util::message(item);
1050         if (!msg) {
1051             return Failed;
1052         }
1053         MessageFactoryNG factory(msg, item.id(), CommonKernel->collectionFromId(item.parentCollection().id()));
1054         factory.setIdentityManager(KMKernel::self()->identityManager());
1055         factory.setFolderIdentity(MailCommon::Util::folderIdentity(item));
1056 
1057         if (transportId == -1) {
1058             const auto transportAttribute = item.attribute<Akonadi::TransportAttribute>();
1059             if (transportAttribute) {
1060                 transportId = transportAttribute->transportId();
1061                 const MailTransport::Transport *transport = MailTransport::TransportManager::self()->transportById(transportId);
1062                 if (!transport) {
1063                     transportId = -1;
1064                 }
1065             }
1066         }
1067 
1068         const auto sentAttribute = item.attribute<Akonadi::SentBehaviourAttribute>();
1069         QString fcc;
1070         if (sentAttribute && (sentAttribute->sentBehaviour() == Akonadi::SentBehaviourAttribute::MoveToCollection)) {
1071             fcc = QString::number(sentAttribute->moveToCollection().id());
1072         }
1073 
1074         const KMime::Message::Ptr newMsg = factory.createRedirect(to, cc, bcc, transportId, fcc, identity);
1075         if (!newMsg) {
1076             return Failed;
1077         }
1078 
1079         MessageStatus status;
1080         status.setStatusFromFlags(item.flags());
1081         if (!status.isRead()) {
1082             FilterAction::sendMDN(item, KMime::MDN::Dispatched);
1083         }
1084 
1085         if (!kmkernel->msgSender()->send(newMsg, method)) {
1086             qCDebug(KMAIL_LOG) << "KMRedirectCommand: could not redirect message (sending failed)";
1087             return Failed; // error: couldn't send
1088         }
1089     }
1090 
1091     return OK;
1092 }
1093 
1094 KMPrintCommand::KMPrintCommand(QWidget *parent, const KMPrintCommandInfo &commandInfo)
1095     : KMCommand(parent, commandInfo.mMsg)
1096     , mPrintCommandInfo(commandInfo)
1097 {
1098     fetchScope().fetchFullPayload(true);
1099     if (MessageCore::MessageCoreSettings::useDefaultFonts()) {
1100         mPrintCommandInfo.mOverrideFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont);
1101     } else {
1102         mPrintCommandInfo.mOverrideFont = MessageViewer::MessageViewerSettings::self()->printFont();
1103     }
1104 }
1105 
1106 KMCommand::Result KMPrintCommand::execute()
1107 {
1108     auto printerWin = new KMReaderWin(nullptr, parentWidget(), nullptr);
1109     printerWin->setPrinting(true);
1110     printerWin->readConfig();
1111     printerWin->setPrintElementBackground(MessageViewer::MessageViewerSettings::self()->printBackgroundColorImages());
1112     if (mPrintCommandInfo.mHeaderStylePlugin) {
1113         printerWin->viewer()->setPluginName(mPrintCommandInfo.mHeaderStylePlugin->name());
1114     }
1115     printerWin->setDisplayFormatMessageOverwrite(mPrintCommandInfo.mFormat);
1116     printerWin->setHtmlLoadExtOverride(mPrintCommandInfo.mHtmlLoadExtOverride);
1117     printerWin->setUseFixedFont(mPrintCommandInfo.mUseFixedFont);
1118     printerWin->setOverrideEncoding(mPrintCommandInfo.mEncoding);
1119     printerWin->cssHelper()->setPrintFont(mPrintCommandInfo.mOverrideFont);
1120     printerWin->setDecryptMessageOverwrite(true);
1121     if (mPrintCommandInfo.mAttachmentStrategy) {
1122         printerWin->setAttachmentStrategy(mPrintCommandInfo.mAttachmentStrategy);
1123     }
1124     printerWin->viewer()->setShowSignatureDetails(mPrintCommandInfo.mShowSignatureDetails);
1125     printerWin->viewer()->setShowEncryptionDetails(mPrintCommandInfo.mShowEncryptionDetails);
1126     if (mPrintCommandInfo.mPrintPreview) {
1127         printerWin->viewer()->printPreviewMessage(retrievedMessage());
1128     } else {
1129         printerWin->viewer()->printMessage(retrievedMessage());
1130     }
1131     return OK;
1132 }
1133 
1134 KMSetStatusCommand::KMSetStatusCommand(const MessageStatus &status, const Akonadi::Item::List &items, bool invert)
1135     : KMCommand(nullptr, items)
1136     , mStatus(status)
1137     , mInvertMark(invert)
1138 {
1139     setDeletesItself(true);
1140 }
1141 
1142 KMCommand::Result KMSetStatusCommand::execute()
1143 {
1144     bool parentStatus = false;
1145     // Toggle actions on threads toggle the whole thread
1146     // depending on the state of the parent.
1147     if (mInvertMark) {
1148         const Akonadi::Item first = retrievedMsgs().first();
1149         MessageStatus pStatus;
1150         pStatus.setStatusFromFlags(first.flags());
1151         if (pStatus & mStatus) {
1152             parentStatus = true;
1153         } else {
1154             parentStatus = false;
1155         }
1156     }
1157 
1158     Akonadi::Item::List itemsToModify;
1159     const Akonadi::Item::List lstItems = retrievedMsgs();
1160     for (const Akonadi::Item &it : lstItems) {
1161         if (mInvertMark) {
1162             // qCDebug(KMAIL_LOG)<<" item ::"<<tmpItem;
1163             if (it.isValid()) {
1164                 bool myStatus;
1165                 MessageStatus itemStatus;
1166                 itemStatus.setStatusFromFlags(it.flags());
1167                 if (itemStatus & mStatus) {
1168                     myStatus = true;
1169                 } else {
1170                     myStatus = false;
1171                 }
1172                 if (myStatus != parentStatus) {
1173                     continue;
1174                 }
1175             }
1176         }
1177         Akonadi::Item item(it);
1178         const Akonadi::Item::Flag flag = *(mStatus.statusFlags().constBegin());
1179         if (mInvertMark) {
1180             if (item.hasFlag(flag)) {
1181                 item.clearFlag(flag);
1182                 itemsToModify.push_back(item);
1183             } else {
1184                 item.setFlag(flag);
1185                 itemsToModify.push_back(item);
1186             }
1187         } else {
1188             if (!item.hasFlag(flag)) {
1189                 item.setFlag(flag);
1190                 itemsToModify.push_back(item);
1191             }
1192         }
1193     }
1194 
1195     if (itemsToModify.isEmpty()) {
1196         slotModifyItemDone(nullptr); // pretend we did something
1197     } else {
1198         auto modifyJob = new Akonadi::ItemModifyJob(itemsToModify, this);
1199         modifyJob->disableRevisionCheck();
1200         modifyJob->setIgnorePayload(true);
1201         connect(modifyJob, &Akonadi::ItemModifyJob::result, this, &KMSetStatusCommand::slotModifyItemDone);
1202     }
1203     return OK;
1204 }
1205 
1206 void KMSetStatusCommand::slotModifyItemDone(KJob *job)
1207 {
1208     if (job && job->error()) {
1209         qCWarning(KMAIL_LOG) << " Error trying to set item status:" << job->errorText();
1210     }
1211     deleteLater();
1212 }
1213 
1214 KMSetTagCommand::KMSetTagCommand(const Akonadi::Tag::List &tags, const Akonadi::Item::List &item, SetTagMode mode)
1215     : mTags(tags)
1216     , mItem(item)
1217     , mMode(mode)
1218 {
1219     setDeletesItself(true);
1220 }
1221 
1222 KMCommand::Result KMSetTagCommand::execute()
1223 {
1224     for (const Akonadi::Tag &tag : std::as_const(mTags)) {
1225         if (!tag.isValid()) {
1226             auto createJob = new Akonadi::TagCreateJob(tag, this);
1227             connect(createJob, &Akonadi::TagCreateJob::result, this, &KMSetTagCommand::slotModifyItemDone);
1228         } else {
1229             mCreatedTags << tag;
1230         }
1231     }
1232 
1233     if (mCreatedTags.size() == mTags.size()) {
1234         setTags();
1235     } else {
1236         deleteLater();
1237     }
1238 
1239     return OK;
1240 }
1241 
1242 void KMSetTagCommand::setTags()
1243 {
1244     Akonadi::Item::List itemsToModify;
1245     itemsToModify.reserve(mItem.count());
1246     for (const Akonadi::Item &i : std::as_const(mItem)) {
1247         Akonadi::Item item(i);
1248         if (mMode == CleanExistingAndAddNew) {
1249             // WorkAround. ClearTags doesn't work.
1250             const Akonadi::Tag::List lstTags = item.tags();
1251             for (const Akonadi::Tag &tag : lstTags) {
1252                 item.clearTag(tag);
1253             }
1254             // item.clearTags();
1255         }
1256 
1257         if (mMode == KMSetTagCommand::Toggle) {
1258             for (const Akonadi::Tag &tag : std::as_const(mCreatedTags)) {
1259                 if (item.hasTag(tag)) {
1260                     item.clearTag(tag);
1261                 } else {
1262                     item.setTag(tag);
1263                 }
1264             }
1265         } else {
1266             if (!mCreatedTags.isEmpty()) {
1267                 item.setTags(mCreatedTags);
1268             }
1269         }
1270         itemsToModify << item;
1271     }
1272     auto modifyJob = new Akonadi::ItemModifyJob(itemsToModify, this);
1273     modifyJob->disableRevisionCheck();
1274     modifyJob->setIgnorePayload(true);
1275     connect(modifyJob, &Akonadi::ItemModifyJob::result, this, &KMSetTagCommand::slotModifyItemDone);
1276 
1277     if (!mCreatedTags.isEmpty()) {
1278         KConfigGroup tag(KMKernel::self()->config(), QStringLiteral("MessageListView"));
1279         const QString oldTagList = tag.readEntry("TagSelected");
1280         QStringList lst = oldTagList.split(QLatin1Char(','));
1281         for (const Akonadi::Tag &createdTag : std::as_const(mCreatedTags)) {
1282             const QString url = createdTag.url().url();
1283             if (!lst.contains(url)) {
1284                 lst.append(url);
1285             }
1286         }
1287         tag.writeEntry("TagSelected", lst);
1288         KMKernel::self()->updatePaneTagComboBox();
1289     }
1290 }
1291 
1292 void KMSetTagCommand::slotModifyItemDone(KJob *job)
1293 {
1294     if (job && job->error()) {
1295         qCWarning(KMAIL_LOG) << " Error trying to set item status:" << job->errorText();
1296     }
1297     deleteLater();
1298 }
1299 
1300 KMFilterActionCommand::KMFilterActionCommand(QWidget *parent, const QList<qlonglong> &msgListId, const QString &filterId)
1301     : KMCommand(parent)
1302     , mMsgListId(msgListId)
1303     , mFilterId(filterId)
1304 {
1305 }
1306 
1307 KMCommand::Result KMFilterActionCommand::execute()
1308 {
1309     KCursorSaver saver(Qt::WaitCursor);
1310     int msgCount = 0;
1311     const int msgCountToFilter = mMsgListId.count();
1312     ProgressItem *progressItem = ProgressManager::createProgressItem(QLatin1StringView("filter") + ProgressManager::getUniqueID(),
1313                                                                      i18n("Filtering messages"),
1314                                                                      QString(),
1315                                                                      true,
1316                                                                      KPIM::ProgressItem::Unknown);
1317     progressItem->setTotalItems(msgCountToFilter);
1318 
1319     for (const qlonglong &id : std::as_const(mMsgListId)) {
1320         int diff = msgCountToFilter - ++msgCount;
1321         if (diff < 10 || !(msgCount % 10) || msgCount <= 10) {
1322             progressItem->updateProgress();
1323             const QString statusMsg = i18n("Filtering message %1 of %2", msgCount, msgCountToFilter);
1324             PimCommon::BroadcastStatus::instance()->setStatusMsg(statusMsg);
1325             qApp->processEvents(QEventLoop::ExcludeUserInputEvents, 50);
1326         }
1327 
1328         MailCommon::FilterManager::instance()->filter(Akonadi::Item(id), mFilterId, QString());
1329         progressItem->incCompletedItems();
1330     }
1331 
1332     progressItem->setComplete();
1333     progressItem = nullptr;
1334     return OK;
1335 }
1336 
1337 KMMetaFilterActionCommand::KMMetaFilterActionCommand(const QString &filterId, KMMainWidget *main)
1338     : QObject(main)
1339     , mFilterId(filterId)
1340     , mMainWidget(main)
1341 {
1342 }
1343 
1344 void KMMetaFilterActionCommand::start()
1345 {
1346     KMCommand *filterCommand = new KMFilterActionCommand(mMainWidget, mMainWidget->messageListPane()->selectionAsMessageItemListId(), mFilterId);
1347     filterCommand->start();
1348 }
1349 
1350 KMMailingListFilterCommand::KMMailingListFilterCommand(QWidget *parent, const Akonadi::Item &msg)
1351     : KMCommand(parent, msg)
1352 {
1353 }
1354 
1355 KMCommand::Result KMMailingListFilterCommand::execute()
1356 {
1357     QByteArray name;
1358     QString value;
1359     Akonadi::Item item = retrievedMessage();
1360     KMime::Message::Ptr msg = MessageComposer::Util::message(item);
1361     if (!msg) {
1362         return Failed;
1363     }
1364     if (!MailingList::name(msg, name, value).isEmpty()) {
1365         FilterIf->openFilterDialog(false);
1366         FilterIf->createFilter(name, value);
1367         return OK;
1368     } else {
1369         return Failed;
1370     }
1371 }
1372 
1373 KMCopyCommand::KMCopyCommand(const Akonadi::Collection &destFolder, const Akonadi::Item::List &msgList)
1374     : KMCommand(nullptr, msgList)
1375     , mDestFolder(destFolder)
1376 {
1377 }
1378 
1379 KMCopyCommand::KMCopyCommand(const Akonadi::Collection &destFolder, const Akonadi::Item &msg)
1380     : KMCommand(nullptr, msg)
1381     , mDestFolder(destFolder)
1382 {
1383 }
1384 
1385 KMCommand::Result KMCopyCommand::execute()
1386 {
1387     setDeletesItself(true);
1388 
1389     Akonadi::Item::List listItem = retrievedMsgs();
1390     auto job = new Akonadi::ItemCopyJob(listItem, Akonadi::Collection(mDestFolder.id()), this);
1391     connect(job, &KIO::Job::result, this, &KMCopyCommand::slotCopyResult);
1392 
1393     return OK;
1394 }
1395 
1396 void KMCopyCommand::slotCopyResult(KJob *job)
1397 {
1398     if (job->error()) {
1399         // handle errors
1400         showJobError(job);
1401         setResult(Failed);
1402     }
1403 
1404     qobject_cast<Akonadi::ItemCopyJob *>(job);
1405 
1406     Q_EMIT completed(this);
1407     deleteLater();
1408 }
1409 
1410 KMCopyDecryptedCommand::KMCopyDecryptedCommand(const Akonadi::Collection &destFolder, const Akonadi::Item::List &msgList)
1411     : KMCommand(nullptr, msgList)
1412     , mDestFolder(destFolder)
1413 {
1414     fetchScope().fetchAllAttributes();
1415     fetchScope().fetchFullPayload();
1416 }
1417 
1418 KMCopyDecryptedCommand::KMCopyDecryptedCommand(const Akonadi::Collection &destFolder, const Akonadi::Item &msg)
1419     : KMCopyDecryptedCommand(destFolder, Akonadi::Item::List{msg})
1420 {
1421 }
1422 
1423 KMCommand::Result KMCopyDecryptedCommand::execute()
1424 {
1425     setDeletesItself(true);
1426 
1427     const auto items = retrievedMsgs();
1428     for (const auto &item : items) {
1429         // Decrypt
1430         if (!item.hasPayload<KMime::Message::Ptr>()) {
1431             continue;
1432         }
1433         const auto msg = item.payload<KMime::Message::Ptr>();
1434         bool wasEncrypted;
1435         auto decMsg = MailCommon::CryptoUtils::decryptMessage(msg, wasEncrypted);
1436         if (!wasEncrypted) {
1437             decMsg = msg;
1438         }
1439 
1440         Akonadi::Item decItem;
1441         decItem.setMimeType(KMime::Message::mimeType());
1442         decItem.setPayload(decMsg);
1443 
1444         auto job = new Akonadi::ItemCreateJob(decItem, mDestFolder, this);
1445         connect(job, &Akonadi::Job::result, this, &KMCopyDecryptedCommand::slotAppendResult);
1446         mPendingJobs << job;
1447     }
1448 
1449     if (mPendingJobs.isEmpty()) {
1450         Q_EMIT completed(this);
1451         deleteLater();
1452     }
1453 
1454     return KMCommand::OK;
1455 }
1456 
1457 void KMCopyDecryptedCommand::slotAppendResult(KJob *job)
1458 {
1459     mPendingJobs.removeOne(job);
1460     if (mPendingJobs.isEmpty()) {
1461         Q_EMIT completed(this);
1462         deleteLater();
1463     }
1464 }
1465 
1466 KMMoveCommand::KMMoveCommand(const Akonadi::Collection &destFolder, const Akonadi::Item::List &msgList, MessageList::Core::MessageItemSetReference ref)
1467     : KMCommand(nullptr, msgList)
1468     , mDestFolder(destFolder)
1469     , mRef(ref)
1470 {
1471     fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
1472 }
1473 
1474 KMMoveCommand::KMMoveCommand(const Akonadi::Collection &destFolder, const Akonadi::Item &msg, MessageList::Core::MessageItemSetReference ref)
1475     : KMCommand(nullptr, msg)
1476     , mDestFolder(destFolder)
1477     , mRef(ref)
1478 {
1479     fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
1480 }
1481 
1482 void KMMoveCommand::slotMoveResult(KJob *job)
1483 {
1484     if (job->error()) {
1485         // handle errors
1486         showJobError(job);
1487         completeMove(Failed);
1488     } else {
1489         completeMove(OK);
1490     }
1491 }
1492 
1493 KMCommand::Result KMMoveCommand::execute()
1494 {
1495     KCursorSaver saver(Qt::WaitCursor);
1496     setEmitsCompletedItself(true);
1497     setDeletesItself(true);
1498     Akonadi::Item::List retrievedList = retrievedMsgs();
1499     if (!retrievedList.isEmpty()) {
1500         if (mDestFolder.isValid()) {
1501             auto job = new Akonadi::ItemMoveJob(retrievedList, mDestFolder, this);
1502             connect(job, &KIO::Job::result, this, &KMMoveCommand::slotMoveResult);
1503 
1504             // group by source folder for undo
1505             std::sort(retrievedList.begin(), retrievedList.end(), [](const Akonadi::Item &lhs, const Akonadi::Item &rhs) {
1506                 return lhs.storageCollectionId() < rhs.storageCollectionId();
1507             });
1508             Akonadi::Collection parent;
1509             int undoId = -1;
1510             for (const Akonadi::Item &item : std::as_const(retrievedList)) {
1511                 if (item.storageCollectionId() <= 0) {
1512                     continue;
1513                 }
1514                 if (parent.id() != item.storageCollectionId()) {
1515                     parent = Akonadi::Collection(item.storageCollectionId());
1516                     undoId = kmkernel->undoStack()->newUndoAction(parent, mDestFolder);
1517                 }
1518                 kmkernel->undoStack()->addMsgToAction(undoId, item);
1519             }
1520         } else {
1521             auto job = new Akonadi::ItemDeleteJob(retrievedList, this);
1522             connect(job, &KIO::Job::result, this, &KMMoveCommand::slotMoveResult);
1523         }
1524     } else {
1525         deleteLater();
1526         return Failed;
1527     }
1528     // TODO set SSL state according to source and destfolder connection?
1529     Q_ASSERT(!mProgressItem);
1530     mProgressItem = ProgressManager::createProgressItem(QLatin1StringView("move") + ProgressManager::getUniqueID(),
1531                                                         mDestFolder.isValid() ? i18n("Moving messages") : i18n("Deleting messages"),
1532                                                         QString(),
1533                                                         true,
1534                                                         KPIM::ProgressItem::Unknown);
1535     mProgressItem->setUsesBusyIndicator(true);
1536     connect(mProgressItem, &ProgressItem::progressItemCanceled, this, &KMMoveCommand::slotMoveCanceled);
1537     return OK;
1538 }
1539 
1540 void KMMoveCommand::completeMove(Result result)
1541 {
1542     if (mProgressItem) {
1543         mProgressItem->setComplete();
1544         mProgressItem = nullptr;
1545     }
1546     setResult(result);
1547     Q_EMIT moveDone(this);
1548     Q_EMIT completed(this);
1549     deleteLater();
1550 }
1551 
1552 void KMMoveCommand::slotMoveCanceled()
1553 {
1554     completeMove(Canceled);
1555 }
1556 
1557 KMTrashMsgCommand::KMTrashMsgCommand(const Akonadi::Collection &srcFolder, const Akonadi::Item::List &msgList, MessageList::Core::MessageItemSetReference ref)
1558     : KMCommand()
1559     , mRef(ref)
1560 {
1561     // When trashing items from a virtual collection, they may each have a different
1562     // trash folder, so we need to handle it here carefully
1563     if (srcFolder.isVirtual()) {
1564         QHash<qint64, Akonadi::Collection> cache;
1565         for (const auto &msg : msgList) {
1566             auto cacheIt = cache.find(msg.storageCollectionId());
1567             if (cacheIt == cache.end()) {
1568                 cacheIt = cache.insert(msg.storageCollectionId(), findTrashFolder(CommonKernel->collectionFromId(msg.storageCollectionId())));
1569             }
1570             auto trashIt = mTrashFolders.find(*cacheIt);
1571             if (trashIt == mTrashFolders.end()) {
1572                 trashIt = mTrashFolders.insert(*cacheIt, {});
1573             }
1574             trashIt->push_back(msg);
1575         }
1576     } else {
1577         mTrashFolders.insert(findTrashFolder(srcFolder), msgList);
1578     }
1579 }
1580 
1581 KMTrashMsgCommand::TrashOperation KMTrashMsgCommand::operation() const
1582 {
1583     if (!mPendingMoves.isEmpty() && !mPendingDeletes.isEmpty()) {
1584         return Both;
1585     } else if (!mPendingMoves.isEmpty()) {
1586         return MoveToTrash;
1587     } else if (!mPendingDeletes.isEmpty()) {
1588         return Delete;
1589     } else {
1590         if (mTrashFolders.size() == 1) {
1591             if (mTrashFolders.begin().key().isValid()) {
1592                 return MoveToTrash;
1593             } else {
1594                 return Delete;
1595             }
1596         } else {
1597             return Unknown;
1598         }
1599     }
1600 }
1601 
1602 KMTrashMsgCommand::KMTrashMsgCommand(const Akonadi::Collection &srcFolder, const Akonadi::Item &msg, MessageList::Core::MessageItemSetReference ref)
1603     : KMTrashMsgCommand(srcFolder, Akonadi::Item::List{msg}, ref)
1604 {
1605 }
1606 
1607 Akonadi::Collection KMTrashMsgCommand::findTrashFolder(const Akonadi::Collection &folder)
1608 {
1609     Akonadi::Collection col = CommonKernel->trashCollectionFromResource(folder);
1610     if (!col.isValid()) {
1611         col = CommonKernel->trashCollectionFolder();
1612     }
1613     if (folder != col) {
1614         return col;
1615     }
1616     return {};
1617 }
1618 
1619 KMCommand::Result KMTrashMsgCommand::execute()
1620 {
1621     KCursorSaver saver(Qt::WaitCursor);
1622     setEmitsCompletedItself(true);
1623     setDeletesItself(true);
1624     for (auto trashIt = mTrashFolders.begin(), end = mTrashFolders.end(); trashIt != end; ++trashIt) {
1625         const auto trash = trashIt.key();
1626         if (trash.isValid()) {
1627             auto job = new Akonadi::ItemMoveJob(*trashIt, trash, this);
1628             connect(job, &KIO::Job::result, this, &KMTrashMsgCommand::slotMoveResult);
1629             mPendingMoves.push_back(job);
1630 
1631             // group by source folder for undo
1632             std::sort(trashIt->begin(), trashIt->end(), [](const Akonadi::Item &lhs, const Akonadi::Item &rhs) {
1633                 return lhs.storageCollectionId() < rhs.storageCollectionId();
1634             });
1635             Akonadi::Collection parent;
1636             int undoId = -1;
1637             for (const Akonadi::Item &item : std::as_const(*trashIt)) {
1638                 if (item.storageCollectionId() <= 0) {
1639                     continue;
1640                 }
1641                 if (parent.id() != item.storageCollectionId()) {
1642                     parent = Akonadi::Collection(item.storageCollectionId());
1643                     undoId = kmkernel->undoStack()->newUndoAction(parent, trash);
1644                 }
1645                 kmkernel->undoStack()->addMsgToAction(undoId, item);
1646             }
1647         } else {
1648             auto job = new Akonadi::ItemDeleteJob(*trashIt, this);
1649             connect(job, &KIO::Job::result, this, &KMTrashMsgCommand::slotDeleteResult);
1650             mPendingDeletes.push_back(job);
1651         }
1652     }
1653 
1654     if (mPendingMoves.isEmpty() && mPendingDeletes.isEmpty()) {
1655         deleteLater();
1656         return Failed;
1657     }
1658 
1659     // TODO set SSL state according to source and destfolder connection?
1660     if (!mPendingMoves.isEmpty()) {
1661         Q_ASSERT(!mMoveProgress);
1662         mMoveProgress = ProgressManager::createProgressItem(QLatin1StringView("move") + ProgressManager::getUniqueID(),
1663                                                             i18n("Moving messages"),
1664                                                             QString(),
1665                                                             true,
1666                                                             KPIM::ProgressItem::Unknown);
1667         mMoveProgress->setUsesBusyIndicator(true);
1668         connect(mMoveProgress, &ProgressItem::progressItemCanceled, this, &KMTrashMsgCommand::slotMoveCanceled);
1669     }
1670     if (!mPendingDeletes.isEmpty()) {
1671         Q_ASSERT(!mDeleteProgress);
1672         mDeleteProgress = ProgressManager::createProgressItem(QLatin1StringView("delete") + ProgressManager::getUniqueID(),
1673                                                               i18n("Deleting messages"),
1674                                                               QString(),
1675                                                               true,
1676                                                               KPIM::ProgressItem::Unknown);
1677         mDeleteProgress->setUsesBusyIndicator(true);
1678         connect(mDeleteProgress, &ProgressItem::progressItemCanceled, this, &KMTrashMsgCommand::slotMoveCanceled);
1679     }
1680     return OK;
1681 }
1682 
1683 void KMTrashMsgCommand::slotMoveResult(KJob *job)
1684 {
1685     mPendingMoves.removeOne(job);
1686     if (job->error()) {
1687         // handle errors
1688         showJobError(job);
1689         completeMove(Failed);
1690     } else if (mPendingMoves.isEmpty() && mPendingDeletes.isEmpty()) {
1691         completeMove(OK);
1692     }
1693 }
1694 
1695 void KMTrashMsgCommand::slotDeleteResult(KJob *job)
1696 {
1697     mPendingDeletes.removeOne(job);
1698     if (job->error()) {
1699         showJobError(job);
1700         completeMove(Failed);
1701     } else if (mPendingDeletes.isEmpty() && mPendingMoves.isEmpty()) {
1702         completeMove(OK);
1703     }
1704 }
1705 
1706 void KMTrashMsgCommand::slotMoveCanceled()
1707 {
1708     completeMove(Canceled);
1709 }
1710 
1711 void KMTrashMsgCommand::completeMove(KMCommand::Result result)
1712 {
1713     if (result == Failed) {
1714         for (auto job : std::as_const(mPendingMoves)) {
1715             job->kill();
1716         }
1717         for (auto job : std::as_const(mPendingDeletes)) {
1718             job->kill();
1719         }
1720     }
1721 
1722     if (mDeleteProgress) {
1723         mDeleteProgress->setComplete();
1724         mDeleteProgress = nullptr;
1725     }
1726     if (mMoveProgress) {
1727         mMoveProgress->setComplete();
1728         mMoveProgress = nullptr;
1729     }
1730 
1731     setResult(result);
1732     Q_EMIT moveDone(this);
1733     Q_EMIT completed(this);
1734     deleteLater();
1735 }
1736 
1737 KMSaveAttachmentsCommand::KMSaveAttachmentsCommand(QWidget *parent, const Akonadi::Item &msg, MessageViewer::Viewer *viewer)
1738     : KMCommand(parent, msg)
1739     , mViewer(viewer)
1740 {
1741     fetchScope().fetchFullPayload(true);
1742 }
1743 
1744 KMSaveAttachmentsCommand::KMSaveAttachmentsCommand(QWidget *parent, const Akonadi::Item::List &msgs, MessageViewer::Viewer *viewer)
1745     : KMCommand(parent, msgs)
1746     , mViewer(viewer)
1747 {
1748     fetchScope().fetchFullPayload(true);
1749 }
1750 
1751 KMCommand::Result KMSaveAttachmentsCommand::execute()
1752 {
1753     KMime::Content::List contentsToSave;
1754     const Akonadi::Item::List lstItems = retrievedMsgs();
1755     for (const Akonadi::Item &item : lstItems) {
1756         if (item.hasPayload<KMime::Message::Ptr>()) {
1757             contentsToSave += item.payload<KMime::Message::Ptr>()->attachments();
1758         } else {
1759             qCWarning(KMAIL_LOG) << "Retrieved item has no payload? Ignoring for saving the attachments";
1760         }
1761     }
1762     QList<QUrl> urlList;
1763     if (MessageViewer::Util::saveAttachments(contentsToSave, parentWidget(), urlList)) {
1764         if (mViewer) {
1765             mViewer->showOpenAttachmentFolderWidget(urlList);
1766         }
1767         return OK;
1768     }
1769     return Failed;
1770 }
1771 
1772 KMDeleteAttachmentsCommand::KMDeleteAttachmentsCommand(QWidget *parent, const Akonadi::Item::List &msgs)
1773     : KMCommand(parent, msgs)
1774 {
1775     fetchScope().fetchFullPayload(true);
1776 }
1777 
1778 KMCommand::Result KMDeleteAttachmentsCommand::execute()
1779 {
1780     setEmitsCompletedItself(true);
1781     setDeletesItself(true);
1782 
1783     for (const auto &item : retrievedMsgs()) {
1784         if (!item.hasPayload<KMime::Message::Ptr>()) {
1785             qCWarning(KMAIL_LOG) << "Retrieved Item" << item.id() << "does not have KMime::Message payload, ignoring.";
1786             continue;
1787         }
1788 
1789         auto message = item.payload<KMime::Message::Ptr>();
1790         const auto attachments = message->attachments();
1791         if (!attachments.empty()) {
1792             if (const auto actuallyDeleted = MessageViewer::Util::deleteAttachments(attachments); actuallyDeleted > 0) {
1793                 qCDebug(KMAIL_LOG) << "Deleted" << actuallyDeleted << "attachments from message" << item.id() << "(out of" << attachments.size()
1794                                    << "attachments found)";
1795                 Akonadi::Item updateItem(item);
1796                 updateItem.setPayloadFromData(message->encodedContent());
1797                 updateItem.setRemoteId(QString()); // clear remoteID as we will be re-uploading the message
1798                 auto job = new Akonadi::ItemModifyJob(updateItem, this);
1799                 job->disableRevisionCheck();
1800                 connect(job, &Akonadi::ItemModifyJob::finished, this, &KMDeleteAttachmentsCommand::slotUpdateResult);
1801                 mRunningJobs.push_back(job);
1802             } else {
1803                 qCDebug(KMAIL_LOG) << "Message" << item.id() << "not modified - no attachments were actually deleted"
1804                                    << "(out of" << attachments.size() << "attachments found)";
1805             }
1806         } else {
1807             qCDebug(KMAIL_LOG) << "Message" << item.id() << "has no attachments to delete, skipping";
1808         }
1809     }
1810 
1811     qCDebug(KMAIL_LOG) << mRunningJobs.size() << "Items now pending update after deleting attachments";
1812 
1813     if (!mRunningJobs.empty()) {
1814         mProgressItem = ProgressManager::createProgressItem(QLatin1StringView("deleteAttachments") + ProgressManager::getUniqueID(),
1815                                                             i18nc("@info:progress", "Deleting Attachments"),
1816                                                             QString(),
1817                                                             true,
1818                                                             KPIM::ProgressItem::Unknown);
1819         mProgressItem->setTotalItems(mRunningJobs.size());
1820         connect(mProgressItem, &ProgressItem::progressItemCanceled, this, &KMDeleteAttachmentsCommand::slotCanceled);
1821     } else {
1822         complete(OK);
1823     }
1824 
1825     return OK;
1826 }
1827 
1828 void KMDeleteAttachmentsCommand::slotCanceled()
1829 {
1830     for (auto job : mRunningJobs) {
1831         job->kill();
1832     }
1833     complete(KMCommand::Canceled);
1834 }
1835 
1836 void KMDeleteAttachmentsCommand::slotUpdateResult(KJob *job)
1837 {
1838     mRunningJobs.removeOne(job);
1839 
1840     if (mProgressItem) {
1841         mProgressItem->setCompletedItems(mProgressItem->completedItems() + 1);
1842     }
1843 
1844     if (job->error()) {
1845         showJobError(job);
1846         complete(Failed);
1847     } else if (mRunningJobs.empty()) {
1848         complete(OK);
1849     }
1850 }
1851 
1852 void KMDeleteAttachmentsCommand::complete(KMCommand::Result result)
1853 {
1854     if (mProgressItem) {
1855         mProgressItem->setComplete();
1856         mProgressItem = nullptr;
1857     }
1858 
1859     qCDebug(KMAIL_LOG) << "Deleting attachments completed with result" << result;
1860     setResult(result);
1861     Q_EMIT completed(this);
1862     deleteLater();
1863 }
1864 
1865 KMResendMessageCommand::KMResendMessageCommand(QWidget *parent, const Akonadi::Item &msg)
1866     : KMCommand(parent, msg)
1867 {
1868     fetchScope().fetchFullPayload(true);
1869     fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
1870 }
1871 
1872 KMCommand::Result KMResendMessageCommand::execute()
1873 {
1874     Akonadi::Item item = retrievedMessage();
1875     KMime::Message::Ptr msg = MessageComposer::Util::message(item);
1876     if (!msg) {
1877         return Failed;
1878     }
1879     MessageFactoryNG factory(msg, item.id(), CommonKernel->collectionFromId(item.parentCollection().id()));
1880     factory.setIdentityManager(KMKernel::self()->identityManager());
1881     factory.setFolderIdentity(MailCommon::Util::folderIdentity(item));
1882     KMime::Message::Ptr newMsg = factory.createResend();
1883     newMsg->contentType()->setCharset(MimeTreeParser::NodeHelper::charset(msg.data()));
1884 
1885     KMail::Composer *win = KMail::makeComposer();
1886     bool lastEncrypt = false;
1887     bool lastSign = false;
1888     KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, msg);
1889     win->setMessage(newMsg, lastSign, lastEncrypt, false, true);
1890 
1891     // Make sure to use current folder as requested by David
1892     // We avoid to use an invalid folder when we open an email on two different computer.
1893     win->setFcc(QString::number(item.parentCollection().id()));
1894     win->show();
1895 
1896     return OK;
1897 }
1898 
1899 KMShareImageCommand::KMShareImageCommand(const QUrl &url, QWidget *parent)
1900     : KMCommand(parent)
1901     , mUrl(url)
1902 {
1903 }
1904 
1905 KMCommand::Result KMShareImageCommand::execute()
1906 {
1907     KMime::Message::Ptr msg(new KMime::Message);
1908     uint id = 0;
1909 
1910     MessageHelper::initHeader(msg, KMKernel::self()->identityManager(), id);
1911     msg->contentType()->setCharset("utf-8");
1912 
1913     KMail::Composer *win = KMail::makeComposer(msg, false, false, KMail::Composer::New, id);
1914     win->setFocusToSubject();
1915     QList<KMail::Composer::AttachmentInfo> infoList;
1916     KMail::Composer::AttachmentInfo info;
1917     info.url = mUrl;
1918     info.comment = i18n("Image");
1919     infoList.append(std::move(info));
1920     win->addAttachment(infoList, false);
1921     win->show();
1922     return OK;
1923 }
1924 
1925 KMFetchMessageCommand::KMFetchMessageCommand(QWidget *parent, const Akonadi::Item &item, MessageViewer::Viewer *viewer, KMReaderMainWin *win)
1926     : KMCommand(parent, item)
1927     , mViewer(viewer)
1928     , mReaderMainWin(win)
1929 {
1930     // Workaround KMCommand::transferSelectedMsgs() expecting non-empty fetchscope
1931     fetchScope().fetchFullPayload(true);
1932 }
1933 
1934 Akonadi::ItemFetchJob *KMFetchMessageCommand::createFetchJob(const Akonadi::Item::List &items)
1935 {
1936     Q_ASSERT(items.size() == 1);
1937     Akonadi::ItemFetchJob *fetch = mViewer->createFetchJob(items.first());
1938     fetchScope() = fetch->fetchScope();
1939     return fetch;
1940 }
1941 
1942 KMCommand::Result KMFetchMessageCommand::execute()
1943 {
1944     Akonadi::Item item = retrievedMessage();
1945     if (!item.isValid() || !item.hasPayload<KMime::Message::Ptr>()) {
1946         return Failed;
1947     }
1948 
1949     mItem = item;
1950     return OK;
1951 }
1952 
1953 KMReaderMainWin *KMFetchMessageCommand::readerMainWin() const
1954 {
1955     return mReaderMainWin;
1956 }
1957 
1958 Akonadi::Item KMFetchMessageCommand::item() const
1959 {
1960     return mItem;
1961 }
1962 
1963 QDebug operator<<(QDebug d, const KMPrintCommandInfo &t)
1964 {
1965     d << "item id " << t.mMsg.id();
1966     d << "mOverrideFont " << t.mOverrideFont;
1967     d << "mEncoding " << t.mEncoding;
1968     d << "mFormat " << t.mFormat;
1969     d << "mHtmlLoadExtOverride " << t.mHtmlLoadExtOverride;
1970     d << "mUseFixedFont " << t.mUseFixedFont;
1971     d << "mPrintPreview " << t.mPrintPreview;
1972     d << "mShowSignatureDetails " << t.mShowSignatureDetails;
1973     d << "mShowEncryptionDetails " << t.mShowEncryptionDetails;
1974     d << "mAttachmentStrategy " << t.mAttachmentStrategy;
1975     d << "mHeaderStylePlugin " << t.mHeaderStylePlugin;
1976     return d;
1977 }
1978 
1979 #include "moc_kmcommands.cpp"