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"