diff --git a/CMakeLists.txt b/CMakeLists.txt index 03a07f5..6012c9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,63 +1,63 @@ # SPDX-FileCopyrightText: 2023 g10 code Gmbh # SPDX-Contributor: Carl Schwan # SPDX-License-Identifier: BSD-2-Clause cmake_minimum_required(VERSION 3.16) project(gpgoljs) set(KF_MIN_VERSION "5.240.0") find_package(ECM ${KF_MIN_VERSION} CONFIG REQUIRED) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ) find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) include(FeatureSummary) include(KDEInstallDirs) include(KDECMakeSettings) include(ECMQtDeclareLoggingCategory) include(ECMAddTests) include(KDECompilerSettings NO_POLICY_SCOPE) set(QT_MIN_VERSION "6.5") set(KF_MIN_VERSION "5.240") set(MIMETREEPARSER_VERSION "5.240.40") set(LIBKLEO_VERSION "5.240.46") set(KLDAP_VERSION "5.240.40") set(LIBKDEPIM_VERSION "5.240.40") set(PIMTEXTEDIT_VERSION "5.240.40") set(PIMIDENTITYMANAGEMENT_VERSION "5.240.40") find_package(Qt6 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Core HttpServer Widgets PrintSupport) set_package_properties(Qt6 PROPERTIES TYPE REQUIRED PURPOSE "Basic application components" ) -find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Contacts Completion CoreAddons WidgetsAddons Config ColorScheme Codecs KIO XmlGui GuiAddons Kio Sonnet CalendarCore Archive) +find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Contacts Completion CoreAddons WidgetsAddons Config ColorScheme Codecs XmlGui GuiAddons JobWidgets Sonnet CalendarCore Archive) set_package_properties(KF6 PROPERTIES TYPE REQUIRED PURPOSE "Basic application components" ) find_package(KPim6Libkleo ${LIBKLEO_VERSION} CONFIG REQUIRED) find_package(KPim6LdapWidgets ${KLDAP_VERSION} CONFIG REQUIRED) find_package(KPim6Libkdepim ${LIBKDEPIM_LIB_VERSION} CONFIG REQUIRED) find_package(KPim6MimeTreeParserWidgets ${MIMETREEPARSER_VERSION} CONFIG REQUIRED) find_package(KPim6TextEdit ${PIMTEXTEDIT_VERSION} CONFIG REQUIRED) find_package(KPim6IdentityManagementWidgets ${PIMIDENTITYMANAGEMENT_VERSION} CONFIG REQUIRED) find_package(KPim6IdentityManagementCore ${PIMIDENTITYMANAGEMENT_VERSION} CONFIG REQUIRED) find_package(KF6TextAutoCorrectionCore CONFIG REQUIRED) if (BUILD_TESTING) find_package(Qt6 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Test) endif() add_subdirectory(common) add_subdirectory(broker) add_subdirectory(server) diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 5d197d2..53ec7dc 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -1,283 +1,283 @@ # SPDX-FileCopyrightText: 2023 g10 code GmbH # SPDX-Contributor: Carl Schwan # SPDX-License-Identifier: BSD-2-Clause add_library(gpgol-server-static STATIC) target_sources(gpgol-server-static PRIVATE websocketclient.cpp websocketclient.h webserver.h webserver.cpp # Identity identity/addressvalidationjob.cpp identity/addressvalidationjob.h identity/identitymanager.cpp identity/identitymanager.h identity/identitydialog.cpp identity/identitydialog.h # HTTP Controller controllers/emailcontroller.cpp controllers/emailcontroller.h # Draft draft/draft.cpp draft/draft.h draft/draftmanager.cpp draft/draftmanager.h # EWS integration ews/ewsattachment.cpp ews/ewsattachment.h ews/ewsattendee.cpp ews/ewsattendee.h ews/ewsclient_debug.cpp ews/ewsclient_debug.h ews/ewsid.cpp ews/ewsid.h ews/ewsitem.cpp ews/ewsitem.h ews/ewsitembase.cpp ews/ewsitembase.h ews/ewsitembase_p.h ews/ewsmailbox.cpp ews/ewsmailbox.h ews/ewsmailfactory.cpp ews/ewsmailfactory.h ews/ewsoccurrence.cpp ews/ewsoccurrence.h ews/ewspropertyfield.cpp ews/ewspropertyfield.h ews/ewsrecurrence.cpp ews/ewsrecurrence.h ews/ewsserverversion.cpp ews/ewsserverversion.h ews/ewstypes.cpp ews/ewstypes.h ews/ewsxml.cpp ews/ewsxml.h # Editor editor/addresseelineedit.cpp editor/addresseelineedit.h editor/addresseelineeditmanager.cpp editor/addresseelineeditmanager.h editor/composer.cpp editor/composer.h editor/composerviewbase.cpp editor/composerviewbase.h editor/composerwindow.cpp editor/composerwindow.h editor/composerwindowfactory.cpp editor/composerwindowfactory.h editor/cryptostateindicatorwidget.cpp editor/cryptostateindicatorwidget.h editor/kmcomposerglobalaction.cpp editor/kmcomposerglobalaction.h editor/nearexpirywarning.cpp editor/nearexpirywarning.h editor/mailtemplates.cpp editor/mailtemplates.h editor/recipient.cpp editor/recipient.h editor/recipientline.cpp editor/recipientline.h editor/recipientseditor.cpp editor/recipientseditor.h editor/util.h editor/util.cpp editor/kmailcompletion.cpp editor/kmailcompletion.h editor/richtextcomposerng.cpp editor/richtextcomposerng.h editor/richtextcomposersignatures.cpp editor/richtextcomposersignatures.h editor/nodehelper.cpp editor/nodehelper.h editor/signaturecontroller.cpp editor/signaturecontroller.h editor/spellcheckerconfigdialog.cpp editor/spellcheckerconfigdialog.h # Editor job editor/job/abstractencryptjob.h editor/job/autocryptheadersjob.h editor/job/contentjobbase.h editor/job/contentjobbase_p.h editor/job/dndfromarkjob.cpp editor/job/dndfromarkjob.h editor/job/encryptjob.h editor/job/inserttextfilejob.h editor/job/itipjob.h editor/job/jobbase.h editor/job/jobbase_p.h editor/job/maintextjob.h editor/job/multipartjob.h editor/job/protectedheadersjob.h editor/job/signencryptjob.h editor/job/signjob.h editor/job/singlepartjob.h editor/job/skeletonmessagejob.h editor/job/transparentjob.h editor/job/autocryptheadersjob.cpp editor/job/contentjobbase.cpp editor/job/encryptjob.cpp editor/job/inserttextfilejob.cpp editor/job/itipjob.cpp editor/job/jobbase.cpp editor/job/maintextjob.cpp editor/job/multipartjob.cpp editor/job/protectedheadersjob.cpp editor/job/saveasfilejob.cpp editor/job/saveasfilejob.h editor/job/signencryptjob.cpp editor/job/signjob.cpp editor/job/singlepartjob.cpp editor/job/skeletonmessagejob.cpp editor/job/transparentjob.cpp ## Editor Part editor/part/globalpart.h editor/part/infopart.h editor/part/itippart.h editor/part/messagepart.h editor/part/textpart.h editor/part/globalpart.cpp editor/part/infopart.cpp editor/part/itippart.cpp editor/part/messagepart.cpp editor/part/textpart.cpp ## Attachment editor/attachment/attachmentjob.cpp editor/attachment/attachmentjob.h editor/attachment/attachmentclipboardjob.cpp editor/attachment/attachmentclipboardjob.h editor/attachment/attachmentcompressjob.cpp editor/attachment/attachmentcompressjob.h editor/attachment/attachmentcontroller.cpp editor/attachment/attachmentcontroller.h editor/attachment/attachmentcontrollerbase.cpp editor/attachment/attachmentcontrollerbase.h editor/attachment/attachmentfromfolderjob.cpp editor/attachment/attachmentfromfolderjob.h editor/attachment/attachmentfrommimecontentjob.cpp editor/attachment/attachmentfrommimecontentjob.h editor/attachment/attachmentfromurlbasejob.cpp editor/attachment/attachmentfromurlbasejob.h editor/attachment/attachmentfromurljob.cpp editor/attachment/attachmentfromurljob.h editor/attachment/attachmentfromurlutils.cpp editor/attachment/attachmentfromurlutils.h editor/attachment/attachmentfrompublickeyjob.cpp editor/attachment/attachmentfrompublickeyjob.h editor/attachment/attachmentloadjob.cpp editor/attachment/attachmentloadjob.h editor/attachment/attachmentmodel.cpp editor/attachment/attachmentmodel.h editor/attachment/attachmentpart.cpp editor/attachment/attachmentpart.h editor/attachment/attachmentpropertiesdialog.cpp editor/attachment/attachmentpropertiesdialog.h editor/attachment/attachmentupdatejob.cpp editor/attachment/attachmentupdatejob.h editor/attachment/attachmentview.cpp editor/attachment/attachmentview.h ) qt_add_resources(gpgol-server-static PREFIX "/" FILES assets/certificate.crt ) ki18n_wrap_ui(gpgol-server-static editor/attachment/ui/attachmentpropertiesdialog.ui editor/attachment/ui/attachmentpropertiesdialog_readonly.ui ) ecm_qt_declare_logging_category(gpgol-server-static_SRCS HEADER websocket_debug.h IDENTIFIER WEBSOCKET_LOG CATEGORY_NAME org.gpgol.server.websocket DESCRIPTION "Websocket connection in the server" EXPORT GPGOL ) ecm_qt_declare_logging_category(gpgol-server-static_SRCS HEADER ewsresource_debug.h IDENTIFIER EWSRES_LOG CATEGORY_NAME org.gpgol.ews DESCRIPTION "Ews mail client" EXPORT GPGOL ) ecm_qt_declare_logging_category(gpgol-server-static_SRCS HEADER ewscli_debug.h IDENTIFIER EWSCLI_LOG CATEGORY_NAME org.gpgol.ews.client DESCRIPTION "ews client (gpgol-server)" EXPORT GPGOL ) ecm_qt_declare_logging_category(gpgol-server-static_SRCS HEADER editor_debug.h IDENTIFIER EDITOR_LOG CATEGORY_NAME org.gpgol.editor DESCRIPTION "mail composer" EXPORT GPGOL ) set(WARN_TOOMANY_RECIPIENTS_DEFAULT true) set(ALLOW_SEMICOLON_AS_ADDRESS_SEPARATOR_DEFAULT true) configure_file(editor/settings/messagecomposer.kcfg.in ${CMAKE_CURRENT_BINARY_DIR}/messagecomposer.kcfg) kconfig_add_kcfg_files(gpgol-server-static editor/settings/messagecomposersettings.kcfgc) install(FILES composerui.rc DESTINATION ${KDE_INSTALL_KXMLGUIDIR}/gpgol-server) target_sources(gpgol-server-static PUBLIC ${gpgol-server-static_SRCS}) target_link_libraries(gpgol-server-static PUBLIC common Qt6::HttpServer Qt6::Widgets Qt6::PrintSupport + KF6::JobWidgets KF6::CalendarCore KF6::ConfigCore KF6::ConfigGui KF6::Contacts KF6::Completion KF6::CoreAddons KF6::ColorScheme KF6::Codecs KF6::GuiAddons KF6::SonnetUi KF6::WidgetsAddons KF6::XmlGui - KF6::KIOFileWidgets KF6::Archive KF6::TextAutoCorrectionCore KPim6::MimeTreeParserWidgets KPim6::Libkleo KPim6::Libkdepim KPim6::LdapWidgets KPim6::PimTextEdit KPim6::IdentityManagementCore KPim6::IdentityManagementWidgets ) add_executable(gpgol-server main.cpp) target_link_libraries(gpgol-server PRIVATE gpgol-server-static) if (BUILD_TESTING) add_subdirectory(autotests) endif() diff --git a/server/editor/attachment/attachmentcontrollerbase.cpp b/server/editor/attachment/attachmentcontrollerbase.cpp index 87ae551..16f5491 100644 --- a/server/editor/attachment/attachmentcontrollerbase.cpp +++ b/server/editor/attachment/attachmentcontrollerbase.cpp @@ -1,737 +1,727 @@ /* * This file is part of KMail. * SPDX-FileCopyrightText: 2009 Constantin Berzan * * Parts based on KMail code by: * Various authors. * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "attachmentcontrollerbase.h" #include "../attachment/attachmentjob.h" #include "../attachment/attachmentmodel.h" #include "../attachment/attachmentupdatejob.h" #include "../attachment/attachmentfrompublickeyjob.h" #include "../attachment/attachmentfromurlbasejob.h" #include "../attachment/attachmentcompressjob.h" #include "../attachment/attachmentclipboardjob.h" #include "../attachment/attachmentfromurlutils.h" #include "../attachment/attachmentpropertiesdialog.h" #include "../composer.h" #include "../part/globalpart.h" #include "editor_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include -#include #include #include #include #include #include #include using namespace MessageComposer; using namespace MessageCore; class MessageComposer::AttachmentControllerBase::AttachmentControllerBasePrivate { public: AttachmentControllerBasePrivate(AttachmentControllerBase *qq); ~AttachmentControllerBasePrivate(); void attachmentRemoved(const AttachmentPart::Ptr &part); // slot void compressJobResult(KJob *job); // slot void loadJobResult(KJob *job); // slot void openSelectedAttachments(); // slot void viewSelectedAttachments(); // slot void removeSelectedAttachments(); // slot void saveSelectedAttachmentAs(); // slot void selectedAttachmentProperties(); // slot void attachPublicKeyJobResult(KJob *job); // slot void slotAttachmentContentCreated(KJob *job); // slot void addAttachmentPart(AttachmentPart::Ptr part); void attachClipBoardElement(KJob *job); void selectedAllAttachment(); void reloadAttachment(); void updateJobResult(KJob *); AttachmentPart::List selectedParts; AttachmentControllerBase *const q; MessageComposer::AttachmentModel *model = nullptr; QWidget *wParent = nullptr; KActionCollection *mActionCollection = nullptr; QAction *attachPublicKeyAction = nullptr; QAction *attachMyPublicKeyAction = nullptr; QAction *openContextAction = nullptr; QAction *viewContextAction = nullptr; QAction *removeAction = nullptr; QAction *removeContextAction = nullptr; QAction *saveAsAction = nullptr; QAction *saveAsContextAction = nullptr; QAction *propertiesAction = nullptr; QAction *propertiesContextAction = nullptr; QAction *addAttachmentFileAction = nullptr; QAction *addAttachmentDirectoryAction = nullptr; QAction *addContextAction = nullptr; QAction *selectAllAction = nullptr; KActionMenu *attachmentMenu = nullptr; QAction *reloadAttachmentAction = nullptr; QAction *attachClipBoardAction = nullptr; // If part p is compressed, uncompressedParts[p] is the uncompressed part. QHash uncompressedParts; bool encryptEnabled = false; bool signEnabled = false; }; AttachmentControllerBase::AttachmentControllerBasePrivate::AttachmentControllerBasePrivate(AttachmentControllerBase *qq) : q(qq) { } AttachmentControllerBase::AttachmentControllerBasePrivate::~AttachmentControllerBasePrivate() = default; void AttachmentControllerBase::setSelectedParts(const AttachmentPart::List &selectedParts) { d->selectedParts = selectedParts; const int selectedCount = selectedParts.count(); d->openContextAction->setEnabled(selectedCount > 0); d->viewContextAction->setEnabled(selectedCount > 0); d->removeAction->setEnabled(selectedCount > 0); d->removeContextAction->setEnabled(selectedCount > 0); d->saveAsAction->setEnabled(selectedCount == 1); d->saveAsContextAction->setEnabled(selectedCount == 1); d->propertiesAction->setEnabled(selectedCount == 1); d->propertiesContextAction->setEnabled(selectedCount == 1); } void AttachmentControllerBase::AttachmentControllerBasePrivate::attachmentRemoved(const AttachmentPart::Ptr &part) { uncompressedParts.remove(part); } void AttachmentControllerBase::AttachmentControllerBasePrivate::compressJobResult(KJob *job) { if (job->error()) { KMessageBox::error(wParent, job->errorString(), i18nc("@title:window", "Failed to compress attachment")); return; } auto ajob = qobject_cast(job); Q_ASSERT(ajob); AttachmentPart::Ptr originalPart = ajob->originalPart(); AttachmentPart::Ptr compressedPart = ajob->compressedPart(); if (ajob->isCompressedPartLarger()) { const int result = KMessageBox::questionTwoActions(wParent, i18n("The compressed attachment is larger than the original. " "Do you want to keep the original one?"), QString(/*caption*/), KGuiItem(i18nc("Do not compress", "Keep")), KGuiItem(i18n("Compress"))); if (result == KMessageBox::ButtonCode::PrimaryAction) { // The user has chosen to keep the uncompressed file. return; } } qCDebug(EDITOR_LOG) << "Replacing uncompressed part in model."; uncompressedParts[compressedPart] = originalPart; bool ok = model->replaceAttachment(originalPart, compressedPart); if (!ok) { // The attachment was removed from the model while we were compressing. qCDebug(EDITOR_LOG) << "Compressed a zombie."; } } void AttachmentControllerBase::AttachmentControllerBasePrivate::loadJobResult(KJob *job) { if (job->error()) { KMessageBox::error(wParent, job->errorString(), i18n("Failed to attach file")); return; } auto ajob = qobject_cast(job); Q_ASSERT(ajob); AttachmentPart::Ptr part = ajob->attachmentPart(); q->addAttachment(part); } void AttachmentControllerBase::AttachmentControllerBasePrivate::openSelectedAttachments() { Q_ASSERT(selectedParts.count() >= 1); for (const AttachmentPart::Ptr &part : std::as_const(selectedParts)) { q->openAttachment(part); } } void AttachmentControllerBase::AttachmentControllerBasePrivate::viewSelectedAttachments() { Q_ASSERT(selectedParts.count() >= 1); for (const AttachmentPart::Ptr &part : std::as_const(selectedParts)) { q->viewAttachment(part); } } void AttachmentControllerBase::AttachmentControllerBasePrivate::removeSelectedAttachments() { Q_ASSERT(selectedParts.count() >= 1); // We must store list, otherwise when we remove it changes selectedParts (as selection changed) => it will crash. const AttachmentPart::List toRemove = selectedParts; for (const AttachmentPart::Ptr &part : toRemove) { if (!model->removeAttachment(part)) { qCWarning(EDITOR_LOG) << "Impossible to remove attachment" << part->fileName(); } } } void AttachmentControllerBase::AttachmentControllerBasePrivate::saveSelectedAttachmentAs() { Q_ASSERT(selectedParts.count() == 1); q->saveAttachmentAs(selectedParts.constFirst()); } void AttachmentControllerBase::AttachmentControllerBasePrivate::selectedAttachmentProperties() { Q_ASSERT(selectedParts.count() == 1); q->attachmentProperties(selectedParts.constFirst()); } void AttachmentControllerBase::AttachmentControllerBasePrivate::reloadAttachment() { Q_ASSERT(selectedParts.count() == 1); auto ajob = new AttachmentUpdateJob(selectedParts.constFirst(), q); connect(ajob, &AttachmentUpdateJob::result, q, [this](KJob *job) { updateJobResult(job); }); ajob->start(); } void AttachmentControllerBase::AttachmentControllerBasePrivate::updateJobResult(KJob *job) { if (job->error()) { KMessageBox::error(wParent, job->errorString(), i18n("Failed to reload attachment")); return; } auto ajob = qobject_cast(job); Q_ASSERT(ajob); AttachmentPart::Ptr originalPart = ajob->originalPart(); AttachmentPart::Ptr updatedPart = ajob->updatedPart(); attachmentRemoved(originalPart); bool ok = model->replaceAttachment(originalPart, updatedPart); if (!ok) { // The attachment was removed from the model while we were compressing. qCDebug(EDITOR_LOG) << "Updated a zombie."; } } void AttachmentControllerBase::exportPublicKey(const GpgME::Key &key) { if (key.isNull() || !QGpgME::openpgp()) { qCWarning(EDITOR_LOG) << "Tried to export key with empty fingerprint, or no OpenPGP."; return; } auto ajob = new MessageComposer::AttachmentFromPublicKeyJob(key, this); connect(ajob, &AttachmentFromPublicKeyJob::result, this, [this](KJob *job) { d->attachPublicKeyJobResult(job); }); ajob->start(); } void AttachmentControllerBase::AttachmentControllerBasePrivate::attachPublicKeyJobResult(KJob *job) { // The only reason we can't use loadJobResult() and need a separate method // is that we want to show the proper caption ("public key" instead of "file")... if (job->error()) { KMessageBox::error(wParent, job->errorString(), i18n("Failed to attach public key")); return; } Q_ASSERT(dynamic_cast(job)); auto ajob = static_cast(job); AttachmentPart::Ptr part = ajob->attachmentPart(); q->addAttachment(part); } void AttachmentControllerBase::AttachmentControllerBasePrivate::attachClipBoardElement(KJob *job) { if (job->error()) { qCDebug(EDITOR_LOG) << " Error during when get try to attach text from clipboard"; KMessageBox::error(wParent, job->errorString(), i18n("Failed to attach text from clipboard")); return; } auto ajob = static_cast(job); AttachmentPart::Ptr part = ajob->attachmentPart(); q->addAttachment(part); } static QTemporaryFile *dumpAttachmentToTempFile(const AttachmentPart::Ptr &part) // local { auto file = new QTemporaryFile; if (!file->open()) { qCCritical(EDITOR_LOG) << "Could not open tempfile" << file->fileName(); delete file; return nullptr; } if (file->write(part->data()) == -1) { qCCritical(EDITOR_LOG) << "Could not dump attachment to tempfile."; delete file; return nullptr; } file->flush(); return file; } AttachmentControllerBase::AttachmentControllerBase(MessageComposer::AttachmentModel *model, QWidget *wParent, KActionCollection *actionCollection) : QObject(wParent) , d(new AttachmentControllerBasePrivate(this)) { d->model = model; connect(model, &MessageComposer::AttachmentModel::attachUrlsRequested, this, &AttachmentControllerBase::addAttachments); connect(model, &MessageComposer::AttachmentModel::attachmentRemoved, this, [this](const MessageCore::AttachmentPart::Ptr &attr) { d->attachmentRemoved(attr); }); connect(model, &AttachmentModel::attachmentCompressRequested, this, &AttachmentControllerBase::compressAttachment); connect(model, &MessageComposer::AttachmentModel::encryptEnabled, this, &AttachmentControllerBase::setEncryptEnabled); connect(model, &MessageComposer::AttachmentModel::signEnabled, this, &AttachmentControllerBase::setSignEnabled); d->wParent = wParent; d->mActionCollection = actionCollection; } AttachmentControllerBase::~AttachmentControllerBase() = default; void AttachmentControllerBase::createActions() { // Create the actions. d->attachPublicKeyAction = new QAction(i18n("Attach &Public Key..."), this); connect(d->attachPublicKeyAction, &QAction::triggered, this, &AttachmentControllerBase::showAttachPublicKeyDialog); d->attachMyPublicKeyAction = new QAction(i18n("Attach &My Public Key"), this); connect(d->attachMyPublicKeyAction, &QAction::triggered, this, &AttachmentControllerBase::attachMyPublicKey); d->attachmentMenu = new KActionMenu(QIcon::fromTheme(QStringLiteral("mail-attachment")), i18n("Attach"), this); connect(d->attachmentMenu, &QAction::triggered, this, &AttachmentControllerBase::showAddAttachmentFileDialog); d->attachmentMenu->setPopupMode(QToolButton::DelayedPopup); d->addAttachmentFileAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-attachment")), i18n("&Attach File..."), this); d->addAttachmentFileAction->setIconText(i18n("Attach")); d->addContextAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-attachment")), i18n("Add Attachment..."), this); connect(d->addAttachmentFileAction, &QAction::triggered, this, &AttachmentControllerBase::showAddAttachmentFileDialog); connect(d->addContextAction, &QAction::triggered, this, &AttachmentControllerBase::showAddAttachmentFileDialog); d->addAttachmentDirectoryAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-attachment")), i18n("&Attach Directory..."), this); d->addAttachmentDirectoryAction->setIconText(i18n("Attach")); connect(d->addAttachmentDirectoryAction, &QAction::triggered, this, &AttachmentControllerBase::showAddAttachmentCompressedDirectoryDialog); d->attachClipBoardAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-attachment")), i18n("&Attach Text From Clipboard..."), this); d->attachClipBoardAction->setIconText(i18n("Attach Text From Clipboard")); connect(d->attachClipBoardAction, &QAction::triggered, this, &AttachmentControllerBase::showAttachClipBoard); d->attachmentMenu->addAction(d->addAttachmentFileAction); d->attachmentMenu->addAction(d->addAttachmentDirectoryAction); d->attachmentMenu->addSeparator(); d->attachmentMenu->addAction(d->attachClipBoardAction); d->removeAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("&Remove Attachment"), this); d->removeContextAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Remove"), this); // FIXME need two texts. is there a better way? connect(d->removeAction, &QAction::triggered, this, [this]() { d->removeSelectedAttachments(); }); connect(d->removeContextAction, &QAction::triggered, this, [this]() { d->removeSelectedAttachments(); }); d->openContextAction = new QAction(i18nc("to open", "Open"), this); connect(d->openContextAction, &QAction::triggered, this, [this]() { d->openSelectedAttachments(); }); d->viewContextAction = new QAction(i18nc("to view", "View"), this); connect(d->viewContextAction, &QAction::triggered, this, [this]() { d->viewSelectedAttachments(); }); d->saveAsAction = new QAction(QIcon::fromTheme(QStringLiteral("document-save-as")), i18n("&Save Attachment As..."), this); d->saveAsContextAction = new QAction(QIcon::fromTheme(QStringLiteral("document-save-as")), i18n("Save As..."), this); connect(d->saveAsAction, &QAction::triggered, this, [this]() { d->saveSelectedAttachmentAs(); }); connect(d->saveAsContextAction, &QAction::triggered, this, [this]() { d->saveSelectedAttachmentAs(); }); d->propertiesAction = new QAction(i18n("Attachment Pr&operties..."), this); d->propertiesContextAction = new QAction(i18n("Properties"), this); connect(d->propertiesAction, &QAction::triggered, this, [this]() { d->selectedAttachmentProperties(); }); connect(d->propertiesContextAction, &QAction::triggered, this, [this]() { d->selectedAttachmentProperties(); }); d->selectAllAction = new QAction(i18n("Select All"), this); connect(d->selectAllAction, &QAction::triggered, this, &AttachmentControllerBase::selectedAllAttachment); d->reloadAttachmentAction = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("Reload"), this); connect(d->reloadAttachmentAction, &QAction::triggered, this, [this]() { d->reloadAttachment(); }); // Insert the actions into the composer window's menu. KActionCollection *collection = d->mActionCollection; collection->addAction(QStringLiteral("attach_public_key"), d->attachPublicKeyAction); collection->addAction(QStringLiteral("attach_my_public_key"), d->attachMyPublicKeyAction); collection->addAction(QStringLiteral("attach"), d->addAttachmentFileAction); collection->setDefaultShortcut(d->addAttachmentFileAction, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_A)); collection->addAction(QStringLiteral("attach_directory"), d->addAttachmentDirectoryAction); collection->addAction(QStringLiteral("remove"), d->removeAction); collection->addAction(QStringLiteral("attach_save"), d->saveAsAction); collection->addAction(QStringLiteral("attach_properties"), d->propertiesAction); collection->addAction(QStringLiteral("select_all_attachment"), d->selectAllAction); collection->addAction(QStringLiteral("attach_menu"), d->attachmentMenu); setSelectedParts(AttachmentPart::List()); Q_EMIT actionsCreated(); } void AttachmentControllerBase::setEncryptEnabled(bool enabled) { d->encryptEnabled = enabled; } void AttachmentControllerBase::setSignEnabled(bool enabled) { d->signEnabled = enabled; } void AttachmentControllerBase::compressAttachment(const AttachmentPart::Ptr &part, bool compress) { if (compress) { qCDebug(EDITOR_LOG) << "Compressing part."; auto ajob = new AttachmentCompressJob(part, this); connect(ajob, &AttachmentCompressJob::result, this, [this](KJob *job) { d->compressJobResult(job); }); ajob->start(); } else { qCDebug(EDITOR_LOG) << "Uncompressing part."; // Replace the compressed part with the original uncompressed part, and delete // the compressed part. AttachmentPart::Ptr originalPart = d->uncompressedParts.take(part); Q_ASSERT(originalPart); // Found in uncompressedParts. bool ok = d->model->replaceAttachment(part, originalPart); Q_ASSERT(ok); Q_UNUSED(ok) } } void AttachmentControllerBase::showContextMenu() { Q_EMIT refreshSelection(); const int numberOfParts(d->selectedParts.count()); QMenu menu; if (numberOfParts > 0) { if (numberOfParts == 1) { const QString mimetype = QString::fromLatin1(d->selectedParts.first()->mimeType()); QMimeDatabase mimeDb; auto mime = mimeDb.mimeTypeForName(mimetype); QStringList parentMimeType; if (mime.isValid()) { parentMimeType = mime.allAncestors(); } if ((mimetype == QLatin1String("text/plain")) || (mimetype == QLatin1String("image/png")) || (mimetype == QLatin1String("image/jpeg")) || parentMimeType.contains(QLatin1String("text/plain")) || parentMimeType.contains(QLatin1String("image/png")) || parentMimeType.contains(QLatin1String("image/jpeg"))) { menu.addAction(d->viewContextAction); } } menu.addAction(d->openContextAction); } menu.addSeparator(); if (numberOfParts == 1) { if (!d->selectedParts.first()->url().isEmpty()) { menu.addAction(d->reloadAttachmentAction); } menu.addAction(d->saveAsContextAction); menu.addSeparator(); menu.addAction(d->propertiesContextAction); menu.addSeparator(); } if (numberOfParts > 0) { menu.addAction(d->removeContextAction); menu.addSeparator(); } const int nbAttachment = d->model->rowCount(); if (nbAttachment != numberOfParts) { menu.addAction(d->selectAllAction); menu.addSeparator(); } if (numberOfParts == 0) { menu.addAction(d->addContextAction); } menu.exec(QCursor::pos()); } void AttachmentControllerBase::openAttachment(const AttachmentPart::Ptr &part) { QTemporaryFile *tempFile = dumpAttachmentToTempFile(part); if (!tempFile) { KMessageBox::error(d->wParent, i18n("KMail was unable to write the attachment to a temporary file."), i18nc("@title:window", "Unable to open attachment")); return; } tempFile->setPermissions(QFile::ReadUser); if (!QDesktopServices::openUrl(QUrl::fromLocalFile(tempFile->fileName()))) { qWarning() << "Unable to open" << QUrl::fromLocalFile(tempFile->fileName()); } tempFile->deleteLater(); } void AttachmentControllerBase::viewAttachment(const AttachmentPart::Ptr &part) { auto composer = new MessageComposer::Composer; composer->globalPart()->setFallbackCharsetEnabled(true); auto attachmentJob = new MessageComposer::AttachmentJob(part, composer); connect(attachmentJob, &AttachmentJob::result, this, [this](KJob *job) { d->slotAttachmentContentCreated(job); }); attachmentJob->start(); } void AttachmentControllerBase::AttachmentControllerBasePrivate::slotAttachmentContentCreated(KJob *job) { if (!job->error()) { const MessageComposer::AttachmentJob *const attachmentJob = qobject_cast(job); Q_ASSERT(attachmentJob); if (attachmentJob) { Q_EMIT q->showAttachment(attachmentJob->content(), QByteArray()); } } else { // TODO: show warning to the user qCWarning(EDITOR_LOG) << "Error creating KMime::Content for attachment:" << job->errorText(); } } void AttachmentControllerBase::saveAttachmentAs(const AttachmentPart::Ptr &part) { QString pname = part->name(); if (pname.isEmpty()) { pname = i18n("unnamed"); } const QString fileName = QFileDialog::getSaveFileName(d->wParent, i18n("Save Attachment As"), pname); if (fileName.isEmpty()) { qCDebug(EDITOR_LOG) << "Save Attachment As dialog canceled."; return; } QSaveFile file(fileName); if (!file.open(QIODevice::WriteOnly)) { KMessageBox::error(d->wParent, i18n("File %1 could not be created.", fileName), i18n("Error saving message")); return; } file.write(part->data()); file.commit(); } void AttachmentControllerBase::attachmentProperties(const AttachmentPart::Ptr &part) { QPointer dialog = new AttachmentPropertiesDialog(part, false, d->wParent); dialog->setEncryptEnabled(d->encryptEnabled); dialog->setSignEnabled(d->signEnabled); if (dialog->exec() && dialog) { d->model->updateAttachment(part); } delete dialog; } void AttachmentControllerBase::attachDirectory(const QUrl &url) { const int rc = KMessageBox::warningTwoActions(d->wParent, i18n("Do you really want to attach this directory \"%1\"?", url.toLocalFile()), i18nc("@title:window", "Attach directory"), KGuiItem(i18nc("@action:button", "Attach")), KStandardGuiItem::cancel()); if (rc == KMessageBox::ButtonCode::PrimaryAction) { addAttachment(url); } } void AttachmentControllerBase::showAttachClipBoard() { auto job = new MessageComposer::AttachmentClipBoardJob(this); connect(job, &AttachmentClipBoardJob::result, this, [this](KJob *job) { d->attachClipBoardElement(job); }); job->start(); } void AttachmentControllerBase::showAddAttachmentCompressedDirectoryDialog() { const QUrl url = QFileDialog::getExistingDirectoryUrl(d->wParent, i18nc("@title:window", "Attach Directory")); if (url.isValid()) { attachDirectory(url); } } static QString fixEncoding(const QString &encoding) { QString returnEncoding = encoding; // According to https://www.iana.org/assignments/character-sets, uppercase is // preferred in MIME headers const QString returnEncodingToUpper = returnEncoding.toUpper(); if (returnEncodingToUpper.contains(QLatin1String("ISO "))) { returnEncoding = returnEncodingToUpper; returnEncoding.replace(QLatin1String("ISO "), QStringLiteral("ISO-")); } return returnEncoding; } void AttachmentControllerBase::showAddAttachmentFileDialog() { - const KEncodingFileDialog::Result result = - KEncodingFileDialog::getOpenUrlsAndEncoding(QString(), QUrl(), QString(), d->wParent, i18nc("@title:window", "Attach File")); - if (!result.URLs.isEmpty()) { - const QString encoding = fixEncoding(result.encoding); - const int numberOfFiles(result.URLs.count()); - for (int i = 0; i < numberOfFiles; ++i) { - const QUrl url = result.URLs.at(i); - QUrl urlWithEncoding = url; - QUrlQuery query; - query.addQueryItem(QStringLiteral("charset"), encoding); - urlWithEncoding.setQuery(query); + const auto urls = QFileDialog::getOpenFileUrls(d->wParent, i18nc("@title:window", "Attach File")); + if (!urls.isEmpty()) { + for (const auto &url : urls) { QMimeDatabase mimeDb; - const auto mimeType = mimeDb.mimeTypeForUrl(urlWithEncoding); + const auto mimeType = mimeDb.mimeTypeForUrl(url); if (mimeType.name() == QLatin1String("inode/directory")) { const int rc = KMessageBox::warningTwoActions(d->wParent, i18n("Do you really want to attach this directory \"%1\"?", url.toLocalFile()), i18nc("@title:window", "Attach directory"), KGuiItem(i18nc("@action:button", "Attach")), KStandardGuiItem::cancel()); if (rc == KMessageBox::ButtonCode::PrimaryAction) { - addAttachment(urlWithEncoding); + addAttachment(url); } } else { - addAttachment(urlWithEncoding); + addAttachment(url); } } } } void AttachmentControllerBase::addAttachment(const AttachmentPart::Ptr &part) { part->setEncrypted(d->model->isEncryptSelected()); part->setSigned(d->model->isSignSelected()); d->model->addAttachment(part); Q_EMIT fileAttached(); } void AttachmentControllerBase::addAttachmentUrlSync(const QUrl &url) { MessageCore::AttachmentFromUrlBaseJob *ajob = MessageCore::AttachmentFromUrlUtils::createAttachmentJob(url, this); if (ajob->exec()) { AttachmentPart::Ptr part = ajob->attachmentPart(); addAttachment(part); } else { if (ajob->error()) { KMessageBox::error(d->wParent, ajob->errorString(), i18nc("@title:window", "Failed to attach file")); } } } void AttachmentControllerBase::addAttachment(const QUrl &url) { MessageCore::AttachmentFromUrlBaseJob *ajob = MessageCore::AttachmentFromUrlUtils::createAttachmentJob(url, this); connect(ajob, &AttachmentFromUrlBaseJob::result, this, [this](KJob *job) { d->loadJobResult(job); }); ajob->start(); } void AttachmentControllerBase::addAttachments(const QList &urls) { for (const QUrl &url : urls) { addAttachment(url); } } void AttachmentControllerBase::showAttachPublicKeyDialog() { using Kleo::KeySelectionDialog; QPointer dialog = new KeySelectionDialog(i18n("Attach Public OpenPGP Key"), i18n("Select the public key which should be attached."), std::vector(), KeySelectionDialog::PublicKeys | KeySelectionDialog::OpenPGPKeys, false /* no multi selection */, false /* no remember choice box */, d->wParent); if (dialog->exec() == QDialog::Accepted) { exportPublicKey(dialog->selectedKey()); } delete dialog; } void AttachmentControllerBase::attachMyPublicKey() { } void AttachmentControllerBase::enableAttachPublicKey(bool enable) { d->attachPublicKeyAction->setEnabled(enable); } void AttachmentControllerBase::enableAttachMyPublicKey(bool enable) { d->attachMyPublicKeyAction->setEnabled(enable); } void AttachmentControllerBase::clear() { const auto parts = d->model->attachments(); for (const auto &attachmentPart : parts) { if (!d->model->removeAttachment(attachmentPart)) { qCWarning(EDITOR_LOG) << "Impossible to remove attachment" << attachmentPart->fileName(); } } } #include "moc_attachmentcontrollerbase.cpp" diff --git a/server/editor/attachment/attachmentcontrollerbase.h b/server/editor/attachment/attachmentcontrollerbase.h index 093224f..6cb105f 100644 --- a/server/editor/attachment/attachmentcontrollerbase.h +++ b/server/editor/attachment/attachmentcontrollerbase.h @@ -1,85 +1,84 @@ /* * This file is part of KMail. * SPDX-FileCopyrightText: 2009 Constantin Berzan * * Parts based on KMail code by: * Various authors. * * SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include #include #include -#include #include "attachmentpart.h" class KActionCollection; class QAction; class KJob; namespace MessageComposer { class AttachmentModel; /** * @brief The AttachmentControllerBase class */ class AttachmentControllerBase : public QObject { Q_OBJECT public: AttachmentControllerBase(MessageComposer::AttachmentModel *model, QWidget *wParent, KActionCollection *actionCollection); ~AttachmentControllerBase() override; void createActions(); // TODO dnd stuff... void setSelectedParts(const MessageCore::AttachmentPart::List &selectedParts); public Q_SLOTS: /// model sets these void setEncryptEnabled(bool enabled); void setSignEnabled(bool enabled); /// compression is async... void compressAttachment(const MessageCore::AttachmentPart::Ptr &part, bool compress); void showContextMenu(); void openAttachment(const MessageCore::AttachmentPart::Ptr &part); void viewAttachment(const MessageCore::AttachmentPart::Ptr &part); void saveAttachmentAs(const MessageCore::AttachmentPart::Ptr &part); void attachmentProperties(const MessageCore::AttachmentPart::Ptr &part); void showAddAttachmentFileDialog(); void showAddAttachmentCompressedDirectoryDialog(); /// sets sign, encrypt, shows properties dialog if so configured void addAttachment(const MessageCore::AttachmentPart::Ptr &part); void addAttachment(const QUrl &url); void addAttachmentUrlSync(const QUrl &url); void addAttachments(const QList &urls); void showAttachPublicKeyDialog(); void showAttachClipBoard(); virtual void attachMyPublicKey(); void clear(); Q_SIGNALS: void actionsCreated(); void refreshSelection(); void showAttachment(KMime::Content *content, const QByteArray &charset); void selectedAllAttachment(); void fileAttached(); protected: void exportPublicKey(const GpgME::Key &key); void enableAttachPublicKey(bool enable); void enableAttachMyPublicKey(bool enable); private: void attachDirectory(const QUrl &url); private: class AttachmentControllerBasePrivate; std::unique_ptr const d; }; } // diff --git a/server/editor/attachment/attachmentfromurljob.cpp b/server/editor/attachment/attachmentfromurljob.cpp index 6c453ab..76ed17a 100644 --- a/server/editor/attachment/attachmentfromurljob.cpp +++ b/server/editor/attachment/attachmentfromurljob.cpp @@ -1,121 +1,119 @@ /* SPDX-FileCopyrightText: 2009 Constantin Berzan Parts based on KMail code by various authors. SPDX-License-Identifier: LGPL-2.0-or-later */ #include "attachmentfromurljob.h" #include "editor_debug.h" #include #include #include #include #include #include #include using namespace MessageCore; class MessageCore::AttachmentFromUrlJob::AttachmentLoadJobPrivate { public: explicit AttachmentLoadJobPrivate(AttachmentFromUrlJob *qq); void slotRequestFinished(QNetworkReply *reply); AttachmentFromUrlJob *const q; QByteArray mData; }; AttachmentFromUrlJob::AttachmentLoadJobPrivate::AttachmentLoadJobPrivate(AttachmentFromUrlJob *qq) : q(qq) { } void AttachmentFromUrlJob::AttachmentLoadJobPrivate::slotRequestFinished(QNetworkReply *reply) { if (reply->error() != QNetworkReply::NoError) { // TODO this loses useful stuff from KIO, like detailed error descriptions, causes+solutions, // ... use UiDelegate somehow? q->setError(reply->error()); q->setErrorText(reply->errorString()); q->emitResult(); return; } mData = reply->readAll(); // Determine the MIME type and filename of the attachment. const QString mimeTypeName = reply->header(QNetworkRequest::ContentTypeHeader).toString(); qCDebug(EDITOR_LOG) << "Mimetype is" << mimeTypeName; QString fileName = q->url().fileName(); fileName.replace(QLatin1Char('\n'), QLatin1Char('_')); if (fileName.isEmpty()) { QMimeDatabase db; const auto mimeType = db.mimeTypeForName(mimeTypeName); if (mimeType.isValid()) { fileName = i18nc("a file called 'unknown.ext'", "unknown%1", mimeType.preferredSuffix()); } else { fileName = i18nc("a file called 'unknown'", "unknown"); } } // Create the AttachmentPart. Q_ASSERT(q->attachmentPart() == nullptr); // Not created before. AttachmentPart::Ptr part = AttachmentPart::Ptr(new AttachmentPart); QUrlQuery query(q->url()); - const QString value = query.queryItemValue(QStringLiteral("charset")); - part->setCharset(value.toLatin1()); part->setMimeType(mimeTypeName.toLatin1()); part->setName(fileName); part->setFileName(fileName); part->setData(mData); part->setUrl(q->url()); q->setAttachmentPart(part); q->emitResult(); // Success. } AttachmentFromUrlJob::AttachmentFromUrlJob(const QUrl &url, QObject *parent) : AttachmentFromUrlBaseJob(url, parent) , d(new AttachmentLoadJobPrivate(this)) { } AttachmentFromUrlJob::~AttachmentFromUrlJob() = default; void AttachmentFromUrlJob::doStart() { if (!url().isValid()) { setError(KJob::UserDefinedError); setErrorText(i18n("\"%1\" not found. Please specify the full path.", url().toDisplayString())); emitResult(); return; } if (maximumAllowedSize() != -1 && url().isLocalFile()) { const qint64 size = QFileInfo(url().toLocalFile()).size(); if (size > maximumAllowedSize()) { setError(KJob::UserDefinedError); KFormat format; setErrorText(i18n("You may not attach files bigger than %1. Share it with storage service.", format.formatByteSize(maximumAllowedSize()))); emitResult(); return; } } Q_ASSERT(d->mData.isEmpty()); // Not started twice. auto qnam = new QNetworkAccessManager(this); QNetworkRequest request(url()); auto reply = qnam->get(request); connect(reply, &QNetworkReply::finished, this, [this, reply]() { d->slotRequestFinished(reply); }); } #include "moc_attachmentfromurljob.cpp" diff --git a/server/editor/job/inserttextfilejob.cpp b/server/editor/job/inserttextfilejob.cpp index b9e6839..9f55602 100644 --- a/server/editor/job/inserttextfilejob.cpp +++ b/server/editor/job/inserttextfilejob.cpp @@ -1,85 +1,82 @@ /* * SPDX-FileCopyrightText: 2010 Thomas McGuire * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "inserttextfilejob.h" #include #include "editor_debug.h" -#include #include #include +#include +#include using namespace MessageComposer; class MessageComposer::InsertTextFileJobPrivate { public: InsertTextFileJobPrivate(QTextEdit *editor, const QUrl &url) : mEditor(editor) , mUrl(url) { } QPointer mEditor; QUrl mUrl; QString mEncoding; QByteArray mFileData; }; InsertTextFileJob::InsertTextFileJob(QTextEdit *editor, const QUrl &url) : KJob(editor) , d(new MessageComposer::InsertTextFileJobPrivate(editor, url)) { } InsertTextFileJob::~InsertTextFileJob() = default; -void InsertTextFileJob::slotFileData(KIO::Job *job, const QByteArray &data) +void InsertTextFileJob::slotGetJobFinished(QNetworkReply *reply) { - Q_UNUSED(job) - d->mFileData += data; -} - -void InsertTextFileJob::slotGetJobFinished(KJob *job) -{ - if (job->error()) { - qCWarning(EDITOR_LOG) << job->errorString(); - setError(job->error()); - setErrorText(job->errorText()); + if (reply->error() != QNetworkReply::NoError) { + qCWarning(EDITOR_LOG) << reply->errorString(); + setError(reply->error()); + setErrorText(reply->errorString()); emitResult(); return; } if (d->mEditor) { if (!d->mEncoding.isEmpty()) { QStringDecoder fileCodec(d->mEncoding.toLatin1().constData()); if (fileCodec.isValid()) { d->mEditor->textCursor().insertText(fileCodec.decode(d->mFileData.data())); } else { d->mEditor->textCursor().insertText(QString::fromLocal8Bit(d->mFileData.data())); } } else { d->mEditor->textCursor().insertText(QString::fromLocal8Bit(d->mFileData.data())); } } emitResult(); } void InsertTextFileJob::setEncoding(const QString &encoding) { d->mEncoding = encoding; } void InsertTextFileJob::start() { - KIO::TransferJob *job = KIO::get(d->mUrl); - connect(job, &KIO::TransferJob::result, this, &InsertTextFileJob::slotGetJobFinished); - connect(job, &KIO::TransferJob::data, this, &InsertTextFileJob::slotFileData); - job->start(); + auto qnam = new QNetworkAccessManager(this); + QNetworkRequest request(d->mUrl); + auto reply = qnam->get(request); + connect(reply, &QNetworkReply::finished, this, [this, reply]() { + slotGetJobFinished(reply); + }); } #include "moc_inserttextfilejob.cpp" diff --git a/server/editor/job/inserttextfilejob.h b/server/editor/job/inserttextfilejob.h index d82ffb4..4ee4a4d 100644 --- a/server/editor/job/inserttextfilejob.h +++ b/server/editor/job/inserttextfilejob.h @@ -1,42 +1,42 @@ /* * SPDX-FileCopyrightText: 2010 Thomas McGuire * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include #include class QTextEdit; +class QNetworkReply; namespace KIO { class Job; } namespace MessageComposer { class InsertTextFileJobPrivate; /** * A job that downloads a given URL, interprets the result as a text file with the * given encoding and then inserts the text into the editor. */ class InsertTextFileJob : public KJob { Q_OBJECT public: explicit InsertTextFileJob(QTextEdit *editor, const QUrl &url); ~InsertTextFileJob() override; void setEncoding(const QString &encoding); void start() override; private: - void slotGetJobFinished(KJob *job); - void slotFileData(KIO::Job *job, const QByteArray &data); + void slotGetJobFinished(QNetworkReply *reply); std::unique_ptr const d; }; }