From 3f4011bcbf4ccf55edb8ea618fbf9b50f9e7bec9 Mon Sep 17 00:00:00 2001 From: Christian Mollekopf Date: Fri, 17 Jun 2016 09:09:33 +0200 Subject: Initial version of the mailtransport resource --- examples/mailtransportresource/CMakeLists.txt | 4 +- examples/mailtransportresource/facade.cpp | 142 --------------- examples/mailtransportresource/facade.h | 36 ---- examples/mailtransportresource/mailtransport.cpp | 11 +- examples/mailtransportresource/mailtransport.h | 2 +- .../mailtransportresource.cpp | 199 ++++++++++++++++++++- .../mailtransportresource/mailtransportresource.h | 20 +++ .../mailtransportresource/tests/CMakeLists.txt | 11 ++ .../tests/mailtransporttest.cpp | 94 ++++++++++ 9 files changed, 331 insertions(+), 188 deletions(-) delete mode 100644 examples/mailtransportresource/facade.cpp delete mode 100644 examples/mailtransportresource/facade.h create mode 100644 examples/mailtransportresource/tests/CMakeLists.txt create mode 100644 examples/mailtransportresource/tests/mailtransporttest.cpp (limited to 'examples') diff --git a/examples/mailtransportresource/CMakeLists.txt b/examples/mailtransportresource/CMakeLists.txt index c9b0401..d16e779 100644 --- a/examples/mailtransportresource/CMakeLists.txt +++ b/examples/mailtransportresource/CMakeLists.txt @@ -9,8 +9,10 @@ find_package(CURL 7.20.0 REQUIRED) include_directories(${CURL_INCLUDE_DIRS}) -add_library(${PROJECT_NAME} SHARED facade.cpp mailtransportresource.cpp mailtransport.cpp) +add_library(${PROJECT_NAME} SHARED mailtransportresource.cpp mailtransport.cpp) qt5_use_modules(${PROJECT_NAME} Core Network) target_link_libraries(${PROJECT_NAME} sink KF5::Mime ${CURL_LIBRARIES}) +add_subdirectory(tests) + install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${SINK_RESOURCE_PLUGINS_PATH}) diff --git a/examples/mailtransportresource/facade.cpp b/examples/mailtransportresource/facade.cpp deleted file mode 100644 index cd0dd62..0000000 --- a/examples/mailtransportresource/facade.cpp +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (C) 2014 Christian Mollekopf - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "facade.h" - -#include -#include -#include -#include -#include -#include - -#include "resultprovider.h" -#include "mailtransport.h" -#include -#include - -static QString dataDirectory(const QByteArray &identifier) -{ - return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/sink/mailtransport/" + identifier; -} - -class Outbox { -public: - Outbox(const QByteArray &identifier) : mIdentifier(identifier) - { - - } - - static QString fileName(const QByteArray &resourceId, const QByteArray &messageId) - { - return dataDirectory(resourceId)+"/" + messageId; - } - - void add(const QByteArray &messageId, const QString &messagePath, QMap config) - { - QDir dir; - dir.mkpath(dataDirectory(mIdentifier)); - if (!QFile(messagePath).rename(fileName(mIdentifier, messageId))) { - ErrorMsg() << "Failed to move the file:"; - ErrorMsg() << messagePath << " to " << fileName(mIdentifier, messageId); - } - //TODO store settings - // QSettings settings(dataDirectory(mIdentifier) + "/messageId.ini", QSettings::IniFormat); - } - - void dispatch(const QByteArray &messageId) - { - QFile mimeMessage(fileName(mIdentifier, messageId)); - if (!mimeMessage.open(QIODevice::ReadOnly)) { - ErrorMsg() << "Failed to open mime message: " << mimeMessage.errorString(); - ErrorMsg() << fileName(mIdentifier, messageId); - return; - } - - auto msg = KMime::Message::Ptr::create(); - msg->setHead(KMime::CRLFtoLF(mimeMessage.readAll())); - msg->parse(); - MailTransport::sendMessage(msg, mServer, mUsername, mPassword, mCaCert); - Trace() << "Sent message: " << msg->subject(); - } - - void setServer(const QByteArray &server, const QByteArray &username, const QByteArray &caCert) - { - mServer = server; - mUsername = username; - mCaCert = caCert; - } - - void setPassword(const QByteArray &password) - { - mPassword = password; - } - -private: - QByteArray mServer; - QByteArray mUsername; - QByteArray mPassword; - QByteArray mCaCert; - QByteArray mIdentifier; -}; - -MailtransportFacade::MailtransportFacade(const QByteArray &identifier) : Sink::StoreFacade(), mIdentifier(identifier) -{ -} - -MailtransportFacade::~MailtransportFacade() -{ -} - -KAsync::Job MailtransportFacade::create(const Sink::ApplicationDomain::Mail &mail) -{ - Trace() << "Called create: "; - return KAsync::start([mail, this]() { - auto config = ResourceConfig::getConfiguration(mIdentifier); - - auto identifier = Sink::Storage::generateUid(); - Trace() << "Sending new message: " << identifier; - Trace() << config.value("server").toByteArray() << config.value("username").toByteArray() << config.value("cacert").toByteArray(); - - Outbox outbox(mIdentifier); - outbox.setServer(config.value("server").toByteArray(), config.value("username").toByteArray(), config.value("cacert").toByteArray()); - //FIXME remove and somehow retrieve the password on demand - outbox.setPassword(config.value("password").toByteArray()); - - const QByteArray mimeMessage = mail.getProperty("mimeMessage").toByteArray(); - QMap configurationValues; - outbox.add(identifier, mimeMessage, configurationValues); - outbox.dispatch(identifier); - }); -} - -KAsync::Job MailtransportFacade::modify(const Sink::ApplicationDomain::Mail &mail) -{ - return KAsync::error(0, "Not implemented."); -} - -KAsync::Job MailtransportFacade::remove(const Sink::ApplicationDomain::Mail &mail) -{ - return KAsync::error(0, "Not implemented."); -} - -QPair, typename Sink::ResultEmitter::Ptr> MailtransportFacade::load(const Sink::Query &query) -{ - return qMakePair(KAsync::error(0, "Not implemented."), Sink::ResultEmitter::Ptr()); -} diff --git a/examples/mailtransportresource/facade.h b/examples/mailtransportresource/facade.h deleted file mode 100644 index 858f73a..0000000 --- a/examples/mailtransportresource/facade.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2014 Christian Mollekopf - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#pragma once - -#include "common/facade.h" - -class MailtransportFacade : public Sink::StoreFacade -{ -public: - MailtransportFacade(const QByteArray &instanceIdentifier); - virtual ~MailtransportFacade(); - KAsync::Job create(const Sink::ApplicationDomain::Mail &resource) Q_DECL_OVERRIDE; - KAsync::Job modify(const Sink::ApplicationDomain::Mail &resource) Q_DECL_OVERRIDE; - KAsync::Job remove(const Sink::ApplicationDomain::Mail &resource) Q_DECL_OVERRIDE; - QPair, typename Sink::ResultEmitter::Ptr> load(const Sink::Query &query) Q_DECL_OVERRIDE; -private: - QByteArray mIdentifier; -}; - diff --git a/examples/mailtransportresource/mailtransport.cpp b/examples/mailtransportresource/mailtransport.cpp index 49d858e..5985562 100644 --- a/examples/mailtransportresource/mailtransport.cpp +++ b/examples/mailtransportresource/mailtransport.cpp @@ -58,7 +58,7 @@ static size_t payload_source(void *ptr, size_t size, size_t nmemb, void *userp) } -void sendMessageCurl(const char *to[], int numTos, const char *cc[], int numCcs, const char *msg, bool useTls, const char* from, const char *username, const char *password, const char *server, bool verifyPeer) +bool sendMessageCurl(const char *to[], int numTos, const char *cc[], int numCcs, const char *msg, bool useTls, const char* from, const char *username, const char *password, const char *server, bool verifyPeer) { //For ssl use "smtps://mainserver.example.net const char* cacert = 0; // = "/path/to/certificate.pem"; @@ -121,15 +121,17 @@ void sendMessageCurl(const char *to[], int numTos, const char *cc[], int numCcs, } curl_slist_free_all(recipients); curl_easy_cleanup(curl); + return res == CURLE_OK; } + return false; } }; -void MailTransport::sendMessage(const KMime::Message::Ptr &message, const QByteArray &server, const QByteArray &username, const QByteArray &password, const QByteArray &cacert) +bool MailTransport::sendMessage(const KMime::Message::Ptr &message, const QByteArray &server, const QByteArray &username, const QByteArray &password, const QByteArray &cacert) { QByteArray msg = message->encodedContent(); - qWarning() << "Sending message " << msg; + qDebug() << "Sending message " << msg; QByteArray from(message->from(true)->mailboxes().isEmpty() ? QByteArray() : message->from(true)->mailboxes().first().address()); QList toList; @@ -155,6 +157,5 @@ void MailTransport::sendMessage(const KMime::Message::Ptr &message, const QByteA cc[i] = ccList.at(i); } - sendMessageCurl(to, numTos, cc, numCcs, msg, useTls, from.isEmpty() ? nullptr : from, username, password, server, verifyPeer); - qWarning() << "Message sent"; + return sendMessageCurl(to, numTos, cc, numCcs, msg, useTls, from.isEmpty() ? nullptr : from, username, password, server, verifyPeer); } diff --git a/examples/mailtransportresource/mailtransport.h b/examples/mailtransportresource/mailtransport.h index 2eb30a0..6cd3452 100644 --- a/examples/mailtransportresource/mailtransport.h +++ b/examples/mailtransportresource/mailtransport.h @@ -24,5 +24,5 @@ namespace MailTransport { - void sendMessage(const KMime::Message::Ptr &message, const QByteArray &server, const QByteArray &username, const QByteArray &password, const QByteArray &cacert); + bool sendMessage(const KMime::Message::Ptr &message, const QByteArray &server, const QByteArray &username, const QByteArray &password, const QByteArray &cacert); }; diff --git a/examples/mailtransportresource/mailtransportresource.cpp b/examples/mailtransportresource/mailtransportresource.cpp index 3b8dfd0..e8a15ee 100644 --- a/examples/mailtransportresource/mailtransportresource.cpp +++ b/examples/mailtransportresource/mailtransportresource.cpp @@ -20,6 +20,196 @@ #include "mailtransportresource.h" #include "facade.h" #include "facadefactory.h" +#include "resourceconfig.h" +#include "definitions.h" +#include "domainadaptor.h" +#include "sourcewriteback.h" +#include +#include +#include +#include +#include +#include + +#include "resultprovider.h" +#include "mailtransport.h" +#include "mail_generated.h" +#include +#include +#include +#include + +#define ENTITY_TYPE_MAIL "mail" + +using namespace Sink; + +class MimeMessageMover : public Sink::EntityPreprocessor +{ +public: + MimeMessageMover(const QByteArray &resourceInstanceIdentifier) : Sink::EntityPreprocessor(), mResourceInstanceIdentifier(resourceInstanceIdentifier) {} + + QString moveMessage(const QString &oldPath, const Sink::ApplicationDomain::Mail &mail) + { + const auto directory = Sink::resourceStorageLocation(mResourceInstanceIdentifier); + const auto filePath = directory + "/" + mail.identifier(); + if (oldPath != filePath) { + if (!QDir().mkpath(directory)) { + Warning() << "Failed to create the directory: " << directory; + } + QFile::remove(filePath); + QFile origFile(oldPath); + if (!origFile.open(QIODevice::ReadWrite)) { + Warning() << "Failed to open the original file with write rights: " << origFile.errorString(); + } + if (!origFile.rename(filePath)) { + Warning() << "Failed to move the file from: " << oldPath << " to " << filePath << ". " << origFile.errorString(); + } + origFile.close(); + return filePath; + } + return oldPath; + } + + void newEntity(Sink::ApplicationDomain::Mail &mail, Sink::Storage::Transaction &transaction) Q_DECL_OVERRIDE + { + if (!mail.getMimeMessagePath().isEmpty()) { + mail.setMimeMessagePath(moveMessage(mail.getMimeMessagePath(), mail)); + } + } + + void modifiedEntity(const Sink::ApplicationDomain::Mail &oldMail, Sink::ApplicationDomain::Mail &newMail, Sink::Storage::Transaction &transaction) Q_DECL_OVERRIDE + { + if (!newMail.getMimeMessagePath().isEmpty()) { + newMail.setMimeMessagePath(moveMessage(newMail.getMimeMessagePath(), newMail)); + } + } + + void deletedEntity(const Sink::ApplicationDomain::Mail &mail, Sink::Storage::Transaction &transaction) Q_DECL_OVERRIDE + { + QFile::remove(mail.getMimeMessagePath()); + } + QByteArray mResourceInstanceIdentifier; +}; + +static KAsync::Jobsend(const ApplicationDomain::Mail &mail, const MailtransportResource::Settings &settings) +{ + const auto data = mail.getMimeMessage(); + auto msg = KMime::Message::Ptr::create(); + msg->setHead(KMime::CRLFtoLF(data)); + msg->parse(); + if (settings.testMode) { + Log() << "I would totally send that mail, but I'm in test mode."; + } else { + if (MailTransport::sendMessage(msg, settings.server.toUtf8(), settings.username.toUtf8(), settings.password.toUtf8(), settings.cacert.toUtf8())) { + Log() << "Sent message successfully"; + } else { + Log() << "Failed to send message"; + return KAsync::error(1, "Failed to send the message."); + } + } + return KAsync::null(); +} + +//TODO fold into synchronizer +class MailtransportWriteback : public Sink::SourceWriteBack +{ +public: + MailtransportWriteback(const QByteArray &resourceType, const QByteArray &resourceInstanceIdentifier) : Sink::SourceWriteBack(resourceType, resourceInstanceIdentifier) + { + + } + + KAsync::Job replay(const ApplicationDomain::Mail &mail, Sink::Operation operation, const QByteArray &oldRemoteId, const QList &changedProperties) Q_DECL_OVERRIDE + { + if (operation == Sink::Operation_Creation) { + Trace() << "Dispatching message."; + // return send(mail, mSettings); + } else if (operation == Sink::Operation_Removal) { + } else if (operation == Sink::Operation_Modification) { + } + return KAsync::null(); + } + +public: + MailtransportResource::Settings mSettings; +}; + +class MailtransportSynchronizer : public Sink::Synchronizer { +public: + MailtransportSynchronizer(const QByteArray &resourceType, const QByteArray &resourceInstanceIdentifier) + : Sink::Synchronizer(resourceType, resourceInstanceIdentifier) + { + + } + + KAsync::Job synchronizeWithSource() Q_DECL_OVERRIDE + { + Log() << " Synchronizing"; + return KAsync::start([this](KAsync::Future future) { + Sink::Query query; + QList toSend; + Log() << " Looking for mail"; + store().reader().query(query, [&](const ApplicationDomain::Mail &mail) -> bool { + Trace() << "Found mail: " << mail.identifier(); + // if (!mail.isSent()) { + toSend << mail; + // } + return true; + }); + auto job = KAsync::null(); + for (const auto &m : toSend) { + job = job.then(send(m, mSettings)).then([]() { + //on success, mark the mail as sent and move it to a separate place + //TODO + }); + } + job = job.then([&future]() { + future.setFinished(); + }, + [&future](int errorCode, const QString &errorString) { + future.setFinished(); + }); + job.exec(); + }); + } + +public: + QByteArray mResourceInstanceIdentifier; + MailtransportResource::Settings mSettings; +}; + +MailtransportResource::MailtransportResource(const QByteArray &instanceIdentifier, const QSharedPointer &pipeline) + : Sink::GenericResource(PLUGIN_NAME, instanceIdentifier, pipeline) +{ + auto config = ResourceConfig::getConfiguration(instanceIdentifier); + mSettings = {config.value("server").toString(), + config.value("username").toString(), + config.value("cacert").toString(), + config.value("password").toString(), + config.value("testmode").toBool() + }; + + auto synchronizer = QSharedPointer::create(PLUGIN_NAME, instanceIdentifier); + synchronizer->mSettings = mSettings; + setupSynchronizer(synchronizer); + + auto changereplay = QSharedPointer::create(PLUGIN_NAME, instanceIdentifier); + changereplay->mSettings = mSettings; + setupChangereplay(changereplay); + + setupPreprocessors(ENTITY_TYPE_MAIL, QVector() << new MimeMessageMover(mResourceInstanceIdentifier)); +} + +void MailtransportResource::removeFromDisk(const QByteArray &instanceIdentifier) +{ + GenericResource::removeFromDisk(instanceIdentifier); + Sink::Storage(Sink::storageLocation(), instanceIdentifier + ".synchronization", Sink::Storage::ReadWrite).removeFromDisk(); +} + +KAsync::Job MailtransportResource::inspect(int inspectionType, const QByteArray &inspectionId, const QByteArray &domainType, const QByteArray &entityId, const QByteArray &property, const QVariant &expectedValue) +{ + return KAsync::null(); +} MailtransportResourceFactory::MailtransportResourceFactory(QObject *parent) : Sink::ResourceFactory(parent) @@ -29,12 +219,15 @@ MailtransportResourceFactory::MailtransportResourceFactory(QObject *parent) Sink::Resource *MailtransportResourceFactory::createResource(const QByteArray &instanceIdentifier) { - ErrorMsg() << "The mailtransport resource has no synchronizer process: " << instanceIdentifier; - return nullptr; + return new MailtransportResource(instanceIdentifier); } void MailtransportResourceFactory::registerFacades(Sink::FacadeFactory &factory) { - factory.registerFacade(PLUGIN_NAME); + factory.registerFacade>>(PLUGIN_NAME); } +void MailtransportResourceFactory::registerAdaptorFactories(Sink::AdaptorFactoryRegistry ®istry) +{ + registry.registerFactory>(PLUGIN_NAME); +} diff --git a/examples/mailtransportresource/mailtransportresource.h b/examples/mailtransportresource/mailtransportresource.h index 2ccca0a..6622ea6 100644 --- a/examples/mailtransportresource/mailtransportresource.h +++ b/examples/mailtransportresource/mailtransportresource.h @@ -20,10 +20,29 @@ #pragma once #include "common/resource.h" +#include "common/genericresource.h" //TODO: a little ugly to have this in two places, once here and once in Q_PLUGIN_METADATA #define PLUGIN_NAME "org.kde.mailtransport" +class MailtransportResource : public Sink::GenericResource +{ +public: + MailtransportResource(const QByteArray &instanceIdentifier, const QSharedPointer &pipeline = QSharedPointer()); + KAsync::Job inspect(int inspectionType, const QByteArray &inspectionId, const QByteArray &domainType, const QByteArray &entityId, const QByteArray &property, const QVariant &expectedValue) Q_DECL_OVERRIDE; + static void removeFromDisk(const QByteArray &instanceIdentifier); + + struct Settings { + QString server; + QString username; + QString cacert; + QString password; + bool testMode; + }; +private: + Settings mSettings; +}; + class MailtransportResourceFactory : public Sink::ResourceFactory { Q_OBJECT @@ -35,5 +54,6 @@ public: Sink::Resource *createResource(const QByteArray &instanceIdentifier) Q_DECL_OVERRIDE; void registerFacades(Sink::FacadeFactory &factory) Q_DECL_OVERRIDE; + void registerAdaptorFactories(Sink::AdaptorFactoryRegistry ®istry) Q_DECL_OVERRIDE; }; diff --git a/examples/mailtransportresource/tests/CMakeLists.txt b/examples/mailtransportresource/tests/CMakeLists.txt new file mode 100644 index 0000000..458b462 --- /dev/null +++ b/examples/mailtransportresource/tests/CMakeLists.txt @@ -0,0 +1,11 @@ +set(CMAKE_AUTOMOC ON) +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} +) + +include(SinkTest) + +auto_tests ( + mailtransporttest +) +target_link_libraries(mailtransporttest sink_resource_mailtransport) diff --git a/examples/mailtransportresource/tests/mailtransporttest.cpp b/examples/mailtransportresource/tests/mailtransporttest.cpp new file mode 100644 index 0000000..564b4cb --- /dev/null +++ b/examples/mailtransportresource/tests/mailtransporttest.cpp @@ -0,0 +1,94 @@ +#include + +#include "tests/testutils.h" +#include "../mailtransport.h" + +#include "common/test.h" +#include "common/store.h" +#include "common/resourcecontrol.h" +#include "common/domain/applicationdomaintype.h" +#include "common/log.h" + +using namespace Sink; +using namespace Sink::ApplicationDomain; + +class MailtransportTest : public QObject +{ + Q_OBJECT + + Sink::ApplicationDomain::SinkResource createResource() + { + auto resource = ApplicationDomain::MailtransportResource::create("account1"); + resource.setProperty("server", "localhost"); + // resource.setProperty("port", 993); + resource.setProperty("user", "doe"); + resource.setProperty("password", "doe"); + resource.setProperty("testmode", true); + return resource; + } + + void removeResourceFromDisk(const QByteArray &identifier) + { + // ::MailtransportResource::removeFromDisk(identifier); + } + QByteArray mResourceInstanceIdentifier; + +private slots: + + void initTestCase() + { + Test::initTest(); + Log::setDebugOutputLevel(Sink::Log::Trace); + // resetTestEnvironment(); + auto resource = createResource(); + QVERIFY(!resource.identifier().isEmpty()); + VERIFYEXEC(Store::create(resource)); + mResourceInstanceIdentifier = resource.identifier(); + // mCapabilities = resource.getProperty("capabilities").value(); + qWarning() << "FOooooooooooooooooooo"; + } + + void cleanup() + { + // VERIFYEXEC(ResourceControl::shutdown(mResourceInstanceIdentifier)); + // removeResourceFromDisk(mResourceInstanceIdentifier); + } + + void init() + { + // qDebug(); + // qDebug() << "-----------------------------------------"; + // qDebug(); + // VERIFYEXEC(ResourceControl::start(mResourceInstanceIdentifier)); + } + + void testSendMail() + { + auto message = KMime::Message::Ptr::create(); + message->subject(true)->fromUnicodeString(QString::fromLatin1("Foobar"), "utf8"); + message->assemble(); + + auto mail = ApplicationDomain::Mail::create(mResourceInstanceIdentifier); + mail.setMimeMessage(message->encodedContent()); + + VERIFYEXEC(Store::create(mail)); + VERIFYEXEC(ResourceControl::flushMessageQueue(QByteArrayList() << mResourceInstanceIdentifier)); + VERIFYEXEC(Store::synchronize(Query::ResourceFilter(mResourceInstanceIdentifier))); + + //TODO verify the mail has been sent + + // auto modifiedMail = Store::readOne(Query::IdentityFilter(mail)); + // VERIFYEXEC(Store::modify(modifiedMail)); + // VERIFYEXEC(ResourceControl::flushMessageQueue(QByteArrayList() << mResourceInstanceIdentifier)); + // VERIFYEXEC(ResourceControl::flushReplayQueue(QByteArrayList() << mResourceInstanceIdentifier)); + + } + + //TODO test mail that fails to be sent. add a special header to the mail and have the resource fail sending. Ensure we can modify the mail to fix sending of the message. + +}; + + +QTEST_MAIN(MailtransportTest) + +#include "mailtransporttest.moc" -- cgit v1.2.3