diff options
author | Christian Mollekopf <chrigi_1@fastmail.fm> | 2017-04-05 15:04:00 +0200 |
---|---|---|
committer | Christian Mollekopf <chrigi_1@fastmail.fm> | 2017-04-05 15:04:00 +0200 |
commit | 4b1798f0cdf87361869e7cf2b341acacd056c410 (patch) | |
tree | 3ff780641acdcb20b81f9b41533afd50a2525d38 /framework/src | |
parent | 71721aa4f3e85bea1a2fe504e86d99f80a3106a9 (diff) | |
download | kube-4b1798f0cdf87361869e7cf2b341acacd056c410.tar.gz kube-4b1798f0cdf87361869e7cf2b341acacd056c410.zip |
Moved cpp code into src directory
Diffstat (limited to 'framework/src')
114 files changed, 13154 insertions, 0 deletions
diff --git a/framework/src/CMakeLists.txt b/framework/src/CMakeLists.txt new file mode 100644 index 00000000..371be4a6 --- /dev/null +++ b/framework/src/CMakeLists.txt | |||
@@ -0,0 +1,66 @@ | |||
1 | |||
2 | find_package(Qt5 COMPONENTS REQUIRED Core Qml) | ||
3 | find_package(KF5MimeTreeParser "5.1.46" CONFIG REQUIRED) | ||
4 | find_package(KF5Mime "4.87.0" CONFIG REQUIRED) | ||
5 | find_package(Sink CONFIG REQUIRED) | ||
6 | find_package(KAsync CONFIG REQUIRED) | ||
7 | find_package(QGpgme CONFIG REQUIRED) | ||
8 | find_package(KF5Codecs CONFIG REQUIRED) | ||
9 | find_package(KF5Package CONFIG REQUIRED) | ||
10 | |||
11 | add_definitions("-Wall -std=c++0x -g") | ||
12 | |||
13 | include_directories(.) | ||
14 | #include_directories(SYSTEM ${KDE_INSTALL_FULL_INCLUDEDIR}/KF5/) | ||
15 | #include_directories(SYSTEM ${KDE_INSTALL_FULL_INCLUDEDIR}/KF5/KMime) | ||
16 | |||
17 | enable_testing() | ||
18 | |||
19 | set(SRCS | ||
20 | frameworkplugin.cpp | ||
21 | actions/action.cpp | ||
22 | actions/actionhandler.cpp | ||
23 | actions/actionbroker.cpp | ||
24 | actions/actionresult.cpp | ||
25 | actions/context.cpp | ||
26 | settings/settings.cpp | ||
27 | domain/attachmentmodel.cpp | ||
28 | domain/maillistmodel.cpp | ||
29 | domain/folderlistmodel.cpp | ||
30 | domain/actions/sinkactions.cpp | ||
31 | domain/objecttreesource.cpp | ||
32 | domain/stringhtmlwriter.cpp | ||
33 | domain/composercontroller.cpp | ||
34 | domain/messageparser.cpp | ||
35 | domain/messageparser_new.cpp | ||
36 | domain/messageparser_old.cpp | ||
37 | domain/mailtemplates.cpp | ||
38 | domain/modeltest.cpp | ||
39 | domain/retriever.cpp | ||
40 | domain/outboxmodel.cpp | ||
41 | domain/identitiesmodel.cpp | ||
42 | domain/recepientautocompletionmodel.cpp | ||
43 | domain/settings/accountsettings.cpp | ||
44 | domain/selector.cpp | ||
45 | domain/completer.cpp | ||
46 | domain/controller.cpp | ||
47 | domain/outboxcontroller.cpp | ||
48 | domain/mailcontroller.cpp | ||
49 | domain/foldercontroller.cpp | ||
50 | domain/mouseproxy.cpp | ||
51 | domain/contactcontroller.cpp | ||
52 | domain/peoplemodel.cpp | ||
53 | accounts/accountfactory.cpp | ||
54 | accounts/accountsmodel.cpp | ||
55 | notifications/notificationhandler.cpp | ||
56 | ) | ||
57 | |||
58 | add_library(frameworkplugin SHARED ${SRCS}) | ||
59 | qt5_use_modules(frameworkplugin Core Quick Qml WebKitWidgets Test) | ||
60 | target_link_libraries(frameworkplugin sink mimetreeparser KF5::MimeTreeParser KF5::Codecs KF5::Package KAsync) | ||
61 | install(TARGETS frameworkplugin DESTINATION ${FRAMEWORK_INSTALL_DIR}) | ||
62 | |||
63 | add_subdirectory(domain/actions/tests) | ||
64 | add_subdirectory(domain/mimetreeparser) | ||
65 | |||
66 | feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) | ||
diff --git a/framework/src/accounts/accountfactory.cpp b/framework/src/accounts/accountfactory.cpp new file mode 100644 index 00000000..9dbb402b --- /dev/null +++ b/framework/src/accounts/accountfactory.cpp | |||
@@ -0,0 +1,71 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsystems.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #include "accountfactory.h" | ||
20 | |||
21 | #include <QDebug> | ||
22 | |||
23 | #include <KPackage/PackageLoader> | ||
24 | #include <KPackage/Package> | ||
25 | #include <KPluginMetaData> | ||
26 | |||
27 | #include "settings/settings.h" | ||
28 | #include <sink/store.h> | ||
29 | |||
30 | AccountFactory::AccountFactory(QObject *parent) | ||
31 | : QObject(parent) | ||
32 | { | ||
33 | } | ||
34 | |||
35 | QString AccountFactory::name() const | ||
36 | { | ||
37 | if (mName.isEmpty()) { | ||
38 | return tr("Account"); | ||
39 | } | ||
40 | return mName; | ||
41 | } | ||
42 | |||
43 | void AccountFactory::setAccountId(const QString &accountId) | ||
44 | { | ||
45 | mAccountId = accountId; | ||
46 | Sink::Store::fetchOne<Sink::ApplicationDomain::SinkAccount>(Sink::Query().filter(accountId.toUtf8())) | ||
47 | .then([this](const Sink::ApplicationDomain::SinkAccount &account) { | ||
48 | mAccountType = account.getProperty("type").toByteArray(); | ||
49 | loadPackage(); | ||
50 | }).exec(); | ||
51 | } | ||
52 | |||
53 | void AccountFactory::setAccountType(const QString &type) | ||
54 | { | ||
55 | mAccountType = type.toLatin1(); | ||
56 | loadPackage(); | ||
57 | } | ||
58 | |||
59 | void AccountFactory::loadPackage() | ||
60 | { | ||
61 | auto package = KPackage::PackageLoader::self()->loadPackage("KPackage/GenericQML", "org.kube.accounts." + mAccountType); | ||
62 | if (!package.isValid()) { | ||
63 | qWarning() << "Failed to load account package: " << "org.kube.accounts." + mAccountType; | ||
64 | return; | ||
65 | } | ||
66 | Q_ASSERT(package.isValid()); | ||
67 | mUiPath = package.filePath("mainscript"); | ||
68 | mName = package.metadata().name(); | ||
69 | mIcon = package.metadata().iconName(); | ||
70 | emit accountLoaded(); | ||
71 | } | ||
diff --git a/framework/src/accounts/accountfactory.h b/framework/src/accounts/accountfactory.h new file mode 100644 index 00000000..b57854e5 --- /dev/null +++ b/framework/src/accounts/accountfactory.h | |||
@@ -0,0 +1,53 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsystems.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | |||
20 | #pragma once | ||
21 | |||
22 | #include <QObject> | ||
23 | #include <QVariant> | ||
24 | |||
25 | /** | ||
26 | * A factory to instantiate account-plugins. | ||
27 | */ | ||
28 | class AccountFactory : public QObject | ||
29 | { | ||
30 | Q_OBJECT | ||
31 | Q_PROPERTY(QString accountId MEMBER mAccountId WRITE setAccountId); | ||
32 | Q_PROPERTY(QString accountType MEMBER mAccountType WRITE setAccountType); | ||
33 | Q_PROPERTY(QString name MEMBER mName READ name NOTIFY accountLoaded); | ||
34 | Q_PROPERTY(QString icon MEMBER mIcon NOTIFY accountLoaded); | ||
35 | Q_PROPERTY(QString uiPath MEMBER mUiPath NOTIFY accountLoaded); | ||
36 | public: | ||
37 | explicit AccountFactory(QObject *parent = Q_NULLPTR); | ||
38 | |||
39 | void setAccountId(const QString &); | ||
40 | void setAccountType(const QString &); | ||
41 | QString name() const; | ||
42 | |||
43 | signals: | ||
44 | void accountLoaded(); | ||
45 | |||
46 | private: | ||
47 | void loadPackage(); | ||
48 | QString mAccountId; | ||
49 | QString mName; | ||
50 | QString mIcon; | ||
51 | QString mUiPath; | ||
52 | QByteArray mAccountType; | ||
53 | }; | ||
diff --git a/framework/src/accounts/accountsmodel.cpp b/framework/src/accounts/accountsmodel.cpp new file mode 100644 index 00000000..f98e8ca3 --- /dev/null +++ b/framework/src/accounts/accountsmodel.cpp | |||
@@ -0,0 +1,101 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #include "accountsmodel.h" | ||
20 | #include <sink/store.h> | ||
21 | |||
22 | using namespace Sink; | ||
23 | using namespace Sink::ApplicationDomain; | ||
24 | |||
25 | AccountsModel::AccountsModel(QObject *parent) : QIdentityProxyModel() | ||
26 | { | ||
27 | Sink::Query query; | ||
28 | query.setFlags(Query::LiveQuery); | ||
29 | query.request<SinkAccount::Name>(); | ||
30 | query.request<SinkAccount::Icon>(); | ||
31 | query.request<SinkAccount::Status>(); | ||
32 | runQuery(query); | ||
33 | } | ||
34 | |||
35 | AccountsModel::~AccountsModel() | ||
36 | { | ||
37 | |||
38 | } | ||
39 | |||
40 | QHash< int, QByteArray > AccountsModel::roleNames() const | ||
41 | { | ||
42 | QHash<int, QByteArray> roles; | ||
43 | |||
44 | roles[Name] = "name"; | ||
45 | roles[Icon] = "icon"; | ||
46 | roles[AccountId] = "accountId"; | ||
47 | roles[Status] = "status"; | ||
48 | |||
49 | return roles; | ||
50 | } | ||
51 | |||
52 | QVariant AccountsModel::data(const QModelIndex &idx, int role) const | ||
53 | { | ||
54 | auto srcIdx = mapToSource(idx); | ||
55 | auto account = srcIdx.data(Sink::Store::DomainObjectRole).value<SinkAccount::Ptr>(); | ||
56 | switch (role) { | ||
57 | case Name: | ||
58 | return account->getName(); | ||
59 | case Icon: | ||
60 | return account->getIcon(); | ||
61 | case AccountId: | ||
62 | return account->identifier(); | ||
63 | case Status: | ||
64 | switch (account->getStatus()) { | ||
65 | case Sink::ApplicationDomain::ErrorStatus: | ||
66 | return ErrorStatus; | ||
67 | case Sink::ApplicationDomain::BusyStatus: | ||
68 | return BusyStatus; | ||
69 | case Sink::ApplicationDomain::ConnectedStatus: | ||
70 | return ConnectedStatus; | ||
71 | case Sink::ApplicationDomain::OfflineStatus: | ||
72 | return OfflineStatus; | ||
73 | } | ||
74 | return NoStatus; | ||
75 | } | ||
76 | return QIdentityProxyModel::data(idx, role); | ||
77 | } | ||
78 | |||
79 | void AccountsModel::runQuery(const Sink::Query &query) | ||
80 | { | ||
81 | mModel = Sink::Store::loadModel<SinkAccount>(query); | ||
82 | setSourceModel(mModel.data()); | ||
83 | } | ||
84 | |||
85 | void AccountsModel::setAccountId(const QByteArray &accountId) | ||
86 | { | ||
87 | qWarning() << "Setting account id" << accountId; | ||
88 | //Get all folders of an account | ||
89 | Sink::Query query; | ||
90 | query.filter(accountId); | ||
91 | query.setFlags(Query::LiveQuery); | ||
92 | query.request<SinkAccount::Name>(); | ||
93 | query.request<SinkAccount::Icon>(); | ||
94 | query.request<SinkAccount::Status>(); | ||
95 | runQuery(query); | ||
96 | } | ||
97 | |||
98 | QByteArray AccountsModel::accountId() const | ||
99 | { | ||
100 | return {}; | ||
101 | } | ||
diff --git a/framework/src/accounts/accountsmodel.h b/framework/src/accounts/accountsmodel.h new file mode 100644 index 00000000..2c82a8b9 --- /dev/null +++ b/framework/src/accounts/accountsmodel.h | |||
@@ -0,0 +1,67 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | |||
20 | #pragma once | ||
21 | |||
22 | #include <QObject> | ||
23 | #include <QIdentityProxyModel> | ||
24 | #include <QSharedPointer> | ||
25 | #include <QStringList> | ||
26 | |||
27 | namespace Sink { | ||
28 | class Query; | ||
29 | } | ||
30 | |||
31 | class AccountsModel : public QIdentityProxyModel | ||
32 | { | ||
33 | Q_OBJECT | ||
34 | |||
35 | Q_PROPERTY (QByteArray accountId READ accountId WRITE setAccountId) | ||
36 | public: | ||
37 | enum Status { | ||
38 | OfflineStatus, | ||
39 | ConnectedStatus, | ||
40 | BusyStatus, | ||
41 | ErrorStatus, | ||
42 | NoStatus | ||
43 | }; | ||
44 | Q_ENUMS(Status) | ||
45 | |||
46 | AccountsModel(QObject *parent = Q_NULLPTR); | ||
47 | ~AccountsModel(); | ||
48 | |||
49 | QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; | ||
50 | |||
51 | enum Roles { | ||
52 | Name = Qt::UserRole + 1, | ||
53 | Icon, | ||
54 | AccountId, | ||
55 | Status | ||
56 | }; | ||
57 | Q_ENUMS(Roles) | ||
58 | |||
59 | QHash<int, QByteArray> roleNames() const; | ||
60 | |||
61 | void setAccountId(const QByteArray &id); | ||
62 | QByteArray accountId() const; | ||
63 | |||
64 | private: | ||
65 | void runQuery(const Sink::Query &query); | ||
66 | QSharedPointer<QAbstractItemModel> mModel; | ||
67 | }; | ||
diff --git a/framework/src/accounts/gmailcontroller.cpp b/framework/src/accounts/gmailcontroller.cpp new file mode 100644 index 00000000..fda24b26 --- /dev/null +++ b/framework/src/accounts/gmailcontroller.cpp | |||
@@ -0,0 +1,89 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2017 Michael Bohlender, <michael.bohlender@kdemail.net> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; either version 2 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License along | ||
15 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
17 | */ | ||
18 | |||
19 | #include "gmailcontroller.h" | ||
20 | |||
21 | #include <sink/store.h> | ||
22 | |||
23 | using namespace Sink; | ||
24 | using namespace Sink::ApplicationDomain; | ||
25 | |||
26 | GmailController::GmailController() : Kube::Controller(), | ||
27 | action_create{new Kube::ControllerAction{this, &GmailController::create}}, | ||
28 | action_modify{new Kube::ControllerAction{this, &GmailController::modify}}, | ||
29 | action_remove{new Kube::ControllerAction{this, &GmailController::remove}} | ||
30 | { | ||
31 | |||
32 | } | ||
33 | |||
34 | void GmailController::create() { | ||
35 | |||
36 | //account | ||
37 | auto account = ApplicationDomainType::createEntity<SinkAccount>(); | ||
38 | account.setProperty("type", "imap"); | ||
39 | account.setProperty("name", getName()); | ||
40 | Store::create(account).exec().waitForFinished(); | ||
41 | |||
42 | //imap | ||
43 | auto resource = ApplicationDomainType::createEntity<SinkResource>(); | ||
44 | resource.setResourceType("sink.imap"); | ||
45 | resource.setAccount(account); | ||
46 | resource.setProperty("server","imaps://imap.gmail.com:993"); | ||
47 | resource.setProperty("username", getEmailAddress()); | ||
48 | resource.setProperty("password", getPassword()); | ||
49 | Store::create(resource).exec().waitForFinished(); | ||
50 | |||
51 | //smtp | ||
52 | resource = ApplicationDomainType::createEntity<SinkResource>(); | ||
53 | resource.setResourceType("sink.mailtransport"); | ||
54 | resource.setAccount(account); | ||
55 | resource.setProperty("server", "smtps://smtp.gmail.com:465"); | ||
56 | resource.setProperty("username", getEmailAddress()); | ||
57 | resource.setProperty("password", getPassword()); | ||
58 | Store::create(resource).exec().waitForFinished(); | ||
59 | |||
60 | //identity | ||
61 | auto identity = ApplicationDomainType::createEntity<Identity>(); | ||
62 | m_identityId = identity.identifier(); | ||
63 | identity.setAccount(account); | ||
64 | identity.setName(getIdentityName()); | ||
65 | identity.setAddress(getEmailAddress()); | ||
66 | Store::create(identity).exec(); | ||
67 | } | ||
68 | |||
69 | void GmailController::modify() { | ||
70 | //TODO | ||
71 | } | ||
72 | |||
73 | void GmailController::remove() { | ||
74 | SinkAccount account(m_accountId); | ||
75 | Store::remove(account).exec(); | ||
76 | } | ||
77 | |||
78 | void GmailController::load(const QByteArray &id) { | ||
79 | |||
80 | m_accountId = id; | ||
81 | |||
82 | Store::fetchOne<SinkAccount>(Query().filter(m_accountId)) | ||
83 | .then([this](const SinkAccount &account) { | ||
84 | setName(account.getName()); | ||
85 | }).exec(); | ||
86 | |||
87 | //TODO | ||
88 | } | ||
89 | |||
diff --git a/framework/src/accounts/gmailcontroller.h b/framework/src/accounts/gmailcontroller.h new file mode 100644 index 00000000..2a5ead65 --- /dev/null +++ b/framework/src/accounts/gmailcontroller.h | |||
@@ -0,0 +1,55 @@ | |||
1 | /* | ||
2 | Copyright (C) 2017 Michael Bohlender, <michael.bohlender@kdemail.net> | ||
3 | |||
4 | This program is free software; you can redistribute it and/or modify | ||
5 | it under the terms of the GNU General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or | ||
7 | (at your option) any later version. | ||
8 | |||
9 | This program is distributed in the hope that it will be useful, | ||
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | GNU General Public License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU General Public License along | ||
15 | with this program; if not, write to the Free Software Foundation, Inc., | ||
16 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
17 | */ | ||
18 | |||
19 | #pragma once | ||
20 | |||
21 | #include <QObject> | ||
22 | #include <QString> | ||
23 | #include <QByteArray> | ||
24 | |||
25 | #include <domain/controller.h> | ||
26 | |||
27 | class GmailController : public Kube::Controller | ||
28 | { | ||
29 | Q_OBJECT | ||
30 | |||
31 | //Interface properties | ||
32 | KUBE_CONTROLLER_PROPERTY(QString, Name, name) | ||
33 | |||
34 | KUBE_CONTROLLER_PROPERTY(QString, EmailAddress, emailAddress) | ||
35 | KUBE_CONTROLLER_PROPERTY(QString, Password, password) | ||
36 | KUBE_CONTROLLER_PROPERTY(QString, IdentityName, identityName) | ||
37 | |||
38 | //Actions | ||
39 | KUBE_CONTROLLER_ACTION(create) | ||
40 | KUBE_CONTROLLER_ACTION(modify) | ||
41 | KUBE_CONTROLLER_ACTION(remove) | ||
42 | |||
43 | public: | ||
44 | explicit GmailController(); | ||
45 | |||
46 | public slots: | ||
47 | void load(const QByteArray &id); | ||
48 | |||
49 | private: | ||
50 | QByteArray m_accountId; | ||
51 | QByteArray m_smtpId; | ||
52 | QByteArray m_imapId; | ||
53 | QByteArray m_identityId; | ||
54 | }; | ||
55 | |||
diff --git a/framework/src/actions/action.cpp b/framework/src/actions/action.cpp new file mode 100644 index 00000000..1344d112 --- /dev/null +++ b/framework/src/actions/action.cpp | |||
@@ -0,0 +1,127 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #include "action.h" | ||
20 | |||
21 | #include <QDebug> | ||
22 | #include <QEvent> | ||
23 | #include <QPointer> | ||
24 | #include <QDynamicPropertyChangeEvent> | ||
25 | #include <QMetaObject> | ||
26 | #include <QMetaProperty> | ||
27 | |||
28 | #include "actionbroker.h" | ||
29 | #include "actionhandler.h" | ||
30 | #include "context.h" | ||
31 | |||
32 | using namespace Kube; | ||
33 | |||
34 | Action::Action(QObject *parent) | ||
35 | : QObject(parent), | ||
36 | mContext(nullptr) | ||
37 | { | ||
38 | } | ||
39 | |||
40 | Action::Action(const QByteArray &actionId, Context &context, QObject *parent) | ||
41 | : QObject(parent), | ||
42 | mContext(&context), | ||
43 | mActionId(actionId) | ||
44 | { | ||
45 | setContext(&context); | ||
46 | } | ||
47 | |||
48 | Action::~Action() | ||
49 | { | ||
50 | } | ||
51 | |||
52 | void Action::setContext(Context *context) | ||
53 | { | ||
54 | //Get notified when any property changes | ||
55 | for (int i = context->metaObject()->propertyOffset(); i < context->metaObject()->propertyCount(); i++) { | ||
56 | auto property = context->metaObject()->property(i) ; | ||
57 | // qWarning() << "Property " << property.name() << property.hasNotifySignal() << property.notifySignal().name(); | ||
58 | if (QString(property.name()) != "objectName") { | ||
59 | //We do what SIGNAL does to connect to the changed signal automatically | ||
60 | QObject::connect(context, "2"+property.notifySignal().name()+"()", this, SLOT(contextChanged())); | ||
61 | } | ||
62 | } | ||
63 | mContext = context; | ||
64 | mContext->installEventFilter(this); | ||
65 | emit readyChanged(); | ||
66 | } | ||
67 | |||
68 | bool Action::eventFilter(QObject *obj, QEvent *e) | ||
69 | { | ||
70 | if (obj == mContext) { | ||
71 | if (e->type() == QEvent::DynamicPropertyChange) { | ||
72 | contextChanged(); | ||
73 | } | ||
74 | } | ||
75 | return QObject::eventFilter(obj, e); | ||
76 | } | ||
77 | |||
78 | void Action::contextChanged() | ||
79 | { | ||
80 | emit readyChanged(); | ||
81 | } | ||
82 | |||
83 | Context *Action::context() const | ||
84 | { | ||
85 | return mContext; | ||
86 | } | ||
87 | |||
88 | void Action::setActionId(const QByteArray &actionId) | ||
89 | { | ||
90 | mActionId = actionId; | ||
91 | emit readyChanged(); | ||
92 | } | ||
93 | |||
94 | QByteArray Action::actionId() const | ||
95 | { | ||
96 | return mActionId; | ||
97 | } | ||
98 | |||
99 | bool Action::ready() const | ||
100 | { | ||
101 | return ActionBroker::instance().isActionReady(mActionId, mContext, mPreHandler); | ||
102 | } | ||
103 | |||
104 | void Action::execute() | ||
105 | { | ||
106 | ActionBroker::instance().executeAction(mActionId, mContext, mPreHandler, mPostHandler); | ||
107 | } | ||
108 | |||
109 | ActionResult Action::executeWithResult() | ||
110 | { | ||
111 | return ActionBroker::instance().executeAction(mActionId, mContext, mPreHandler, mPostHandler); | ||
112 | } | ||
113 | |||
114 | void Action::addPreHandler(ActionHandler *handler) | ||
115 | { | ||
116 | //For cleanup | ||
117 | handler->setParent(this); | ||
118 | mPreHandler << handler; | ||
119 | } | ||
120 | |||
121 | void Action::addPostHandler(ActionHandler *handler) | ||
122 | { | ||
123 | //For cleanup | ||
124 | handler->setParent(this); | ||
125 | mPostHandler << handler; | ||
126 | } | ||
127 | |||
diff --git a/framework/src/actions/action.h b/framework/src/actions/action.h new file mode 100644 index 00000000..dda8c987 --- /dev/null +++ b/framework/src/actions/action.h | |||
@@ -0,0 +1,71 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #pragma once | ||
20 | |||
21 | #include <QObject> | ||
22 | #include "context.h" | ||
23 | #include "actionresult.h" | ||
24 | |||
25 | namespace Kube { | ||
26 | |||
27 | class ActionHandler; | ||
28 | |||
29 | class Action : public QObject | ||
30 | { | ||
31 | Q_OBJECT | ||
32 | Q_PROPERTY(QByteArray actionId READ actionId WRITE setActionId) | ||
33 | //FIXME if I set the property to Context* qml fails to assign the registered type which is calle Kube::Context_QML_90 in QML... | ||
34 | Q_PROPERTY(Context* context READ context WRITE setContext) | ||
35 | Q_PROPERTY(bool ready READ ready NOTIFY readyChanged) | ||
36 | |||
37 | public: | ||
38 | Action(QObject *parent = 0); | ||
39 | Action(const QByteArray &actionId, Context &context, QObject *parent = 0); | ||
40 | ~Action(); | ||
41 | |||
42 | void setContext(Context *); | ||
43 | Context *context() const; | ||
44 | |||
45 | void setActionId(const QByteArray &); | ||
46 | QByteArray actionId() const; | ||
47 | |||
48 | bool ready() const; | ||
49 | |||
50 | Q_INVOKABLE void execute(); | ||
51 | ActionResult executeWithResult(); | ||
52 | |||
53 | void addPreHandler(ActionHandler *handler); | ||
54 | void addPostHandler(ActionHandler *handler); | ||
55 | |||
56 | bool eventFilter(QObject *obj, QEvent *e) Q_DECL_OVERRIDE; | ||
57 | |||
58 | Q_SIGNALS: | ||
59 | void readyChanged(); | ||
60 | |||
61 | private Q_SLOTS: | ||
62 | void contextChanged(); | ||
63 | |||
64 | private: | ||
65 | Context *mContext; | ||
66 | QByteArray mActionId; | ||
67 | QList<QPointer<ActionHandler>> mPreHandler; | ||
68 | QList<QPointer<ActionHandler>> mPostHandler; | ||
69 | }; | ||
70 | |||
71 | } | ||
diff --git a/framework/src/actions/actionbroker.cpp b/framework/src/actions/actionbroker.cpp new file mode 100644 index 00000000..f6bfdd8e --- /dev/null +++ b/framework/src/actions/actionbroker.cpp | |||
@@ -0,0 +1,101 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | |||
20 | #include "actionbroker.h" | ||
21 | |||
22 | #include "context.h" | ||
23 | #include "actionhandler.h" | ||
24 | #include <sink/log.h> | ||
25 | |||
26 | #include <QDebug> | ||
27 | |||
28 | using namespace Kube; | ||
29 | |||
30 | SINK_DEBUG_AREA("actionbroker") | ||
31 | |||
32 | ActionBroker::ActionBroker(QObject *parent) | ||
33 | : QObject(parent) | ||
34 | { | ||
35 | |||
36 | } | ||
37 | |||
38 | ActionBroker &ActionBroker::instance() | ||
39 | { | ||
40 | static ActionBroker instance; | ||
41 | return instance; | ||
42 | } | ||
43 | |||
44 | bool ActionBroker::isActionReady(const QByteArray &actionId, Context *context, const QList<QPointer<ActionHandler>> &preHandler) | ||
45 | { | ||
46 | if (!context) { | ||
47 | return false; | ||
48 | } | ||
49 | for (const auto handler : preHandler) { | ||
50 | if (!handler->isActionReady(context)) { | ||
51 | return false; | ||
52 | } | ||
53 | } | ||
54 | |||
55 | for (const auto handler : mHandler.values(actionId)) { | ||
56 | if (handler) { | ||
57 | if (handler->isActionReady(context)) { | ||
58 | return true; | ||
59 | } | ||
60 | } | ||
61 | } | ||
62 | |||
63 | return false; | ||
64 | } | ||
65 | |||
66 | ActionResult ActionBroker::executeAction(const QByteArray &actionId, Context *context, const QList<QPointer<ActionHandler>> &preHandler, const QList<QPointer<ActionHandler>> &postHandler) | ||
67 | { | ||
68 | ActionResult result; | ||
69 | if (context) { | ||
70 | SinkLog() << "Executing action " << actionId; | ||
71 | SinkLog() << *context; | ||
72 | for (const auto handler : preHandler) { | ||
73 | handler->execute(context); | ||
74 | } | ||
75 | //TODO the main handler should only execute once the pre handler is done | ||
76 | for (const auto handler : mHandler.values(actionId)) { | ||
77 | if (handler) { | ||
78 | result += handler->execute(context); | ||
79 | } | ||
80 | } | ||
81 | //TODO the post handler should only execute once the main handler is done | ||
82 | for (const auto handler : postHandler) { | ||
83 | handler->execute(context); | ||
84 | } | ||
85 | } else { | ||
86 | SinkWarning() << "Can't execute without context"; | ||
87 | result.setDone(); | ||
88 | result.setError(1); | ||
89 | } | ||
90 | return result; | ||
91 | } | ||
92 | |||
93 | void ActionBroker::registerHandler(const QByteArray &actionId, ActionHandler *handler) | ||
94 | { | ||
95 | mHandler.insert(actionId, handler); | ||
96 | } | ||
97 | |||
98 | void ActionBroker::unregisterHandler(const QByteArray &actionId, ActionHandler *handler) | ||
99 | { | ||
100 | mHandler.remove(actionId, handler); | ||
101 | } | ||
diff --git a/framework/src/actions/actionbroker.h b/framework/src/actions/actionbroker.h new file mode 100644 index 00000000..d893a3e7 --- /dev/null +++ b/framework/src/actions/actionbroker.h | |||
@@ -0,0 +1,49 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #pragma once | ||
20 | |||
21 | #include <QObject> | ||
22 | #include <QMultiMap> | ||
23 | |||
24 | namespace Kube { | ||
25 | class Context; | ||
26 | class ActionHandler; | ||
27 | class ActionResult; | ||
28 | |||
29 | class ActionBroker : public QObject | ||
30 | { | ||
31 | Q_OBJECT | ||
32 | public: | ||
33 | static ActionBroker &instance(); | ||
34 | |||
35 | bool isActionReady(const QByteArray &actionId, Context *context, const QList<QPointer<ActionHandler>> &preHandler); | ||
36 | ActionResult executeAction(const QByteArray &actionId, Context *context, const QList<QPointer<ActionHandler>> &preHandler, const QList<QPointer<ActionHandler>> &postHandler); | ||
37 | |||
38 | void registerHandler(const QByteArray &actionId, ActionHandler *handler); | ||
39 | void unregisterHandler(const QByteArray &actionId, ActionHandler *handler); | ||
40 | |||
41 | Q_SIGNALS: | ||
42 | void readyChanged(); | ||
43 | |||
44 | private: | ||
45 | ActionBroker(QObject *parent = 0); | ||
46 | QMultiMap<QByteArray, QPointer<ActionHandler>> mHandler; | ||
47 | }; | ||
48 | |||
49 | } | ||
diff --git a/framework/src/actions/actionhandler.cpp b/framework/src/actions/actionhandler.cpp new file mode 100644 index 00000000..99fdf66a --- /dev/null +++ b/framework/src/actions/actionhandler.cpp | |||
@@ -0,0 +1,151 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | |||
20 | #include "actionhandler.h" | ||
21 | |||
22 | #include "context.h" | ||
23 | #include "actionbroker.h" | ||
24 | #include <QDebug> | ||
25 | |||
26 | using namespace Kube; | ||
27 | |||
28 | ActionHandler::ActionHandler(QObject *parent) | ||
29 | : QObject(parent) | ||
30 | { | ||
31 | |||
32 | } | ||
33 | |||
34 | ActionHandler::~ActionHandler() | ||
35 | { | ||
36 | ActionBroker::instance().unregisterHandler(mActionId, this); | ||
37 | } | ||
38 | |||
39 | bool ActionHandler::isActionReady(Context *context) | ||
40 | { | ||
41 | if (context) { | ||
42 | QVariant returnedValue; | ||
43 | QMetaObject::invokeMethod(this, "isReady", | ||
44 | Q_RETURN_ARG(QVariant, returnedValue), | ||
45 | Q_ARG(QVariant, QVariant::fromValue(context))); | ||
46 | return returnedValue.toBool(); | ||
47 | } else { | ||
48 | qWarning() << "The handler didn't get a context"; | ||
49 | } | ||
50 | return false; | ||
51 | } | ||
52 | |||
53 | ActionResult ActionHandler::execute(Context *context) | ||
54 | { | ||
55 | ActionResult result; | ||
56 | QVariant returnedValue; | ||
57 | qWarning() << "Executing the handler"; | ||
58 | if (context) { | ||
59 | //The base implementation to call the handler in QML | ||
60 | QMetaObject::invokeMethod(this, "handler", | ||
61 | Q_RETURN_ARG(QVariant, returnedValue), | ||
62 | Q_ARG(QVariant, QVariant::fromValue(context))); | ||
63 | //TODO: support async handlers in QML | ||
64 | result.setDone(); | ||
65 | } else { | ||
66 | qWarning() << "The handler didn't get a context"; | ||
67 | result.setDone(); | ||
68 | result.setError(1); | ||
69 | } | ||
70 | return result; | ||
71 | } | ||
72 | |||
73 | void ActionHandler::setActionId(const QByteArray &actionId) | ||
74 | { | ||
75 | //Reassigning the id is not supported | ||
76 | Q_ASSERT(mActionId.isEmpty()); | ||
77 | mActionId = actionId; | ||
78 | ActionBroker::instance().registerHandler(actionId, this); | ||
79 | } | ||
80 | |||
81 | QByteArray ActionHandler::actionId() const | ||
82 | { | ||
83 | return mActionId; | ||
84 | } | ||
85 | |||
86 | void ActionHandler::setRequiredProperties(const QSet<QByteArray> &requiredProperties) | ||
87 | { | ||
88 | mRequiredProperties = requiredProperties; | ||
89 | } | ||
90 | |||
91 | QSet<QByteArray> ActionHandler::requiredProperties() const | ||
92 | { | ||
93 | return mRequiredProperties; | ||
94 | } | ||
95 | |||
96 | |||
97 | ActionHandlerHelper::ActionHandlerHelper(const Handler &handler) | ||
98 | : ActionHandler(nullptr), | ||
99 | handlerFunction(handler) | ||
100 | { | ||
101 | } | ||
102 | |||
103 | ActionHandlerHelper::ActionHandlerHelper(const IsReadyFunction &isReady, const Handler &handler) | ||
104 | : ActionHandler(nullptr), | ||
105 | isReadyFunction(isReady), | ||
106 | handlerFunction(handler) | ||
107 | { | ||
108 | } | ||
109 | |||
110 | ActionHandlerHelper::ActionHandlerHelper(const QByteArray &actionId, const IsReadyFunction &isReady, const Handler &handler) | ||
111 | : ActionHandler(nullptr), | ||
112 | isReadyFunction(isReady), | ||
113 | handlerFunction(handler) | ||
114 | { | ||
115 | setActionId(actionId); | ||
116 | } | ||
117 | |||
118 | ActionHandlerHelper::ActionHandlerHelper(const QByteArray &actionId, const IsReadyFunction &isReady, const JobHandler &handler) | ||
119 | : ActionHandler(nullptr), | ||
120 | isReadyFunction(isReady), | ||
121 | jobHandlerFunction(handler) | ||
122 | { | ||
123 | setActionId(actionId); | ||
124 | } | ||
125 | |||
126 | bool ActionHandlerHelper::isActionReady(Context *context) | ||
127 | { | ||
128 | if (isReadyFunction) { | ||
129 | return isReadyFunction(context); | ||
130 | } | ||
131 | return true; | ||
132 | } | ||
133 | |||
134 | ActionResult ActionHandlerHelper::execute(Context *context) | ||
135 | { | ||
136 | ActionResult result; | ||
137 | if (handlerFunction) { | ||
138 | handlerFunction(context); | ||
139 | result.setDone(); | ||
140 | } else { | ||
141 | jobHandlerFunction(context).then([=](const KAsync::Error &error) { | ||
142 | auto modifyableResult = result; | ||
143 | if (error) { | ||
144 | qWarning() << "Job failed: " << error.errorCode << error.errorMessage; | ||
145 | modifyableResult.setError(1); | ||
146 | } | ||
147 | modifyableResult.setDone(); | ||
148 | }).exec(); | ||
149 | } | ||
150 | return result; | ||
151 | } | ||
diff --git a/framework/src/actions/actionhandler.h b/framework/src/actions/actionhandler.h new file mode 100644 index 00000000..fbaedad1 --- /dev/null +++ b/framework/src/actions/actionhandler.h | |||
@@ -0,0 +1,112 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #pragma once | ||
20 | |||
21 | #include <QObject> | ||
22 | #include <QMultiMap> | ||
23 | #include <functional> | ||
24 | #include <KAsync/Async> | ||
25 | |||
26 | #include "actionresult.h" | ||
27 | #include "context.h" | ||
28 | |||
29 | namespace Kube { | ||
30 | |||
31 | class ActionHandler : public QObject | ||
32 | { | ||
33 | Q_OBJECT | ||
34 | Q_PROPERTY(QByteArray actionId READ actionId WRITE setActionId) | ||
35 | |||
36 | public: | ||
37 | ActionHandler(QObject *parent = 0); | ||
38 | virtual ~ActionHandler(); | ||
39 | |||
40 | virtual bool isActionReady(Context *context); | ||
41 | |||
42 | virtual ActionResult execute(Context *context); | ||
43 | |||
44 | void setActionId(const QByteArray &); | ||
45 | QByteArray actionId() const; | ||
46 | |||
47 | void setRequiredProperties(const QSet<QByteArray> &requiredProperties); | ||
48 | QSet<QByteArray> requiredProperties() const; | ||
49 | |||
50 | private: | ||
51 | QByteArray mActionId; | ||
52 | QSet<QByteArray> mRequiredProperties; | ||
53 | }; | ||
54 | |||
55 | template <typename ContextType> | ||
56 | class ActionHandlerBase : public ActionHandler | ||
57 | { | ||
58 | public: | ||
59 | ActionHandlerBase(const QByteArray &actionId) | ||
60 | : ActionHandler{} | ||
61 | { | ||
62 | setActionId(actionId); | ||
63 | } | ||
64 | |||
65 | bool isActionReady(Context *c) Q_DECL_OVERRIDE | ||
66 | { | ||
67 | auto wrapper = ContextType{*c}; | ||
68 | return isActionReady(wrapper); | ||
69 | } | ||
70 | |||
71 | ActionResult execute(Context *c) Q_DECL_OVERRIDE | ||
72 | { | ||
73 | ActionResult result; | ||
74 | auto wrapper = ContextType{*c}; | ||
75 | execute(wrapper) | ||
76 | .template then([=](const KAsync::Error &error) { | ||
77 | auto modifyableResult = result; | ||
78 | if (error) { | ||
79 | qWarning() << "Job failed: " << error.errorCode << error.errorMessage; | ||
80 | modifyableResult.setError(1); | ||
81 | } | ||
82 | modifyableResult.setDone(); | ||
83 | }).exec(); | ||
84 | return result; | ||
85 | } | ||
86 | protected: | ||
87 | |||
88 | virtual bool isActionReady(ContextType &) { return true; } | ||
89 | virtual KAsync::Job<void> execute(ContextType &) = 0; | ||
90 | }; | ||
91 | |||
92 | class ActionHandlerHelper : public ActionHandler | ||
93 | { | ||
94 | public: | ||
95 | typedef std::function<bool(Context *)> IsReadyFunction; | ||
96 | typedef std::function<void(Context *)> Handler; | ||
97 | typedef std::function<KAsync::Job<void>(Context *)> JobHandler; | ||
98 | |||
99 | ActionHandlerHelper(const Handler &); | ||
100 | ActionHandlerHelper(const IsReadyFunction &, const Handler &); | ||
101 | ActionHandlerHelper(const QByteArray &actionId, const IsReadyFunction &, const Handler &); | ||
102 | ActionHandlerHelper(const QByteArray &actionId, const IsReadyFunction &, const JobHandler &); | ||
103 | |||
104 | bool isActionReady(Context *) Q_DECL_OVERRIDE; | ||
105 | ActionResult execute(Context *) Q_DECL_OVERRIDE; | ||
106 | private: | ||
107 | const IsReadyFunction isReadyFunction; | ||
108 | const Handler handlerFunction; | ||
109 | const JobHandler jobHandlerFunction; | ||
110 | }; | ||
111 | |||
112 | } | ||
diff --git a/framework/src/actions/actionresult.cpp b/framework/src/actions/actionresult.cpp new file mode 100644 index 00000000..631c61c4 --- /dev/null +++ b/framework/src/actions/actionresult.cpp | |||
@@ -0,0 +1,20 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #include "actionresult.h" | ||
20 | |||
diff --git a/framework/src/actions/actionresult.h b/framework/src/actions/actionresult.h new file mode 100644 index 00000000..dcf1a9ec --- /dev/null +++ b/framework/src/actions/actionresult.h | |||
@@ -0,0 +1,80 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #pragma once | ||
20 | |||
21 | #include <QObject> | ||
22 | #include <QSharedPointer> | ||
23 | |||
24 | namespace Kube { | ||
25 | |||
26 | struct ActionResultData | ||
27 | { | ||
28 | ActionResultData() : mError(0), mDone(false) {} | ||
29 | int mError; | ||
30 | bool mDone; | ||
31 | }; | ||
32 | |||
33 | class ActionResult : public QObject | ||
34 | { | ||
35 | Q_OBJECT | ||
36 | public: | ||
37 | ActionResult() : QObject(), mData(new ActionResultData()) {} | ||
38 | ActionResult(const ActionResult &rhs) : QObject(), mData(rhs.mData) {} | ||
39 | ActionResult &operator=(const ActionResult &rhs) | ||
40 | { | ||
41 | mData = rhs.mData; | ||
42 | return *this; | ||
43 | } | ||
44 | virtual ~ActionResult() {} | ||
45 | |||
46 | ActionResult &operator+=(const ActionResult &rhs) | ||
47 | { | ||
48 | if (!error() && rhs.error()) { | ||
49 | setError(rhs.error()); | ||
50 | } | ||
51 | if (isDone() && rhs.isDone()) { | ||
52 | mData->mDone = false; | ||
53 | } | ||
54 | mData = rhs.mData; | ||
55 | return *this; | ||
56 | } | ||
57 | |||
58 | void setDone() { | ||
59 | mData->mDone = true; | ||
60 | } | ||
61 | |||
62 | bool isDone() const { | ||
63 | return mData->mDone; | ||
64 | } | ||
65 | |||
66 | void setError(int error) { | ||
67 | mData->mError = error; | ||
68 | } | ||
69 | |||
70 | int error() const { | ||
71 | return mData->mError; | ||
72 | } | ||
73 | |||
74 | private: | ||
75 | QSharedPointer<ActionResultData> mData; | ||
76 | }; | ||
77 | |||
78 | } | ||
79 | |||
80 | Q_DECLARE_METATYPE(Kube::ActionResult); | ||
diff --git a/framework/src/actions/context.cpp b/framework/src/actions/context.cpp new file mode 100644 index 00000000..45b660a9 --- /dev/null +++ b/framework/src/actions/context.cpp | |||
@@ -0,0 +1,91 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #include "context.h" | ||
20 | |||
21 | #include <QDebug> | ||
22 | #include <QMetaProperty> | ||
23 | |||
24 | using namespace Kube; | ||
25 | |||
26 | Context::Context(QObject *parent) | ||
27 | : QObject(parent) | ||
28 | { | ||
29 | |||
30 | } | ||
31 | |||
32 | Context::Context(const Context &other) | ||
33 | : QObject() | ||
34 | { | ||
35 | *this = other; | ||
36 | } | ||
37 | |||
38 | Context &Context::operator=(const Context &other) | ||
39 | { | ||
40 | for (const auto &p : other.availableProperties()) { | ||
41 | setProperty(p, other.property(p)); | ||
42 | } | ||
43 | return *this; | ||
44 | } | ||
45 | |||
46 | void Context::clear() | ||
47 | { | ||
48 | auto meta = metaObject(); | ||
49 | for (auto i = meta->propertyOffset(); i < meta->propertyCount(); i++) { | ||
50 | auto property = meta->property(i); | ||
51 | setProperty(property.name(), QVariant()); | ||
52 | } | ||
53 | for (const auto &p : dynamicPropertyNames()) { | ||
54 | setProperty(p, QVariant()); | ||
55 | } | ||
56 | } | ||
57 | |||
58 | QSet<QByteArray> Context::availableProperties() const | ||
59 | { | ||
60 | QSet<QByteArray> names; | ||
61 | auto meta = metaObject(); | ||
62 | for (auto i = meta->propertyOffset(); i < meta->propertyCount(); i++) { | ||
63 | auto property = meta->property(i); | ||
64 | names << property.name(); | ||
65 | } | ||
66 | for (const auto &p : dynamicPropertyNames()) { | ||
67 | names << p; | ||
68 | } | ||
69 | return names; | ||
70 | } | ||
71 | |||
72 | QDebug operator<<(QDebug dbg, const Kube::Context &context) | ||
73 | { | ||
74 | dbg << "Kube::Context {\n"; | ||
75 | auto metaObject = context.metaObject(); | ||
76 | for (auto i = metaObject->propertyOffset(); i < metaObject->propertyCount(); i++) { | ||
77 | auto property = metaObject->property(i); | ||
78 | dbg << property.name() << context.property(property.name()) << "\n"; | ||
79 | } | ||
80 | for (const auto &p : context.dynamicPropertyNames()) { | ||
81 | dbg << p << context.property(p) << "\n"; | ||
82 | } | ||
83 | dbg << "\n}"; | ||
84 | return dbg; | ||
85 | } | ||
86 | |||
87 | QDebug operator<<(QDebug dbg, const Kube::ContextWrapper &context) | ||
88 | { | ||
89 | dbg << context.context; | ||
90 | return dbg; | ||
91 | } | ||
diff --git a/framework/src/actions/context.h b/framework/src/actions/context.h new file mode 100644 index 00000000..52fbdbc1 --- /dev/null +++ b/framework/src/actions/context.h | |||
@@ -0,0 +1,67 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #pragma once | ||
20 | |||
21 | #include <QObject> | ||
22 | #define KUBE_CONTEXT_PROPERTY(TYPE, NAME, LOWERCASENAME) \ | ||
23 | public: Q_PROPERTY(TYPE LOWERCASENAME MEMBER m##NAME NOTIFY LOWERCASENAME##Changed) \ | ||
24 | Q_SIGNALS: void LOWERCASENAME##Changed(); \ | ||
25 | private: TYPE m##NAME; | ||
26 | |||
27 | #define KUBE_CONTEXTWRAPPER_PROPERTY(TYPE, NAME, LOWERCASENAME) \ | ||
28 | public: \ | ||
29 | struct NAME { \ | ||
30 | static constexpr const char *name = #LOWERCASENAME; \ | ||
31 | typedef TYPE Type; \ | ||
32 | }; \ | ||
33 | void set##NAME(const TYPE &value) { context.setProperty(NAME::name, QVariant::fromValue(value)); } \ | ||
34 | void clear##NAME() { context.setProperty(NAME::name, QVariant{}); } \ | ||
35 | TYPE get##NAME() const { return context.property(NAME::name).value<TYPE>(); } \ | ||
36 | |||
37 | |||
38 | namespace Kube { | ||
39 | |||
40 | class Context : public QObject { | ||
41 | Q_OBJECT | ||
42 | public: | ||
43 | Context(QObject *parent = 0); | ||
44 | Context(const Context &); | ||
45 | |||
46 | virtual ~Context(){}; | ||
47 | |||
48 | Context &operator=(const Context &); | ||
49 | |||
50 | virtual void clear(); | ||
51 | |||
52 | QSet<QByteArray> availableProperties() const; | ||
53 | }; | ||
54 | |||
55 | class ContextWrapper { | ||
56 | public: | ||
57 | ContextWrapper(Context &c) : context(c) {} | ||
58 | Context &context; | ||
59 | }; | ||
60 | |||
61 | } | ||
62 | |||
63 | QDebug operator<<(QDebug dbg, const Kube::Context &); | ||
64 | QDebug operator<<(QDebug dbg, const Kube::ContextWrapper &); | ||
65 | |||
66 | Q_DECLARE_METATYPE(Kube::Context*); | ||
67 | |||
diff --git a/framework/src/actions/tests/CMakeLists.txt b/framework/src/actions/tests/CMakeLists.txt new file mode 100644 index 00000000..af872a3b --- /dev/null +++ b/framework/src/actions/tests/CMakeLists.txt | |||
@@ -0,0 +1,6 @@ | |||
1 | include_directories(${CMAKE_CURRENT_BINARY_DIR}) | ||
2 | cmake_policy(SET CMP0063 NEW) | ||
3 | add_executable(actiontest actiontest.cpp) | ||
4 | add_test(actiontest sinkactiontest) | ||
5 | qt5_use_modules(actiontest Core Test) | ||
6 | target_link_libraries(actiontest actionplugin) | ||
diff --git a/framework/src/actions/tests/actiontest.cpp b/framework/src/actions/tests/actiontest.cpp new file mode 100644 index 00000000..a4ec4432 --- /dev/null +++ b/framework/src/actions/tests/actiontest.cpp | |||
@@ -0,0 +1,102 @@ | |||
1 | #include <QTest> | ||
2 | #include <QDebug> | ||
3 | #include <QSignalSpy> | ||
4 | |||
5 | #include <actions/action.h> | ||
6 | #include <actions/context.h> | ||
7 | #include <actions/actionhandler.h> | ||
8 | |||
9 | #include <sink/log.h> | ||
10 | |||
11 | SINK_DEBUG_AREA("actiontest") | ||
12 | |||
13 | class HandlerContext : public Kube::Context { | ||
14 | Q_OBJECT | ||
15 | KUBE_CONTEXT_PROPERTY(QString, Property1, property1) | ||
16 | KUBE_CONTEXT_PROPERTY(QString, Property2, property2) | ||
17 | }; | ||
18 | |||
19 | class HandlerContextWrapper : public Kube::ContextWrapper { | ||
20 | using Kube::ContextWrapper::ContextWrapper; | ||
21 | KUBE_CONTEXTWRAPPER_PROPERTY(QString, Property1, property1) | ||
22 | KUBE_CONTEXTWRAPPER_PROPERTY(QString, Property2, property2) | ||
23 | }; | ||
24 | |||
25 | |||
26 | |||
27 | class Handler : public Kube::ActionHandlerBase<HandlerContextWrapper> | ||
28 | { | ||
29 | public: | ||
30 | Handler() : Kube::ActionHandlerBase<HandlerContextWrapper>{"org.kde.kube.test.action1"} | ||
31 | {} | ||
32 | |||
33 | //TODO default implementation checks that all defined properties are available in the context | ||
34 | // bool isReady() override { | ||
35 | // auto accountId = context->property("accountId").value<QByteArray>(); | ||
36 | // return !accountId.isEmpty(); | ||
37 | // } | ||
38 | |||
39 | KAsync::Job<void> execute(HandlerContextWrapper &context) | ||
40 | { | ||
41 | SinkLog() << "Executing action1"; | ||
42 | SinkLog() << context; | ||
43 | executions.append(context.context); | ||
44 | return KAsync::null<void>(); | ||
45 | } | ||
46 | mutable QList<Kube::Context> executions; | ||
47 | }; | ||
48 | |||
49 | class Context1 : public Kube::ContextWrapper { | ||
50 | using Kube::ContextWrapper::ContextWrapper; | ||
51 | KUBE_CONTEXTWRAPPER_PROPERTY(QString, Property1, property1) | ||
52 | KUBE_CONTEXTWRAPPER_PROPERTY(QByteArray, Property2, property2) | ||
53 | }; | ||
54 | |||
55 | class Context2 : public Kube::ContextWrapper { | ||
56 | using Kube::ContextWrapper::ContextWrapper; | ||
57 | KUBE_CONTEXTWRAPPER_PROPERTY(QByteArray, Property2, property2) | ||
58 | }; | ||
59 | |||
60 | |||
61 | class ActionTest : public QObject | ||
62 | { | ||
63 | Q_OBJECT | ||
64 | private slots: | ||
65 | |||
66 | void initTestCase() | ||
67 | { | ||
68 | } | ||
69 | |||
70 | void testActionExecution() | ||
71 | { | ||
72 | Handler actionHandler; | ||
73 | |||
74 | HandlerContext context; | ||
75 | //Kube::Context context; | ||
76 | HandlerContextWrapper{context}.setProperty1(QString("property1")); | ||
77 | context.setProperty("property2", QVariant::fromValue(QString("property2"))); | ||
78 | auto future = Kube::Action("org.kde.kube.test.action1", context).executeWithResult(); | ||
79 | |||
80 | QTRY_VERIFY(future.isDone()); | ||
81 | QVERIFY(!future.error()); | ||
82 | |||
83 | QCOMPARE(actionHandler.executions.size(), 1); | ||
84 | QCOMPARE(actionHandler.executions.first().availableProperties().size(), 2); | ||
85 | } | ||
86 | |||
87 | void testContextCasting() | ||
88 | { | ||
89 | Kube::Context c; | ||
90 | |||
91 | Context1 context1{c}; | ||
92 | context1.setProperty1("property1"); | ||
93 | context1.setProperty2("property2"); | ||
94 | |||
95 | auto context2 = Context2{c}; | ||
96 | QCOMPARE(context2.getProperty2(), QByteArray("property2")); | ||
97 | } | ||
98 | |||
99 | }; | ||
100 | |||
101 | QTEST_GUILESS_MAIN(ActionTest) | ||
102 | #include "actiontest.moc" | ||
diff --git a/framework/src/domain/actions/sinkactions.cpp b/framework/src/domain/actions/sinkactions.cpp new file mode 100644 index 00000000..460cb6a1 --- /dev/null +++ b/framework/src/domain/actions/sinkactions.cpp | |||
@@ -0,0 +1,56 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | |||
20 | #include <actions/actionhandler.h> | ||
21 | |||
22 | #include <KMime/Message> | ||
23 | #include <QFile> | ||
24 | |||
25 | #include <sink/store.h> | ||
26 | #include <sink/log.h> | ||
27 | |||
28 | SINK_DEBUG_AREA("sinkactions") | ||
29 | |||
30 | using namespace Kube; | ||
31 | using namespace Sink; | ||
32 | using namespace Sink::ApplicationDomain; | ||
33 | |||
34 | class FolderContext : public Kube::ContextWrapper { | ||
35 | using Kube::ContextWrapper::ContextWrapper; | ||
36 | KUBE_CONTEXTWRAPPER_PROPERTY(Sink::ApplicationDomain::Folder::Ptr, Folder, folder) | ||
37 | }; | ||
38 | |||
39 | static ActionHandlerHelper synchronizeHandler("org.kde.kube.actions.synchronize", | ||
40 | [](Context *context) -> bool { | ||
41 | return true; | ||
42 | }, | ||
43 | [](Context *context_) { | ||
44 | auto context = FolderContext{*context_}; | ||
45 | if (auto folder = context.getFolder()) { | ||
46 | SinkLog() << "Synchronizing folder " << folder->resourceInstanceIdentifier() << folder->identifier(); | ||
47 | auto scope = SyncScope().resourceFilter(folder->resourceInstanceIdentifier()).filter<Mail::Folder>(QVariant::fromValue(folder->identifier())); | ||
48 | scope.setType<ApplicationDomain::Mail>(); | ||
49 | Store::synchronize(scope).exec(); | ||
50 | } else { | ||
51 | SinkLog() << "Synchronizing all"; | ||
52 | Store::synchronize(SyncScope()).exec(); | ||
53 | } | ||
54 | } | ||
55 | ); | ||
56 | |||
diff --git a/framework/src/domain/actions/tests/CMakeLists.txt b/framework/src/domain/actions/tests/CMakeLists.txt new file mode 100644 index 00000000..2c34d628 --- /dev/null +++ b/framework/src/domain/actions/tests/CMakeLists.txt | |||
@@ -0,0 +1,6 @@ | |||
1 | include_directories(${CMAKE_CURRENT_BINARY_DIR}) | ||
2 | cmake_policy(SET CMP0063 NEW) | ||
3 | add_executable(sinkactiontest sinkactiontest.cpp) | ||
4 | add_test(sinkactiontest sinkactiontest) | ||
5 | qt5_use_modules(sinkactiontest Core Test Concurrent) | ||
6 | target_link_libraries(sinkactiontest sink frameworkplugin KF5::Mime) | ||
diff --git a/framework/src/domain/actions/tests/sinkactiontest.cpp b/framework/src/domain/actions/tests/sinkactiontest.cpp new file mode 100644 index 00000000..79375503 --- /dev/null +++ b/framework/src/domain/actions/tests/sinkactiontest.cpp | |||
@@ -0,0 +1,58 @@ | |||
1 | #include <QTest> | ||
2 | #include <QDebug> | ||
3 | #include <QSignalSpy> | ||
4 | #include <sink/test.h> | ||
5 | #include <sink/store.h> | ||
6 | #include <sink/log.h> | ||
7 | #include <KMime/Message> | ||
8 | |||
9 | #include <actions/action.h> | ||
10 | #include <actions/context.h> | ||
11 | |||
12 | using namespace Sink; | ||
13 | |||
14 | class SinkActionTest : public QObject | ||
15 | { | ||
16 | Q_OBJECT | ||
17 | private slots: | ||
18 | |||
19 | void initTestCase() | ||
20 | { | ||
21 | Sink::Test::initTest(); | ||
22 | Sink::Log::setDebugOutputLevel(Sink::Log::Trace); | ||
23 | } | ||
24 | |||
25 | void testSaveAsDraftFail() | ||
26 | { | ||
27 | Kube::Context context; | ||
28 | auto future = Kube::Action("org.kde.kube.actions.save-as-draft", context).executeWithResult(); | ||
29 | |||
30 | QTRY_VERIFY(future.isDone()); | ||
31 | //because of empty context | ||
32 | QVERIFY(future.error()); | ||
33 | } | ||
34 | |||
35 | void testSaveAsDraftNew() | ||
36 | { | ||
37 | auto message = KMime::Message::Ptr::create(); | ||
38 | message->subject(true)->fromUnicodeString(QString::fromLatin1("Foobar"), "utf8"); | ||
39 | message->assemble(); | ||
40 | |||
41 | auto &&account = Test::TestAccount::registerAccount(); | ||
42 | |||
43 | Kube::Context context; | ||
44 | context.setProperty("message", QVariant::fromValue(message)); | ||
45 | context.setProperty("accountId", QVariant::fromValue(account.identifier)); | ||
46 | auto future = Kube::Action("org.kde.kube.actions.save-as-draft", context).executeWithResult(); | ||
47 | |||
48 | QTRY_VERIFY(future.isDone()); | ||
49 | QVERIFY(!future.error()); | ||
50 | auto mails = account.entities<Sink::ApplicationDomain::Mail>(); | ||
51 | QCOMPARE(mails.size(), 1); | ||
52 | auto mail = mails.first(); | ||
53 | QVERIFY(mail->getProperty("draft").toBool()); | ||
54 | } | ||
55 | }; | ||
56 | |||
57 | QTEST_GUILESS_MAIN(SinkActionTest) | ||
58 | #include "sinkactiontest.moc" | ||
diff --git a/framework/src/domain/attachmentmodel.cpp b/framework/src/domain/attachmentmodel.cpp new file mode 100644 index 00000000..e98c6fc0 --- /dev/null +++ b/framework/src/domain/attachmentmodel.cpp | |||
@@ -0,0 +1,145 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Sandro Knauß <knauss@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | |||
20 | #include "messageparser.h" | ||
21 | #include "mimetreeparser/interface.h" | ||
22 | |||
23 | #include <QIcon> | ||
24 | #include <QDebug> | ||
25 | |||
26 | QString sizeHuman(const Content::Ptr &content) | ||
27 | { | ||
28 | float num = content->content().size(); | ||
29 | QStringList list; | ||
30 | list << "KB" << "MB" << "GB" << "TB"; | ||
31 | |||
32 | QStringListIterator i(list); | ||
33 | QString unit("Bytes"); | ||
34 | |||
35 | while(num >= 1024.0 && i.hasNext()) | ||
36 | { | ||
37 | unit = i.next(); | ||
38 | num /= 1024.0; | ||
39 | } | ||
40 | |||
41 | if (unit == "Bytes") { | ||
42 | return QString().setNum(num) + " " + unit; | ||
43 | } else { | ||
44 | return QString().setNum(num,'f',2)+" "+unit; | ||
45 | } | ||
46 | } | ||
47 | |||
48 | class AttachmentModelPrivate | ||
49 | { | ||
50 | public: | ||
51 | AttachmentModelPrivate(AttachmentModel *q_ptr, const std::shared_ptr<Parser> &parser); | ||
52 | |||
53 | AttachmentModel *q; | ||
54 | std::shared_ptr<Parser> mParser; | ||
55 | QVector<Part::Ptr> mAttachments; | ||
56 | }; | ||
57 | |||
58 | AttachmentModelPrivate::AttachmentModelPrivate(AttachmentModel* q_ptr, const std::shared_ptr<Parser>& parser) | ||
59 | : q(q_ptr) | ||
60 | , mParser(parser) | ||
61 | { | ||
62 | mAttachments = mParser->collectAttachmentParts(); | ||
63 | } | ||
64 | |||
65 | AttachmentModel::AttachmentModel(std::shared_ptr<Parser> parser) | ||
66 | : d(std::unique_ptr<AttachmentModelPrivate>(new AttachmentModelPrivate(this, parser))) | ||
67 | { | ||
68 | } | ||
69 | |||
70 | AttachmentModel::~AttachmentModel() | ||
71 | { | ||
72 | } | ||
73 | |||
74 | QHash<int, QByteArray> AttachmentModel::roleNames() const | ||
75 | { | ||
76 | QHash<int, QByteArray> roles; | ||
77 | roles[TypeRole] = "type"; | ||
78 | roles[NameRole] = "name"; | ||
79 | roles[SizeRole] = "size"; | ||
80 | roles[IconRole] = "icon"; | ||
81 | roles[IsEncryptedRole] = "encrypted"; | ||
82 | roles[IsSignedRole] = "signed"; | ||
83 | return roles; | ||
84 | } | ||
85 | |||
86 | QModelIndex AttachmentModel::index(int row, int column, const QModelIndex &parent) const | ||
87 | { | ||
88 | if (row < 0 || column != 0) { | ||
89 | return QModelIndex(); | ||
90 | } | ||
91 | |||
92 | if (row < d->mAttachments.size()) { | ||
93 | return createIndex(row, column, d->mAttachments.at(row).get()); | ||
94 | } | ||
95 | return QModelIndex(); | ||
96 | } | ||
97 | |||
98 | QVariant AttachmentModel::data(const QModelIndex &index, int role) const | ||
99 | { | ||
100 | if (!index.isValid()) { | ||
101 | switch (role) { | ||
102 | case Qt::DisplayRole: | ||
103 | return QString("root"); | ||
104 | } | ||
105 | return QVariant(); | ||
106 | } | ||
107 | |||
108 | if (index.internalPointer()) { | ||
109 | const auto entry = static_cast<Part *>(index.internalPointer()); | ||
110 | const auto content = entry->content().at(0); | ||
111 | switch(role) { | ||
112 | case TypeRole: | ||
113 | return content->mailMime()->mimetype().name(); | ||
114 | case NameRole: | ||
115 | return entry->mailMime()->filename(); | ||
116 | case IconRole: | ||
117 | return QIcon::fromTheme(content->mailMime()->mimetype().iconName()); | ||
118 | case SizeRole: | ||
119 | return sizeHuman(content); | ||
120 | case IsEncryptedRole: | ||
121 | return content->encryptions().size() > 0; | ||
122 | case IsSignedRole: | ||
123 | return content->signatures().size() > 0; | ||
124 | } | ||
125 | } | ||
126 | return QVariant(); | ||
127 | } | ||
128 | |||
129 | QModelIndex AttachmentModel::parent(const QModelIndex &index) const | ||
130 | { | ||
131 | return QModelIndex(); | ||
132 | } | ||
133 | |||
134 | int AttachmentModel::rowCount(const QModelIndex &parent) const | ||
135 | { | ||
136 | if (!parent.isValid()) { | ||
137 | return d->mAttachments.size(); | ||
138 | } | ||
139 | return 0; | ||
140 | } | ||
141 | |||
142 | int AttachmentModel::columnCount(const QModelIndex &parent) const | ||
143 | { | ||
144 | return 1; | ||
145 | } | ||
diff --git a/framework/src/domain/completer.cpp b/framework/src/domain/completer.cpp new file mode 100644 index 00000000..cacb4faa --- /dev/null +++ b/framework/src/domain/completer.cpp | |||
@@ -0,0 +1,26 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekofp <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #include "completer.h" | ||
20 | |||
21 | #include <QQmlEngine> | ||
22 | |||
23 | Completer::Completer(QAbstractItemModel *model) : mModel{model} | ||
24 | { | ||
25 | QQmlEngine::setObjectOwnership(mModel, QQmlEngine::CppOwnership); | ||
26 | } | ||
diff --git a/framework/src/domain/completer.h b/framework/src/domain/completer.h new file mode 100644 index 00000000..a672b809 --- /dev/null +++ b/framework/src/domain/completer.h | |||
@@ -0,0 +1,40 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekofp <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #pragma once | ||
20 | |||
21 | #include <QAbstractItemModel> | ||
22 | #include <QString> | ||
23 | #include <QQmlEngine> | ||
24 | |||
25 | class Completer : public QObject { | ||
26 | Q_OBJECT | ||
27 | Q_PROPERTY (QAbstractItemModel* model READ model CONSTANT) | ||
28 | Q_PROPERTY (QString searchString WRITE setSearchString READ searchString) | ||
29 | |||
30 | public: | ||
31 | Completer(QAbstractItemModel *model); | ||
32 | QAbstractItemModel *model() { return mModel; } | ||
33 | virtual void setSearchString(const QString &s) { mSearchString = s; } | ||
34 | QString searchString() const { return mSearchString; } | ||
35 | |||
36 | private: | ||
37 | QAbstractItemModel *mModel = nullptr; | ||
38 | QString mSearchString; | ||
39 | }; | ||
40 | |||
diff --git a/framework/src/domain/composercontroller.cpp b/framework/src/domain/composercontroller.cpp new file mode 100644 index 00000000..3328d9eb --- /dev/null +++ b/framework/src/domain/composercontroller.cpp | |||
@@ -0,0 +1,307 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Michael Bohlender <michael.bohlender@kdemail.net> | ||
3 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
4 | |||
5 | This library is free software; you can redistribute it and/or modify it | ||
6 | under the terms of the GNU Library General Public License as published by | ||
7 | the Free Software Foundation; either version 2 of the License, or (at your | ||
8 | option) any later version. | ||
9 | |||
10 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
12 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
13 | License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU Library General Public License | ||
16 | along with this library; see the file COPYING.LIB. If not, write to the | ||
17 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
18 | 02110-1301, USA. | ||
19 | */ | ||
20 | |||
21 | |||
22 | #include "composercontroller.h" | ||
23 | #include <settings/settings.h> | ||
24 | #include <KMime/Message> | ||
25 | #include <KCodecs/KEmailAddress> | ||
26 | #include <QVariant> | ||
27 | #include <QSortFilterProxyModel> | ||
28 | #include <QList> | ||
29 | #include <QDebug> | ||
30 | #include <QQmlEngine> | ||
31 | #include <sink/store.h> | ||
32 | #include <sink/log.h> | ||
33 | |||
34 | #include "identitiesmodel.h" | ||
35 | #include "recepientautocompletionmodel.h" | ||
36 | #include "mailtemplates.h" | ||
37 | |||
38 | SINK_DEBUG_AREA("composercontroller"); | ||
39 | |||
40 | class IdentitySelector : public Selector { | ||
41 | public: | ||
42 | IdentitySelector(ComposerController &controller) : Selector(new IdentitiesModel), mController(controller) | ||
43 | { | ||
44 | } | ||
45 | |||
46 | void setCurrent(const QModelIndex &index) Q_DECL_OVERRIDE | ||
47 | { | ||
48 | if (index.isValid()) { | ||
49 | auto currentAccountId = index.data(IdentitiesModel::AccountId).toByteArray(); | ||
50 | |||
51 | KMime::Types::Mailbox mb; | ||
52 | mb.setName(index.data(IdentitiesModel::Username).toString()); | ||
53 | mb.setAddress(index.data(IdentitiesModel::Address).toString().toUtf8()); | ||
54 | SinkLog() << "Setting current identity: " << mb.prettyAddress() << "Account: " << currentAccountId; | ||
55 | |||
56 | mController.setIdentity(mb); | ||
57 | mController.setAccountId(currentAccountId); | ||
58 | } else { | ||
59 | SinkWarning() << "No valid identity for index: " << index; | ||
60 | mController.clearIdentity(); | ||
61 | mController.clearAccountId(); | ||
62 | } | ||
63 | |||
64 | } | ||
65 | private: | ||
66 | ComposerController &mController; | ||
67 | }; | ||
68 | |||
69 | class RecipientCompleter : public Completer { | ||
70 | public: | ||
71 | RecipientCompleter() : Completer(new RecipientAutocompletionModel) | ||
72 | { | ||
73 | } | ||
74 | |||
75 | void setSearchString(const QString &s) { | ||
76 | static_cast<RecipientAutocompletionModel*>(model())->setFilter(s); | ||
77 | Completer::setSearchString(s); | ||
78 | } | ||
79 | }; | ||
80 | |||
81 | |||
82 | ComposerController::ComposerController() | ||
83 | : Kube::Controller(), | ||
84 | action_send{new Kube::ControllerAction{this, &ComposerController::send}}, | ||
85 | action_saveAsDraft{new Kube::ControllerAction{this, &ComposerController::saveAsDraft}}, | ||
86 | mRecipientCompleter{new RecipientCompleter}, | ||
87 | mIdentitySelector{new IdentitySelector{*this}} | ||
88 | { | ||
89 | updateSaveAsDraftAction(); | ||
90 | // mSendAction->monitorProperty<To>(); | ||
91 | // mSendAction->monitorProperty<Send>([] (const QString &) -> bool{ | ||
92 | // //validate | ||
93 | // }); | ||
94 | // registerAction<ControllerAction>(&ComposerController::send); | ||
95 | // actionDepends<ControllerAction, To, Subject>(); | ||
96 | // TODO do in constructor | ||
97 | |||
98 | QObject::connect(this, &ComposerController::toChanged, &ComposerController::updateSendAction); | ||
99 | QObject::connect(this, &ComposerController::subjectChanged, &ComposerController::updateSendAction); | ||
100 | QObject::connect(this, &ComposerController::accountIdChanged, &ComposerController::updateSendAction); | ||
101 | QObject::connect(this, &ComposerController::toChanged, &ComposerController::updateSaveAsDraftAction); | ||
102 | QObject::connect(this, &ComposerController::subjectChanged, &ComposerController::updateSaveAsDraftAction); | ||
103 | QObject::connect(this, &ComposerController::accountIdChanged, &ComposerController::updateSaveAsDraftAction); | ||
104 | updateSendAction(); | ||
105 | } | ||
106 | |||
107 | void ComposerController::clear() | ||
108 | { | ||
109 | Controller::clear(); | ||
110 | //Reapply account and identity from selection | ||
111 | mIdentitySelector->reapplyCurrentIndex(); | ||
112 | } | ||
113 | |||
114 | Completer *ComposerController::recipientCompleter() const | ||
115 | { | ||
116 | return mRecipientCompleter.data(); | ||
117 | } | ||
118 | |||
119 | Selector *ComposerController::identitySelector() const | ||
120 | { | ||
121 | return mIdentitySelector.data(); | ||
122 | } | ||
123 | |||
124 | void ComposerController::setMessage(const KMime::Message::Ptr &msg) | ||
125 | { | ||
126 | setTo(msg->to(true)->asUnicodeString()); | ||
127 | setCc(msg->cc(true)->asUnicodeString()); | ||
128 | setSubject(msg->subject(true)->asUnicodeString()); | ||
129 | setBody(msg->body()); | ||
130 | setExistingMessage(msg); | ||
131 | } | ||
132 | |||
133 | void ComposerController::loadMessage(const QVariant &message, bool loadAsDraft) | ||
134 | { | ||
135 | using namespace Sink; | ||
136 | using namespace Sink::ApplicationDomain; | ||
137 | |||
138 | Query query(*message.value<Mail::Ptr>()); | ||
139 | query.request<Mail::MimeMessage>(); | ||
140 | Store::fetchOne<Mail>(query).then([this, loadAsDraft](const Mail &mail) { | ||
141 | setExistingMail(mail); | ||
142 | |||
143 | //TODO this should probably happen as reaction to the property being set. | ||
144 | const auto mailData = KMime::CRLFtoLF(mail.getMimeMessage()); | ||
145 | if (!mailData.isEmpty()) { | ||
146 | KMime::Message::Ptr mail(new KMime::Message); | ||
147 | mail->setContent(mailData); | ||
148 | mail->parse(); | ||
149 | if (loadAsDraft) { | ||
150 | setMessage(mail); | ||
151 | } else { | ||
152 | auto reply = MailTemplates::reply(mail); | ||
153 | //We assume reply | ||
154 | setMessage(reply); | ||
155 | } | ||
156 | } else { | ||
157 | qWarning() << "Retrieved empty message"; | ||
158 | } | ||
159 | }).exec(); | ||
160 | } | ||
161 | |||
162 | void ComposerController::recordForAutocompletion(const QByteArray &addrSpec, const QByteArray &displayName) | ||
163 | { | ||
164 | if (auto model = static_cast<RecipientAutocompletionModel*>(recipientCompleter()->model())) { | ||
165 | model->addEntry(addrSpec, displayName); | ||
166 | } | ||
167 | } | ||
168 | |||
169 | void applyAddresses(const QString &list, std::function<void(const QByteArray &, const QByteArray &)> callback) | ||
170 | { | ||
171 | for (const auto &to : KEmailAddress::splitAddressList(list)) { | ||
172 | QByteArray displayName; | ||
173 | QByteArray addrSpec; | ||
174 | QByteArray comment; | ||
175 | KEmailAddress::splitAddress(to.toUtf8(), displayName, addrSpec, comment); | ||
176 | callback(addrSpec, displayName); | ||
177 | } | ||
178 | } | ||
179 | |||
180 | KMime::Message::Ptr ComposerController::assembleMessage() | ||
181 | { | ||
182 | auto mail = mExistingMessage; | ||
183 | if (!mail) { | ||
184 | mail = KMime::Message::Ptr::create(); | ||
185 | } | ||
186 | applyAddresses(getTo(), [&](const QByteArray &addrSpec, const QByteArray &displayName) { | ||
187 | mail->to(true)->addAddress(addrSpec, displayName); | ||
188 | recordForAutocompletion(addrSpec, displayName); | ||
189 | }); | ||
190 | applyAddresses(getCc(), [&](const QByteArray &addrSpec, const QByteArray &displayName) { | ||
191 | mail->cc(true)->addAddress(addrSpec, displayName); | ||
192 | recordForAutocompletion(addrSpec, displayName); | ||
193 | }); | ||
194 | applyAddresses(getBcc(), [&](const QByteArray &addrSpec, const QByteArray &displayName) { | ||
195 | mail->bcc(true)->addAddress(addrSpec, displayName); | ||
196 | recordForAutocompletion(addrSpec, displayName); | ||
197 | }); | ||
198 | |||
199 | mail->from(true)->addAddress(getIdentity()); | ||
200 | |||
201 | mail->subject(true)->fromUnicodeString(getSubject(), "utf-8"); | ||
202 | mail->setBody(getBody().toUtf8()); | ||
203 | if (!mail->messageID()) { | ||
204 | mail->messageID(true)->generate("org.kde.kube"); | ||
205 | } | ||
206 | if (!mail->date(true)->dateTime().isValid()) { | ||
207 | mail->date(true)->setDateTime(QDateTime::currentDateTimeUtc()); | ||
208 | } | ||
209 | |||
210 | mail->assemble(); | ||
211 | return mail; | ||
212 | } | ||
213 | |||
214 | void ComposerController::updateSendAction() | ||
215 | { | ||
216 | auto enabled = !getTo().isEmpty() && !getSubject().isEmpty() && !getAccountId().isEmpty(); | ||
217 | sendAction()->setEnabled(enabled); | ||
218 | } | ||
219 | |||
220 | void ComposerController::send() | ||
221 | { | ||
222 | // verify<To, Subject>() | ||
223 | // && verify<Subject>(); | ||
224 | auto message = assembleMessage(); | ||
225 | |||
226 | auto accountId = getAccountId(); | ||
227 | //SinkLog() << "Sending a mail: " << *this; | ||
228 | using namespace Sink; | ||
229 | using namespace Sink::ApplicationDomain; | ||
230 | |||
231 | Q_ASSERT(!accountId.isEmpty()); | ||
232 | Query query; | ||
233 | query.containsFilter<SinkResource::Capabilities>(ResourceCapabilities::Mail::transport); | ||
234 | query.filter<SinkResource::Account>(accountId); | ||
235 | auto job = Store::fetchAll<SinkResource>(query) | ||
236 | .then([=](const QList<SinkResource::Ptr> &resources) { | ||
237 | if (!resources.isEmpty()) { | ||
238 | auto resourceId = resources[0]->identifier(); | ||
239 | SinkLog() << "Sending message via resource: " << resourceId; | ||
240 | Mail mail(resourceId); | ||
241 | mail.setMimeMessage(message->encodedContent()); | ||
242 | return Store::create(mail) | ||
243 | .then<void>([=] { | ||
244 | //Trigger a sync, but don't wait for it. | ||
245 | Store::synchronize(Sink::SyncScope{}.resourceFilter(resourceId)).exec(); | ||
246 | }); | ||
247 | } | ||
248 | SinkWarning() << "Failed to find a mailtransport resource"; | ||
249 | return KAsync::error<void>(0, "Failed to find a MailTransport resource."); | ||
250 | }) | ||
251 | .then([&] (const KAsync::Error &error) { | ||
252 | SinkLog() << "Message was sent: "; | ||
253 | emit done(); | ||
254 | }); | ||
255 | run(job); | ||
256 | } | ||
257 | |||
258 | void ComposerController::updateSaveAsDraftAction() | ||
259 | { | ||
260 | bool enabled = !getAccountId().isEmpty(); | ||
261 | sendAction()->setEnabled(enabled); | ||
262 | } | ||
263 | |||
264 | void ComposerController::saveAsDraft() | ||
265 | { | ||
266 | SinkLog() << "Save as draft"; | ||
267 | const auto accountId = getAccountId(); | ||
268 | auto existingMail = getExistingMail(); | ||
269 | |||
270 | auto message = assembleMessage(); | ||
271 | //FIXME this is something for the validation | ||
272 | if (!message) { | ||
273 | SinkWarning() << "Failed to get the mail: "; | ||
274 | return; | ||
275 | } | ||
276 | |||
277 | using namespace Sink; | ||
278 | using namespace Sink::ApplicationDomain; | ||
279 | |||
280 | auto job = [&] { | ||
281 | if (existingMail.identifier().isEmpty()) { | ||
282 | SinkLog() << "Creating a new draft" << existingMail.identifier(); | ||
283 | Query query; | ||
284 | query.containsFilter<SinkResource::Capabilities>(ResourceCapabilities::Mail::drafts); | ||
285 | query.filter<SinkResource::Account>(accountId); | ||
286 | return Store::fetchOne<SinkResource>(query) | ||
287 | .then([=](const SinkResource &resource) { | ||
288 | Mail mail(resource.identifier()); | ||
289 | mail.setDraft(true); | ||
290 | mail.setMimeMessage(message->encodedContent()); | ||
291 | return Store::create(mail); | ||
292 | }) | ||
293 | .onError([] (const KAsync::Error &error) { | ||
294 | SinkWarning() << "Error while creating draft: " << error.errorMessage; | ||
295 | }); | ||
296 | } else { | ||
297 | SinkLog() << "Modifying an existing mail" << existingMail.identifier(); | ||
298 | existingMail.setDraft(true); | ||
299 | existingMail.setMimeMessage(message->encodedContent()); | ||
300 | return Store::modify(existingMail); | ||
301 | } | ||
302 | }(); | ||
303 | job = job.then([&] (const KAsync::Error &) { | ||
304 | emit done(); | ||
305 | }); | ||
306 | run(job); | ||
307 | } | ||
diff --git a/framework/src/domain/composercontroller.h b/framework/src/domain/composercontroller.h new file mode 100644 index 00000000..92467d05 --- /dev/null +++ b/framework/src/domain/composercontroller.h | |||
@@ -0,0 +1,93 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Michael Bohlender <michael.bohlender@kdemail.net> | ||
3 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
4 | |||
5 | This library is free software; you can redistribute it and/or modify it | ||
6 | under the terms of the GNU Library General Public License as published by | ||
7 | the Free Software Foundation; either version 2 of the License, or (at your | ||
8 | option) any later version. | ||
9 | |||
10 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
12 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
13 | License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU Library General Public License | ||
16 | along with this library; see the file COPYING.LIB. If not, write to the | ||
17 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
18 | 02110-1301, USA. | ||
19 | */ | ||
20 | |||
21 | #pragma once | ||
22 | |||
23 | #include <QObject> | ||
24 | #include <QString> | ||
25 | #include <QStringList> | ||
26 | #include <QVariant> | ||
27 | #include <sink/applicationdomaintype.h> | ||
28 | #include <KMime/Message> | ||
29 | |||
30 | #include "completer.h" | ||
31 | #include "selector.h" | ||
32 | #include "controller.h" | ||
33 | |||
34 | inline bool operator !=(const KMime::Types::Mailbox &l, const KMime::Types::Mailbox &r) | ||
35 | { | ||
36 | return !(l.prettyAddress() == r.prettyAddress()); | ||
37 | } | ||
38 | |||
39 | Q_DECLARE_METATYPE(KMime::Types::Mailbox); | ||
40 | |||
41 | namespace KMime { | ||
42 | class Message; | ||
43 | } | ||
44 | |||
45 | class ComposerController : public Kube::Controller | ||
46 | { | ||
47 | Q_OBJECT | ||
48 | |||
49 | //Interface properties | ||
50 | KUBE_CONTROLLER_PROPERTY(QString, To, to) | ||
51 | KUBE_CONTROLLER_PROPERTY(QString, Cc, cc) | ||
52 | KUBE_CONTROLLER_PROPERTY(QString, Bcc, bcc) | ||
53 | KUBE_CONTROLLER_PROPERTY(QString, Subject, subject) | ||
54 | KUBE_CONTROLLER_PROPERTY(QString, Body, body) | ||
55 | |||
56 | //Set by identitySelector | ||
57 | KUBE_CONTROLLER_PROPERTY(KMime::Types::Mailbox, Identity, identity) | ||
58 | KUBE_CONTROLLER_PROPERTY(QByteArray, AccountId, accountId) | ||
59 | |||
60 | //Set by loadMessage | ||
61 | KUBE_CONTROLLER_PROPERTY(KMime::Message::Ptr, ExistingMessage, existingMessage) | ||
62 | KUBE_CONTROLLER_PROPERTY(Sink::ApplicationDomain::Mail, ExistingMail, existingMail) | ||
63 | |||
64 | Q_PROPERTY (Completer* recipientCompleter READ recipientCompleter CONSTANT) | ||
65 | Q_PROPERTY (Selector* identitySelector READ identitySelector CONSTANT) | ||
66 | //Q_PROPERTY (QValidator* subjectValidator READ subjectValidator CONSTANT) | ||
67 | |||
68 | KUBE_CONTROLLER_ACTION(send) | ||
69 | KUBE_CONTROLLER_ACTION(saveAsDraft) | ||
70 | |||
71 | public: | ||
72 | explicit ComposerController(); | ||
73 | |||
74 | Completer *recipientCompleter() const; | ||
75 | Selector *identitySelector() const; | ||
76 | |||
77 | Q_INVOKABLE void loadMessage(const QVariant &draft, bool loadAsDraft); | ||
78 | |||
79 | public slots: | ||
80 | virtual void clear() Q_DECL_OVERRIDE; | ||
81 | |||
82 | private slots: | ||
83 | void updateSendAction(); | ||
84 | void updateSaveAsDraftAction(); | ||
85 | |||
86 | private: | ||
87 | void recordForAutocompletion(const QByteArray &addrSpec, const QByteArray &displayName); | ||
88 | void setMessage(const QSharedPointer<KMime::Message> &msg); | ||
89 | KMime::Message::Ptr assembleMessage(); | ||
90 | |||
91 | QScopedPointer<Completer> mRecipientCompleter; | ||
92 | QScopedPointer<Selector> mIdentitySelector; | ||
93 | }; | ||
diff --git a/framework/src/domain/contactcontroller.cpp b/framework/src/domain/contactcontroller.cpp new file mode 100644 index 00000000..49f78b40 --- /dev/null +++ b/framework/src/domain/contactcontroller.cpp | |||
@@ -0,0 +1,68 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2017 Michael Bohlender, <michael.bohlender@kdemail.net> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; either version 2 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License along | ||
15 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
17 | */ | ||
18 | |||
19 | #include "contactcontroller.h" | ||
20 | |||
21 | #include <sink/applicationdomaintype.h> | ||
22 | |||
23 | ContactController::ContactController() | ||
24 | : Kube::Controller(), | ||
25 | action_save{new Kube::ControllerAction{this, &ContactController::save}} | ||
26 | { | ||
27 | updateSaveAction(); | ||
28 | } | ||
29 | |||
30 | void ContactController::save() { | ||
31 | qWarning() << "Saving is not implemented"; | ||
32 | } | ||
33 | |||
34 | void ContactController::updateSaveAction() | ||
35 | { | ||
36 | saveAction()->setEnabled(!getName().isEmpty()); | ||
37 | } | ||
38 | |||
39 | void ContactController::loadContact(const QVariant &contact) | ||
40 | { | ||
41 | if (auto c = contact.value<Sink::ApplicationDomain::Contact::Ptr>()) { | ||
42 | setName(c->getFn()); | ||
43 | QStringList emails; | ||
44 | for (const auto &e : c->getEmails()) { | ||
45 | emails << e.email; | ||
46 | } | ||
47 | setEmails(emails); | ||
48 | } | ||
49 | } | ||
50 | |||
51 | QVariant ContactController::contact() const | ||
52 | { | ||
53 | return QVariant{}; | ||
54 | } | ||
55 | |||
56 | void ContactController::removeEmail(const QString &email) | ||
57 | { | ||
58 | auto emails = getEmails(); | ||
59 | emails.removeAll(email); | ||
60 | setEmails(emails); | ||
61 | } | ||
62 | |||
63 | void ContactController::addEmail(const QString &email) | ||
64 | { | ||
65 | auto emails = getEmails(); | ||
66 | emails.append(email); | ||
67 | setEmails(emails); | ||
68 | } | ||
diff --git a/framework/src/domain/contactcontroller.h b/framework/src/domain/contactcontroller.h new file mode 100644 index 00000000..d1973d9c --- /dev/null +++ b/framework/src/domain/contactcontroller.h | |||
@@ -0,0 +1,52 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2017 Michael Bohlender, <michael.bohlender@kdemail.net> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; either version 2 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License along | ||
15 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
17 | */ | ||
18 | |||
19 | |||
20 | #pragma once | ||
21 | |||
22 | #include <QObject> | ||
23 | #include <QString> | ||
24 | #include <QStringList> | ||
25 | |||
26 | #include "controller.h" | ||
27 | |||
28 | class ContactController : public Kube::Controller | ||
29 | { | ||
30 | Q_OBJECT | ||
31 | |||
32 | // Input properties | ||
33 | Q_PROPERTY(QVariant contact READ contact WRITE loadContact) | ||
34 | |||
35 | //Interface properties | ||
36 | KUBE_CONTROLLER_PROPERTY(QString, Name, name) | ||
37 | KUBE_CONTROLLER_PROPERTY(QStringList, Emails, emails) | ||
38 | |||
39 | KUBE_CONTROLLER_ACTION(save) | ||
40 | |||
41 | public: | ||
42 | explicit ContactController(); | ||
43 | |||
44 | Q_INVOKABLE void loadContact(const QVariant &contact); | ||
45 | Q_INVOKABLE void removeEmail(const QString &email); | ||
46 | Q_INVOKABLE void addEmail(const QString &email); | ||
47 | |||
48 | QVariant contact() const; | ||
49 | |||
50 | private slots: | ||
51 | void updateSaveAction(); | ||
52 | }; | ||
diff --git a/framework/src/domain/controller.cpp b/framework/src/domain/controller.cpp new file mode 100644 index 00000000..52f4cd1f --- /dev/null +++ b/framework/src/domain/controller.cpp | |||
@@ -0,0 +1,59 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #include "controller.h" | ||
20 | |||
21 | #include <QQmlEngine> | ||
22 | #include <QMetaProperty> | ||
23 | #include <sink/log.h> | ||
24 | |||
25 | using namespace Kube; | ||
26 | |||
27 | ControllerAction::ControllerAction() | ||
28 | : QObject() | ||
29 | { | ||
30 | QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); | ||
31 | } | ||
32 | |||
33 | void ControllerAction::execute() | ||
34 | { | ||
35 | emit triggered(); | ||
36 | } | ||
37 | |||
38 | void Controller::clear() | ||
39 | { | ||
40 | auto meta = metaObject(); | ||
41 | for (auto i = meta->propertyOffset(); i < meta->propertyCount(); i++) { | ||
42 | auto property = meta->property(i); | ||
43 | setProperty(property.name(), QVariant()); | ||
44 | } | ||
45 | for (const auto &p : dynamicPropertyNames()) { | ||
46 | setProperty(p, QVariant()); | ||
47 | } | ||
48 | } | ||
49 | |||
50 | void Controller::run(const KAsync::Job<void> &job) | ||
51 | { | ||
52 | auto jobToExec = job; | ||
53 | jobToExec.onError([] (const KAsync::Error &error) { | ||
54 | SinkWarningCtx(Sink::Log::Context{"controller"}) << "Error while executing job: " << error.errorMessage; | ||
55 | }); | ||
56 | //TODO handle error | ||
57 | //TODO attach a log context to the execution that we can gather from the job? | ||
58 | jobToExec.exec(); | ||
59 | } | ||
diff --git a/framework/src/domain/controller.h b/framework/src/domain/controller.h new file mode 100644 index 00000000..bf2a7ace --- /dev/null +++ b/framework/src/domain/controller.h | |||
@@ -0,0 +1,90 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #pragma once | ||
20 | |||
21 | #include <QObject> | ||
22 | #include <QVariant> | ||
23 | #include <KAsync/Async> | ||
24 | |||
25 | #define KUBE_CONTROLLER_PROPERTY(TYPE, NAME, LOWERCASENAME) \ | ||
26 | public: Q_PROPERTY(TYPE LOWERCASENAME MEMBER m##NAME NOTIFY LOWERCASENAME##Changed) \ | ||
27 | Q_SIGNALS: void LOWERCASENAME##Changed(); \ | ||
28 | private: TYPE m##NAME; \ | ||
29 | public: \ | ||
30 | struct NAME { \ | ||
31 | static constexpr const char *name = #LOWERCASENAME; \ | ||
32 | typedef TYPE Type; \ | ||
33 | }; \ | ||
34 | void set##NAME(const TYPE &value) { setProperty(NAME::name, QVariant::fromValue(value)); } \ | ||
35 | void clear##NAME() { setProperty(NAME::name, QVariant{}); } \ | ||
36 | TYPE get##NAME() const { return m##NAME; } \ | ||
37 | |||
38 | |||
39 | #define KUBE_CONTROLLER_ACTION(NAME) \ | ||
40 | Q_PROPERTY (Kube::ControllerAction* NAME##Action READ NAME##Action CONSTANT) \ | ||
41 | private: QScopedPointer<Kube::ControllerAction> action_##NAME; \ | ||
42 | public: Kube::ControllerAction* NAME##Action() const { Q_ASSERT(action_##NAME); return action_##NAME.data(); } \ | ||
43 | private slots: void NAME(); \ | ||
44 | |||
45 | |||
46 | namespace Kube { | ||
47 | |||
48 | class ControllerAction : public QObject { | ||
49 | Q_OBJECT | ||
50 | Q_PROPERTY(bool enabled MEMBER mEnabled NOTIFY enabledChanged) | ||
51 | public: | ||
52 | ControllerAction(); | ||
53 | template <typename Func> | ||
54 | ControllerAction(const typename QtPrivate::FunctionPointer<Func>::Object *obj, Func slot) | ||
55 | : ControllerAction() | ||
56 | { | ||
57 | QObject::connect(this, &ControllerAction::triggered, obj, slot); | ||
58 | } | ||
59 | |||
60 | ~ControllerAction() = default; | ||
61 | |||
62 | Q_INVOKABLE void execute(); | ||
63 | void setEnabled(bool enabled) { setProperty("enabled", enabled); } | ||
64 | |||
65 | signals: | ||
66 | void enabledChanged(); | ||
67 | void triggered(); | ||
68 | |||
69 | private: | ||
70 | bool mEnabled = true; | ||
71 | }; | ||
72 | |||
73 | class Controller : public QObject { | ||
74 | Q_OBJECT | ||
75 | public: | ||
76 | Controller() = default; | ||
77 | virtual ~Controller() = default; | ||
78 | |||
79 | public slots: | ||
80 | virtual void clear(); | ||
81 | |||
82 | signals: | ||
83 | void done(); | ||
84 | void error(); | ||
85 | |||
86 | protected: | ||
87 | void run(const KAsync::Job<void> &job); | ||
88 | }; | ||
89 | |||
90 | } | ||
diff --git a/framework/src/domain/foldercontroller.cpp b/framework/src/domain/foldercontroller.cpp new file mode 100644 index 00000000..3c10f773 --- /dev/null +++ b/framework/src/domain/foldercontroller.cpp | |||
@@ -0,0 +1,62 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #include "foldercontroller.h" | ||
20 | |||
21 | #include <sink/store.h> | ||
22 | #include <sink/log.h> | ||
23 | |||
24 | SINK_DEBUG_AREA("foldercontroller"); | ||
25 | |||
26 | using namespace Sink; | ||
27 | using namespace Sink::ApplicationDomain; | ||
28 | |||
29 | FolderController::FolderController() | ||
30 | : Kube::Controller(), | ||
31 | action_synchronize{new Kube::ControllerAction{this, &FolderController::synchronize}}, | ||
32 | action_moveToFolder{new Kube::ControllerAction{this, &FolderController::moveToFolder}} | ||
33 | { | ||
34 | } | ||
35 | |||
36 | void FolderController::synchronize() | ||
37 | { | ||
38 | auto job = [&] { | ||
39 | auto accountId = getAccountId(); | ||
40 | if (auto folder = getFolder()) { | ||
41 | SinkLog() << "Synchronizing folder " << folder->resourceInstanceIdentifier() << folder->identifier(); | ||
42 | auto scope = SyncScope().resourceFilter(folder->resourceInstanceIdentifier()).filter<ApplicationDomain::Mail::Folder>(QVariant::fromValue(folder->identifier())); | ||
43 | scope.setType<ApplicationDomain::Mail>(); | ||
44 | return Store::synchronize(scope); | ||
45 | } else if (!accountId.isEmpty()) { | ||
46 | return Store::synchronize(SyncScope{}.resourceFilter<ApplicationDomain::SinkResource::Account>(accountId)); | ||
47 | } else { | ||
48 | SinkLog() << "Synchronizing all"; | ||
49 | return Store::synchronize(SyncScope()); | ||
50 | } | ||
51 | }(); | ||
52 | run(job); | ||
53 | } | ||
54 | |||
55 | void FolderController::moveToFolder() | ||
56 | { | ||
57 | auto mail = getMail(); | ||
58 | auto targetFolder = getFolder(); | ||
59 | mail->setFolder(*targetFolder); | ||
60 | SinkLog() << "Moving to folder " << mail->identifier() << targetFolder->identifier(); | ||
61 | run(Store::modify(*mail)); | ||
62 | } | ||
diff --git a/framework/src/domain/foldercontroller.h b/framework/src/domain/foldercontroller.h new file mode 100644 index 00000000..c0815546 --- /dev/null +++ b/framework/src/domain/foldercontroller.h | |||
@@ -0,0 +1,36 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #pragma once | ||
20 | |||
21 | #include <QObject> | ||
22 | #include "controller.h" | ||
23 | #include "sink/applicationdomaintype.h" | ||
24 | |||
25 | class FolderController : public Kube::Controller | ||
26 | { | ||
27 | Q_OBJECT | ||
28 | KUBE_CONTROLLER_PROPERTY(Sink::ApplicationDomain::Folder::Ptr, Folder, folder) | ||
29 | KUBE_CONTROLLER_PROPERTY(Sink::ApplicationDomain::Mail::Ptr, Mail, mail) | ||
30 | KUBE_CONTROLLER_PROPERTY(QByteArray, AccountId, accountId) | ||
31 | KUBE_CONTROLLER_ACTION(synchronize) | ||
32 | KUBE_CONTROLLER_ACTION(moveToFolder) | ||
33 | |||
34 | public: | ||
35 | explicit FolderController(); | ||
36 | }; | ||
diff --git a/framework/src/domain/folderlistmodel.cpp b/framework/src/domain/folderlistmodel.cpp new file mode 100644 index 00000000..14405beb --- /dev/null +++ b/framework/src/domain/folderlistmodel.cpp | |||
@@ -0,0 +1,145 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Michael Bohlender <michael.bohlender@kdemail.net> | ||
3 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
4 | |||
5 | This library is free software; you can redistribute it and/or modify it | ||
6 | under the terms of the GNU Library General Public License as published by | ||
7 | the Free Software Foundation; either version 2 of the License, or (at your | ||
8 | option) any later version. | ||
9 | |||
10 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
12 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
13 | License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU Library General Public License | ||
16 | along with this library; see the file COPYING.LIB. If not, write to the | ||
17 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
18 | 02110-1301, USA. | ||
19 | */ | ||
20 | |||
21 | #include "folderlistmodel.h" | ||
22 | #include <sink/store.h> | ||
23 | #include <sink/log.h> | ||
24 | #include <settings/settings.h> | ||
25 | |||
26 | using namespace Sink; | ||
27 | using namespace Sink::ApplicationDomain; | ||
28 | |||
29 | FolderListModel::FolderListModel(QObject *parent) : QSortFilterProxyModel() | ||
30 | { | ||
31 | setDynamicSortFilter(true); | ||
32 | sort(0, Qt::AscendingOrder); | ||
33 | |||
34 | Query query; | ||
35 | query.setFlags(Sink::Query::LiveQuery | Sink::Query::UpdateStatus); | ||
36 | query.request<Folder::Name>().request<Folder::Icon>().request<Folder::Parent>().request<Folder::SpecialPurpose>(); | ||
37 | query.requestTree<Folder::Parent>(); | ||
38 | query.setId("foldertree"); | ||
39 | runQuery(query); | ||
40 | } | ||
41 | |||
42 | FolderListModel::~FolderListModel() | ||
43 | { | ||
44 | |||
45 | } | ||
46 | |||
47 | QHash< int, QByteArray > FolderListModel::roleNames() const | ||
48 | { | ||
49 | QHash<int, QByteArray> roles; | ||
50 | |||
51 | roles[Name] = "name"; | ||
52 | roles[Icon] = "icon"; | ||
53 | roles[Id] = "id"; | ||
54 | roles[DomainObject] = "domainObject"; | ||
55 | roles[Status] = "status"; | ||
56 | |||
57 | return roles; | ||
58 | } | ||
59 | |||
60 | QVariant FolderListModel::data(const QModelIndex &idx, int role) const | ||
61 | { | ||
62 | auto srcIdx = mapToSource(idx); | ||
63 | auto folder = srcIdx.data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Folder::Ptr>(); | ||
64 | switch (role) { | ||
65 | case Name: | ||
66 | return folder->getName(); | ||
67 | case Icon: | ||
68 | return folder->getIcon(); | ||
69 | case Id: | ||
70 | return folder->identifier(); | ||
71 | case DomainObject: | ||
72 | return QVariant::fromValue(folder); | ||
73 | case Status: { | ||
74 | switch (srcIdx.data(Sink::Store::StatusRole).toInt()) { | ||
75 | case Sink::ApplicationDomain::SyncStatus::SyncInProgress: | ||
76 | return InProgressStatus; | ||
77 | case Sink::ApplicationDomain::SyncStatus::SyncError: | ||
78 | return ErrorStatus; | ||
79 | case Sink::ApplicationDomain::SyncStatus::SyncSuccess: | ||
80 | return SuccessStatus; | ||
81 | } | ||
82 | return NoStatus; | ||
83 | } | ||
84 | } | ||
85 | return QSortFilterProxyModel::data(idx, role); | ||
86 | } | ||
87 | |||
88 | void FolderListModel::runQuery(const Query &query) | ||
89 | { | ||
90 | mModel = Store::loadModel<Folder>(query); | ||
91 | setSourceModel(mModel.data()); | ||
92 | } | ||
93 | |||
94 | void FolderListModel::setAccountId(const QVariant &accountId) | ||
95 | { | ||
96 | const auto account = accountId.toString().toUtf8(); | ||
97 | |||
98 | //Get all folders of an account | ||
99 | auto query = Query(); | ||
100 | query.resourceFilter<SinkResource::Account>(account); | ||
101 | query.setFlags(Sink::Query::LiveQuery | Sink::Query::UpdateStatus); | ||
102 | query.requestTree<Folder::Parent>(); | ||
103 | query.request<Folder::Name>() | ||
104 | .request<Folder::Icon>() | ||
105 | .request<Folder::Parent>() | ||
106 | .request<Folder::SpecialPurpose>(); | ||
107 | query.requestTree<Folder::Parent>(); | ||
108 | query.setId("foldertree" + account); | ||
109 | runQuery(query); | ||
110 | } | ||
111 | |||
112 | static int getPriority(const Sink::ApplicationDomain::Folder &folder) | ||
113 | { | ||
114 | auto specialPurpose = folder.getSpecialPurpose(); | ||
115 | if (specialPurpose.contains(Sink::ApplicationDomain::SpecialPurpose::Mail::inbox)) { | ||
116 | return 5; | ||
117 | } else if (specialPurpose.contains(Sink::ApplicationDomain::SpecialPurpose::Mail::drafts)) { | ||
118 | return 6; | ||
119 | } else if (specialPurpose.contains(Sink::ApplicationDomain::SpecialPurpose::Mail::sent)) { | ||
120 | return 7; | ||
121 | } else if (specialPurpose.contains(Sink::ApplicationDomain::SpecialPurpose::Mail::trash)) { | ||
122 | return 8; | ||
123 | } else if (!specialPurpose.isEmpty()) { | ||
124 | return 9; | ||
125 | } | ||
126 | return 10; | ||
127 | } | ||
128 | |||
129 | bool FolderListModel::lessThan(const QModelIndex &left, const QModelIndex &right) const | ||
130 | { | ||
131 | const auto leftFolder = left.data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Folder::Ptr>(); | ||
132 | const auto rightFolder = right.data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Folder::Ptr>(); | ||
133 | const auto leftPriority = getPriority(*leftFolder); | ||
134 | const auto rightPriority = getPriority(*rightFolder); | ||
135 | if (leftPriority == rightPriority) { | ||
136 | return leftFolder->getName() < rightFolder->getName(); | ||
137 | } | ||
138 | return leftPriority < rightPriority; | ||
139 | } | ||
140 | |||
141 | QVariant FolderListModel::accountId() const | ||
142 | { | ||
143 | return QVariant(); | ||
144 | } | ||
145 | |||
diff --git a/framework/src/domain/folderlistmodel.h b/framework/src/domain/folderlistmodel.h new file mode 100644 index 00000000..17645bb5 --- /dev/null +++ b/framework/src/domain/folderlistmodel.h | |||
@@ -0,0 +1,71 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Michael Bohlender <michael.bohlender@kdemail.net> | ||
3 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
4 | |||
5 | This library is free software; you can redistribute it and/or modify it | ||
6 | under the terms of the GNU Library General Public License as published by | ||
7 | the Free Software Foundation; either version 2 of the License, or (at your | ||
8 | option) any later version. | ||
9 | |||
10 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
12 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
13 | License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU Library General Public License | ||
16 | along with this library; see the file COPYING.LIB. If not, write to the | ||
17 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
18 | 02110-1301, USA. | ||
19 | */ | ||
20 | |||
21 | #pragma once | ||
22 | |||
23 | #include <QObject> | ||
24 | #include <QSortFilterProxyModel> | ||
25 | #include <QSharedPointer> | ||
26 | #include <QStringList> | ||
27 | |||
28 | namespace Sink { | ||
29 | class Query; | ||
30 | } | ||
31 | |||
32 | class FolderListModel : public QSortFilterProxyModel | ||
33 | { | ||
34 | Q_OBJECT | ||
35 | |||
36 | Q_PROPERTY (QVariant accountId READ accountId WRITE setAccountId) | ||
37 | |||
38 | public: | ||
39 | enum Status { | ||
40 | NoStatus, | ||
41 | InProgressStatus, | ||
42 | ErrorStatus, | ||
43 | SuccessStatus, | ||
44 | }; | ||
45 | Q_ENUMS(Status) | ||
46 | |||
47 | FolderListModel(QObject *parent = Q_NULLPTR); | ||
48 | ~FolderListModel(); | ||
49 | |||
50 | QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; | ||
51 | |||
52 | enum Roles { | ||
53 | Name = Qt::UserRole + 1, | ||
54 | Icon, | ||
55 | Id, | ||
56 | DomainObject, | ||
57 | Status | ||
58 | }; | ||
59 | Q_ENUMS(Roles) | ||
60 | |||
61 | QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE; | ||
62 | |||
63 | void setAccountId(const QVariant &accountId); | ||
64 | QVariant accountId() const; | ||
65 | protected: | ||
66 | bool lessThan(const QModelIndex &left, const QModelIndex &right) const Q_DECL_OVERRIDE; | ||
67 | |||
68 | private: | ||
69 | void runQuery(const Sink::Query &query); | ||
70 | QSharedPointer<QAbstractItemModel> mModel; | ||
71 | }; | ||
diff --git a/framework/src/domain/identitiesmodel.cpp b/framework/src/domain/identitiesmodel.cpp new file mode 100644 index 00000000..0d97fc6a --- /dev/null +++ b/framework/src/domain/identitiesmodel.cpp | |||
@@ -0,0 +1,99 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #include "identitiesmodel.h" | ||
20 | #include <sink/store.h> | ||
21 | #include <sink/log.h> | ||
22 | |||
23 | using namespace Sink; | ||
24 | |||
25 | IdentitiesModel::IdentitiesModel(QObject *parent) : QIdentityProxyModel() | ||
26 | { | ||
27 | Sink::Query query; | ||
28 | query.setFlags(Sink::Query::LiveQuery); | ||
29 | query.request<Sink::ApplicationDomain::Identity::Name>() | ||
30 | .request<Sink::ApplicationDomain::Identity::Address>() | ||
31 | .request<Sink::ApplicationDomain::Identity::Account>(); | ||
32 | runQuery(query); | ||
33 | } | ||
34 | |||
35 | IdentitiesModel::~IdentitiesModel() | ||
36 | { | ||
37 | |||
38 | } | ||
39 | |||
40 | QHash< int, QByteArray > IdentitiesModel::roleNames() const | ||
41 | { | ||
42 | QHash<int, QByteArray> roles; | ||
43 | |||
44 | roles[Name] = "name"; | ||
45 | roles[Username] = "username"; | ||
46 | roles[Address] = "address"; | ||
47 | roles[IdentityId] = "identityId"; | ||
48 | roles[AccountId] = "accountId"; | ||
49 | roles[AccountName] = "accountName"; | ||
50 | roles[AccountIcon] = "accountIcon"; | ||
51 | roles[DisplayName] = "displayName"; | ||
52 | |||
53 | return roles; | ||
54 | } | ||
55 | |||
56 | QVariant IdentitiesModel::data(const QModelIndex &idx, int role) const | ||
57 | { | ||
58 | auto srcIdx = mapToSource(idx); | ||
59 | switch (role) { | ||
60 | case Name: | ||
61 | return srcIdx.data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Identity::Ptr>()->getName(); | ||
62 | case Username: | ||
63 | return srcIdx.data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Identity::Ptr>()->getName(); | ||
64 | case Address: | ||
65 | return srcIdx.data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Identity::Ptr>()->getAddress(); | ||
66 | case IdentityId: | ||
67 | return srcIdx.data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Identity::Ptr>()->identifier(); | ||
68 | case AccountId: | ||
69 | return srcIdx.data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Identity::Ptr>()->getAccount(); | ||
70 | case AccountName: { | ||
71 | const auto accountId = srcIdx.data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Identity::Ptr>()->getAccount(); | ||
72 | return mAccountNames.value(accountId); | ||
73 | } | ||
74 | case AccountIcon: { | ||
75 | const auto accountId = srcIdx.data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Identity::Ptr>()->getAccount(); | ||
76 | return mAccountIcons.value(accountId); | ||
77 | } | ||
78 | case DisplayName: { | ||
79 | return data(idx, AccountName).toString() + ": " + data(idx, Username).toString() + ", " + data(idx, Address).toString(); | ||
80 | } | ||
81 | } | ||
82 | return QIdentityProxyModel::data(idx, role); | ||
83 | } | ||
84 | |||
85 | void IdentitiesModel::runQuery(const Sink::Query &query) | ||
86 | { | ||
87 | using namespace Sink::ApplicationDomain; | ||
88 | mModel = Sink::Store::loadModel<Identity>(query); | ||
89 | setSourceModel(mModel.data()); | ||
90 | |||
91 | Sink::Store::fetchAll<SinkAccount>(Sink::Query{}.request<SinkAccount::Icon>().request<SinkAccount::Name>()) | ||
92 | .then([this](const QList<SinkAccount::Ptr> &accounts) { | ||
93 | for (const auto &account : accounts) { | ||
94 | mAccountNames.insert(account->identifier(), account->getName()); | ||
95 | mAccountIcons.insert(account->identifier(), account->getIcon()); | ||
96 | } | ||
97 | emit layoutChanged(); | ||
98 | }).exec(); | ||
99 | } | ||
diff --git a/framework/src/domain/identitiesmodel.h b/framework/src/domain/identitiesmodel.h new file mode 100644 index 00000000..9b172251 --- /dev/null +++ b/framework/src/domain/identitiesmodel.h | |||
@@ -0,0 +1,60 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | |||
20 | #pragma once | ||
21 | |||
22 | #include <QObject> | ||
23 | #include <QIdentityProxyModel> | ||
24 | #include <QSharedPointer> | ||
25 | #include <QStringList> | ||
26 | |||
27 | namespace Sink { | ||
28 | class Query; | ||
29 | } | ||
30 | |||
31 | class IdentitiesModel : public QIdentityProxyModel | ||
32 | { | ||
33 | Q_OBJECT | ||
34 | |||
35 | public: | ||
36 | IdentitiesModel(QObject *parent = Q_NULLPTR); | ||
37 | ~IdentitiesModel(); | ||
38 | |||
39 | QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; | ||
40 | |||
41 | enum Roles { | ||
42 | Name = Qt::UserRole + 1, | ||
43 | Username, | ||
44 | Address, | ||
45 | IdentityId, | ||
46 | AccountId, | ||
47 | AccountName, | ||
48 | AccountIcon, | ||
49 | DisplayName | ||
50 | }; | ||
51 | Q_ENUMS(Roles) | ||
52 | |||
53 | QHash<int, QByteArray> roleNames() const; | ||
54 | |||
55 | private: | ||
56 | void runQuery(const Sink::Query &query); | ||
57 | QSharedPointer<QAbstractItemModel> mModel; | ||
58 | QHash <QByteArray, QString> mAccountNames; | ||
59 | QHash <QByteArray, QString> mAccountIcons; | ||
60 | }; | ||
diff --git a/framework/src/domain/mailcontroller.cpp b/framework/src/domain/mailcontroller.cpp new file mode 100644 index 00000000..b912567a --- /dev/null +++ b/framework/src/domain/mailcontroller.cpp | |||
@@ -0,0 +1,125 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #include "mailcontroller.h" | ||
20 | |||
21 | #include <sink/store.h> | ||
22 | #include <sink/log.h> | ||
23 | #include <sink/standardqueries.h> | ||
24 | |||
25 | SINK_DEBUG_AREA("mailcontroller"); | ||
26 | |||
27 | using namespace Sink; | ||
28 | using namespace Sink::ApplicationDomain; | ||
29 | |||
30 | MailController::MailController() | ||
31 | : Kube::Controller(), | ||
32 | action_markAsRead{new Kube::ControllerAction{this, &MailController::markAsRead}}, | ||
33 | action_markAsUnread{new Kube::ControllerAction{this, &MailController::markAsUnread}}, | ||
34 | action_markAsImportant{new Kube::ControllerAction{this, &MailController::markAsImportant}}, | ||
35 | action_moveToTrash{new Kube::ControllerAction{this, &MailController::moveToTrash}}, | ||
36 | action_restoreFromTrash{new Kube::ControllerAction{this, &MailController::restoreFromTrash}}, | ||
37 | action_remove{new Kube::ControllerAction{this, &MailController::remove}}, | ||
38 | action_moveToFolder{new Kube::ControllerAction{this, &MailController::moveToFolder}} | ||
39 | { | ||
40 | QObject::connect(this, &MailController::mailChanged, &MailController::updateActions); | ||
41 | updateActions(); | ||
42 | } | ||
43 | |||
44 | void MailController::runModification(const std::function<void(ApplicationDomain::Mail &)> &f) | ||
45 | { | ||
46 | if (auto mail = getMail()) { | ||
47 | f(*mail); | ||
48 | run(Store::modify(*mail)); | ||
49 | } else if (auto mail = getThreadLeader()) { | ||
50 | f(*mail); | ||
51 | run(Store::modify(Sink::StandardQueries::completeThread(*mail), *mail)); | ||
52 | } | ||
53 | } | ||
54 | |||
55 | void MailController::updateActions() | ||
56 | { | ||
57 | auto mail = getMail(); | ||
58 | if (!mail) { | ||
59 | mail= getThreadLeader(); | ||
60 | } | ||
61 | if (mail) { | ||
62 | action_moveToTrash->setEnabled(!mail->getTrash()); | ||
63 | action_restoreFromTrash->setEnabled(mail->getTrash()); | ||
64 | action_markAsRead->setEnabled(mail->getUnread()); | ||
65 | action_markAsUnread->setEnabled(!mail->getUnread()); | ||
66 | } | ||
67 | } | ||
68 | |||
69 | void MailController::markAsRead() | ||
70 | { | ||
71 | runModification([] (ApplicationDomain::Mail &mail) { | ||
72 | mail.setUnread(false); | ||
73 | SinkLog() << "Mark as read " << mail.identifier(); | ||
74 | }); | ||
75 | } | ||
76 | |||
77 | void MailController::markAsUnread() | ||
78 | { | ||
79 | runModification([] (ApplicationDomain::Mail &mail) { | ||
80 | mail.setUnread(true); | ||
81 | SinkLog() << "Mark as unread " << mail.identifier(); | ||
82 | }); | ||
83 | } | ||
84 | |||
85 | void MailController::markAsImportant() | ||
86 | { | ||
87 | runModification([] (ApplicationDomain::Mail &mail) { | ||
88 | mail.setImportant(true); | ||
89 | SinkLog() << "Mark as important " << mail.identifier(); | ||
90 | }); | ||
91 | } | ||
92 | |||
93 | void MailController::moveToTrash() | ||
94 | { | ||
95 | runModification([] (ApplicationDomain::Mail &mail) { | ||
96 | mail.setTrash(true); | ||
97 | SinkLog() << "Move to trash " << mail.identifier(); | ||
98 | }); | ||
99 | } | ||
100 | |||
101 | void MailController::restoreFromTrash() | ||
102 | { | ||
103 | runModification([] (ApplicationDomain::Mail &mail) { | ||
104 | mail.setTrash(false); | ||
105 | SinkLog() << "Restore from trash " << mail.identifier(); | ||
106 | }); | ||
107 | } | ||
108 | |||
109 | void MailController::remove() | ||
110 | { | ||
111 | runModification([] (ApplicationDomain::Mail &mail) { | ||
112 | mail.setTrash(true); | ||
113 | SinkLog() << "Remove " << mail.identifier(); | ||
114 | }); | ||
115 | } | ||
116 | |||
117 | void MailController::moveToFolder() | ||
118 | { | ||
119 | runModification([&] (ApplicationDomain::Mail &mail) { | ||
120 | auto targetFolder = getTargetFolder(); | ||
121 | mail.setFolder(*targetFolder); | ||
122 | SinkLog() << "Moving to folder " << mail.identifier() << targetFolder->identifier(); | ||
123 | }); | ||
124 | } | ||
125 | |||
diff --git a/framework/src/domain/mailcontroller.h b/framework/src/domain/mailcontroller.h new file mode 100644 index 00000000..ae0f32d5 --- /dev/null +++ b/framework/src/domain/mailcontroller.h | |||
@@ -0,0 +1,44 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #pragma once | ||
20 | |||
21 | #include <QObject> | ||
22 | #include "controller.h" | ||
23 | #include "sink/applicationdomaintype.h" | ||
24 | |||
25 | class MailController : public Kube::Controller | ||
26 | { | ||
27 | Q_OBJECT | ||
28 | KUBE_CONTROLLER_PROPERTY(Sink::ApplicationDomain::Mail::Ptr, Mail, mail) | ||
29 | KUBE_CONTROLLER_PROPERTY(Sink::ApplicationDomain::Mail::Ptr, ThreadLeader, threadLeader) | ||
30 | KUBE_CONTROLLER_PROPERTY(Sink::ApplicationDomain::Folder::Ptr, TargetFolder, targetFolder) | ||
31 | KUBE_CONTROLLER_ACTION(markAsRead) | ||
32 | KUBE_CONTROLLER_ACTION(markAsUnread) | ||
33 | KUBE_CONTROLLER_ACTION(markAsImportant) | ||
34 | KUBE_CONTROLLER_ACTION(moveToTrash) | ||
35 | KUBE_CONTROLLER_ACTION(restoreFromTrash) | ||
36 | KUBE_CONTROLLER_ACTION(remove) | ||
37 | KUBE_CONTROLLER_ACTION(moveToFolder) | ||
38 | |||
39 | public: | ||
40 | explicit MailController(); | ||
41 | private slots: | ||
42 | void updateActions(); | ||
43 | void runModification(const std::function<void(Sink::ApplicationDomain::Mail &)> &f); | ||
44 | }; | ||
diff --git a/framework/src/domain/maillistmodel.cpp b/framework/src/domain/maillistmodel.cpp new file mode 100644 index 00000000..83340f69 --- /dev/null +++ b/framework/src/domain/maillistmodel.cpp | |||
@@ -0,0 +1,265 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Michael Bohlender <michael.bohlender@kdemail.net> | ||
3 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
4 | |||
5 | This library is free software; you can redistribute it and/or modify it | ||
6 | under the terms of the GNU Library General Public License as published by | ||
7 | the Free Software Foundation; either version 2 of the License, or (at your | ||
8 | option) any later version. | ||
9 | |||
10 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
12 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
13 | License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU Library General Public License | ||
16 | along with this library; see the file COPYING.LIB. If not, write to the | ||
17 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
18 | 02110-1301, USA. | ||
19 | */ | ||
20 | |||
21 | #include "maillistmodel.h" | ||
22 | |||
23 | #include <sink/standardqueries.h> | ||
24 | |||
25 | MailListModel::MailListModel(QObject *parent) | ||
26 | : QSortFilterProxyModel() | ||
27 | { | ||
28 | setDynamicSortFilter(true); | ||
29 | sort(0, Qt::DescendingOrder); | ||
30 | setFilterCaseSensitivity(Qt::CaseInsensitive); | ||
31 | } | ||
32 | |||
33 | MailListModel::~MailListModel() | ||
34 | { | ||
35 | |||
36 | } | ||
37 | |||
38 | void MailListModel::setFilter(const QString &filter) | ||
39 | { | ||
40 | setFilterWildcard(filter); | ||
41 | } | ||
42 | |||
43 | QString MailListModel::filter() const | ||
44 | { | ||
45 | return {}; | ||
46 | } | ||
47 | |||
48 | QHash< int, QByteArray > MailListModel::roleNames() const | ||
49 | { | ||
50 | QHash<int, QByteArray> roles; | ||
51 | |||
52 | roles[Subject] = "subject"; | ||
53 | roles[Sender] = "sender"; | ||
54 | roles[SenderName] = "senderName"; | ||
55 | roles[To] = "to"; | ||
56 | roles[Cc] = "cc"; | ||
57 | roles[Bcc] = "bcc"; | ||
58 | roles[Date] = "date"; | ||
59 | roles[Unread] = "unread"; | ||
60 | roles[Important] = "important"; | ||
61 | roles[Draft] = "draft"; | ||
62 | roles[Sent] = "sent"; | ||
63 | roles[Trash] = "trash"; | ||
64 | roles[Id] = "id"; | ||
65 | roles[MimeMessage] = "mimeMessage"; | ||
66 | roles[DomainObject] = "domainObject"; | ||
67 | roles[ThreadSize] = "threadSize"; | ||
68 | roles[Mail] = "mail"; | ||
69 | roles[Incomplete] = "incomplete"; | ||
70 | roles[Status] = "status"; | ||
71 | |||
72 | return roles; | ||
73 | } | ||
74 | |||
75 | static QString join(const QList<Sink::ApplicationDomain::Mail::Contact> &contacts) | ||
76 | { | ||
77 | QStringList list; | ||
78 | for (const auto &contact : contacts) { | ||
79 | if (!contact.name.isEmpty()) { | ||
80 | list << QString("%1 <%2>").arg(contact.name).arg(contact.emailAddress); | ||
81 | } else { | ||
82 | list << contact.emailAddress; | ||
83 | } | ||
84 | } | ||
85 | return list.join(", "); | ||
86 | } | ||
87 | |||
88 | void MailListModel::fetchMail(Sink::ApplicationDomain::Mail::Ptr mail) | ||
89 | { | ||
90 | if (mail && !mail->getFullPayloadAvailable() && !mFetchedMails.contains(mail->identifier())) { | ||
91 | qDebug() << "Fetching mail: " << mail->identifier() << mail->getSubject(); | ||
92 | mFetchedMails.insert(mail->identifier()); | ||
93 | Sink::Store::synchronize(Sink::SyncScope{*mail}).exec(); | ||
94 | } | ||
95 | } | ||
96 | |||
97 | QVariant MailListModel::data(const QModelIndex &idx, int role) const | ||
98 | { | ||
99 | auto srcIdx = mapToSource(idx); | ||
100 | auto mail = srcIdx.data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Mail::Ptr>(); | ||
101 | switch (role) { | ||
102 | case Subject: | ||
103 | return mail->getSubject(); | ||
104 | case Sender: | ||
105 | return mail->getSender().emailAddress; | ||
106 | case SenderName: | ||
107 | return mail->getSender().name; | ||
108 | case To: | ||
109 | return join(mail->getTo()); | ||
110 | case Cc: | ||
111 | return join(mail->getCc()); | ||
112 | case Bcc: | ||
113 | return join(mail->getBcc()); | ||
114 | case Date: | ||
115 | return mail->getDate(); | ||
116 | case Unread: | ||
117 | return mail->getProperty("unreadCollected").toList().contains(true); | ||
118 | case Important: | ||
119 | return mail->getProperty("importantCollected").toList().contains(true); | ||
120 | case Draft: | ||
121 | return mail->getDraft(); | ||
122 | case Sent: | ||
123 | return mail->getSent(); | ||
124 | case Trash: | ||
125 | return mail->getTrash(); | ||
126 | case Id: | ||
127 | return mail->identifier(); | ||
128 | case DomainObject: | ||
129 | return QVariant::fromValue(mail); | ||
130 | case MimeMessage: | ||
131 | if (mFetchMails) { | ||
132 | const_cast<MailListModel*>(this)->fetchMail(mail); | ||
133 | } | ||
134 | return mail->getMimeMessage(); | ||
135 | case ThreadSize: | ||
136 | return mail->getProperty("count").toInt(); | ||
137 | case Mail: | ||
138 | return QVariant::fromValue(mail); | ||
139 | case Incomplete: | ||
140 | return !mail->getFullPayloadAvailable(); | ||
141 | case Status: | ||
142 | const auto status = srcIdx.data(Sink::Store::StatusRole).toInt(); | ||
143 | if (status == Sink::ApplicationDomain::SyncStatus::SyncInProgress) { | ||
144 | return InProgressStatus; | ||
145 | } | ||
146 | if (status == Sink::ApplicationDomain::SyncStatus::SyncError) { | ||
147 | return ErrorStatus; | ||
148 | } | ||
149 | return NoStatus; | ||
150 | } | ||
151 | return QSortFilterProxyModel::data(idx, role); | ||
152 | } | ||
153 | |||
154 | bool MailListModel::lessThan(const QModelIndex &left, const QModelIndex &right) const | ||
155 | { | ||
156 | const auto leftDate = left.data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Mail::Ptr>()->getDate(); | ||
157 | const auto rightDate = right.data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Mail::Ptr>()->getDate(); | ||
158 | return leftDate < rightDate; | ||
159 | } | ||
160 | |||
161 | bool MailListModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const | ||
162 | { | ||
163 | auto idx = sourceModel()->index(sourceRow, 0, sourceParent); | ||
164 | auto regExp = filterRegExp(); | ||
165 | if (regExp.isEmpty()) { | ||
166 | return true; | ||
167 | } | ||
168 | auto mail = idx.data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Mail::Ptr>(); | ||
169 | return mail->getSubject().contains(regExp) || | ||
170 | mail->getSender().name.contains(regExp); | ||
171 | } | ||
172 | |||
173 | void MailListModel::runQuery(const Sink::Query &query) | ||
174 | { | ||
175 | m_model = Sink::Store::loadModel<Sink::ApplicationDomain::Mail>(query); | ||
176 | setSourceModel(m_model.data()); | ||
177 | } | ||
178 | |||
179 | void MailListModel::setParentFolder(const QVariant &parentFolder) | ||
180 | { | ||
181 | using namespace Sink::ApplicationDomain; | ||
182 | auto folder = parentFolder.value<Folder::Ptr>(); | ||
183 | if (!folder) { | ||
184 | mCurrentQueryItem.clear(); | ||
185 | setSourceModel(nullptr); | ||
186 | return; | ||
187 | } | ||
188 | if (mCurrentQueryItem == folder->identifier()) { | ||
189 | return; | ||
190 | } | ||
191 | mCurrentQueryItem = folder->identifier(); | ||
192 | Sink::Query query = Sink::StandardQueries::threadLeaders(*folder); | ||
193 | if (!folder->getSpecialPurpose().contains(Sink::ApplicationDomain::SpecialPurpose::Mail::trash)) { | ||
194 | //Filter trash if this is not a trash folder | ||
195 | query.filter<Sink::ApplicationDomain::Mail::Trash>(false); | ||
196 | } | ||
197 | query.setFlags(Sink::Query::LiveQuery); | ||
198 | query.limit(100); | ||
199 | query.request<Mail::Subject>(); | ||
200 | query.request<Mail::Sender>(); | ||
201 | query.request<Mail::To>(); | ||
202 | query.request<Mail::Cc>(); | ||
203 | query.request<Mail::Bcc>(); | ||
204 | query.request<Mail::Date>(); | ||
205 | query.request<Mail::Unread>(); | ||
206 | query.request<Mail::Important>(); | ||
207 | query.request<Mail::Draft>(); | ||
208 | query.request<Mail::Sent>(); | ||
209 | query.request<Mail::Trash>(); | ||
210 | query.request<Mail::Folder>(); | ||
211 | mFetchMails = false; | ||
212 | qDebug() << "Running folder query: " << folder->resourceInstanceIdentifier() << folder->identifier(); | ||
213 | //Latest mail on top | ||
214 | sort(0, Qt::DescendingOrder); | ||
215 | runQuery(query); | ||
216 | } | ||
217 | |||
218 | QVariant MailListModel::parentFolder() const | ||
219 | { | ||
220 | return QVariant(); | ||
221 | } | ||
222 | |||
223 | void MailListModel::setMail(const QVariant &variant) | ||
224 | { | ||
225 | using namespace Sink::ApplicationDomain; | ||
226 | auto mail = variant.value<Sink::ApplicationDomain::Mail::Ptr>(); | ||
227 | if (!mail) { | ||
228 | mCurrentQueryItem.clear(); | ||
229 | setSourceModel(nullptr); | ||
230 | return; | ||
231 | } | ||
232 | if (mCurrentQueryItem == mail->identifier()) { | ||
233 | return; | ||
234 | } | ||
235 | mCurrentQueryItem = mail->identifier(); | ||
236 | Sink::Query query = Sink::StandardQueries::completeThread(*mail); | ||
237 | query.setFlags(Sink::Query::LiveQuery | Sink::Query::UpdateStatus); | ||
238 | query.request<Mail::Subject>(); | ||
239 | query.request<Mail::Sender>(); | ||
240 | query.request<Mail::To>(); | ||
241 | query.request<Mail::Cc>(); | ||
242 | query.request<Mail::Bcc>(); | ||
243 | query.request<Mail::Date>(); | ||
244 | query.request<Mail::Unread>(); | ||
245 | query.request<Mail::Important>(); | ||
246 | query.request<Mail::Draft>(); | ||
247 | query.request<Mail::Folder>(); | ||
248 | query.request<Mail::Sent>(); | ||
249 | query.request<Mail::Trash>(); | ||
250 | query.request<Mail::MimeMessage>(); | ||
251 | query.request<Mail::FullPayloadAvailable>(); | ||
252 | mFetchMails = true; | ||
253 | mFetchedMails.clear(); | ||
254 | qDebug() << "Running mail query: " << mail->resourceInstanceIdentifier() << mail->identifier(); | ||
255 | //Latest mail at the bottom | ||
256 | sort(0, Qt::AscendingOrder); | ||
257 | runQuery(query); | ||
258 | } | ||
259 | |||
260 | QVariant MailListModel::mail() const | ||
261 | { | ||
262 | return QVariant(); | ||
263 | } | ||
264 | |||
265 | |||
diff --git a/framework/src/domain/maillistmodel.h b/framework/src/domain/maillistmodel.h new file mode 100644 index 00000000..44347661 --- /dev/null +++ b/framework/src/domain/maillistmodel.h | |||
@@ -0,0 +1,94 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Michael Bohlender <michael.bohlender@kdemail.net> | ||
3 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
4 | |||
5 | This library is free software; you can redistribute it and/or modify it | ||
6 | under the terms of the GNU Library General Public License as published by | ||
7 | the Free Software Foundation; either version 2 of the License, or (at your | ||
8 | option) any later version. | ||
9 | |||
10 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
12 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
13 | License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU Library General Public License | ||
16 | along with this library; see the file COPYING.LIB. If not, write to the | ||
17 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
18 | 02110-1301, USA. | ||
19 | */ | ||
20 | |||
21 | #pragma once | ||
22 | |||
23 | #include <sink/store.h> | ||
24 | |||
25 | #include <QSortFilterProxyModel> | ||
26 | #include <QSharedPointer> | ||
27 | #include <QStringList> | ||
28 | |||
29 | class MailListModel : public QSortFilterProxyModel | ||
30 | { | ||
31 | Q_OBJECT | ||
32 | Q_PROPERTY (QVariant parentFolder READ parentFolder WRITE setParentFolder) | ||
33 | Q_PROPERTY (QVariant mail READ mail WRITE setMail) | ||
34 | Q_PROPERTY (QString filter READ filter WRITE setFilter) | ||
35 | |||
36 | public: | ||
37 | enum Status { | ||
38 | NoStatus, | ||
39 | InProgressStatus, | ||
40 | ErrorStatus | ||
41 | }; | ||
42 | Q_ENUMS(Status) | ||
43 | |||
44 | MailListModel(QObject *parent = Q_NULLPTR); | ||
45 | ~MailListModel(); | ||
46 | |||
47 | QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; | ||
48 | |||
49 | bool lessThan(const QModelIndex &left, const QModelIndex &right) const Q_DECL_OVERRIDE; | ||
50 | bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const Q_DECL_OVERRIDE; | ||
51 | |||
52 | enum Roles { | ||
53 | Subject = Qt::UserRole + 1, | ||
54 | Sender, | ||
55 | SenderName, | ||
56 | To, | ||
57 | Cc, | ||
58 | Bcc, | ||
59 | Date, | ||
60 | Unread, | ||
61 | Important, | ||
62 | Draft, | ||
63 | Sent, | ||
64 | Trash, | ||
65 | Id, | ||
66 | MimeMessage, | ||
67 | DomainObject, | ||
68 | ThreadSize, | ||
69 | Mail, | ||
70 | Incomplete, | ||
71 | Status | ||
72 | }; | ||
73 | |||
74 | QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE; | ||
75 | |||
76 | void runQuery(const Sink::Query &query); | ||
77 | |||
78 | void setParentFolder(const QVariant &parentFolder); | ||
79 | QVariant parentFolder() const; | ||
80 | |||
81 | void setMail(const QVariant &mail); | ||
82 | QVariant mail() const; | ||
83 | |||
84 | void setFilter(const QString &mail); | ||
85 | QString filter() const; | ||
86 | |||
87 | private: | ||
88 | void fetchMail(Sink::ApplicationDomain::Mail::Ptr mail); | ||
89 | |||
90 | QSharedPointer<QAbstractItemModel> m_model; | ||
91 | bool mFetchMails = false; | ||
92 | QSet<QByteArray> mFetchedMails; | ||
93 | QByteArray mCurrentQueryItem; | ||
94 | }; | ||
diff --git a/framework/src/domain/mailtemplates.cpp b/framework/src/domain/mailtemplates.cpp new file mode 100644 index 00000000..6af381d2 --- /dev/null +++ b/framework/src/domain/mailtemplates.cpp | |||
@@ -0,0 +1,801 @@ | |||
1 | /* | ||
2 | Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com | ||
3 | Copyright (c) 2010 Leo Franchi <lfranchi@kde.org> | ||
4 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
5 | |||
6 | This library is free software; you can redistribute it and/or modify it | ||
7 | under the terms of the GNU Library General Public License as published by | ||
8 | the Free Software Foundation; either version 2 of the License, or (at your | ||
9 | option) any later version. | ||
10 | |||
11 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
12 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
13 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
14 | License for more details. | ||
15 | |||
16 | You should have received a copy of the GNU Library General Public License | ||
17 | along with this library; see the file COPYING.LIB. If not, write to the | ||
18 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
19 | 02110-1301, USA. | ||
20 | */ | ||
21 | #include "mailtemplates.h" | ||
22 | |||
23 | #include <QByteArray> | ||
24 | #include <QList> | ||
25 | #include <QDebug> | ||
26 | #include <QImage> | ||
27 | #include <QWebPage> | ||
28 | #include <QWebFrame> | ||
29 | #include <QSysInfo> | ||
30 | #include <QTextCodec> | ||
31 | #include <QApplication> | ||
32 | |||
33 | #include <KCodecs/KCharsets> | ||
34 | #include <KMime/Types> | ||
35 | |||
36 | #include "stringhtmlwriter.h" | ||
37 | #include "objecttreesource.h" | ||
38 | |||
39 | #include <MimeTreeParser/ObjectTreeParser> | ||
40 | |||
41 | namespace KMime { | ||
42 | namespace Types { | ||
43 | static bool operator==(const KMime::Types::AddrSpec &left, const KMime::Types::AddrSpec &right) | ||
44 | { | ||
45 | return (left.asString() == right.asString()); | ||
46 | } | ||
47 | |||
48 | static bool operator==(const KMime::Types::Mailbox &left, const KMime::Types::Mailbox &right) | ||
49 | { | ||
50 | return (left.addrSpec().asString() == right.addrSpec().asString()); | ||
51 | } | ||
52 | } | ||
53 | } | ||
54 | |||
55 | static KMime::Types::Mailbox::List stripMyAddressesFromAddressList(const KMime::Types::Mailbox::List &list, const KMime::Types::AddrSpecList me) | ||
56 | { | ||
57 | KMime::Types::Mailbox::List addresses(list); | ||
58 | for (KMime::Types::Mailbox::List::Iterator it = addresses.begin(); it != addresses.end();) { | ||
59 | if (me.contains(it->addrSpec())) { | ||
60 | it = addresses.erase(it); | ||
61 | } else { | ||
62 | ++it; | ||
63 | } | ||
64 | } | ||
65 | |||
66 | return addresses; | ||
67 | } | ||
68 | |||
69 | void initHeader(const KMime::Message::Ptr &message) | ||
70 | { | ||
71 | message->removeHeader<KMime::Headers::To>(); | ||
72 | message->removeHeader<KMime::Headers::Subject>(); | ||
73 | message->date()->setDateTime(QDateTime::currentDateTime()); | ||
74 | |||
75 | const QStringList extraInfo = QStringList() << QSysInfo::prettyProductName(); | ||
76 | message->userAgent()->fromUnicodeString(QString("%1/%2(%3)").arg(QString::fromLocal8Bit("Kube")).arg("0.1").arg(extraInfo.join(",")), "utf-8"); | ||
77 | // This will allow to change Content-Type: | ||
78 | message->contentType()->setMimeType("text/plain"); | ||
79 | } | ||
80 | |||
81 | QString replacePrefixes(const QString &str, const QStringList &prefixRegExps, | ||
82 | bool replace, const QString &newPrefix) | ||
83 | { | ||
84 | bool recognized = false; | ||
85 | // construct a big regexp that | ||
86 | // 1. is anchored to the beginning of str (sans whitespace) | ||
87 | // 2. matches at least one of the part regexps in prefixRegExps | ||
88 | QString bigRegExp = QStringLiteral("^(?:\\s+|(?:%1))+\\s*").arg(prefixRegExps.join(QStringLiteral(")|(?:"))); | ||
89 | QRegExp rx(bigRegExp, Qt::CaseInsensitive); | ||
90 | if (rx.isValid()) { | ||
91 | QString tmp = str; | ||
92 | if (rx.indexIn(tmp) == 0) { | ||
93 | recognized = true; | ||
94 | if (replace) { | ||
95 | return tmp.replace(0, rx.matchedLength(), newPrefix + QLatin1String(" ")); | ||
96 | } | ||
97 | } | ||
98 | } else { | ||
99 | qWarning() << "bigRegExp = \"" | ||
100 | << bigRegExp << "\"\n" | ||
101 | << "prefix regexp is invalid!"; | ||
102 | // try good ole Re/Fwd: | ||
103 | recognized = str.startsWith(newPrefix); | ||
104 | } | ||
105 | |||
106 | if (!recognized) { | ||
107 | return newPrefix + QLatin1String(" ") + str; | ||
108 | } else { | ||
109 | return str; | ||
110 | } | ||
111 | } | ||
112 | |||
113 | QString cleanSubject(const KMime::Message::Ptr &msg, const QStringList &prefixRegExps, bool replace, const QString &newPrefix) | ||
114 | { | ||
115 | return replacePrefixes(msg->subject()->asUnicodeString(), prefixRegExps, replace, newPrefix); | ||
116 | } | ||
117 | |||
118 | QString forwardSubject(const KMime::Message::Ptr &msg) | ||
119 | { | ||
120 | bool replaceForwardPrefix = true; | ||
121 | QStringList forwardPrefixes; | ||
122 | forwardPrefixes << "Fwd:"; | ||
123 | forwardPrefixes << "FW:"; | ||
124 | return cleanSubject(msg, forwardPrefixes, replaceForwardPrefix, QStringLiteral("Fwd:")); | ||
125 | } | ||
126 | |||
127 | QString replySubject(const KMime::Message::Ptr &msg) | ||
128 | { | ||
129 | bool replaceReplyPrefix = true; | ||
130 | QStringList replyPrefixes; | ||
131 | //We're escaping the regex escape sequences. awesome | ||
132 | replyPrefixes << "Re\\\\s*:"; | ||
133 | replyPrefixes << "Re[\\\\d+\\\\]:"; | ||
134 | replyPrefixes << "Re\\\\d+:"; | ||
135 | return cleanSubject(msg, replyPrefixes, replaceReplyPrefix, QStringLiteral("Re:")); | ||
136 | } | ||
137 | |||
138 | QByteArray getRefStr(const KMime::Message::Ptr &msg) | ||
139 | { | ||
140 | QByteArray firstRef, lastRef, refStr, retRefStr; | ||
141 | int i, j; | ||
142 | |||
143 | if (auto hdr = msg->references(false)) { | ||
144 | refStr = hdr->as7BitString(false).trimmed(); | ||
145 | } | ||
146 | |||
147 | if (refStr.isEmpty()) { | ||
148 | return msg->messageID()->as7BitString(false); | ||
149 | } | ||
150 | |||
151 | i = refStr.indexOf('<'); | ||
152 | j = refStr.indexOf('>'); | ||
153 | firstRef = refStr.mid(i, j - i + 1); | ||
154 | if (!firstRef.isEmpty()) { | ||
155 | retRefStr = firstRef + ' '; | ||
156 | } | ||
157 | |||
158 | i = refStr.lastIndexOf('<'); | ||
159 | j = refStr.lastIndexOf('>'); | ||
160 | |||
161 | lastRef = refStr.mid(i, j - i + 1); | ||
162 | if (!lastRef.isEmpty() && lastRef != firstRef) { | ||
163 | retRefStr += lastRef + ' '; | ||
164 | } | ||
165 | |||
166 | retRefStr += msg->messageID()->as7BitString(false); | ||
167 | return retRefStr; | ||
168 | } | ||
169 | |||
170 | KMime::Content *createPlainPartContent(const KMime::Message::Ptr &msg, const QString &plainBody) | ||
171 | { | ||
172 | KMime::Content *textPart = new KMime::Content(msg.data()); | ||
173 | textPart->contentType()->setMimeType("text/plain"); | ||
174 | //FIXME This is supposed to select a charset out of the available charsets that contains all necessary characters to render the text | ||
175 | // QTextCodec *charset = selectCharset(m_charsets, plainBody); | ||
176 | // textPart->contentType()->setCharset(charset->name()); | ||
177 | textPart->contentType()->setCharset("utf-8"); | ||
178 | textPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE8Bit); | ||
179 | textPart->fromUnicodeString(plainBody); | ||
180 | return textPart; | ||
181 | } | ||
182 | |||
183 | KMime::Content *createMultipartAlternativeContent(const KMime::Message::Ptr &msg, const QString &plainBody, const QString &htmlBody) | ||
184 | { | ||
185 | KMime::Content *multipartAlternative = new KMime::Content(msg.data()); | ||
186 | multipartAlternative->contentType()->setMimeType("multipart/alternative"); | ||
187 | const QByteArray boundary = KMime::multiPartBoundary(); | ||
188 | multipartAlternative->contentType()->setBoundary(boundary); | ||
189 | |||
190 | KMime::Content *textPart = createPlainPartContent(msg, plainBody); | ||
191 | multipartAlternative->addContent(textPart); | ||
192 | |||
193 | KMime::Content *htmlPart = new KMime::Content(msg.data()); | ||
194 | htmlPart->contentType()->setMimeType("text/html"); | ||
195 | //FIXME This is supposed to select a charset out of the available charsets that contains all necessary characters to render the text | ||
196 | // QTextCodec *charset = selectCharset(m_charsets, htmlBody); | ||
197 | // htmlPart->contentType()->setCharset(charset->name()); | ||
198 | textPart->contentType()->setCharset("utf-8"); | ||
199 | htmlPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE8Bit); | ||
200 | htmlPart->fromUnicodeString(htmlBody); | ||
201 | multipartAlternative->addContent(htmlPart); | ||
202 | |||
203 | return multipartAlternative; | ||
204 | } | ||
205 | |||
206 | void addProcessedBodyToMessage(const KMime::Message::Ptr &msg, const QString &plainBody, const QString &htmlBody, bool forward) | ||
207 | { | ||
208 | //FIXME | ||
209 | // MessageCore::ImageCollector ic; | ||
210 | // ic.collectImagesFrom(mOrigMsg.data()); | ||
211 | |||
212 | // Now, delete the old content and set the new content, which | ||
213 | // is either only the new text or the new text with some attachments. | ||
214 | auto parts = msg->contents(); | ||
215 | foreach (KMime::Content *content, parts) { | ||
216 | msg->removeContent(content, true/*delete*/); | ||
217 | } | ||
218 | |||
219 | msg->contentType()->clear(); // to get rid of old boundary | ||
220 | |||
221 | const QByteArray boundary = KMime::multiPartBoundary(); | ||
222 | KMime::Content *const mainTextPart = | ||
223 | htmlBody.isEmpty() ? | ||
224 | createPlainPartContent(msg, plainBody) : | ||
225 | createMultipartAlternativeContent(msg, plainBody, htmlBody); | ||
226 | mainTextPart->assemble(); | ||
227 | |||
228 | KMime::Content *textPart = mainTextPart; | ||
229 | // if (!ic.images().empty()) { | ||
230 | // textPart = createMultipartRelated(ic, mainTextPart); | ||
231 | // textPart->assemble(); | ||
232 | // } | ||
233 | |||
234 | // If we have some attachments, create a multipart/mixed mail and | ||
235 | // add the normal body as well as the attachments | ||
236 | KMime::Content *mainPart = textPart; | ||
237 | //FIXME | ||
238 | // if (forward) { | ||
239 | // auto attachments = mOrigMsg->attachments(); | ||
240 | // attachments += mOtp->nodeHelper()->attachmentsOfExtraContents(); | ||
241 | // if (!attachments.isEmpty()) { | ||
242 | // mainPart = createMultipartMixed(attachments, textPart); | ||
243 | // mainPart->assemble(); | ||
244 | // } | ||
245 | // } | ||
246 | |||
247 | msg->setBody(mainPart->encodedBody()); | ||
248 | msg->setHeader(mainPart->contentType()); | ||
249 | msg->setHeader(mainPart->contentTransferEncoding()); | ||
250 | msg->assemble(); | ||
251 | msg->parse(); | ||
252 | } | ||
253 | |||
254 | QString plainToHtml(const QString &body) | ||
255 | { | ||
256 | QString str = body; | ||
257 | str = str.toHtmlEscaped(); | ||
258 | str.replace(QStringLiteral("\n"), QStringLiteral("<br />\n")); | ||
259 | return str; | ||
260 | } | ||
261 | |||
262 | //TODO implement this function using a DOM tree parser | ||
263 | void makeValidHtml(QString &body, const QString &headElement) | ||
264 | { | ||
265 | QRegExp regEx; | ||
266 | regEx.setMinimal(true); | ||
267 | regEx.setPattern(QStringLiteral("<html.*>")); | ||
268 | |||
269 | if (!body.isEmpty() && !body.contains(regEx)) { | ||
270 | regEx.setPattern(QStringLiteral("<body.*>")); | ||
271 | if (!body.contains(regEx)) { | ||
272 | body = QLatin1String("<body>") + body + QLatin1String("<br/></body>"); | ||
273 | } | ||
274 | regEx.setPattern(QStringLiteral("<head.*>")); | ||
275 | if (!body.contains(regEx)) { | ||
276 | body = QLatin1String("<head>") + headElement + QLatin1String("</head>") + body; | ||
277 | } | ||
278 | body = QLatin1String("<html>") + body + QLatin1String("</html>"); | ||
279 | } | ||
280 | } | ||
281 | |||
282 | QString stripSignature(const QString &msg) | ||
283 | { | ||
284 | // Following RFC 3676, only > before -- | ||
285 | // I prefer to not delete a SB instead of delete good mail content. | ||
286 | const QRegExp sbDelimiterSearch = QRegExp(QLatin1String("(^|\n)[> ]*-- \n")); | ||
287 | // The regular expression to look for prefix change | ||
288 | const QRegExp commonReplySearch = QRegExp(QLatin1String("^[ ]*>")); | ||
289 | |||
290 | QString res = msg; | ||
291 | int posDeletingStart = 1; // to start looking at 0 | ||
292 | |||
293 | // While there are SB delimiters (start looking just before the deleted SB) | ||
294 | while ((posDeletingStart = res.indexOf(sbDelimiterSearch, posDeletingStart - 1)) >= 0) { | ||
295 | QString prefix; // the current prefix | ||
296 | QString line; // the line to check if is part of the SB | ||
297 | int posNewLine = -1; | ||
298 | |||
299 | // Look for the SB beginning | ||
300 | int posSignatureBlock = res.indexOf(QLatin1Char('-'), posDeletingStart); | ||
301 | // The prefix before "-- "$ | ||
302 | if (res.at(posDeletingStart) == QLatin1Char('\n')) { | ||
303 | ++posDeletingStart; | ||
304 | } | ||
305 | |||
306 | prefix = res.mid(posDeletingStart, posSignatureBlock - posDeletingStart); | ||
307 | posNewLine = res.indexOf(QLatin1Char('\n'), posSignatureBlock) + 1; | ||
308 | |||
309 | // now go to the end of the SB | ||
310 | while (posNewLine < res.size() && posNewLine > 0) { | ||
311 | // handle the undefined case for mid ( x , -n ) where n>1 | ||
312 | int nextPosNewLine = res.indexOf(QLatin1Char('\n'), posNewLine); | ||
313 | |||
314 | if (nextPosNewLine < 0) { | ||
315 | nextPosNewLine = posNewLine - 1; | ||
316 | } | ||
317 | |||
318 | line = res.mid(posNewLine, nextPosNewLine - posNewLine); | ||
319 | |||
320 | // check when the SB ends: | ||
321 | // * does not starts with prefix or | ||
322 | // * starts with prefix+(any substring of prefix) | ||
323 | if ((prefix.isEmpty() && line.indexOf(commonReplySearch) < 0) || | ||
324 | (!prefix.isEmpty() && line.startsWith(prefix) && | ||
325 | line.mid(prefix.size()).indexOf(commonReplySearch) < 0)) { | ||
326 | posNewLine = res.indexOf(QLatin1Char('\n'), posNewLine) + 1; | ||
327 | } else { | ||
328 | break; // end of the SB | ||
329 | } | ||
330 | } | ||
331 | |||
332 | // remove the SB or truncate when is the last SB | ||
333 | if (posNewLine > 0) { | ||
334 | res.remove(posDeletingStart, posNewLine - posDeletingStart); | ||
335 | } else { | ||
336 | res.truncate(posDeletingStart); | ||
337 | } | ||
338 | } | ||
339 | |||
340 | return res; | ||
341 | } | ||
342 | |||
343 | QString plainMessageText(MimeTreeParser::ObjectTreeParser &otp, bool aStripSignature) | ||
344 | { | ||
345 | QString result = otp.plainTextContent(); | ||
346 | if (result.isEmpty()) { //HTML-only mails | ||
347 | QWebPage doc; | ||
348 | doc.mainFrame()->setHtml(otp.htmlContent()); | ||
349 | result = doc.mainFrame()->toPlainText(); | ||
350 | } | ||
351 | |||
352 | if (aStripSignature) { | ||
353 | result = stripSignature(result); | ||
354 | } | ||
355 | |||
356 | return result; | ||
357 | } | ||
358 | |||
359 | QString htmlMessageText(MimeTreeParser::ObjectTreeParser &otp, bool aStripSignature, QString &headElement) | ||
360 | { | ||
361 | QString htmlElement = otp.htmlContent(); | ||
362 | |||
363 | if (htmlElement.isEmpty()) { //plain mails only | ||
364 | QString htmlReplace = otp.plainTextContent().toHtmlEscaped(); | ||
365 | htmlReplace = htmlReplace.replace(QStringLiteral("\n"), QStringLiteral("<br />")); | ||
366 | htmlElement = QStringLiteral("<html><head></head><body>%1</body></html>\n").arg(htmlReplace); | ||
367 | } | ||
368 | |||
369 | //QWebPage relies on this | ||
370 | Q_ASSERT(QApplication::style()); | ||
371 | QWebPage page; | ||
372 | page.settings()->setAttribute(QWebSettings::JavascriptEnabled, false); | ||
373 | page.settings()->setAttribute(QWebSettings::JavaEnabled, false); | ||
374 | page.settings()->setAttribute(QWebSettings::PluginsEnabled, false); | ||
375 | page.settings()->setAttribute(QWebSettings::AutoLoadImages, false); | ||
376 | |||
377 | page.currentFrame()->setHtml(htmlElement); | ||
378 | |||
379 | //TODO to be tested/verified if this is not an issue | ||
380 | page.settings()->setAttribute(QWebSettings::JavascriptEnabled, true); | ||
381 | const QString bodyElement = page.currentFrame()->evaluateJavaScript( | ||
382 | QStringLiteral("document.getElementsByTagName('body')[0].innerHTML")).toString(); | ||
383 | |||
384 | headElement = page.currentFrame()->evaluateJavaScript( | ||
385 | QStringLiteral("document.getElementsByTagName('head')[0].innerHTML")).toString(); | ||
386 | |||
387 | page.settings()->setAttribute(QWebSettings::JavascriptEnabled, false); | ||
388 | |||
389 | if (!bodyElement.isEmpty()) { | ||
390 | if (aStripSignature) { | ||
391 | //FIXME strip signature works partially for HTML mails | ||
392 | return stripSignature(bodyElement); | ||
393 | } | ||
394 | return bodyElement; | ||
395 | } | ||
396 | |||
397 | if (aStripSignature) { | ||
398 | //FIXME strip signature works partially for HTML mails | ||
399 | return stripSignature(htmlElement); | ||
400 | } | ||
401 | return htmlElement; | ||
402 | } | ||
403 | |||
404 | QString formatQuotePrefix(const QString &wildString, const QString &fromDisplayString) | ||
405 | { | ||
406 | QString result; | ||
407 | |||
408 | if (wildString.isEmpty()) { | ||
409 | return wildString; | ||
410 | } | ||
411 | |||
412 | unsigned int strLength(wildString.length()); | ||
413 | for (uint i = 0; i < strLength;) { | ||
414 | QChar ch = wildString[i++]; | ||
415 | if (ch == QLatin1Char('%') && i < strLength) { | ||
416 | ch = wildString[i++]; | ||
417 | switch (ch.toLatin1()) { | ||
418 | case 'f': { // sender's initals | ||
419 | if (fromDisplayString.isEmpty()) { | ||
420 | break; | ||
421 | } | ||
422 | |||
423 | uint j = 0; | ||
424 | const unsigned int strLength(fromDisplayString.length()); | ||
425 | for (; j < strLength && fromDisplayString[j] > QLatin1Char(' '); ++j) | ||
426 | ; | ||
427 | for (; j < strLength && fromDisplayString[j] <= QLatin1Char(' '); ++j) | ||
428 | ; | ||
429 | result += fromDisplayString[0]; | ||
430 | if (j < strLength && fromDisplayString[j] > QLatin1Char(' ')) { | ||
431 | result += fromDisplayString[j]; | ||
432 | } else if (strLength > 1) { | ||
433 | if (fromDisplayString[1] > QLatin1Char(' ')) { | ||
434 | result += fromDisplayString[1]; | ||
435 | } | ||
436 | } | ||
437 | } | ||
438 | break; | ||
439 | case '_': | ||
440 | result += QLatin1Char(' '); | ||
441 | break; | ||
442 | case '%': | ||
443 | result += QLatin1Char('%'); | ||
444 | break; | ||
445 | default: | ||
446 | result += QLatin1Char('%'); | ||
447 | result += ch; | ||
448 | break; | ||
449 | } | ||
450 | } else { | ||
451 | result += ch; | ||
452 | } | ||
453 | } | ||
454 | return result; | ||
455 | } | ||
456 | |||
457 | QString quotedPlainText(const QString &selection, const QString &fromDisplayString) | ||
458 | { | ||
459 | QString content = selection; | ||
460 | // Remove blank lines at the beginning: | ||
461 | const int firstNonWS = content.indexOf(QRegExp(QLatin1String("\\S"))); | ||
462 | const int lineStart = content.lastIndexOf(QLatin1Char('\n'), firstNonWS); | ||
463 | if (lineStart >= 0) { | ||
464 | content.remove(0, static_cast<unsigned int>(lineStart)); | ||
465 | } | ||
466 | |||
467 | const auto quoteString = QStringLiteral("> "); | ||
468 | const QString indentStr = formatQuotePrefix(quoteString, fromDisplayString); | ||
469 | //FIXME | ||
470 | // if (TemplateParserSettings::self()->smartQuote() && mWrap) { | ||
471 | // content = MessageCore::StringUtil::smartQuote(content, mColWrap - indentStr.length()); | ||
472 | // } | ||
473 | content.replace(QLatin1Char('\n'), QLatin1Char('\n') + indentStr); | ||
474 | content.prepend(indentStr); | ||
475 | content += QLatin1Char('\n'); | ||
476 | |||
477 | return content; | ||
478 | } | ||
479 | |||
480 | QString quotedHtmlText(const QString &selection) | ||
481 | { | ||
482 | QString content = selection; | ||
483 | //TODO 1) look for all the variations of <br> and remove the blank lines | ||
484 | //2) implement vertical bar for quoted HTML mail. | ||
485 | //3) After vertical bar is implemented, If a user wants to edit quoted message, | ||
486 | // then the <blockquote> tags below should open and close as when required. | ||
487 | |||
488 | //Add blockquote tag, so that quoted message can be differentiated from normal message | ||
489 | content = QLatin1String("<blockquote>") + content + QLatin1String("</blockquote>"); | ||
490 | return content; | ||
491 | } | ||
492 | |||
493 | void applyCharset(const KMime::Message::Ptr msg, const KMime::Message::Ptr &origMsg) | ||
494 | { | ||
495 | // first convert the body from its current encoding to unicode representation | ||
496 | QTextCodec *bodyCodec = KCharsets::charsets()->codecForName(QString::fromLatin1(msg->contentType()->charset())); | ||
497 | if (!bodyCodec) { | ||
498 | bodyCodec = KCharsets::charsets()->codecForName(QStringLiteral("UTF-8")); | ||
499 | } | ||
500 | |||
501 | const QString body = bodyCodec->toUnicode(msg->body()); | ||
502 | |||
503 | // then apply the encoding of the original message | ||
504 | msg->contentType()->setCharset(origMsg->contentType()->charset()); | ||
505 | |||
506 | QTextCodec *codec = KCharsets::charsets()->codecForName(QString::fromLatin1(msg->contentType()->charset())); | ||
507 | if (!codec) { | ||
508 | qCritical() << "Could not get text codec for charset" << msg->contentType()->charset(); | ||
509 | } else if (!codec->canEncode(body)) { // charset can't encode body, fall back to preferred | ||
510 | const QStringList charsets /*= preferredCharsets() */; | ||
511 | |||
512 | QList<QByteArray> chars; | ||
513 | chars.reserve(charsets.count()); | ||
514 | foreach (const QString &charset, charsets) { | ||
515 | chars << charset.toLatin1(); | ||
516 | } | ||
517 | |||
518 | //FIXME | ||
519 | QByteArray fallbackCharset/* = selectCharset(chars, body)*/; | ||
520 | if (fallbackCharset.isEmpty()) { // UTF-8 as fall-through | ||
521 | fallbackCharset = "UTF-8"; | ||
522 | } | ||
523 | |||
524 | codec = KCharsets::charsets()->codecForName(QString::fromLatin1(fallbackCharset)); | ||
525 | msg->setBody(codec->fromUnicode(body)); | ||
526 | } else { | ||
527 | msg->setBody(codec->fromUnicode(body)); | ||
528 | } | ||
529 | } | ||
530 | |||
531 | enum ReplyStrategy { | ||
532 | ReplyList, | ||
533 | ReplySmart, | ||
534 | ReplyAll, | ||
535 | ReplyAuthor, | ||
536 | ReplyNone | ||
537 | }; | ||
538 | |||
539 | KMime::Message::Ptr MailTemplates::reply(const KMime::Message::Ptr &origMsg) | ||
540 | { | ||
541 | //FIXME | ||
542 | const bool alwaysPlain = true; | ||
543 | //FIXME | ||
544 | const ReplyStrategy replyStrategy = ReplySmart; | ||
545 | KMime::Message::Ptr msg(new KMime::Message); | ||
546 | //FIXME | ||
547 | //Personal email addresses | ||
548 | KMime::Types::AddrSpecList me; | ||
549 | KMime::Types::Mailbox::List toList; | ||
550 | KMime::Types::Mailbox::List replyToList; | ||
551 | KMime::Types::Mailbox::List mailingListAddresses; | ||
552 | |||
553 | // const uint originalIdentity = identityUoid(origMsg); | ||
554 | initHeader(msg); | ||
555 | replyToList = origMsg->replyTo()->mailboxes(); | ||
556 | |||
557 | msg->contentType()->setCharset("utf-8"); | ||
558 | |||
559 | if (origMsg->headerByType("List-Post") && | ||
560 | origMsg->headerByType("List-Post")->asUnicodeString().contains(QStringLiteral("mailto:"), Qt::CaseInsensitive)) { | ||
561 | |||
562 | const QString listPost = origMsg->headerByType("List-Post")->asUnicodeString(); | ||
563 | QRegExp rx(QStringLiteral("<mailto:([^@>]+)@([^>]+)>"), Qt::CaseInsensitive); | ||
564 | if (rx.indexIn(listPost, 0) != -1) { // matched | ||
565 | KMime::Types::Mailbox mailbox; | ||
566 | mailbox.fromUnicodeString(rx.cap(1) + QLatin1Char('@') + rx.cap(2)); | ||
567 | mailingListAddresses << mailbox; | ||
568 | } | ||
569 | } | ||
570 | |||
571 | switch (replyStrategy) { | ||
572 | case ReplySmart: { | ||
573 | if (auto hdr = origMsg->headerByType("Mail-Followup-To")) { | ||
574 | toList << KMime::Types::Mailbox::listFrom7BitString(hdr->as7BitString(false)); | ||
575 | } else if (!replyToList.isEmpty()) { | ||
576 | toList = replyToList; | ||
577 | } else if (!mailingListAddresses.isEmpty()) { | ||
578 | toList = (KMime::Types::Mailbox::List() << mailingListAddresses.at(0)); | ||
579 | } else { | ||
580 | // doesn't seem to be a mailing list, reply to From: address | ||
581 | toList = origMsg->from()->mailboxes(); | ||
582 | |||
583 | bool listContainsMe = false; | ||
584 | for (const auto &m : me) { | ||
585 | KMime::Types::Mailbox mailbox; | ||
586 | mailbox.setAddress(m); | ||
587 | if (toList.contains(mailbox)) { | ||
588 | listContainsMe = true; | ||
589 | } | ||
590 | } | ||
591 | if (listContainsMe) { | ||
592 | // sender seems to be one of our own identities, so we assume that this | ||
593 | // is a reply to a "sent" mail where the users wants to add additional | ||
594 | // information for the recipient. | ||
595 | toList = origMsg->to()->mailboxes(); | ||
596 | } | ||
597 | } | ||
598 | // strip all my addresses from the list of recipients | ||
599 | const KMime::Types::Mailbox::List recipients = toList; | ||
600 | |||
601 | toList = stripMyAddressesFromAddressList(recipients, me); | ||
602 | |||
603 | // ... unless the list contains only my addresses (reply to self) | ||
604 | if (toList.isEmpty() && !recipients.isEmpty()) { | ||
605 | toList << recipients.first(); | ||
606 | } | ||
607 | } | ||
608 | break; | ||
609 | case ReplyList: { | ||
610 | if (auto hdr = origMsg->headerByType("Mail-Followup-To")) { | ||
611 | KMime::Types::Mailbox mailbox; | ||
612 | mailbox.from7BitString(hdr->as7BitString(false)); | ||
613 | toList << mailbox; | ||
614 | } else if (!mailingListAddresses.isEmpty()) { | ||
615 | toList << mailingListAddresses[ 0 ]; | ||
616 | } else if (!replyToList.isEmpty()) { | ||
617 | // assume a Reply-To header mangling mailing list | ||
618 | toList = replyToList; | ||
619 | } | ||
620 | |||
621 | //FIXME | ||
622 | // strip all my addresses from the list of recipients | ||
623 | const KMime::Types::Mailbox::List recipients = toList; | ||
624 | toList = stripMyAddressesFromAddressList(recipients, me); | ||
625 | } | ||
626 | break; | ||
627 | case ReplyAll: { | ||
628 | KMime::Types::Mailbox::List recipients; | ||
629 | KMime::Types::Mailbox::List ccRecipients; | ||
630 | |||
631 | // add addresses from the Reply-To header to the list of recipients | ||
632 | if (!replyToList.isEmpty()) { | ||
633 | recipients = replyToList; | ||
634 | |||
635 | // strip all possible mailing list addresses from the list of Reply-To addresses | ||
636 | foreach (const KMime::Types::Mailbox &mailbox, mailingListAddresses) { | ||
637 | foreach (const KMime::Types::Mailbox &recipient, recipients) { | ||
638 | if (mailbox == recipient) { | ||
639 | recipients.removeAll(recipient); | ||
640 | } | ||
641 | } | ||
642 | } | ||
643 | } | ||
644 | |||
645 | if (!mailingListAddresses.isEmpty()) { | ||
646 | // this is a mailing list message | ||
647 | if (recipients.isEmpty() && !origMsg->from()->asUnicodeString().isEmpty()) { | ||
648 | // The sender didn't set a Reply-to address, so we add the From | ||
649 | // address to the list of CC recipients. | ||
650 | ccRecipients += origMsg->from()->mailboxes(); | ||
651 | qDebug() << "Added" << origMsg->from()->asUnicodeString() << "to the list of CC recipients"; | ||
652 | } | ||
653 | |||
654 | // if it is a mailing list, add the posting address | ||
655 | recipients.prepend(mailingListAddresses[ 0 ]); | ||
656 | } else { | ||
657 | // this is a normal message | ||
658 | if (recipients.isEmpty() && !origMsg->from()->asUnicodeString().isEmpty()) { | ||
659 | // in case of replying to a normal message only then add the From | ||
660 | // address to the list of recipients if there was no Reply-to address | ||
661 | recipients += origMsg->from()->mailboxes(); | ||
662 | qDebug() << "Added" << origMsg->from()->asUnicodeString() << "to the list of recipients"; | ||
663 | } | ||
664 | } | ||
665 | |||
666 | // strip all my addresses from the list of recipients | ||
667 | toList = stripMyAddressesFromAddressList(recipients, me); | ||
668 | |||
669 | // merge To header and CC header into a list of CC recipients | ||
670 | if (!origMsg->cc()->asUnicodeString().isEmpty() || !origMsg->to()->asUnicodeString().isEmpty()) { | ||
671 | KMime::Types::Mailbox::List list; | ||
672 | if (!origMsg->to()->asUnicodeString().isEmpty()) { | ||
673 | list += origMsg->to()->mailboxes(); | ||
674 | } | ||
675 | if (!origMsg->cc()->asUnicodeString().isEmpty()) { | ||
676 | list += origMsg->cc()->mailboxes(); | ||
677 | } | ||
678 | |||
679 | foreach (const KMime::Types::Mailbox &mailbox, list) { | ||
680 | if (!recipients.contains(mailbox) && | ||
681 | !ccRecipients.contains(mailbox)) { | ||
682 | ccRecipients += mailbox; | ||
683 | qDebug() << "Added" << mailbox.prettyAddress() << "to the list of CC recipients"; | ||
684 | } | ||
685 | } | ||
686 | } | ||
687 | |||
688 | if (!ccRecipients.isEmpty()) { | ||
689 | // strip all my addresses from the list of CC recipients | ||
690 | ccRecipients = stripMyAddressesFromAddressList(ccRecipients, me); | ||
691 | |||
692 | // in case of a reply to self, toList might be empty. if that's the case | ||
693 | // then propagate a cc recipient to To: (if there is any). | ||
694 | if (toList.isEmpty() && !ccRecipients.isEmpty()) { | ||
695 | toList << ccRecipients.at(0); | ||
696 | ccRecipients.pop_front(); | ||
697 | } | ||
698 | |||
699 | foreach (const KMime::Types::Mailbox &mailbox, ccRecipients) { | ||
700 | msg->cc()->addAddress(mailbox); | ||
701 | } | ||
702 | } | ||
703 | |||
704 | if (toList.isEmpty() && !recipients.isEmpty()) { | ||
705 | // reply to self without other recipients | ||
706 | toList << recipients.at(0); | ||
707 | } | ||
708 | } | ||
709 | break; | ||
710 | case ReplyAuthor: { | ||
711 | if (!replyToList.isEmpty()) { | ||
712 | KMime::Types::Mailbox::List recipients = replyToList; | ||
713 | |||
714 | // strip the mailing list post address from the list of Reply-To | ||
715 | // addresses since we want to reply in private | ||
716 | foreach (const KMime::Types::Mailbox &mailbox, mailingListAddresses) { | ||
717 | foreach (const KMime::Types::Mailbox &recipient, recipients) { | ||
718 | if (mailbox == recipient) { | ||
719 | recipients.removeAll(recipient); | ||
720 | } | ||
721 | } | ||
722 | } | ||
723 | |||
724 | if (!recipients.isEmpty()) { | ||
725 | toList = recipients; | ||
726 | } else { | ||
727 | // there was only the mailing list post address in the Reply-To header, | ||
728 | // so use the From address instead | ||
729 | toList = origMsg->from()->mailboxes(); | ||
730 | } | ||
731 | } else if (!origMsg->from()->asUnicodeString().isEmpty()) { | ||
732 | toList = origMsg->from()->mailboxes(); | ||
733 | } | ||
734 | } | ||
735 | break; | ||
736 | case ReplyNone: | ||
737 | // the addressees will be set by the caller | ||
738 | break; | ||
739 | } | ||
740 | |||
741 | foreach (const KMime::Types::Mailbox &mailbox, toList) { | ||
742 | msg->to()->addAddress(mailbox); | ||
743 | } | ||
744 | |||
745 | const QByteArray refStr = getRefStr(origMsg); | ||
746 | if (!refStr.isEmpty()) { | ||
747 | msg->references()->fromUnicodeString(QString::fromLocal8Bit(refStr), "utf-8"); | ||
748 | } | ||
749 | |||
750 | //In-Reply-To = original msg-id | ||
751 | msg->inReplyTo()->from7BitString(origMsg->messageID()->as7BitString(false)); | ||
752 | |||
753 | msg->subject()->fromUnicodeString(replySubject(origMsg), "utf-8"); | ||
754 | |||
755 | auto definedLocale = QLocale::system(); | ||
756 | |||
757 | //TODO set empty source instead | ||
758 | StringHtmlWriter htmlWriter; | ||
759 | MimeTreeParser::NodeHelper nodeHelper; | ||
760 | ObjectTreeSource source(&htmlWriter); | ||
761 | MimeTreeParser::ObjectTreeParser otp(&source, &nodeHelper); | ||
762 | otp.setAllowAsync(false); | ||
763 | otp.parseObjectTree(origMsg.data()); | ||
764 | |||
765 | //Add quoted body | ||
766 | QString plainBody; | ||
767 | QString htmlBody; | ||
768 | |||
769 | //On $datetime you wrote: | ||
770 | const QDateTime date = origMsg->date()->dateTime(); | ||
771 | const auto dateTimeString = QString("%1 %2").arg(definedLocale.toString(date.date(), QLocale::LongFormat)).arg(definedLocale.toString(date.time(), QLocale::LongFormat)); | ||
772 | const auto onDateYouWroteLine = QString("On %1 you wrote:").arg(dateTimeString); | ||
773 | plainBody.append(onDateYouWroteLine); | ||
774 | htmlBody.append(plainToHtml(onDateYouWroteLine)); | ||
775 | |||
776 | //Strip signature for replies | ||
777 | const bool stripSignature = true; | ||
778 | |||
779 | //Quoted body | ||
780 | QString plainQuote = quotedPlainText(plainMessageText(otp, stripSignature), origMsg->from()->displayString()); | ||
781 | if (plainQuote.endsWith(QLatin1Char('\n'))) { | ||
782 | plainQuote.chop(1); | ||
783 | } | ||
784 | plainBody.append(plainQuote); | ||
785 | QString headElement; | ||
786 | htmlBody.append(quotedHtmlText(htmlMessageText(otp, stripSignature, headElement))); | ||
787 | |||
788 | if (alwaysPlain) { | ||
789 | htmlBody.clear(); | ||
790 | } else { | ||
791 | makeValidHtml(htmlBody, headElement); | ||
792 | } | ||
793 | |||
794 | addProcessedBodyToMessage(msg, plainBody, htmlBody, false); | ||
795 | |||
796 | applyCharset(msg, origMsg); | ||
797 | |||
798 | msg->assemble(); | ||
799 | |||
800 | return msg; | ||
801 | } | ||
diff --git a/framework/src/domain/mailtemplates.h b/framework/src/domain/mailtemplates.h new file mode 100644 index 00000000..6519122a --- /dev/null +++ b/framework/src/domain/mailtemplates.h | |||
@@ -0,0 +1,28 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | |||
20 | #pragma once | ||
21 | |||
22 | #include <QByteArray> | ||
23 | #include <KMime/Message> | ||
24 | |||
25 | namespace MailTemplates | ||
26 | { | ||
27 | KMime::Message::Ptr reply(const KMime::Message::Ptr &message); | ||
28 | }; | ||
diff --git a/framework/src/domain/messageparser.cpp b/framework/src/domain/messageparser.cpp new file mode 100644 index 00000000..ea2ecbf1 --- /dev/null +++ b/framework/src/domain/messageparser.cpp | |||
@@ -0,0 +1,114 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #include "messageparser.h" | ||
20 | |||
21 | #include "modeltest.h" | ||
22 | #include "stringhtmlwriter.h" | ||
23 | #include "objecttreesource.h" | ||
24 | |||
25 | #include "mimetreeparser/interface.h" | ||
26 | |||
27 | #include <QRegExp> | ||
28 | |||
29 | #include <QFile> | ||
30 | #include <QImage> | ||
31 | #include <QDebug> | ||
32 | #include <QTime> | ||
33 | #include <QUrl> | ||
34 | #include <MimeTreeParser/ObjectTreeParser> | ||
35 | #include <MimeTreeParser/MessagePart> | ||
36 | |||
37 | |||
38 | class MessagePartPrivate | ||
39 | { | ||
40 | public: | ||
41 | QSharedPointer<MimeTreeParser::MessagePart> mPartTree; | ||
42 | QString mHtml; | ||
43 | QMap<QByteArray, QUrl> mEmbeddedPartMap; | ||
44 | std::shared_ptr<MimeTreeParser::NodeHelper> mNodeHelper; | ||
45 | std::shared_ptr<Parser> mParser; | ||
46 | }; | ||
47 | |||
48 | MessageParser::MessageParser(QObject *parent) | ||
49 | : QObject(parent) | ||
50 | , d(std::unique_ptr<MessagePartPrivate>(new MessagePartPrivate)) | ||
51 | { | ||
52 | |||
53 | } | ||
54 | |||
55 | MessageParser::~MessageParser() | ||
56 | { | ||
57 | |||
58 | } | ||
59 | |||
60 | QString MessageParser::html() const | ||
61 | { | ||
62 | return d->mHtml; | ||
63 | } | ||
64 | |||
65 | QVariant MessageParser::message() const | ||
66 | { | ||
67 | return QVariant(); | ||
68 | } | ||
69 | |||
70 | void MessageParser::setMessage(const QVariant &message) | ||
71 | { | ||
72 | // QTime time; | ||
73 | // time.start(); | ||
74 | d->mParser = std::shared_ptr<Parser>(new Parser(message.toByteArray())); | ||
75 | |||
76 | const auto mailData = KMime::CRLFtoLF(message.toByteArray()); | ||
77 | KMime::Message::Ptr msg(new KMime::Message); | ||
78 | msg->setContent(mailData); | ||
79 | msg->parse(); | ||
80 | // qWarning() << "parsed: " << time.elapsed(); | ||
81 | |||
82 | // render the mail | ||
83 | StringHtmlWriter htmlWriter; | ||
84 | //temporary files only have the lifetime of the nodehelper, so we keep it around until the mail changes. | ||
85 | d->mNodeHelper = std::make_shared<MimeTreeParser::NodeHelper>(); | ||
86 | ObjectTreeSource source(&htmlWriter); | ||
87 | MimeTreeParser::ObjectTreeParser otp(&source, d->mNodeHelper.get()); | ||
88 | |||
89 | otp.parseObjectTree(msg.data()); | ||
90 | d->mPartTree = otp.parsedPart().dynamicCast<MimeTreeParser::MessagePart>(); | ||
91 | |||
92 | d->mEmbeddedPartMap = htmlWriter.embeddedParts(); | ||
93 | d->mHtml = htmlWriter.html(); | ||
94 | emit htmlChanged(); | ||
95 | } | ||
96 | |||
97 | QAbstractItemModel *MessageParser::partTree() const | ||
98 | { | ||
99 | return new PartModel(d->mPartTree, d->mParser); | ||
100 | } | ||
101 | |||
102 | QAbstractItemModel *MessageParser::newTree() const | ||
103 | { | ||
104 | const auto model = new NewModel(d->mParser); | ||
105 | // new ModelTest(model, model); | ||
106 | return model; | ||
107 | } | ||
108 | |||
109 | QAbstractItemModel *MessageParser::attachments() const | ||
110 | { | ||
111 | const auto model = new AttachmentModel(d->mParser); | ||
112 | // new ModelTest(model, model); | ||
113 | return model; | ||
114 | } | ||
diff --git a/framework/src/domain/messageparser.h b/framework/src/domain/messageparser.h new file mode 100644 index 00000000..aeeed93c --- /dev/null +++ b/framework/src/domain/messageparser.h | |||
@@ -0,0 +1,157 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | |||
20 | #pragma once | ||
21 | |||
22 | #include <QObject> | ||
23 | #include <QString> | ||
24 | #include <QStringList> | ||
25 | |||
26 | #include <QAbstractItemModel> | ||
27 | #include <QModelIndex> | ||
28 | |||
29 | #include <memory> | ||
30 | #include <MimeTreeParser/MessagePart> | ||
31 | |||
32 | class QAbstractItemModel; | ||
33 | |||
34 | class Parser; | ||
35 | class Part; | ||
36 | class Encryption; | ||
37 | class Signature; | ||
38 | typedef std::shared_ptr<Part> PartPtr; | ||
39 | class Content; | ||
40 | typedef std::shared_ptr<Content> ContentPtr; | ||
41 | class MessagePartPrivate; | ||
42 | |||
43 | class NewModelPrivate; | ||
44 | class AttachmentModelPrivate; | ||
45 | |||
46 | class MessageParser : public QObject | ||
47 | { | ||
48 | Q_OBJECT | ||
49 | Q_PROPERTY (QVariant message READ message WRITE setMessage) | ||
50 | Q_PROPERTY (QString html READ html NOTIFY htmlChanged) | ||
51 | Q_PROPERTY (QAbstractItemModel* partTree READ partTree NOTIFY htmlChanged) | ||
52 | Q_PROPERTY (QAbstractItemModel* newTree READ newTree NOTIFY htmlChanged) | ||
53 | Q_PROPERTY (QAbstractItemModel* attachments READ attachments NOTIFY htmlChanged) | ||
54 | |||
55 | public: | ||
56 | explicit MessageParser(QObject *parent = Q_NULLPTR); | ||
57 | ~MessageParser(); | ||
58 | |||
59 | QString html() const; | ||
60 | |||
61 | QVariant message() const; | ||
62 | void setMessage(const QVariant &to); | ||
63 | QAbstractItemModel *partTree() const; | ||
64 | QAbstractItemModel *newTree() const; | ||
65 | QAbstractItemModel *attachments() const; | ||
66 | |||
67 | signals: | ||
68 | void htmlChanged(); | ||
69 | |||
70 | private: | ||
71 | std::unique_ptr<MessagePartPrivate> d; | ||
72 | }; | ||
73 | |||
74 | class PartModel : public QAbstractItemModel { | ||
75 | Q_OBJECT | ||
76 | public: | ||
77 | PartModel(QSharedPointer<MimeTreeParser::MessagePart> partTree, std::shared_ptr<Parser> parser); | ||
78 | |||
79 | public: | ||
80 | enum Roles { | ||
81 | Text = Qt::UserRole + 1, | ||
82 | IsHtml, | ||
83 | IsEncrypted, | ||
84 | IsAttachment, | ||
85 | HasContent, | ||
86 | Type, | ||
87 | IsHidden | ||
88 | }; | ||
89 | |||
90 | QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE; | ||
91 | QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; | ||
92 | QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; | ||
93 | QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE; | ||
94 | int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; | ||
95 | int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; | ||
96 | |||
97 | private: | ||
98 | QSharedPointer<MimeTreeParser::MessagePart> mPartTree; | ||
99 | QMap<QByteArray, QUrl> mEmbeddedPartMap; | ||
100 | std::shared_ptr<Parser> mParser; | ||
101 | }; | ||
102 | |||
103 | |||
104 | class NewModel : public QAbstractItemModel { | ||
105 | Q_OBJECT | ||
106 | public: | ||
107 | NewModel(std::shared_ptr<Parser> parser); | ||
108 | ~NewModel(); | ||
109 | |||
110 | public: | ||
111 | enum Roles { | ||
112 | TypeRole = Qt::UserRole + 1, | ||
113 | ContentsRole, | ||
114 | ContentRole, | ||
115 | IsEmbededRole, | ||
116 | SecurityLevelRole, | ||
117 | EncryptionErrorType, | ||
118 | EncryptionErrorString | ||
119 | }; | ||
120 | |||
121 | QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE; | ||
122 | QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; | ||
123 | QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; | ||
124 | QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE; | ||
125 | int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; | ||
126 | int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; | ||
127 | |||
128 | private: | ||
129 | std::unique_ptr<NewModelPrivate> d; | ||
130 | }; | ||
131 | |||
132 | class AttachmentModel : public QAbstractItemModel { | ||
133 | Q_OBJECT | ||
134 | public: | ||
135 | AttachmentModel(std::shared_ptr<Parser> parser); | ||
136 | ~AttachmentModel(); | ||
137 | |||
138 | public: | ||
139 | enum Roles { | ||
140 | TypeRole = Qt::UserRole + 1, | ||
141 | IconRole, | ||
142 | NameRole, | ||
143 | SizeRole, | ||
144 | IsEncryptedRole, | ||
145 | IsSignedRole | ||
146 | }; | ||
147 | |||
148 | QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE; | ||
149 | QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; | ||
150 | QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; | ||
151 | QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE; | ||
152 | int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; | ||
153 | int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; | ||
154 | |||
155 | private: | ||
156 | std::unique_ptr<AttachmentModelPrivate> d; | ||
157 | }; | ||
diff --git a/framework/src/domain/messageparser_new.cpp b/framework/src/domain/messageparser_new.cpp new file mode 100644 index 00000000..cb399523 --- /dev/null +++ b/framework/src/domain/messageparser_new.cpp | |||
@@ -0,0 +1,486 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Sandro Knauß <knauss@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | |||
20 | #include "messageparser.h" | ||
21 | #include "mimetreeparser/interface.h" | ||
22 | |||
23 | #include <QDebug> | ||
24 | |||
25 | Q_DECLARE_METATYPE(Part *) | ||
26 | Q_DECLARE_METATYPE(Content *) | ||
27 | Q_DECLARE_METATYPE(Signature *) | ||
28 | Q_DECLARE_METATYPE(Encryption *) | ||
29 | |||
30 | class Entry; | ||
31 | |||
32 | class NewModelPrivate | ||
33 | { | ||
34 | public: | ||
35 | NewModelPrivate(NewModel *q_ptr, const std::shared_ptr<Parser> &parser); | ||
36 | ~NewModelPrivate(); | ||
37 | |||
38 | void createTree(); | ||
39 | |||
40 | QSharedPointer<QVariant> getVar(const std::shared_ptr<Signature> &sig); | ||
41 | QSharedPointer<QVariant> getVar(const std::shared_ptr<Encryption> &enc); | ||
42 | QSharedPointer<QVariant> getVar(const std::shared_ptr<Part> &part); | ||
43 | QSharedPointer<QVariant> getVar(Part *part); | ||
44 | QSharedPointer<QVariant> getVar(const std::shared_ptr<Content> &content); | ||
45 | QSharedPointer<QVariant> getVar(Content *content); | ||
46 | |||
47 | int getPos(Signature *sig); | ||
48 | int getPos(Encryption *enc); | ||
49 | int getPos(Part *part); | ||
50 | int getPos(Content *content); | ||
51 | |||
52 | NewModel *q; | ||
53 | QVector<Part::Ptr> mParts; | ||
54 | std::unique_ptr<Entry> mRoot; | ||
55 | |||
56 | std::shared_ptr<Parser> mParser; | ||
57 | private: | ||
58 | QMap<std::shared_ptr<Signature>, QSharedPointer<QVariant>> mSignatureMap; | ||
59 | QMap<std::shared_ptr<Encryption>, QSharedPointer<QVariant>> mEncryptionMap; | ||
60 | QMap<Part *, QSharedPointer<QVariant>> mPartMap; | ||
61 | QMap<Content *, QSharedPointer<QVariant>> mCMap; | ||
62 | }; | ||
63 | |||
64 | class Entry | ||
65 | { | ||
66 | public: | ||
67 | Entry(NewModelPrivate *model) | ||
68 | : mParent(nullptr) | ||
69 | , mNewModelPrivate(model) | ||
70 | { | ||
71 | } | ||
72 | |||
73 | ~Entry() | ||
74 | { | ||
75 | foreach(auto child, mChildren) { | ||
76 | delete child; | ||
77 | } | ||
78 | mChildren.clear(); | ||
79 | } | ||
80 | |||
81 | void addChild(Entry *entry) | ||
82 | { | ||
83 | mChildren.append(entry); | ||
84 | entry->mParent = this; | ||
85 | } | ||
86 | |||
87 | Entry *addSignatures(QVector<Signature::Ptr> signatures) | ||
88 | { | ||
89 | auto ret = this; | ||
90 | foreach(const auto &sig, signatures) { | ||
91 | auto entry = new Entry(mNewModelPrivate); | ||
92 | entry->mData = mNewModelPrivate->getVar(sig); | ||
93 | ret->addChild(entry); | ||
94 | ret = entry; | ||
95 | } | ||
96 | return ret; | ||
97 | } | ||
98 | |||
99 | Entry *addEncryptions(QVector<Encryption::Ptr> encryptions) | ||
100 | { | ||
101 | auto ret = this; | ||
102 | foreach(const auto &enc, encryptions) { | ||
103 | auto entry = new Entry(mNewModelPrivate); | ||
104 | entry->mData = mNewModelPrivate->getVar(enc); | ||
105 | ret->addChild(entry); | ||
106 | ret = entry; | ||
107 | } | ||
108 | return ret; | ||
109 | } | ||
110 | |||
111 | Entry *addPart(Part *part) | ||
112 | { | ||
113 | auto entry = new Entry(mNewModelPrivate); | ||
114 | entry->mData = mNewModelPrivate->getVar(part); | ||
115 | addChild(entry); | ||
116 | |||
117 | foreach(const auto &content, part->content()) { | ||
118 | auto _entry = entry; | ||
119 | _entry = _entry->addEncryptions(content->encryptions().mid(part->encryptions().size())); | ||
120 | _entry = _entry->addSignatures(content->signatures().mid(part->signatures().size())); | ||
121 | auto c = new Entry(mNewModelPrivate); | ||
122 | c->mData = mNewModelPrivate->getVar(content); | ||
123 | _entry->addChild(c); | ||
124 | } | ||
125 | // foreach(const auto &content, part->availableContents()) { | ||
126 | // foreach(const auto &contentPart, part->content(content)) { | ||
127 | // auto _entry = entry; | ||
128 | // _entry = _entry->addEncryptions(contentPart->encryptions().mid(part->encryptions().size())); | ||
129 | // _entry = _entry->addSignatures(contentPart->signatures().mid(part->signatures().size())); | ||
130 | // auto c = new Entry(mNewModelPrivate); | ||
131 | // c->mData = mNewModelPrivate->getVar(contentPart); | ||
132 | // _entry->addChild(c); | ||
133 | // } | ||
134 | // } | ||
135 | foreach(const auto &sp, part->subParts()) { | ||
136 | auto _entry = entry; | ||
137 | _entry = _entry->addEncryptions(sp->encryptions().mid(part->encryptions().size())); | ||
138 | _entry = _entry->addSignatures(sp->signatures().mid(part->signatures().size())); | ||
139 | _entry->addPart(sp.get()); | ||
140 | } | ||
141 | return entry; | ||
142 | } | ||
143 | |||
144 | int pos() | ||
145 | { | ||
146 | if(!mParent) { | ||
147 | return -1; | ||
148 | } | ||
149 | int i=0; | ||
150 | foreach(const auto &child, mParent->mChildren) { | ||
151 | if (child == this) { | ||
152 | return i; | ||
153 | } | ||
154 | i++; | ||
155 | } | ||
156 | return -1; | ||
157 | } | ||
158 | |||
159 | QSharedPointer<QVariant> mData; | ||
160 | |||
161 | Entry *mParent; | ||
162 | QVector<Entry *> mChildren; | ||
163 | NewModelPrivate *mNewModelPrivate; | ||
164 | }; | ||
165 | |||
166 | |||
167 | NewModelPrivate::NewModelPrivate(NewModel *q_ptr, const std::shared_ptr<Parser> &parser) | ||
168 | : q(q_ptr) | ||
169 | , mRoot(std::unique_ptr<Entry>(new Entry(this))) | ||
170 | , mParser(parser) | ||
171 | { | ||
172 | mParts = mParser->collectContentParts(); | ||
173 | createTree(); | ||
174 | } | ||
175 | |||
176 | NewModelPrivate::~NewModelPrivate() | ||
177 | { | ||
178 | } | ||
179 | |||
180 | void NewModelPrivate::createTree() | ||
181 | { | ||
182 | auto root = mRoot.get(); | ||
183 | auto parent = root; | ||
184 | Part *pPart = nullptr; | ||
185 | QVector<Signature::Ptr> signatures; | ||
186 | QVector<Encryption::Ptr> encryptions; | ||
187 | foreach(const auto part, mParts) { | ||
188 | auto _parent = parent; | ||
189 | if (pPart != part->parent()) { | ||
190 | auto _parent = root; | ||
191 | _parent = _parent->addEncryptions(part->parent()->encryptions()); | ||
192 | _parent = _parent->addSignatures(part->parent()->signatures()); | ||
193 | signatures = part->parent()->signatures(); | ||
194 | encryptions = part->parent()->encryptions(); | ||
195 | parent = _parent; | ||
196 | pPart = part->parent(); | ||
197 | } | ||
198 | _parent = _parent->addEncryptions(part->encryptions().mid(encryptions.size())); | ||
199 | _parent = _parent->addSignatures(part->signatures().mid(signatures.size())); | ||
200 | _parent->addPart(part.get()); | ||
201 | } | ||
202 | } | ||
203 | |||
204 | QSharedPointer<QVariant> NewModelPrivate::getVar(const std::shared_ptr<Signature> &sig) | ||
205 | { | ||
206 | if (!mSignatureMap.contains(sig)) { | ||
207 | auto var = new QVariant(); | ||
208 | var->setValue(sig.get()); | ||
209 | mSignatureMap.insert(sig, QSharedPointer<QVariant>(var)); | ||
210 | } | ||
211 | return mSignatureMap.value(sig); | ||
212 | } | ||
213 | |||
214 | QSharedPointer<QVariant> NewModelPrivate::getVar(const std::shared_ptr<Encryption> &enc) | ||
215 | { | ||
216 | if (!mEncryptionMap.contains(enc)) { | ||
217 | auto var = new QVariant(); | ||
218 | var->setValue(enc.get()); | ||
219 | mEncryptionMap.insert(enc, QSharedPointer<QVariant>(var)); | ||
220 | } | ||
221 | return mEncryptionMap.value(enc); | ||
222 | } | ||
223 | |||
224 | QSharedPointer<QVariant> NewModelPrivate::getVar(const std::shared_ptr<Part> &part) | ||
225 | { | ||
226 | return getVar(part.get()); | ||
227 | } | ||
228 | |||
229 | QSharedPointer<QVariant> NewModelPrivate::getVar(Part *part) | ||
230 | { | ||
231 | if (!mPartMap.contains(part)) { | ||
232 | auto var = new QVariant(); | ||
233 | var->setValue(part); | ||
234 | mPartMap.insert(part, QSharedPointer<QVariant>(var)); | ||
235 | } | ||
236 | return mPartMap.value(part); | ||
237 | } | ||
238 | |||
239 | QSharedPointer<QVariant> NewModelPrivate::getVar(const std::shared_ptr<Content> &content) | ||
240 | { | ||
241 | return getVar(content.get()); | ||
242 | } | ||
243 | |||
244 | QSharedPointer<QVariant> NewModelPrivate::getVar(Content *content) | ||
245 | { | ||
246 | if (!mCMap.contains(content)) { | ||
247 | auto var = new QVariant(); | ||
248 | var->setValue(content); | ||
249 | mCMap.insert(content, QSharedPointer<QVariant>(var)); | ||
250 | } | ||
251 | return mCMap.value(content); | ||
252 | } | ||
253 | |||
254 | int NewModelPrivate::getPos(Signature *signature) | ||
255 | { | ||
256 | const auto first = mParts.first(); | ||
257 | int i = 0; | ||
258 | foreach(const auto &sig, first->signatures()) { | ||
259 | if (sig.get() == signature) { | ||
260 | break; | ||
261 | } | ||
262 | i++; | ||
263 | } | ||
264 | return i; | ||
265 | } | ||
266 | |||
267 | int NewModelPrivate::getPos(Encryption *encryption) | ||
268 | { | ||
269 | const auto first = mParts.first(); | ||
270 | int i = 0; | ||
271 | foreach(const auto &enc, first->encryptions()) { | ||
272 | if (enc.get() == encryption) { | ||
273 | break; | ||
274 | } | ||
275 | i++; | ||
276 | } | ||
277 | return i; | ||
278 | } | ||
279 | |||
280 | int NewModelPrivate::getPos(Part *part) | ||
281 | { | ||
282 | int i = 0; | ||
283 | foreach(const auto &p, mParts) { | ||
284 | if (p.get() == part) { | ||
285 | break; | ||
286 | } | ||
287 | i++; | ||
288 | } | ||
289 | return i; | ||
290 | } | ||
291 | |||
292 | int NewModelPrivate::getPos(Content *content) | ||
293 | { | ||
294 | int i = 0; | ||
295 | foreach(const auto &c, content->parent()->content()) { | ||
296 | if (c.get() == content) { | ||
297 | break; | ||
298 | } | ||
299 | i++; | ||
300 | } | ||
301 | return i; | ||
302 | } | ||
303 | |||
304 | NewModel::NewModel(std::shared_ptr<Parser> parser) | ||
305 | : d(std::unique_ptr<NewModelPrivate>(new NewModelPrivate(this, parser))) | ||
306 | { | ||
307 | } | ||
308 | |||
309 | NewModel::~NewModel() | ||
310 | { | ||
311 | } | ||
312 | |||
313 | QHash<int, QByteArray> NewModel::roleNames() const | ||
314 | { | ||
315 | QHash<int, QByteArray> roles; | ||
316 | roles[TypeRole] = "type"; | ||
317 | roles[ContentRole] = "content"; | ||
318 | roles[IsEmbededRole] = "embeded"; | ||
319 | roles[SecurityLevelRole] = "securityLevel"; | ||
320 | roles[EncryptionErrorType] = "errorType"; | ||
321 | roles[EncryptionErrorString] = "errorString"; | ||
322 | return roles; | ||
323 | } | ||
324 | |||
325 | QModelIndex NewModel::index(int row, int column, const QModelIndex &parent) const | ||
326 | { | ||
327 | if (row < 0 || column != 0) { | ||
328 | return QModelIndex(); | ||
329 | } | ||
330 | Entry *entry = d->mRoot.get(); | ||
331 | if (parent.isValid()) { | ||
332 | entry = static_cast<Entry *>(parent.internalPointer()); | ||
333 | } | ||
334 | |||
335 | if (row < entry->mChildren.size()) { | ||
336 | return createIndex(row, column, entry->mChildren.at(row)); | ||
337 | } | ||
338 | return QModelIndex(); | ||
339 | } | ||
340 | |||
341 | QVariant NewModel::data(const QModelIndex &index, int role) const | ||
342 | { | ||
343 | if (!index.isValid()) { | ||
344 | switch (role) { | ||
345 | case Qt::DisplayRole: | ||
346 | return QString("root"); | ||
347 | case IsEmbededRole: | ||
348 | return false; | ||
349 | } | ||
350 | return QVariant(); | ||
351 | } | ||
352 | |||
353 | if (index.internalPointer()) { | ||
354 | const auto entry = static_cast<Entry *>(index.internalPointer()); | ||
355 | const auto _data = entry->mData; | ||
356 | if (entry == d->mRoot.get()|| !_data) { | ||
357 | switch (role) { | ||
358 | case Qt::DisplayRole: | ||
359 | return QString("root"); | ||
360 | case IsEmbededRole: | ||
361 | return false; | ||
362 | } | ||
363 | return QVariant(); | ||
364 | } | ||
365 | if (_data->userType() == qMetaTypeId<Signature *>()) { | ||
366 | const auto signature = _data->value<Signature *>(); | ||
367 | int i = d->getPos(signature); | ||
368 | switch(role) { | ||
369 | case Qt::DisplayRole: | ||
370 | return QStringLiteral("Signature%1").arg(i); | ||
371 | case TypeRole: | ||
372 | return QStringLiteral("Signature"); | ||
373 | case SecurityLevelRole: | ||
374 | return QStringLiteral("RED"); | ||
375 | case IsEmbededRole: | ||
376 | return data(index.parent(), IsEmbededRole); | ||
377 | } | ||
378 | } else if (_data->userType() == qMetaTypeId<Encryption *>()) { | ||
379 | const auto encryption = _data->value<Encryption *>(); | ||
380 | int i = d->getPos(encryption); | ||
381 | switch(role) { | ||
382 | case Qt::DisplayRole: | ||
383 | return QStringLiteral("Encryption%1").arg(i); | ||
384 | case TypeRole: | ||
385 | return QStringLiteral("Encryption"); | ||
386 | case SecurityLevelRole: | ||
387 | return QStringLiteral("GREEN"); | ||
388 | case IsEmbededRole: | ||
389 | return data(index.parent(), IsEmbededRole); | ||
390 | case EncryptionErrorType: | ||
391 | { | ||
392 | switch(encryption->errorType()) { | ||
393 | case Encryption::NoError: | ||
394 | return QString(); | ||
395 | case Encryption::PassphraseError: | ||
396 | return QStringLiteral("PassphraseError"); | ||
397 | case Encryption::KeyMissing: | ||
398 | return QStringLiteral("KeyMissing"); | ||
399 | default: | ||
400 | return QStringLiteral("UnknownError"); | ||
401 | } | ||
402 | } | ||
403 | case EncryptionErrorString: | ||
404 | return encryption->errorString(); | ||
405 | } | ||
406 | } else if (_data->userType() == qMetaTypeId<Part *>()) { | ||
407 | const auto part = _data->value<Part *>(); | ||
408 | switch (role) { | ||
409 | case Qt::DisplayRole: | ||
410 | case TypeRole: | ||
411 | return QString::fromLatin1(part->type()); | ||
412 | case IsEmbededRole: | ||
413 | return data(index.parent(), IsEmbededRole); | ||
414 | } | ||
415 | } else if (_data->userType() == qMetaTypeId<Content *>()) { | ||
416 | const auto content = _data->value<Content *>(); | ||
417 | int i = d->getPos(content); | ||
418 | switch(role) { | ||
419 | case Qt::DisplayRole: | ||
420 | return QStringLiteral("Content%1").arg(i); | ||
421 | case TypeRole: | ||
422 | return QString::fromLatin1(content->type()); | ||
423 | case IsEmbededRole: | ||
424 | return data(index.parent(), IsEmbededRole); | ||
425 | case ContentRole: { | ||
426 | auto text = content->encodedContent(); | ||
427 | if (data(index, TypeRole).toString() == "HtmlContent") { | ||
428 | const auto rx = QRegExp("(src)\\s*=\\s*(\"|')(cid:[^\"']+)\\2"); | ||
429 | int pos = 0; | ||
430 | while ((pos = rx.indexIn(text, pos)) != -1) { | ||
431 | const auto link = QUrl(rx.cap(3).toUtf8()); | ||
432 | pos += rx.matchedLength(); | ||
433 | const auto repl = d->mParser->getPart(link); | ||
434 | if (!repl) { | ||
435 | continue; | ||
436 | } | ||
437 | const auto content = repl->content(); | ||
438 | if(content.size() < 1) { | ||
439 | continue; | ||
440 | } | ||
441 | const auto mailMime = content.first()->mailMime(); | ||
442 | const auto mimetype = mailMime->mimetype().name(); | ||
443 | if (mimetype.startsWith("image/")) { | ||
444 | const auto data = content.first()->content(); | ||
445 | text.replace(rx.cap(0), QString("src=\"data:%1;base64,%2\"").arg(mimetype, QString::fromLatin1(data.toBase64()))); | ||
446 | } | ||
447 | } | ||
448 | } | ||
449 | return text; | ||
450 | } | ||
451 | } | ||
452 | } | ||
453 | } | ||
454 | return QVariant(); | ||
455 | } | ||
456 | |||
457 | QModelIndex NewModel::parent(const QModelIndex &index) const | ||
458 | { | ||
459 | if (!index.internalPointer()) { | ||
460 | return QModelIndex(); | ||
461 | } | ||
462 | const auto entry = static_cast<Entry *>(index.internalPointer()); | ||
463 | if (entry->mParent && entry->mParent != d->mRoot.get()) { | ||
464 | return createIndex(entry->pos(), 0, entry->mParent); | ||
465 | } | ||
466 | return QModelIndex(); | ||
467 | } | ||
468 | |||
469 | int NewModel::rowCount(const QModelIndex &parent) const | ||
470 | { | ||
471 | if (!parent.isValid()) { | ||
472 | return d->mRoot->mChildren.size(); | ||
473 | } else { | ||
474 | if (!parent.internalPointer()) { | ||
475 | return 0; | ||
476 | } | ||
477 | const auto entry = static_cast<Entry *>(parent.internalPointer()); | ||
478 | return entry->mChildren.size(); | ||
479 | } | ||
480 | return 0; | ||
481 | } | ||
482 | |||
483 | int NewModel::columnCount(const QModelIndex &parent) const | ||
484 | { | ||
485 | return 1; | ||
486 | } | ||
diff --git a/framework/src/domain/messageparser_old.cpp b/framework/src/domain/messageparser_old.cpp new file mode 100644 index 00000000..a4247d8c --- /dev/null +++ b/framework/src/domain/messageparser_old.cpp | |||
@@ -0,0 +1,151 @@ | |||
1 | /* | ||
2 | This library is free software; you can redistribute it and/or modify it | ||
3 | under the terms of the GNU Library General Public License as published by | ||
4 | the Free Software Foundation; either version 2 of the License, or (at your | ||
5 | option) any later version. | ||
6 | |||
7 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
8 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
9 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
10 | License for more details. | ||
11 | |||
12 | You should have received a copy of the GNU Library General Public License | ||
13 | along with this library; see the file COPYING.LIB. If not, write to the | ||
14 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
15 | 02110-1301, USA. | ||
16 | */ | ||
17 | |||
18 | #include "messageparser.h" | ||
19 | #include "mimetreeparser/interface.h" | ||
20 | |||
21 | PartModel::PartModel(QSharedPointer<MimeTreeParser::MessagePart> partTree, std::shared_ptr<Parser> parser) | ||
22 | : mPartTree(partTree) | ||
23 | , mParser(parser) | ||
24 | { | ||
25 | } | ||
26 | |||
27 | QHash<int, QByteArray> PartModel::roleNames() const | ||
28 | { | ||
29 | QHash<int, QByteArray> roles; | ||
30 | roles[Text] = "text"; | ||
31 | roles[IsHtml] = "isHtml"; | ||
32 | roles[IsHidden] = "isHidden"; | ||
33 | roles[IsEncrypted] = "isEncrypted"; | ||
34 | roles[IsAttachment] = "isAttachment"; | ||
35 | roles[HasContent] = "hasContent"; | ||
36 | roles[Type] = "type"; | ||
37 | roles[IsHidden] = "isHidden"; | ||
38 | return roles; | ||
39 | } | ||
40 | |||
41 | QModelIndex PartModel::index(int row, int column, const QModelIndex &parent) const | ||
42 | { | ||
43 | // qDebug() << "index " << parent << row << column << mPartTree->subParts().size(); | ||
44 | if (!parent.isValid()) { | ||
45 | if (row < mPartTree->subParts().size()) { | ||
46 | auto part = mPartTree->subParts().at(row); | ||
47 | return createIndex(row, column, part.data()); | ||
48 | } | ||
49 | } else { | ||
50 | auto part = static_cast<MimeTreeParser::MessagePart*>(parent.internalPointer()); | ||
51 | auto subPart = part->subParts().at(row); | ||
52 | return createIndex(row, column, subPart.data()); | ||
53 | } | ||
54 | return QModelIndex(); | ||
55 | } | ||
56 | |||
57 | QVariant PartModel::data(const QModelIndex &index, int role) const | ||
58 | { | ||
59 | // qDebug() << "Getting data for index"; | ||
60 | if (index.isValid()) { | ||
61 | auto part = static_cast<MimeTreeParser::MessagePart*>(index.internalPointer()); | ||
62 | switch (role) { | ||
63 | case Text: { | ||
64 | // qDebug() << "Getting text: " << part->property("text").toString(); | ||
65 | // FIXME: we should have a list per part, and not one for all parts. | ||
66 | auto text = part->property("htmlContent").toString(); | ||
67 | const auto rx = QRegExp("(src)\\s*=\\s*(\"|')(cid:[^\"']+)\\2"); | ||
68 | int pos = 0; | ||
69 | while ((pos = rx.indexIn(text, pos)) != -1) { | ||
70 | const auto link = QUrl(rx.cap(3).toUtf8()); | ||
71 | pos += rx.matchedLength(); | ||
72 | const auto repl = mParser->getPart(link); | ||
73 | if (!repl) { | ||
74 | continue; | ||
75 | } | ||
76 | const auto content = repl->content(); | ||
77 | if(content.size() < 1) { | ||
78 | continue; | ||
79 | } | ||
80 | const auto mailMime = content.first()->mailMime(); | ||
81 | const auto mimetype = mailMime->mimetype().name(); | ||
82 | if (mimetype.startsWith("image/")) { | ||
83 | const auto data = content.first()->content(); | ||
84 | text.replace(rx.cap(0), QString("src=\"data:%1;base64,%2\"").arg(mimetype, QString::fromLatin1(data.toBase64()))); | ||
85 | } | ||
86 | } | ||
87 | return text; | ||
88 | } | ||
89 | case IsAttachment: | ||
90 | return part->property("attachment").toBool(); | ||
91 | case IsEncrypted: | ||
92 | return part->property("isEncrypted").toBool(); | ||
93 | case IsHtml: | ||
94 | return part->property("isHtml").toBool(); | ||
95 | case HasContent: | ||
96 | return !part->property("htmlContent").toString().isEmpty(); | ||
97 | case Type: | ||
98 | return part->metaObject()->className(); | ||
99 | case IsHidden: | ||
100 | return false; | ||
101 | //return part->property("isHidden").toBool(); | ||
102 | |||
103 | } | ||
104 | } | ||
105 | return QVariant(); | ||
106 | } | ||
107 | |||
108 | QModelIndex PartModel::parent(const QModelIndex &index) const | ||
109 | { | ||
110 | // qDebug() << "parent " << index; | ||
111 | if (index.isValid()) { | ||
112 | auto part = static_cast<MimeTreeParser::MessagePart*>(index.internalPointer()); | ||
113 | auto parentPart = static_cast<MimeTreeParser::MessagePart*>(part->parentPart()); | ||
114 | auto row = 0;//get the parents parent to find the index | ||
115 | if (!parentPart) { | ||
116 | parentPart = mPartTree.data(); | ||
117 | } | ||
118 | int i = 0; | ||
119 | for (const auto &p : parentPart->subParts()) { | ||
120 | if (p.data() == part) { | ||
121 | row = i; | ||
122 | break; | ||
123 | } | ||
124 | i++; | ||
125 | } | ||
126 | return createIndex(row, index.column(), parentPart); | ||
127 | } | ||
128 | return QModelIndex(); | ||
129 | } | ||
130 | |||
131 | int PartModel::rowCount(const QModelIndex &parent) const | ||
132 | { | ||
133 | // qDebug() << "Row count " << parent; | ||
134 | if (!parent.isValid()) { | ||
135 | // qDebug() << "Row count " << mPartTree->subParts().size(); | ||
136 | return mPartTree->subParts().size(); | ||
137 | } else { | ||
138 | auto part = static_cast<MimeTreeParser::MessagePart*>(parent.internalPointer()); | ||
139 | if (part) { | ||
140 | return part->subParts().size(); | ||
141 | } | ||
142 | } | ||
143 | return 0; | ||
144 | } | ||
145 | |||
146 | int PartModel::columnCount(const QModelIndex &parent) const | ||
147 | { | ||
148 | // qDebug() << "Column count " << parent; | ||
149 | return 1; | ||
150 | } | ||
151 | |||
diff --git a/framework/src/domain/mimetreeparser/CMakeLists.txt b/framework/src/domain/mimetreeparser/CMakeLists.txt new file mode 100644 index 00000000..64da2656 --- /dev/null +++ b/framework/src/domain/mimetreeparser/CMakeLists.txt | |||
@@ -0,0 +1,15 @@ | |||
1 | set(mimetreeparser_SRCS | ||
2 | interface.cpp | ||
3 | objecttreesource.cpp | ||
4 | stringhtmlwriter.cpp | ||
5 | ) | ||
6 | |||
7 | add_library(mimetreeparser SHARED ${mimetreeparser_SRCS}) | ||
8 | |||
9 | qt5_use_modules(mimetreeparser Core Gui) | ||
10 | target_link_libraries(mimetreeparser KF5::Mime KF5::MimeTreeParser) | ||
11 | |||
12 | install(TARGETS mimetreeparser | ||
13 | DESTINATION ${LIB_INSTALL_DIR}) | ||
14 | |||
15 | add_subdirectory(tests) | ||
diff --git a/framework/src/domain/mimetreeparser/interface.cpp b/framework/src/domain/mimetreeparser/interface.cpp new file mode 100644 index 00000000..663a2013 --- /dev/null +++ b/framework/src/domain/mimetreeparser/interface.cpp | |||
@@ -0,0 +1,1179 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Sandro Knauß <knauss@kolabsystems.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | |||
20 | #include "interface.h" | ||
21 | #include "interface_p.h" | ||
22 | |||
23 | #include "stringhtmlwriter.h" | ||
24 | #include "objecttreesource.h" | ||
25 | |||
26 | #include <QGpgME/KeyListJob> | ||
27 | #include <QGpgME/Protocol> | ||
28 | #include <gpgme++/key.h> | ||
29 | #include <gpgme++/keylistresult.h> | ||
30 | |||
31 | #include <KMime/Content> | ||
32 | #include <MimeTreeParser/ObjectTreeParser> | ||
33 | #include <MimeTreeParser/MessagePart> | ||
34 | #include <MimeTreeParser/NodeHelper> | ||
35 | |||
36 | #include <QMimeDatabase> | ||
37 | #include <QMimeType> | ||
38 | #include <QTextCodec> | ||
39 | #include <QDebug> | ||
40 | |||
41 | class MailMimePrivate | ||
42 | { | ||
43 | public: | ||
44 | MailMimePrivate(MailMime *p); | ||
45 | |||
46 | MailMime *q; | ||
47 | KMime::Content *mNode; | ||
48 | std::shared_ptr<MailMime> parent; | ||
49 | }; | ||
50 | |||
51 | MailMimePrivate::MailMimePrivate(MailMime* p) | ||
52 | : q(p) | ||
53 | , mNode(nullptr) | ||
54 | , parent(nullptr) | ||
55 | { | ||
56 | } | ||
57 | |||
58 | |||
59 | MailMime::MailMime() | ||
60 | : d(std::unique_ptr<MailMimePrivate>(new MailMimePrivate(this))) | ||
61 | { | ||
62 | } | ||
63 | |||
64 | QByteArray MailMime::cid() const | ||
65 | { | ||
66 | if (!d->mNode || !d->mNode->contentID()) { | ||
67 | return QByteArray(); | ||
68 | } | ||
69 | return d->mNode->contentID()->identifier(); | ||
70 | } | ||
71 | |||
72 | QByteArray MailMime::charset() const | ||
73 | { | ||
74 | if(!d->mNode || !d->mNode->contentType(false)) { | ||
75 | return QByteArray(); | ||
76 | } | ||
77 | if (d->mNode->contentType(false)) { | ||
78 | return d->mNode->contentType(false)->charset(); | ||
79 | } | ||
80 | return d->mNode->defaultCharset(); | ||
81 | } | ||
82 | |||
83 | bool MailMime::isFirstTextPart() const | ||
84 | { | ||
85 | if (!d->mNode || !d->mNode->topLevel()) { | ||
86 | return false; | ||
87 | } | ||
88 | return (d->mNode->topLevel()->textContent() == d->mNode); | ||
89 | } | ||
90 | |||
91 | bool MailMime::isFirstPart() const | ||
92 | { | ||
93 | if (!d->mNode || !d->mNode->parent()) { | ||
94 | return false; | ||
95 | } | ||
96 | return (d->mNode->parent()->contents().first() == d->mNode); | ||
97 | } | ||
98 | |||
99 | bool MailMime::isTopLevelPart() const | ||
100 | { | ||
101 | if (!d->mNode) { | ||
102 | return false; | ||
103 | } | ||
104 | return (d->mNode->topLevel() == d->mNode); | ||
105 | } | ||
106 | |||
107 | MailMime::Disposition MailMime::disposition() const | ||
108 | { | ||
109 | if (!d->mNode) { | ||
110 | return Invalid; | ||
111 | } | ||
112 | const auto cd = d->mNode->contentDisposition(false); | ||
113 | if (!cd) { | ||
114 | return Invalid; | ||
115 | } | ||
116 | switch (cd->disposition()){ | ||
117 | case KMime::Headers::CDinline: | ||
118 | return Inline; | ||
119 | case KMime::Headers::CDattachment: | ||
120 | return Attachment; | ||
121 | default: | ||
122 | return Invalid; | ||
123 | } | ||
124 | } | ||
125 | |||
126 | QString MailMime::filename() const | ||
127 | { | ||
128 | if (!d->mNode) { | ||
129 | return QString(); | ||
130 | } | ||
131 | const auto cd = d->mNode->contentDisposition(false); | ||
132 | if (!cd) { | ||
133 | return QString(); | ||
134 | } | ||
135 | return cd->filename(); | ||
136 | } | ||
137 | |||
138 | QMimeType MailMime::mimetype() const | ||
139 | { | ||
140 | if (!d->mNode) { | ||
141 | return QMimeType(); | ||
142 | } | ||
143 | |||
144 | const auto ct = d->mNode->contentType(false); | ||
145 | if (!ct) { | ||
146 | return QMimeType(); | ||
147 | } | ||
148 | |||
149 | QMimeDatabase mimeDb; | ||
150 | return mimeDb.mimeTypeForName(ct->mimeType()); | ||
151 | } | ||
152 | |||
153 | MailMime::Ptr MailMime::parent() const | ||
154 | { | ||
155 | if (!d->parent) { | ||
156 | d->parent = std::shared_ptr<MailMime>(new MailMime()); | ||
157 | d->parent->d->mNode = d->mNode->parent(); | ||
158 | } | ||
159 | return d->parent; | ||
160 | } | ||
161 | |||
162 | QByteArray MailMime::decodedContent() const | ||
163 | { | ||
164 | if (!d->mNode) { | ||
165 | return QByteArray(); | ||
166 | } | ||
167 | return d->mNode->decodedContent(); | ||
168 | } | ||
169 | |||
170 | class KeyPrivate | ||
171 | { | ||
172 | public: | ||
173 | Key *q; | ||
174 | GpgME::Key mKey; | ||
175 | QByteArray mKeyID; | ||
176 | }; | ||
177 | |||
178 | Key::Key() | ||
179 | :d(std::unique_ptr<KeyPrivate>(new KeyPrivate)) | ||
180 | { | ||
181 | d->q = this; | ||
182 | } | ||
183 | |||
184 | |||
185 | Key::Key(KeyPrivate *d_ptr) | ||
186 | :d(std::unique_ptr<KeyPrivate>(d_ptr)) | ||
187 | { | ||
188 | d->q = this; | ||
189 | } | ||
190 | |||
191 | Key::~Key() | ||
192 | { | ||
193 | |||
194 | } | ||
195 | |||
196 | QString Key::keyid() const | ||
197 | { | ||
198 | if (!d->mKey.isNull()) { | ||
199 | return d->mKey.keyID(); | ||
200 | } | ||
201 | |||
202 | return d->mKeyID; | ||
203 | } | ||
204 | |||
205 | QString Key::name() const | ||
206 | { | ||
207 | //FIXME: is this the correct way to get the primary UID? | ||
208 | if (!d->mKey.isNull()) { | ||
209 | return d->mKey.userID(0).name(); | ||
210 | } | ||
211 | |||
212 | return QString(); | ||
213 | } | ||
214 | |||
215 | QString Key::email() const | ||
216 | { | ||
217 | if (!d->mKey.isNull()) { | ||
218 | return d->mKey.userID(0).email(); | ||
219 | } | ||
220 | return QString(); | ||
221 | } | ||
222 | |||
223 | QString Key::comment() const | ||
224 | { | ||
225 | if (!d->mKey.isNull()) { | ||
226 | return d->mKey.userID(0).comment(); | ||
227 | } | ||
228 | return QString(); | ||
229 | } | ||
230 | |||
231 | class SignaturePrivate | ||
232 | { | ||
233 | public: | ||
234 | Signature *q; | ||
235 | GpgME::Signature mSignature; | ||
236 | Key::Ptr mKey; | ||
237 | }; | ||
238 | |||
239 | Signature::Signature() | ||
240 | :d(std::unique_ptr<SignaturePrivate>(new SignaturePrivate)) | ||
241 | { | ||
242 | d->q = this; | ||
243 | } | ||
244 | |||
245 | |||
246 | Signature::Signature(SignaturePrivate *d_ptr) | ||
247 | :d(std::unique_ptr<SignaturePrivate>(d_ptr)) | ||
248 | { | ||
249 | d->q = this; | ||
250 | } | ||
251 | |||
252 | Signature::~Signature() | ||
253 | { | ||
254 | |||
255 | } | ||
256 | |||
257 | QDateTime Signature::creationDateTime() const | ||
258 | { | ||
259 | QDateTime dt; | ||
260 | dt.setTime_t(d->mSignature.creationTime()); | ||
261 | return dt; | ||
262 | } | ||
263 | |||
264 | QDateTime Signature::expirationDateTime() const | ||
265 | { | ||
266 | QDateTime dt; | ||
267 | dt.setTime_t(d->mSignature.expirationTime()); | ||
268 | return dt; | ||
269 | } | ||
270 | |||
271 | bool Signature::neverExpires() const | ||
272 | { | ||
273 | return d->mSignature.neverExpires(); | ||
274 | } | ||
275 | |||
276 | Key::Ptr Signature::key() const | ||
277 | { | ||
278 | return d->mKey; | ||
279 | } | ||
280 | |||
281 | class EncryptionPrivate | ||
282 | { | ||
283 | public: | ||
284 | Encryption *q; | ||
285 | std::vector<Key::Ptr> mRecipients; | ||
286 | Encryption::ErrorType mErrorType; | ||
287 | QString mErrorString; | ||
288 | }; | ||
289 | |||
290 | Encryption::Encryption(EncryptionPrivate *d_ptr) | ||
291 | :d(std::unique_ptr<EncryptionPrivate>(d_ptr)) | ||
292 | { | ||
293 | d->q = this; | ||
294 | } | ||
295 | |||
296 | Encryption::Encryption() | ||
297 | :d(std::unique_ptr<EncryptionPrivate>(new EncryptionPrivate)) | ||
298 | { | ||
299 | d->q = this; | ||
300 | d->mErrorType = Encryption::NoError; | ||
301 | } | ||
302 | |||
303 | Encryption::~Encryption() | ||
304 | { | ||
305 | |||
306 | } | ||
307 | |||
308 | std::vector<Key::Ptr> Encryption::recipients() const | ||
309 | { | ||
310 | return d->mRecipients; | ||
311 | } | ||
312 | |||
313 | QString Encryption::errorString() | ||
314 | { | ||
315 | return d->mErrorString; | ||
316 | } | ||
317 | |||
318 | Encryption::ErrorType Encryption::errorType() | ||
319 | { | ||
320 | return d->mErrorType; | ||
321 | } | ||
322 | |||
323 | |||
324 | class PartPrivate | ||
325 | { | ||
326 | public: | ||
327 | PartPrivate(Part *part); | ||
328 | void appendSubPart(Part::Ptr subpart); | ||
329 | |||
330 | QVector<Part::Ptr> subParts(); | ||
331 | |||
332 | Part *parent() const; | ||
333 | |||
334 | const MailMime::Ptr &mailMime() const; | ||
335 | void createMailMime(const MimeTreeParser::MimeMessagePart::Ptr &part); | ||
336 | void createMailMime(const MimeTreeParser::TextMessagePart::Ptr &part); | ||
337 | void createMailMime(const MimeTreeParser::AlternativeMessagePart::Ptr &part); | ||
338 | void createMailMime(const MimeTreeParser::HtmlMessagePart::Ptr &part); | ||
339 | void createMailMime(const MimeTreeParser::EncryptedMessagePart::Ptr &part); | ||
340 | |||
341 | static Encryption::Ptr createEncryption(const MimeTreeParser::EncryptedMessagePart::Ptr& part); | ||
342 | void appendEncryption(const MimeTreeParser::EncryptedMessagePart::Ptr &part); | ||
343 | static QVector<Signature::Ptr> createSignature(const MimeTreeParser::SignedMessagePart::Ptr& part); | ||
344 | void appendSignature(const MimeTreeParser::SignedMessagePart::Ptr &part); | ||
345 | |||
346 | void setSignatures(const QVector<Signature::Ptr> &sigs); | ||
347 | void setEncryptions(const QVector<Encryption::Ptr> &encs); | ||
348 | |||
349 | const QVector<Encryption::Ptr> &encryptions() const; | ||
350 | const QVector<Signature::Ptr> &signatures() const; | ||
351 | private: | ||
352 | Part *q; | ||
353 | Part *mParent; | ||
354 | QVector<Part::Ptr> mSubParts; | ||
355 | QVector<Encryption::Ptr> mEncryptions; | ||
356 | QVector<Signature::Ptr> mSignatures; | ||
357 | MailMime::Ptr mMailMime; | ||
358 | }; | ||
359 | |||
360 | PartPrivate::PartPrivate(Part* part) | ||
361 | : q(part) | ||
362 | , mParent(Q_NULLPTR) | ||
363 | { | ||
364 | |||
365 | } | ||
366 | |||
367 | void PartPrivate::createMailMime(const MimeTreeParser::HtmlMessagePart::Ptr& part) | ||
368 | { | ||
369 | mMailMime = MailMime::Ptr(new MailMime); | ||
370 | mMailMime->d->mNode = part->mNode; | ||
371 | } | ||
372 | |||
373 | void PartPrivate::createMailMime(const MimeTreeParser::AlternativeMessagePart::Ptr& part) | ||
374 | { | ||
375 | mMailMime = MailMime::Ptr(new MailMime); | ||
376 | mMailMime->d->mNode = part->mNode; | ||
377 | } | ||
378 | |||
379 | void PartPrivate::createMailMime(const MimeTreeParser::TextMessagePart::Ptr& part) | ||
380 | { | ||
381 | mMailMime = MailMime::Ptr(new MailMime); | ||
382 | mMailMime->d->mNode = part->mNode; | ||
383 | } | ||
384 | |||
385 | void PartPrivate::createMailMime(const MimeTreeParser::MimeMessagePart::Ptr& part) | ||
386 | { | ||
387 | mMailMime = MailMime::Ptr(new MailMime); | ||
388 | mMailMime->d->mNode = part->mNode; | ||
389 | } | ||
390 | |||
391 | void PartPrivate::createMailMime(const MimeTreeParser::EncryptedMessagePart::Ptr& part) | ||
392 | { | ||
393 | mMailMime = MailMime::Ptr(new MailMime); | ||
394 | mMailMime->d->mNode = part->mNode; | ||
395 | } | ||
396 | |||
397 | void PartPrivate::appendSubPart(Part::Ptr subpart) | ||
398 | { | ||
399 | subpart->d->mParent = q; | ||
400 | mSubParts.append(subpart); | ||
401 | } | ||
402 | |||
403 | Encryption::Ptr PartPrivate::createEncryption(const MimeTreeParser::EncryptedMessagePart::Ptr& part) | ||
404 | { | ||
405 | QGpgME::KeyListJob *job = part->mCryptoProto->keyListJob(false); // local, no sigs | ||
406 | if (!job) { | ||
407 | qWarning() << "The Crypto backend does not support listing keys. "; | ||
408 | return Encryption::Ptr(); | ||
409 | } | ||
410 | |||
411 | auto encpriv = new EncryptionPrivate(); | ||
412 | if (part->passphraseError()) { | ||
413 | encpriv->mErrorType = Encryption::PassphraseError; | ||
414 | encpriv->mErrorString = part->mMetaData.errorText; | ||
415 | } else if (part->isEncrypted() && !part->isDecryptable()) { | ||
416 | encpriv->mErrorType = Encryption::KeyMissing; | ||
417 | encpriv->mErrorString = part->mMetaData.errorText; | ||
418 | } else if (!part->isEncrypted() && !part->isDecryptable()) { | ||
419 | encpriv->mErrorType = Encryption::UnknownError; | ||
420 | encpriv->mErrorString = part->mMetaData.errorText; | ||
421 | } else { | ||
422 | encpriv->mErrorType = Encryption::NoError; | ||
423 | } | ||
424 | |||
425 | foreach(const auto &recipient, part->mDecryptRecipients) { | ||
426 | std::vector<GpgME::Key> found_keys; | ||
427 | const auto &keyid = recipient.keyID(); | ||
428 | GpgME::KeyListResult res = job->exec(QStringList(QLatin1String(keyid)), false, found_keys); | ||
429 | if (res.error()) { | ||
430 | qWarning() << "Error while searching key for Fingerprint: " << keyid; | ||
431 | continue; | ||
432 | } | ||
433 | if (found_keys.size() > 1) { | ||
434 | // Should not Happen | ||
435 | qWarning() << "Oops: Found more then one Key for Fingerprint: " << keyid; | ||
436 | } | ||
437 | if (found_keys.size() != 1) { | ||
438 | // Should not Happen at this point | ||
439 | qWarning() << "Oops: Found no Key for Fingerprint: " << keyid; | ||
440 | auto keypriv = new KeyPrivate; | ||
441 | keypriv->mKeyID = keyid; | ||
442 | encpriv->mRecipients.push_back(Key::Ptr(new Key(keypriv))); | ||
443 | } else { | ||
444 | auto key = found_keys[0]; | ||
445 | auto keypriv = new KeyPrivate; | ||
446 | keypriv->mKey = key; | ||
447 | encpriv->mRecipients.push_back(Key::Ptr(new Key(keypriv))); | ||
448 | } | ||
449 | } | ||
450 | return Encryption::Ptr(new Encryption(encpriv)); | ||
451 | } | ||
452 | |||
453 | void PartPrivate::appendEncryption(const MimeTreeParser::EncryptedMessagePart::Ptr& part) | ||
454 | { | ||
455 | mEncryptions.append(createEncryption(part)); | ||
456 | } | ||
457 | |||
458 | void PartPrivate::setEncryptions(const QVector< Encryption::Ptr >& encs) | ||
459 | { | ||
460 | mEncryptions = encs; | ||
461 | } | ||
462 | |||
463 | QVector<Signature::Ptr> PartPrivate::createSignature(const MimeTreeParser::SignedMessagePart::Ptr& part) | ||
464 | { | ||
465 | QVector<Signature::Ptr> sigs; | ||
466 | QGpgME::KeyListJob *job = part->mCryptoProto->keyListJob(false); // local, no sigs | ||
467 | if (!job) { | ||
468 | qWarning() << "The Crypto backend does not support listing keys. "; | ||
469 | return sigs; | ||
470 | } | ||
471 | |||
472 | foreach(const auto &sig, part->mSignatures) { | ||
473 | auto sigpriv = new SignaturePrivate(); | ||
474 | sigpriv->mSignature = sig; | ||
475 | auto signature = std::make_shared<Signature>(sigpriv); | ||
476 | sigs.append(signature); | ||
477 | |||
478 | std::vector<GpgME::Key> found_keys; | ||
479 | const auto &keyid = sig.fingerprint(); | ||
480 | GpgME::KeyListResult res = job->exec(QStringList(QLatin1String(keyid)), false, found_keys); | ||
481 | if (res.error()) { | ||
482 | qWarning() << "Error while searching key for Fingerprint: " << keyid; | ||
483 | continue; | ||
484 | } | ||
485 | if (found_keys.size() > 1) { | ||
486 | // Should not Happen | ||
487 | qWarning() << "Oops: Found more then one Key for Fingerprint: " << keyid; | ||
488 | continue; | ||
489 | } | ||
490 | if (found_keys.size() != 1) { | ||
491 | // Should not Happen at this point | ||
492 | qWarning() << "Oops: Found no Key for Fingerprint: " << keyid; | ||
493 | continue; | ||
494 | } else { | ||
495 | auto key = found_keys[0]; | ||
496 | auto keypriv = new KeyPrivate; | ||
497 | keypriv->mKey = key; | ||
498 | sigpriv->mKey = Key::Ptr(new Key(keypriv)); | ||
499 | } | ||
500 | } | ||
501 | return sigs; | ||
502 | } | ||
503 | |||
504 | void PartPrivate::appendSignature(const MimeTreeParser::SignedMessagePart::Ptr& part) | ||
505 | { | ||
506 | mSignatures.append(createSignature(part)); | ||
507 | } | ||
508 | |||
509 | |||
510 | void PartPrivate::setSignatures(const QVector< Signature::Ptr >& sigs) | ||
511 | { | ||
512 | mSignatures = sigs; | ||
513 | } | ||
514 | |||
515 | Part *PartPrivate::parent() const | ||
516 | { | ||
517 | return mParent; | ||
518 | } | ||
519 | |||
520 | QVector< Part::Ptr > PartPrivate::subParts() | ||
521 | { | ||
522 | return mSubParts; | ||
523 | } | ||
524 | |||
525 | const MailMime::Ptr& PartPrivate::mailMime() const | ||
526 | { | ||
527 | return mMailMime; | ||
528 | } | ||
529 | |||
530 | const QVector< Encryption::Ptr >& PartPrivate::encryptions() const | ||
531 | { | ||
532 | return mEncryptions; | ||
533 | } | ||
534 | |||
535 | const QVector< Signature::Ptr >& PartPrivate::signatures() const | ||
536 | { | ||
537 | return mSignatures; | ||
538 | } | ||
539 | |||
540 | Part::Part() | ||
541 | : d(std::unique_ptr<PartPrivate>(new PartPrivate(this))) | ||
542 | { | ||
543 | |||
544 | } | ||
545 | |||
546 | bool Part::hasSubParts() const | ||
547 | { | ||
548 | return !subParts().isEmpty(); | ||
549 | } | ||
550 | |||
551 | QVector<Part::Ptr> Part::subParts() const | ||
552 | { | ||
553 | return d->subParts(); | ||
554 | } | ||
555 | |||
556 | QByteArray Part::type() const | ||
557 | { | ||
558 | return "Part"; | ||
559 | } | ||
560 | |||
561 | QVector<QByteArray> Part::availableContents() const | ||
562 | { | ||
563 | return QVector<QByteArray>(); | ||
564 | } | ||
565 | |||
566 | QVector<Content::Ptr> Part::content() const | ||
567 | { | ||
568 | return content(availableContents().first()); | ||
569 | } | ||
570 | |||
571 | QVector<Content::Ptr> Part::content(const QByteArray& ct) const | ||
572 | { | ||
573 | return QVector<Content::Ptr>(); | ||
574 | } | ||
575 | |||
576 | QVector<Encryption::Ptr> Part::encryptions() const | ||
577 | { | ||
578 | auto ret = d->encryptions(); | ||
579 | auto parent = d->parent(); | ||
580 | if (parent) { | ||
581 | ret.append(parent->encryptions()); | ||
582 | } | ||
583 | return ret; | ||
584 | } | ||
585 | |||
586 | QVector<Signature::Ptr> Part::signatures() const | ||
587 | { | ||
588 | auto ret = d->signatures(); | ||
589 | auto parent = d->parent(); | ||
590 | if (parent) { | ||
591 | ret.append(parent->signatures()); | ||
592 | } | ||
593 | return ret; | ||
594 | } | ||
595 | |||
596 | MailMime::Ptr Part::mailMime() const | ||
597 | { | ||
598 | return d->mailMime(); | ||
599 | } | ||
600 | |||
601 | Part *Part::parent() const | ||
602 | { | ||
603 | return d->parent(); | ||
604 | } | ||
605 | |||
606 | class ContentPrivate | ||
607 | { | ||
608 | public: | ||
609 | QByteArray mContent; | ||
610 | QByteArray mCodec; | ||
611 | Part *mParent; | ||
612 | Content *q; | ||
613 | MailMime::Ptr mMailMime; | ||
614 | QVector<Encryption::Ptr> mEncryptions; | ||
615 | QVector<Signature::Ptr> mSignatures; | ||
616 | void appendSignature(const MimeTreeParser::SignedMessagePart::Ptr &sig); | ||
617 | void appendEncryption(const MimeTreeParser::EncryptedMessagePart::Ptr &enc); | ||
618 | }; | ||
619 | |||
620 | void ContentPrivate::appendEncryption(const MimeTreeParser::EncryptedMessagePart::Ptr& enc) | ||
621 | { | ||
622 | mEncryptions.append(PartPrivate::createEncryption(enc)); | ||
623 | } | ||
624 | |||
625 | void ContentPrivate::appendSignature(const MimeTreeParser::SignedMessagePart::Ptr& sig) | ||
626 | { | ||
627 | mSignatures.append(PartPrivate::createSignature(sig)); | ||
628 | } | ||
629 | |||
630 | |||
631 | Content::Content(const QByteArray& content, Part *parent) | ||
632 | : d(std::unique_ptr<ContentPrivate>(new ContentPrivate)) | ||
633 | { | ||
634 | d->q = this; | ||
635 | d->mContent = content; | ||
636 | d->mCodec = "utf-8"; | ||
637 | d->mParent = parent; | ||
638 | } | ||
639 | |||
640 | Content::Content(ContentPrivate* d_ptr) | ||
641 | : d(std::unique_ptr<ContentPrivate>(d_ptr)) | ||
642 | { | ||
643 | d->q = this; | ||
644 | } | ||
645 | |||
646 | Content::~Content() | ||
647 | { | ||
648 | } | ||
649 | |||
650 | QVector<Encryption::Ptr> Content::encryptions() const | ||
651 | { | ||
652 | auto ret = d->mEncryptions; | ||
653 | if (d->mParent) { | ||
654 | ret.append(d->mParent->encryptions()); | ||
655 | } | ||
656 | return ret; | ||
657 | } | ||
658 | |||
659 | QVector<Signature::Ptr> Content::signatures() const | ||
660 | { | ||
661 | auto ret = d->mSignatures; | ||
662 | if (d->mParent) { | ||
663 | ret.append(d->mParent->signatures()); | ||
664 | } | ||
665 | return ret; | ||
666 | } | ||
667 | |||
668 | QByteArray Content::content() const | ||
669 | { | ||
670 | return d->mContent; | ||
671 | } | ||
672 | |||
673 | QByteArray Content::charset() const | ||
674 | { | ||
675 | return d->mCodec; | ||
676 | } | ||
677 | |||
678 | QString Content::encodedContent() const | ||
679 | { | ||
680 | return QString::fromUtf8(content()); | ||
681 | // TODO: should set "raw" content not the already utf8 encoded content | ||
682 | // return encodedContent(charset()); | ||
683 | } | ||
684 | |||
685 | QString Content::encodedContent(const QByteArray &charset) const | ||
686 | { | ||
687 | QTextCodec *codec = QTextCodec::codecForName(charset); | ||
688 | return codec->toUnicode(content()); | ||
689 | } | ||
690 | |||
691 | QByteArray Content::type() const | ||
692 | { | ||
693 | return "Content"; | ||
694 | } | ||
695 | |||
696 | MailMime::Ptr Content::mailMime() const | ||
697 | { | ||
698 | if (d->mMailMime) { | ||
699 | return d->mMailMime; | ||
700 | } else { | ||
701 | return d->mParent->mailMime(); | ||
702 | } | ||
703 | } | ||
704 | |||
705 | Part *Content::parent() const | ||
706 | { | ||
707 | return d->mParent; | ||
708 | } | ||
709 | |||
710 | HtmlContent::HtmlContent(const QByteArray& content, Part* parent) | ||
711 | : Content(content, parent) | ||
712 | { | ||
713 | |||
714 | } | ||
715 | |||
716 | QByteArray HtmlContent::type() const | ||
717 | { | ||
718 | return "HtmlContent"; | ||
719 | } | ||
720 | |||
721 | PlainTextContent::PlainTextContent(const QByteArray& content, Part* parent) | ||
722 | : Content(content, parent) | ||
723 | { | ||
724 | |||
725 | } | ||
726 | |||
727 | PlainTextContent::PlainTextContent(ContentPrivate* d_ptr) | ||
728 | : Content(d_ptr) | ||
729 | { | ||
730 | |||
731 | } | ||
732 | |||
733 | HtmlContent::HtmlContent(ContentPrivate* d_ptr) | ||
734 | : Content(d_ptr) | ||
735 | { | ||
736 | |||
737 | } | ||
738 | |||
739 | |||
740 | QByteArray PlainTextContent::type() const | ||
741 | { | ||
742 | return "PlainTextContent"; | ||
743 | } | ||
744 | |||
745 | class AlternativePartPrivate | ||
746 | { | ||
747 | public: | ||
748 | void fillFrom(MimeTreeParser::AlternativeMessagePart::Ptr part); | ||
749 | |||
750 | QVector<Content::Ptr> content(const QByteArray &ct) const; | ||
751 | |||
752 | AlternativePart *q; | ||
753 | |||
754 | QVector<QByteArray> types() const; | ||
755 | |||
756 | private: | ||
757 | QMap<QByteArray, QVector<Content::Ptr>> mContent; | ||
758 | QVector<QByteArray> mTypes; | ||
759 | }; | ||
760 | |||
761 | void AlternativePartPrivate::fillFrom(MimeTreeParser::AlternativeMessagePart::Ptr part) | ||
762 | { | ||
763 | mTypes = QVector<QByteArray>() << "html" << "plaintext"; | ||
764 | |||
765 | Content::Ptr content = std::make_shared<HtmlContent>(part->htmlContent().toLocal8Bit(), q); | ||
766 | mContent["html"].append(content); | ||
767 | content = std::make_shared<PlainTextContent>(part->plaintextContent().toLocal8Bit(), q); | ||
768 | mContent["plaintext"].append(content); | ||
769 | q->reachParentD()->createMailMime(part); | ||
770 | } | ||
771 | |||
772 | QVector<QByteArray> AlternativePartPrivate::types() const | ||
773 | { | ||
774 | return mTypes; | ||
775 | } | ||
776 | |||
777 | QVector<Content::Ptr> AlternativePartPrivate::content(const QByteArray& ct) const | ||
778 | { | ||
779 | return mContent[ct]; | ||
780 | } | ||
781 | |||
782 | AlternativePart::AlternativePart() | ||
783 | : d(std::unique_ptr<AlternativePartPrivate>(new AlternativePartPrivate)) | ||
784 | { | ||
785 | d->q = this; | ||
786 | } | ||
787 | |||
788 | AlternativePart::~AlternativePart() | ||
789 | { | ||
790 | |||
791 | } | ||
792 | |||
793 | QByteArray AlternativePart::type() const | ||
794 | { | ||
795 | return "AlternativePart"; | ||
796 | } | ||
797 | |||
798 | QVector<QByteArray> AlternativePart::availableContents() const | ||
799 | { | ||
800 | return d->types(); | ||
801 | } | ||
802 | |||
803 | QVector<Content::Ptr> AlternativePart::content(const QByteArray& ct) const | ||
804 | { | ||
805 | return d->content(ct); | ||
806 | } | ||
807 | |||
808 | PartPrivate* AlternativePart::reachParentD() const | ||
809 | { | ||
810 | return Part::d.get(); | ||
811 | } | ||
812 | |||
813 | class SinglePartPrivate | ||
814 | { | ||
815 | public: | ||
816 | void fillFrom(const MimeTreeParser::TextMessagePart::Ptr &part); | ||
817 | void fillFrom(const MimeTreeParser::HtmlMessagePart::Ptr &part); | ||
818 | void fillFrom(const MimeTreeParser::AttachmentMessagePart::Ptr &part); | ||
819 | void createEncryptionFailBlock(const MimeTreeParser::EncryptedMessagePart::Ptr &part); | ||
820 | SinglePart *q; | ||
821 | |||
822 | QVector<Content::Ptr> mContent; | ||
823 | QByteArray mType; | ||
824 | }; | ||
825 | |||
826 | void SinglePartPrivate::fillFrom(const MimeTreeParser::TextMessagePart::Ptr &part) | ||
827 | { | ||
828 | mType = "plaintext"; | ||
829 | mContent.clear(); | ||
830 | foreach (const auto &mp, part->subParts()) { | ||
831 | auto d_ptr = new ContentPrivate; | ||
832 | d_ptr->mContent = mp->text().toUtf8(); // TODO: should set "raw" content not the already utf8 encoded content | ||
833 | d_ptr->mParent = q; | ||
834 | const auto enc = mp.dynamicCast<MimeTreeParser::EncryptedMessagePart>(); | ||
835 | auto sig = mp.dynamicCast<MimeTreeParser::SignedMessagePart>(); | ||
836 | if (enc) { | ||
837 | d_ptr->appendEncryption(enc); | ||
838 | if (!enc->isDecryptable()) { | ||
839 | d_ptr->mContent = QByteArray(); | ||
840 | } | ||
841 | const auto s = enc->subParts(); | ||
842 | if (s.size() == 1) { | ||
843 | sig = s[0].dynamicCast<MimeTreeParser::SignedMessagePart>(); | ||
844 | } | ||
845 | } | ||
846 | if (sig) { | ||
847 | d_ptr->appendSignature(sig); | ||
848 | } | ||
849 | mContent.append(std::make_shared<PlainTextContent>(d_ptr)); | ||
850 | q->reachParentD()->createMailMime(part); | ||
851 | d_ptr->mCodec = q->mailMime()->charset(); | ||
852 | } | ||
853 | } | ||
854 | |||
855 | void SinglePartPrivate::fillFrom(const MimeTreeParser::HtmlMessagePart::Ptr &part) | ||
856 | { | ||
857 | mType = "html"; | ||
858 | mContent.clear(); | ||
859 | mContent.append(std::make_shared<HtmlContent>(part->text().toUtf8(), q)); | ||
860 | q->reachParentD()->createMailMime(part); | ||
861 | } | ||
862 | |||
863 | void SinglePartPrivate::fillFrom(const MimeTreeParser::AttachmentMessagePart::Ptr &part) | ||
864 | { | ||
865 | QMimeDatabase mimeDb; | ||
866 | q->reachParentD()->createMailMime(part.staticCast<MimeTreeParser::TextMessagePart>()); | ||
867 | const auto mimetype = q->mailMime()->mimetype(); | ||
868 | const auto content = q->mailMime()->decodedContent(); | ||
869 | mContent.clear(); | ||
870 | if (mimetype == mimeDb.mimeTypeForName("text/plain")) { | ||
871 | mType = "plaintext"; | ||
872 | mContent.append(std::make_shared<PlainTextContent>(content, q)); | ||
873 | } else if (mimetype == mimeDb.mimeTypeForName("text/html")) { | ||
874 | mType = "html"; | ||
875 | mContent.append(std::make_shared<HtmlContent>(content, q)); | ||
876 | } else { | ||
877 | mType = mimetype.name().toUtf8(); | ||
878 | mContent.append(std::make_shared<Content>(content, q)); | ||
879 | } | ||
880 | } | ||
881 | |||
882 | void SinglePartPrivate::createEncryptionFailBlock(const MimeTreeParser::EncryptedMessagePart::Ptr &part) | ||
883 | { | ||
884 | mType = "plaintext"; | ||
885 | mContent.clear(); | ||
886 | mContent.append(std::make_shared<PlainTextContent>(QByteArray(), q)); | ||
887 | q->reachParentD()->createMailMime(part); | ||
888 | } | ||
889 | |||
890 | SinglePart::SinglePart() | ||
891 | : d(std::unique_ptr<SinglePartPrivate>(new SinglePartPrivate)) | ||
892 | { | ||
893 | d->q = this; | ||
894 | } | ||
895 | |||
896 | SinglePart::~SinglePart() | ||
897 | { | ||
898 | |||
899 | } | ||
900 | |||
901 | QVector<QByteArray> SinglePart::availableContents() const | ||
902 | { | ||
903 | return QVector<QByteArray>() << d->mType; | ||
904 | } | ||
905 | |||
906 | QVector< Content::Ptr > SinglePart::content(const QByteArray &ct) const | ||
907 | { | ||
908 | if (ct == d->mType) { | ||
909 | return d->mContent; | ||
910 | } | ||
911 | return QVector<Content::Ptr>(); | ||
912 | } | ||
913 | |||
914 | QByteArray SinglePart::type() const | ||
915 | { | ||
916 | return "SinglePart"; | ||
917 | } | ||
918 | |||
919 | PartPrivate* SinglePart::reachParentD() const | ||
920 | { | ||
921 | return Part::d.get(); | ||
922 | } | ||
923 | |||
924 | ParserPrivate::ParserPrivate(Parser* parser) | ||
925 | : q(parser) | ||
926 | , mNodeHelper(std::make_shared<MimeTreeParser::NodeHelper>()) | ||
927 | { | ||
928 | |||
929 | } | ||
930 | |||
931 | void ParserPrivate::setMessage(const QByteArray& mimeMessage) | ||
932 | { | ||
933 | const auto mailData = KMime::CRLFtoLF(mimeMessage); | ||
934 | mMsg = KMime::Message::Ptr(new KMime::Message); | ||
935 | mMsg->setContent(mailData); | ||
936 | mMsg->parse(); | ||
937 | |||
938 | // render the mail | ||
939 | StringHtmlWriter htmlWriter; | ||
940 | ObjectTreeSource source(&htmlWriter); | ||
941 | MimeTreeParser::ObjectTreeParser otp(&source, mNodeHelper.get()); | ||
942 | |||
943 | otp.parseObjectTree(mMsg.data()); | ||
944 | mPartTree = otp.parsedPart().dynamicCast<MimeTreeParser::MessagePart>(); | ||
945 | |||
946 | mEmbeddedPartMap = htmlWriter.embeddedParts(); | ||
947 | mHtml = htmlWriter.html(); | ||
948 | |||
949 | mTree = std::make_shared<Part>(); | ||
950 | createTree(mPartTree, mTree); | ||
951 | } | ||
952 | |||
953 | |||
954 | void ParserPrivate::createTree(const MimeTreeParser::MessagePart::Ptr &start, const Part::Ptr &tree) | ||
955 | { | ||
956 | foreach (const auto &mp, start->subParts()) { | ||
957 | const auto m = mp.dynamicCast<MimeTreeParser::MessagePart>(); | ||
958 | const auto text = mp.dynamicCast<MimeTreeParser::TextMessagePart>(); | ||
959 | const auto alternative = mp.dynamicCast<MimeTreeParser::AlternativeMessagePart>(); | ||
960 | const auto html = mp.dynamicCast<MimeTreeParser::HtmlMessagePart>(); | ||
961 | const auto attachment = mp.dynamicCast<MimeTreeParser::AttachmentMessagePart>(); | ||
962 | if (attachment) { | ||
963 | auto part = std::make_shared<SinglePart>(); | ||
964 | part->d->fillFrom(attachment); | ||
965 | tree->d->appendSubPart(part); | ||
966 | } else if (text) { | ||
967 | auto part = std::make_shared<SinglePart>(); | ||
968 | part->d->fillFrom(text); | ||
969 | tree->d->appendSubPart(part); | ||
970 | } else if (alternative) { | ||
971 | auto part = std::make_shared<AlternativePart>(); | ||
972 | part->d->fillFrom(alternative); | ||
973 | tree->d->appendSubPart(part); | ||
974 | } else if (html) { | ||
975 | auto part = std::make_shared<SinglePart>(); | ||
976 | part->d->fillFrom(html); | ||
977 | tree->d->appendSubPart(part); | ||
978 | } else { | ||
979 | const auto enc = mp.dynamicCast<MimeTreeParser::EncryptedMessagePart>(); | ||
980 | const auto sig = mp.dynamicCast<MimeTreeParser::SignedMessagePart>(); | ||
981 | if (enc || sig) { | ||
982 | auto subTree = std::make_shared<Part>(); | ||
983 | if (enc) { | ||
984 | subTree->d->appendEncryption(enc); | ||
985 | if (!enc->isDecryptable()) { | ||
986 | auto part = std::make_shared<SinglePart>(); | ||
987 | part->d->createEncryptionFailBlock(enc); | ||
988 | part->reachParentD()->setEncryptions(subTree->d->encryptions()); | ||
989 | tree->d->appendSubPart(part); | ||
990 | return; | ||
991 | } | ||
992 | } | ||
993 | if (sig) { | ||
994 | subTree->d->appendSignature(sig); | ||
995 | } | ||
996 | createTree(m, subTree); | ||
997 | foreach(const auto &p, subTree->subParts()) { | ||
998 | tree->d->appendSubPart(p); | ||
999 | if (enc) { | ||
1000 | p->d->setEncryptions(subTree->d->encryptions()); | ||
1001 | } | ||
1002 | if (sig) { | ||
1003 | p->d->setSignatures(subTree->d->signatures()); | ||
1004 | } | ||
1005 | } | ||
1006 | } else { | ||
1007 | createTree(m, tree); | ||
1008 | } | ||
1009 | } | ||
1010 | } | ||
1011 | } | ||
1012 | |||
1013 | Parser::Parser(const QByteArray& mimeMessage) | ||
1014 | :d(std::unique_ptr<ParserPrivate>(new ParserPrivate(this))) | ||
1015 | { | ||
1016 | d->setMessage(mimeMessage); | ||
1017 | } | ||
1018 | |||
1019 | Parser::~Parser() | ||
1020 | { | ||
1021 | } | ||
1022 | |||
1023 | Part::Ptr Parser::getPart(const QUrl &url) | ||
1024 | { | ||
1025 | if (url.scheme() == QStringLiteral("cid") && !url.path().isEmpty()) { | ||
1026 | const auto cid = url.path(); | ||
1027 | return find(d->mTree, [&cid](const Part::Ptr &p){ | ||
1028 | const auto mime = p->mailMime(); | ||
1029 | return mime->cid() == cid; | ||
1030 | }); | ||
1031 | } | ||
1032 | return Part::Ptr(); | ||
1033 | } | ||
1034 | |||
1035 | QVector<Part::Ptr> Parser::collectContentParts() const | ||
1036 | { | ||
1037 | return collect(d->mTree, [](const Part::Ptr &p){return p->type() != "EncapsulatedPart";}, | ||
1038 | [](const Content::Ptr &content){ | ||
1039 | const auto mime = content->mailMime(); | ||
1040 | |||
1041 | if (!mime) { | ||
1042 | return true; | ||
1043 | } | ||
1044 | |||
1045 | if (mime->isFirstTextPart()) { | ||
1046 | return true; | ||
1047 | } | ||
1048 | |||
1049 | { | ||
1050 | auto _mime = content->parent()->mailMime(); | ||
1051 | while (_mime) { | ||
1052 | if (_mime && (_mime->isTopLevelPart() || _mime->isFirstTextPart())) { | ||
1053 | return true; | ||
1054 | } | ||
1055 | if (_mime->isFirstPart()) { | ||
1056 | _mime = _mime->parent(); | ||
1057 | } else { | ||
1058 | break; | ||
1059 | } | ||
1060 | } | ||
1061 | } | ||
1062 | |||
1063 | const auto ctname = mime->mimetype().name().trimmed().toLower(); | ||
1064 | bool mightContent = (content->type() != "Content"); //Content we understand | ||
1065 | |||
1066 | const auto cd = mime->disposition(); | ||
1067 | if (cd && cd == MailMime::Inline) { | ||
1068 | return mightContent; | ||
1069 | } | ||
1070 | |||
1071 | if (cd && cd == MailMime::Attachment) { | ||
1072 | return false; | ||
1073 | } | ||
1074 | |||
1075 | if ((ctname.startsWith("text/") || ctname.isEmpty()) && | ||
1076 | (!mime || mime->filename().trimmed().isEmpty())) { | ||
1077 | // text/* w/o filename parameter: | ||
1078 | return true; | ||
1079 | } | ||
1080 | return false; | ||
1081 | }); | ||
1082 | } | ||
1083 | |||
1084 | |||
1085 | QVector<Part::Ptr> Parser::collectAttachmentParts() const | ||
1086 | { | ||
1087 | return collect(d->mTree, [](const Part::Ptr &p){return p->type() != "EncapsulatedPart";}, | ||
1088 | [](const Content::Ptr &content){ | ||
1089 | const auto mime = content->mailMime(); | ||
1090 | |||
1091 | if (!mime) { | ||
1092 | return false; | ||
1093 | } | ||
1094 | |||
1095 | if (mime->isFirstTextPart()) { | ||
1096 | return false; | ||
1097 | } | ||
1098 | |||
1099 | { | ||
1100 | QMimeDatabase mimeDb; | ||
1101 | auto _mime = content->parent()->mailMime(); | ||
1102 | const auto parent = _mime->parent(); | ||
1103 | if (parent) { | ||
1104 | const auto mimetype = parent->mimetype(); | ||
1105 | if (mimetype == mimeDb.mimeTypeForName("multipart/related")) { | ||
1106 | return false; | ||
1107 | } | ||
1108 | } | ||
1109 | while (_mime) { | ||
1110 | if (_mime && (_mime->isTopLevelPart() || _mime->isFirstTextPart())) { | ||
1111 | return false; | ||
1112 | } | ||
1113 | if (_mime->isFirstPart()) { | ||
1114 | _mime = _mime->parent(); | ||
1115 | } else { | ||
1116 | break; | ||
1117 | } | ||
1118 | } | ||
1119 | } | ||
1120 | |||
1121 | const auto ctname = mime->mimetype().name().trimmed().toLower(); | ||
1122 | bool mightContent = (content->type() != "Content"); //Content we understand | ||
1123 | |||
1124 | const auto cd = mime->disposition(); | ||
1125 | if (cd && cd == MailMime::Inline) { | ||
1126 | // explict "inline" disposition: | ||
1127 | return !mightContent; | ||
1128 | } | ||
1129 | if (cd && cd == MailMime::Attachment) { | ||
1130 | // explicit "attachment" disposition: | ||
1131 | return true; | ||
1132 | } | ||
1133 | |||
1134 | const auto ct = mime->mimetype(); | ||
1135 | if ((ctname.startsWith("text/") || ctname.isEmpty()) && | ||
1136 | (!mime || mime->filename().trimmed().isEmpty())) { | ||
1137 | // text/* w/o filename parameter: | ||
1138 | return false; | ||
1139 | } | ||
1140 | return true; | ||
1141 | }); | ||
1142 | } | ||
1143 | |||
1144 | QVector<Part::Ptr> Parser::collect(const Part::Ptr &start, std::function<bool(const Part::Ptr &)> select, std::function<bool(const Content::Ptr &)> filter) const | ||
1145 | { | ||
1146 | QVector<Part::Ptr> ret; | ||
1147 | foreach (const auto &part, start->subParts()) { | ||
1148 | QVector<QByteArray> contents; | ||
1149 | foreach(const auto &ct, part->availableContents()) { | ||
1150 | foreach(const auto &content, part->content(ct)) { | ||
1151 | if (filter(content)) { | ||
1152 | contents.append(ct); | ||
1153 | break; | ||
1154 | } | ||
1155 | } | ||
1156 | } | ||
1157 | if (!contents.isEmpty()) { | ||
1158 | ret.append(part); | ||
1159 | } | ||
1160 | if (select(part)){ | ||
1161 | ret += collect(part, select, filter); | ||
1162 | } | ||
1163 | } | ||
1164 | return ret; | ||
1165 | } | ||
1166 | |||
1167 | Part::Ptr Parser::find(const Part::Ptr &start, std::function<bool(const Part::Ptr &)> select) const | ||
1168 | { | ||
1169 | foreach (const auto &part, start->subParts()) { | ||
1170 | if (select(part)) { | ||
1171 | return part; | ||
1172 | } | ||
1173 | const auto ret = find(part, select); | ||
1174 | if (ret) { | ||
1175 | return ret; | ||
1176 | } | ||
1177 | } | ||
1178 | return Part::Ptr(); | ||
1179 | } | ||
diff --git a/framework/src/domain/mimetreeparser/interface.h b/framework/src/domain/mimetreeparser/interface.h new file mode 100644 index 00000000..7c3ea28b --- /dev/null +++ b/framework/src/domain/mimetreeparser/interface.h | |||
@@ -0,0 +1,378 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Sandro Knauß <knauss@kolabsystems.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | |||
20 | #pragma once | ||
21 | |||
22 | #include <functional> | ||
23 | #include <memory> | ||
24 | #include <vector> | ||
25 | |||
26 | #include <QDateTime> | ||
27 | #include <QUrl> | ||
28 | #include <QMimeType> | ||
29 | |||
30 | class Part; | ||
31 | class PartPrivate; | ||
32 | |||
33 | class MailMime; | ||
34 | class MailMimePrivate; | ||
35 | |||
36 | class AlternativePart; | ||
37 | class AlternativePartPrivate; | ||
38 | |||
39 | class SinglePart; | ||
40 | class SinglePartPrivate; | ||
41 | |||
42 | class EncryptionPart; | ||
43 | class EncryptionPartPrivate; | ||
44 | |||
45 | class EncapsulatedPart; | ||
46 | class EncapsulatedPartPrivate; | ||
47 | |||
48 | class Content; | ||
49 | class ContentPrivate; | ||
50 | |||
51 | class CertContent; | ||
52 | class CertContentPrivate; | ||
53 | |||
54 | class EncryptionError; | ||
55 | |||
56 | class Key; | ||
57 | class KeyPrivate; | ||
58 | class Signature; | ||
59 | class SignaturePrivate; | ||
60 | class Encryption; | ||
61 | class EncryptionPrivate; | ||
62 | |||
63 | typedef std::shared_ptr<Signature> SignaturePtr; | ||
64 | typedef std::shared_ptr<Encryption> EncryptionPtr; | ||
65 | |||
66 | class Parser; | ||
67 | class ParserPrivate; | ||
68 | |||
69 | /* | ||
70 | * A MessagePart that is based on a KMime::Content | ||
71 | */ | ||
72 | class MailMime | ||
73 | { | ||
74 | public: | ||
75 | typedef std::shared_ptr<MailMime> Ptr; | ||
76 | /** | ||
77 | * Various possible values for the "Content-Disposition" header. | ||
78 | */ | ||
79 | enum Disposition { | ||
80 | Invalid, ///< Default, invalid value | ||
81 | Inline, ///< inline | ||
82 | Attachment ///< attachment | ||
83 | }; | ||
84 | |||
85 | MailMime(); | ||
86 | |||
87 | // interessting header parts of a KMime::Content | ||
88 | QMimeType mimetype() const; | ||
89 | Disposition disposition() const; | ||
90 | QUrl label() const; | ||
91 | QByteArray cid() const; | ||
92 | QByteArray charset() const; | ||
93 | QString filename() const; | ||
94 | |||
95 | // Unique identifier to ecactly this KMime::Content | ||
96 | QByteArray link() const; | ||
97 | |||
98 | QByteArray content() const; | ||
99 | //Use default charset | ||
100 | QString encodedContent() const; | ||
101 | |||
102 | // overwrite default charset with given charset | ||
103 | QString encodedContent(QByteArray charset) const; | ||
104 | |||
105 | QByteArray decodedContent() const; | ||
106 | |||
107 | bool isFirstTextPart() const; | ||
108 | bool isFirstPart() const; | ||
109 | bool isTopLevelPart() const; | ||
110 | |||
111 | MailMime::Ptr parent() const; | ||
112 | |||
113 | private: | ||
114 | std::unique_ptr<MailMimePrivate> d; | ||
115 | |||
116 | friend class PartPrivate; | ||
117 | }; | ||
118 | |||
119 | class Content | ||
120 | { | ||
121 | public: | ||
122 | typedef std::shared_ptr<Content> Ptr; | ||
123 | Content(const QByteArray &content, Part *parent); | ||
124 | Content(ContentPrivate *d_ptr); | ||
125 | virtual ~Content(); | ||
126 | |||
127 | QByteArray content() const; | ||
128 | |||
129 | QByteArray charset() const; | ||
130 | |||
131 | //Use default charset | ||
132 | QString encodedContent() const; | ||
133 | |||
134 | // overwrite default charset with given charset | ||
135 | QString encodedContent(const QByteArray &charset) const; | ||
136 | |||
137 | QVector<SignaturePtr> signatures() const; | ||
138 | QVector<EncryptionPtr> encryptions() const; | ||
139 | MailMime::Ptr mailMime() const; | ||
140 | virtual QByteArray type() const; | ||
141 | Part* parent() const; | ||
142 | private: | ||
143 | std::unique_ptr<ContentPrivate> d; | ||
144 | }; | ||
145 | |||
146 | class PlainTextContent : public Content | ||
147 | { | ||
148 | public: | ||
149 | PlainTextContent(const QByteArray &content, Part *parent); | ||
150 | PlainTextContent(ContentPrivate *d_ptr); | ||
151 | QByteArray type() const Q_DECL_OVERRIDE; | ||
152 | }; | ||
153 | |||
154 | class HtmlContent : public Content | ||
155 | { | ||
156 | public: | ||
157 | HtmlContent(const QByteArray &content, Part *parent); | ||
158 | HtmlContent(ContentPrivate* d_ptr); | ||
159 | QByteArray type() const Q_DECL_OVERRIDE; | ||
160 | }; | ||
161 | |||
162 | /* | ||
163 | * importing a cert GpgMe::ImportResult | ||
164 | * checking a cert (if it is a valid cert) | ||
165 | */ | ||
166 | |||
167 | class CertContent : public Content | ||
168 | { | ||
169 | public: | ||
170 | typedef std::shared_ptr<CertContent> Ptr; | ||
171 | CertContent(const QByteArray &content, Part *parent); | ||
172 | |||
173 | QByteArray type() const Q_DECL_OVERRIDE; | ||
174 | enum CertType { | ||
175 | Pgp, | ||
176 | SMime | ||
177 | }; | ||
178 | |||
179 | enum CertSubType { | ||
180 | Public, | ||
181 | Private | ||
182 | }; | ||
183 | |||
184 | CertType certType() const; | ||
185 | CertSubType certSubType() const; | ||
186 | int keyLength() const; | ||
187 | |||
188 | private: | ||
189 | std::unique_ptr<CertContentPrivate> d; | ||
190 | }; | ||
191 | |||
192 | class Part | ||
193 | { | ||
194 | public: | ||
195 | typedef std::shared_ptr<Part> Ptr; | ||
196 | Part(); | ||
197 | virtual QByteArray type() const; | ||
198 | |||
199 | virtual QVector<QByteArray> availableContents() const; | ||
200 | virtual QVector<Content::Ptr> content(const QByteArray& ct) const; | ||
201 | QVector<Content::Ptr> content() const; | ||
202 | |||
203 | bool hasSubParts() const; | ||
204 | QVector<Part::Ptr> subParts() const; | ||
205 | Part *parent() const; | ||
206 | |||
207 | QVector<SignaturePtr> signatures() const; | ||
208 | QVector<EncryptionPtr> encryptions() const; | ||
209 | virtual MailMime::Ptr mailMime() const; | ||
210 | protected: | ||
211 | std::unique_ptr<PartPrivate> d; | ||
212 | private: | ||
213 | friend class ParserPrivate; | ||
214 | friend class PartPrivate; | ||
215 | }; | ||
216 | |||
217 | class AlternativePart : public Part | ||
218 | { | ||
219 | public: | ||
220 | typedef std::shared_ptr<AlternativePart> Ptr; | ||
221 | |||
222 | AlternativePart(); | ||
223 | virtual ~AlternativePart(); | ||
224 | |||
225 | QVector<QByteArray> availableContents() const Q_DECL_OVERRIDE; | ||
226 | QVector<Content::Ptr> content(const QByteArray& ct) const Q_DECL_OVERRIDE; | ||
227 | |||
228 | QByteArray type() const Q_DECL_OVERRIDE; | ||
229 | |||
230 | private: | ||
231 | PartPrivate *reachParentD() const; | ||
232 | std::unique_ptr<AlternativePartPrivate> d; | ||
233 | |||
234 | friend class ParserPrivate; | ||
235 | friend class AlternativePartPrivate; | ||
236 | }; | ||
237 | |||
238 | class SinglePart : public Part | ||
239 | { | ||
240 | public: | ||
241 | typedef std::shared_ptr<SinglePart> Ptr; | ||
242 | |||
243 | SinglePart(); | ||
244 | virtual ~SinglePart(); | ||
245 | |||
246 | QVector<Content::Ptr> content(const QByteArray& ct) const Q_DECL_OVERRIDE; | ||
247 | QVector<QByteArray> availableContents() const Q_DECL_OVERRIDE; | ||
248 | |||
249 | QByteArray type() const Q_DECL_OVERRIDE; | ||
250 | private: | ||
251 | PartPrivate *reachParentD() const; | ||
252 | std::unique_ptr<SinglePartPrivate> d; | ||
253 | |||
254 | friend class ParserPrivate; | ||
255 | friend class SinglePartPrivate; | ||
256 | }; | ||
257 | |||
258 | /* | ||
259 | * we want to request complete headers like: | ||
260 | * from/to... | ||
261 | */ | ||
262 | |||
263 | class EncapsulatedPart : public SinglePart | ||
264 | { | ||
265 | public: | ||
266 | typedef std::shared_ptr<EncapsulatedPart> Ptr; | ||
267 | QByteArray type() const Q_DECL_OVERRIDE; | ||
268 | |||
269 | //template <class T> QByteArray header<T>(); | ||
270 | private: | ||
271 | std::unique_ptr<EncapsulatedPartPrivate> d; | ||
272 | }; | ||
273 | |||
274 | class EncryptionError | ||
275 | { | ||
276 | public: | ||
277 | int errorId() const; | ||
278 | QString errorString() const; | ||
279 | }; | ||
280 | |||
281 | class Key | ||
282 | { | ||
283 | public: | ||
284 | typedef std::shared_ptr<Key> Ptr; | ||
285 | Key(); | ||
286 | Key(KeyPrivate *); | ||
287 | ~Key(); | ||
288 | |||
289 | QString keyid() const; | ||
290 | QString name() const; | ||
291 | QString email() const; | ||
292 | QString comment() const; | ||
293 | QVector<QString> emails() const; | ||
294 | enum KeyTrust { | ||
295 | Unknown, Undefined, Never, Marginal, Full, Ultimate | ||
296 | }; | ||
297 | KeyTrust keyTrust() const; | ||
298 | |||
299 | bool isRevokation() const; | ||
300 | bool isInvalid() const; | ||
301 | bool isExpired() const; | ||
302 | |||
303 | std::vector<Key::Ptr> subkeys(); | ||
304 | Key parentkey() const; | ||
305 | private: | ||
306 | std::unique_ptr<KeyPrivate> d; | ||
307 | }; | ||
308 | |||
309 | class Signature | ||
310 | { | ||
311 | public: | ||
312 | typedef std::shared_ptr<Signature> Ptr; | ||
313 | Signature(); | ||
314 | Signature(SignaturePrivate *); | ||
315 | ~Signature(); | ||
316 | |||
317 | Key::Ptr key() const; | ||
318 | QDateTime creationDateTime() const; | ||
319 | QDateTime expirationDateTime() const; | ||
320 | bool neverExpires() const; | ||
321 | |||
322 | //template <> StatusObject<SignatureVerificationResult> verify() const; | ||
323 | private: | ||
324 | std::unique_ptr<SignaturePrivate> d; | ||
325 | }; | ||
326 | |||
327 | /* | ||
328 | * Normally the Keys for encryption are subkeys | ||
329 | * for clients the parentkeys are "more interessting", because they store the name, email etc. | ||
330 | * but a client may also wants show to what subkey the mail is really encrypted, an if this subkey isRevoked or something else | ||
331 | */ | ||
332 | class Encryption | ||
333 | { | ||
334 | public: | ||
335 | enum ErrorType { | ||
336 | NoError, | ||
337 | PassphraseError, | ||
338 | KeyMissing, | ||
339 | UnknownError | ||
340 | }; | ||
341 | typedef std::shared_ptr<Encryption> Ptr; | ||
342 | Encryption(); | ||
343 | Encryption(EncryptionPrivate *); | ||
344 | ~Encryption(); | ||
345 | std::vector<Key::Ptr> recipients() const; | ||
346 | QString errorString(); | ||
347 | ErrorType errorType(); | ||
348 | private: | ||
349 | std::unique_ptr<EncryptionPrivate> d; | ||
350 | }; | ||
351 | |||
352 | class Parser | ||
353 | { | ||
354 | public: | ||
355 | typedef std::shared_ptr<Parser> Ptr; | ||
356 | Parser(const QByteArray &mimeMessage); | ||
357 | ~Parser(); | ||
358 | |||
359 | Part::Ptr getPart(const QUrl &url); | ||
360 | |||
361 | QVector<Part::Ptr> collect(const Part::Ptr &start, std::function<bool(const Part::Ptr &)> select, std::function<bool(const Content::Ptr &)> filter) const; | ||
362 | Part::Ptr find(const Part::Ptr &start, std::function<bool(const Part::Ptr &)> select) const; | ||
363 | QVector<Part::Ptr> collectContentParts() const; | ||
364 | QVector<Part::Ptr> collectAttachmentParts() const; | ||
365 | //template <> QVector<ContentPart::Ptr> collect<ContentPart>() const; | ||
366 | |||
367 | //template <> static StatusObject<SignatureVerificationResult> verifySignature(const Signature signature) const; | ||
368 | //template <> static StatusObject<Part> decrypt(const EncryptedPart part) const; | ||
369 | |||
370 | signals: | ||
371 | void partsChanged(); | ||
372 | |||
373 | private: | ||
374 | std::unique_ptr<ParserPrivate> d; | ||
375 | |||
376 | friend class InterfaceTest; | ||
377 | }; | ||
378 | |||
diff --git a/framework/src/domain/mimetreeparser/interface_p.h b/framework/src/domain/mimetreeparser/interface_p.h new file mode 100644 index 00000000..8fab221a --- /dev/null +++ b/framework/src/domain/mimetreeparser/interface_p.h | |||
@@ -0,0 +1,56 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Sandro Knauß <knauss@kolabsystems.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | |||
20 | #pragma once | ||
21 | |||
22 | #include "interface.h" | ||
23 | |||
24 | #include <QSharedPointer> | ||
25 | #include <QMap> | ||
26 | |||
27 | namespace KMime | ||
28 | { | ||
29 | class Message; | ||
30 | typedef QSharedPointer<Message> MessagePtr; | ||
31 | } | ||
32 | |||
33 | namespace MimeTreeParser | ||
34 | { | ||
35 | class MessagePart; | ||
36 | class NodeHelper; | ||
37 | typedef QSharedPointer<MessagePart> MessagePartPtr; | ||
38 | } | ||
39 | |||
40 | class ParserPrivate | ||
41 | { | ||
42 | public: | ||
43 | ParserPrivate(Parser *parser); | ||
44 | |||
45 | void setMessage(const QByteArray &mimeMessage); | ||
46 | void createTree(const MimeTreeParser::MessagePartPtr& start, const Part::Ptr& tree); | ||
47 | |||
48 | Part::Ptr mTree; | ||
49 | Parser *q; | ||
50 | |||
51 | MimeTreeParser::MessagePartPtr mPartTree; | ||
52 | KMime::MessagePtr mMsg; | ||
53 | std::shared_ptr<MimeTreeParser::NodeHelper> mNodeHelper; | ||
54 | QString mHtml; | ||
55 | QMap<QByteArray, QUrl> mEmbeddedPartMap; | ||
56 | }; | ||
diff --git a/framework/src/domain/mimetreeparser/objecttreesource.cpp b/framework/src/domain/mimetreeparser/objecttreesource.cpp new file mode 100644 index 00000000..567f3516 --- /dev/null +++ b/framework/src/domain/mimetreeparser/objecttreesource.cpp | |||
@@ -0,0 +1,152 @@ | |||
1 | /* | ||
2 | Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net | ||
3 | Copyright (c) 2009 Andras Mantia <andras@kdab.net> | ||
4 | |||
5 | This program is free software; you can redistribute it and/or modify | ||
6 | it under the terms of the GNU General Public License as published by | ||
7 | the Free Software Foundation; either version 2 of the License, or | ||
8 | (at your option) any later version. | ||
9 | |||
10 | This program is distributed in the hope that it will be useful, | ||
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | GNU General Public License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU General Public License along | ||
16 | with this program; if not, write to the Free Software Foundation, Inc., | ||
17 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
18 | */ | ||
19 | |||
20 | #include "objecttreesource.h" | ||
21 | |||
22 | #include <MimeTreeParser/AttachmentStrategy> | ||
23 | #include <MimeTreeParser/BodyPartFormatterBaseFactory> | ||
24 | #include <MimeTreeParser/MessagePart> | ||
25 | #include <MimeTreeParser/MessagePartRenderer> | ||
26 | |||
27 | class ObjectSourcePrivate | ||
28 | { | ||
29 | public: | ||
30 | ObjectSourcePrivate() | ||
31 | : mWriter(0) | ||
32 | , mAllowDecryption(true) | ||
33 | , mHtmlLoadExternal(true) | ||
34 | , mPreferredMode(MimeTreeParser::Util::Html) | ||
35 | { | ||
36 | |||
37 | } | ||
38 | MimeTreeParser::HtmlWriter *mWriter; | ||
39 | MimeTreeParser::BodyPartFormatterBaseFactory mBodyPartFormatterBaseFactory; | ||
40 | bool mAllowDecryption; | ||
41 | bool mHtmlLoadExternal; | ||
42 | MimeTreeParser::Util::HtmlMode mPreferredMode; | ||
43 | }; | ||
44 | |||
45 | ObjectTreeSource::ObjectTreeSource(MimeTreeParser::HtmlWriter *writer) | ||
46 | : MimeTreeParser::Interface::ObjectTreeSource() | ||
47 | , d(new ObjectSourcePrivate) | ||
48 | { | ||
49 | d->mWriter = writer; | ||
50 | } | ||
51 | |||
52 | ObjectTreeSource::~ObjectTreeSource() | ||
53 | { | ||
54 | delete d; | ||
55 | } | ||
56 | |||
57 | void ObjectTreeSource::setAllowDecryption(bool allowDecryption) | ||
58 | { | ||
59 | d->mAllowDecryption = allowDecryption; | ||
60 | } | ||
61 | |||
62 | MimeTreeParser::HtmlWriter *ObjectTreeSource::htmlWriter() | ||
63 | { | ||
64 | return d->mWriter; | ||
65 | } | ||
66 | |||
67 | bool ObjectTreeSource::htmlLoadExternal() const | ||
68 | { | ||
69 | return d->mHtmlLoadExternal; | ||
70 | } | ||
71 | |||
72 | void ObjectTreeSource::setHtmlLoadExternal(bool loadExternal) | ||
73 | { | ||
74 | d->mHtmlLoadExternal = loadExternal; | ||
75 | } | ||
76 | |||
77 | bool ObjectTreeSource::decryptMessage() const | ||
78 | { | ||
79 | return d->mAllowDecryption; | ||
80 | } | ||
81 | |||
82 | bool ObjectTreeSource::showSignatureDetails() const | ||
83 | { | ||
84 | return true; | ||
85 | } | ||
86 | |||
87 | int ObjectTreeSource::levelQuote() const | ||
88 | { | ||
89 | return 1; | ||
90 | } | ||
91 | |||
92 | const QTextCodec *ObjectTreeSource::overrideCodec() | ||
93 | { | ||
94 | return Q_NULLPTR; | ||
95 | } | ||
96 | |||
97 | QString ObjectTreeSource::createMessageHeader(KMime::Message *message) | ||
98 | { | ||
99 | return QString(); | ||
100 | } | ||
101 | |||
102 | const MimeTreeParser::AttachmentStrategy *ObjectTreeSource::attachmentStrategy() | ||
103 | { | ||
104 | return MimeTreeParser::AttachmentStrategy::smart(); | ||
105 | } | ||
106 | |||
107 | QObject *ObjectTreeSource::sourceObject() | ||
108 | { | ||
109 | return Q_NULLPTR; | ||
110 | } | ||
111 | |||
112 | void ObjectTreeSource::setHtmlMode(MimeTreeParser::Util::HtmlMode mode, const QList<MimeTreeParser::Util::HtmlMode> &availableModes) | ||
113 | { | ||
114 | Q_UNUSED(mode); | ||
115 | Q_UNUSED(availableModes); | ||
116 | } | ||
117 | |||
118 | MimeTreeParser::Util::HtmlMode ObjectTreeSource::preferredMode() const | ||
119 | { | ||
120 | return d->mPreferredMode; | ||
121 | } | ||
122 | |||
123 | bool ObjectTreeSource::autoImportKeys() const | ||
124 | { | ||
125 | return false; | ||
126 | } | ||
127 | |||
128 | bool ObjectTreeSource::showEmoticons() const | ||
129 | { | ||
130 | return false; | ||
131 | } | ||
132 | |||
133 | bool ObjectTreeSource::showExpandQuotesMark() const | ||
134 | { | ||
135 | return false; | ||
136 | } | ||
137 | |||
138 | bool ObjectTreeSource::isPrinting() const | ||
139 | { | ||
140 | return false; | ||
141 | } | ||
142 | |||
143 | const MimeTreeParser::BodyPartFormatterBaseFactory *ObjectTreeSource::bodyPartFormatterFactory() | ||
144 | { | ||
145 | return &(d->mBodyPartFormatterBaseFactory); | ||
146 | } | ||
147 | |||
148 | MimeTreeParser::Interface::MessagePartRenderer::Ptr ObjectTreeSource::messagePartTheme(MimeTreeParser::Interface::MessagePart::Ptr msgPart) | ||
149 | { | ||
150 | Q_UNUSED(msgPart); | ||
151 | return MimeTreeParser::Interface::MessagePartRenderer::Ptr(); | ||
152 | } | ||
diff --git a/framework/src/domain/mimetreeparser/objecttreesource.h b/framework/src/domain/mimetreeparser/objecttreesource.h new file mode 100644 index 00000000..93812dc3 --- /dev/null +++ b/framework/src/domain/mimetreeparser/objecttreesource.h | |||
@@ -0,0 +1,57 @@ | |||
1 | /* | ||
2 | Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net | ||
3 | Copyright (c) 2009 Andras Mantia <andras@kdab.net> | ||
4 | |||
5 | This program is free software; you can redistribute it and/or modify | ||
6 | it under the terms of the GNU General Public License as published by | ||
7 | the Free Software Foundation; either version 2 of the License, or | ||
8 | (at your option) any later version. | ||
9 | |||
10 | This program is distributed in the hope that it will be useful, | ||
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | GNU General Public License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU General Public License along | ||
16 | with this program; if not, write to the Free Software Foundation, Inc., | ||
17 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
18 | */ | ||
19 | |||
20 | #ifndef MAILVIEWER_OBJECTTREEEMPTYSOURCE_H | ||
21 | #define MAILVIEWER_OBJECTTREEEMPTYSOURCE_H | ||
22 | |||
23 | #include <MimeTreeParser/ObjectTreeSource> | ||
24 | |||
25 | class QString; | ||
26 | |||
27 | class ObjectSourcePrivate; | ||
28 | class ObjectTreeSource : public MimeTreeParser::Interface::ObjectTreeSource | ||
29 | { | ||
30 | public: | ||
31 | ObjectTreeSource(MimeTreeParser::HtmlWriter *writer); | ||
32 | virtual ~ObjectTreeSource(); | ||
33 | void setHtmlLoadExternal(bool loadExternal); | ||
34 | bool decryptMessage() const Q_DECL_OVERRIDE; | ||
35 | bool htmlLoadExternal() const Q_DECL_OVERRIDE; | ||
36 | bool showSignatureDetails() const Q_DECL_OVERRIDE; | ||
37 | void setHtmlMode(MimeTreeParser::Util::HtmlMode mode, const QList<MimeTreeParser::Util::HtmlMode> &availableModes) Q_DECL_OVERRIDE; | ||
38 | MimeTreeParser::Util::HtmlMode preferredMode() const Q_DECL_OVERRIDE; | ||
39 | void setAllowDecryption(bool allowDecryption); | ||
40 | int levelQuote() const Q_DECL_OVERRIDE; | ||
41 | const QTextCodec *overrideCodec() Q_DECL_OVERRIDE; | ||
42 | QString createMessageHeader(KMime::Message *message) Q_DECL_OVERRIDE; | ||
43 | const MimeTreeParser::AttachmentStrategy *attachmentStrategy() Q_DECL_OVERRIDE; | ||
44 | MimeTreeParser::HtmlWriter *htmlWriter() Q_DECL_OVERRIDE; | ||
45 | QObject *sourceObject() Q_DECL_OVERRIDE; | ||
46 | bool autoImportKeys() const Q_DECL_OVERRIDE; | ||
47 | bool showEmoticons() const Q_DECL_OVERRIDE; | ||
48 | bool showExpandQuotesMark() const Q_DECL_OVERRIDE; | ||
49 | bool isPrinting() const Q_DECL_OVERRIDE; | ||
50 | const MimeTreeParser::BodyPartFormatterBaseFactory *bodyPartFormatterFactory() Q_DECL_OVERRIDE; | ||
51 | MimeTreeParser::Interface::MessagePartRendererPtr messagePartTheme(MimeTreeParser::Interface::MessagePartPtr msgPart) Q_DECL_OVERRIDE; | ||
52 | private: | ||
53 | ObjectSourcePrivate *const d; | ||
54 | }; | ||
55 | |||
56 | #endif | ||
57 | |||
diff --git a/framework/src/domain/mimetreeparser/stringhtmlwriter.cpp b/framework/src/domain/mimetreeparser/stringhtmlwriter.cpp new file mode 100644 index 00000000..88034492 --- /dev/null +++ b/framework/src/domain/mimetreeparser/stringhtmlwriter.cpp | |||
@@ -0,0 +1,150 @@ | |||
1 | /* -*- c++ -*- | ||
2 | filehtmlwriter.cpp | ||
3 | |||
4 | This file is part of KMail, the KDE mail client. | ||
5 | Copyright (c) 2003 Marc Mutz <mutz@kde.org> | ||
6 | |||
7 | KMail is free software; you can redistribute it and/or modify it | ||
8 | under the terms of the GNU General Public License, version 2, as | ||
9 | published by the Free Software Foundation. | ||
10 | |||
11 | KMail is distributed in the hope that it will be useful, but | ||
12 | WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
14 | General Public License for more details. | ||
15 | |||
16 | You should have received a copy of the GNU General Public License | ||
17 | along with this program; if not, write to the Free Software | ||
18 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
19 | |||
20 | In addition, as a special exception, the copyright holders give | ||
21 | permission to link the code of this program with any edition of | ||
22 | the Qt library by Trolltech AS, Norway (or with modified versions | ||
23 | of Qt that use the same license as Qt), and distribute linked | ||
24 | combinations including the two. You must obey the GNU General | ||
25 | Public License in all respects for all of the code used other than | ||
26 | Qt. If you modify this file, you may extend this exception to | ||
27 | your version of the file, but you are not obligated to do so. If | ||
28 | you do not wish to do so, delete this exception statement from | ||
29 | your version. | ||
30 | */ | ||
31 | |||
32 | #include "stringhtmlwriter.h" | ||
33 | |||
34 | #include <QDebug> | ||
35 | #include <QTextStream> | ||
36 | #include <QUrl> | ||
37 | |||
38 | StringHtmlWriter::StringHtmlWriter() | ||
39 | : MimeTreeParser::HtmlWriter() | ||
40 | , mState(Ended) | ||
41 | { | ||
42 | } | ||
43 | |||
44 | StringHtmlWriter::~StringHtmlWriter() | ||
45 | { | ||
46 | } | ||
47 | |||
48 | void StringHtmlWriter::begin(const QString &css) | ||
49 | { | ||
50 | if (mState != Ended) { | ||
51 | qWarning() << "begin() called on non-ended session!"; | ||
52 | reset(); | ||
53 | } | ||
54 | |||
55 | mState = Begun; | ||
56 | mExtraHead.clear(); | ||
57 | mHtml.clear(); | ||
58 | |||
59 | if (!css.isEmpty()) { | ||
60 | write(QLatin1String("<!-- CSS Definitions \n") + css + QLatin1String("-->\n")); | ||
61 | } | ||
62 | } | ||
63 | |||
64 | void StringHtmlWriter::end() | ||
65 | { | ||
66 | if (mState != Begun) { | ||
67 | qWarning() << "Called on non-begun or queued session!"; | ||
68 | } | ||
69 | |||
70 | if (!mExtraHead.isEmpty()) { | ||
71 | insertExtraHead(); | ||
72 | mExtraHead.clear(); | ||
73 | } | ||
74 | resolveCidUrls(); | ||
75 | mState = Ended; | ||
76 | } | ||
77 | |||
78 | void StringHtmlWriter::reset() | ||
79 | { | ||
80 | if (mState != Ended) { | ||
81 | mHtml.clear(); | ||
82 | mExtraHead.clear(); | ||
83 | mState = Begun; // don't run into end()'s warning | ||
84 | end(); | ||
85 | mState = Ended; | ||
86 | } | ||
87 | } | ||
88 | |||
89 | void StringHtmlWriter::write(const QString &str) | ||
90 | { | ||
91 | if (mState != Begun) { | ||
92 | qWarning() << "Called in Ended or Queued state!"; | ||
93 | } | ||
94 | mHtml.append(str); | ||
95 | } | ||
96 | |||
97 | void StringHtmlWriter::queue(const QString &str) | ||
98 | { | ||
99 | write(str); | ||
100 | } | ||
101 | |||
102 | void StringHtmlWriter::flush() | ||
103 | { | ||
104 | mState = Begun; // don't run into end()'s warning | ||
105 | end(); | ||
106 | } | ||
107 | |||
108 | void StringHtmlWriter::embedPart(const QByteArray &contentId, const QString &url) | ||
109 | { | ||
110 | write("<!-- embedPart(contentID=" + contentId + ", url=" + url + ") -->\n"); | ||
111 | mEmbeddedPartMap.insert(contentId, url); | ||
112 | } | ||
113 | |||
114 | void StringHtmlWriter::resolveCidUrls() | ||
115 | { | ||
116 | for (const auto &cid : mEmbeddedPartMap.keys()) { | ||
117 | mHtml.replace(QString("src=\"cid:%1\"").arg(QString(cid)), QString("src=\"%1\"").arg(mEmbeddedPartMap.value(cid).toString())); | ||
118 | } | ||
119 | } | ||
120 | |||
121 | void StringHtmlWriter::extraHead(const QString &extraHead) | ||
122 | { | ||
123 | if (mState != Ended) { | ||
124 | qWarning() << "Called on non-started session!"; | ||
125 | } | ||
126 | mExtraHead.append(extraHead); | ||
127 | } | ||
128 | |||
129 | |||
130 | void StringHtmlWriter::insertExtraHead() | ||
131 | { | ||
132 | const QString headTag(QStringLiteral("<head>")); | ||
133 | const int index = mHtml.indexOf(headTag); | ||
134 | if (index != -1) { | ||
135 | mHtml.insert(index + headTag.length(), mExtraHead); | ||
136 | } | ||
137 | } | ||
138 | |||
139 | QMap<QByteArray, QUrl> StringHtmlWriter::embeddedParts() const | ||
140 | { | ||
141 | return mEmbeddedPartMap; | ||
142 | } | ||
143 | |||
144 | QString StringHtmlWriter::html() const | ||
145 | { | ||
146 | if (mState != Ended) { | ||
147 | qWarning() << "Called on non-ended session!"; | ||
148 | } | ||
149 | return mHtml; | ||
150 | } | ||
diff --git a/framework/src/domain/mimetreeparser/stringhtmlwriter.h b/framework/src/domain/mimetreeparser/stringhtmlwriter.h new file mode 100644 index 00000000..fa5b760e --- /dev/null +++ b/framework/src/domain/mimetreeparser/stringhtmlwriter.h | |||
@@ -0,0 +1,71 @@ | |||
1 | /* -*- c++ -*- | ||
2 | |||
3 | Copyright (c) 2016 Sandro Knauß <sknauss@kde.org> | ||
4 | |||
5 | Kube is free software; you can redistribute it and/or modify it | ||
6 | under the terms of the GNU General Public License, version 2, as | ||
7 | published by the Free Software Foundation. | ||
8 | |||
9 | Kube is distributed in the hope that it will be useful, but | ||
10 | WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
12 | General Public License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU General Public License | ||
15 | along with this program; if not, write to the Free Software | ||
16 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
17 | |||
18 | In addition, as a special exception, the copyright holders give | ||
19 | permission to link the code of this program with any edition of | ||
20 | the Qt library by Trolltech AS, Norway (or with modified versions | ||
21 | of Qt that use the same license as Qt), and distribute linked | ||
22 | combinations including the two. You must obey the GNU General | ||
23 | Public License in all respects for all of the code used other than | ||
24 | Qt. If you modify this file, you may extend this exception to | ||
25 | your version of the file, but you are not obligated to do so. If | ||
26 | you do not wish to do so, delete this exception statement from | ||
27 | your version. | ||
28 | */ | ||
29 | |||
30 | #ifndef __KUBE_FRAMEWORK_MAIL_STRINGHTMLWRITER_H__ | ||
31 | #define __KUBE_FRAMEWORK_MAIL_STRINGHTMLWRITER_H__ | ||
32 | |||
33 | #include <MimeTreeParser/HtmlWriter> | ||
34 | |||
35 | #include <QFile> | ||
36 | #include <QTextStream> | ||
37 | |||
38 | class QString; | ||
39 | |||
40 | class StringHtmlWriter : public MimeTreeParser::HtmlWriter | ||
41 | { | ||
42 | public: | ||
43 | explicit StringHtmlWriter(); | ||
44 | virtual ~StringHtmlWriter(); | ||
45 | |||
46 | void begin(const QString &cssDefs) Q_DECL_OVERRIDE; | ||
47 | void end() Q_DECL_OVERRIDE; | ||
48 | void reset() Q_DECL_OVERRIDE; | ||
49 | void write(const QString &str) Q_DECL_OVERRIDE; | ||
50 | void queue(const QString &str) Q_DECL_OVERRIDE; | ||
51 | void flush() Q_DECL_OVERRIDE; | ||
52 | void embedPart(const QByteArray &contentId, const QString &url) Q_DECL_OVERRIDE; | ||
53 | void extraHead(const QString &str) Q_DECL_OVERRIDE; | ||
54 | |||
55 | QString html() const; | ||
56 | QMap<QByteArray, QUrl> embeddedParts() const; | ||
57 | private: | ||
58 | void insertExtraHead(); | ||
59 | void resolveCidUrls(); | ||
60 | |||
61 | QString mHtml; | ||
62 | QString mExtraHead; | ||
63 | enum State { | ||
64 | Begun, | ||
65 | Queued, | ||
66 | Ended | ||
67 | } mState; | ||
68 | QMap<QByteArray, QUrl> mEmbeddedPartMap; | ||
69 | }; | ||
70 | |||
71 | #endif // __MESSAGEVIEWER_FILEHTMLWRITER_H__ | ||
diff --git a/framework/src/domain/mimetreeparser/tests/CMakeLists.txt b/framework/src/domain/mimetreeparser/tests/CMakeLists.txt new file mode 100644 index 00000000..71afb903 --- /dev/null +++ b/framework/src/domain/mimetreeparser/tests/CMakeLists.txt | |||
@@ -0,0 +1,23 @@ | |||
1 | add_subdirectory(gnupg_home) | ||
2 | add_definitions( -DMAIL_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data" ) | ||
3 | include(${CMAKE_CURRENT_SOURCE_DIR}/kdepim_add_gpg_crypto_test.cmake) | ||
4 | include_directories( | ||
5 | ${CMAKE_CURRENT_BINARY_DIR} | ||
6 | ${CMAKE_CURRENT_SOURCE_DIR}/.. | ||
7 | ) | ||
8 | |||
9 | include(ECMAddTests) | ||
10 | |||
11 | add_executable(mimetreeparsertest interfacetest.cpp) | ||
12 | add_gpg_crypto_test(mimetreeparsertest mimetreeparsertest) | ||
13 | qt5_use_modules(mimetreeparsertest Core Test) | ||
14 | target_link_libraries(mimetreeparsertest mimetreeparser) | ||
15 | |||
16 | find_package(Gpgmepp 1.7.1 CONFIG) | ||
17 | find_package(QGpgme 1.7.1 CONFIG) | ||
18 | |||
19 | ecm_add_test(gpgerrortest.cpp | ||
20 | TEST_NAME "gpgerrortest" | ||
21 | NAME_PREFIX "mimetreeparser-" | ||
22 | LINK_LIBRARIES Qt5::Core Qt5::Test mimetreeparser Gpgmepp QGpgme | ||
23 | ) | ||
diff --git a/framework/src/domain/mimetreeparser/tests/data/alternative.mbox b/framework/src/domain/mimetreeparser/tests/data/alternative.mbox new file mode 100644 index 00000000..6522c34b --- /dev/null +++ b/framework/src/domain/mimetreeparser/tests/data/alternative.mbox | |||
@@ -0,0 +1,28 @@ | |||
1 | Return-Path: <konqi@example.org> | ||
2 | Date: Wed, 8 Jun 2016 20:34:44 -0700 | ||
3 | From: Konqi <konqi@example.org> | ||
4 | To: konqi@kde.org | ||
5 | Subject: A random subject with alternative contenttype | ||
6 | MIME-Version: 1.0 | ||
7 | Content-Type: multipart/alternative; | ||
8 | boundary="----=_Part_12345678_12345678" | ||
9 | |||
10 | |||
11 | ------=_Part_12345678_12345678 | ||
12 | Content-Type: text/plain; charset=utf-8 | ||
13 | Content-Transfer-Encoding: quoted-printable | ||
14 | |||
15 | If you can see this text it means that your email client couldn't display o= | ||
16 | ur newsletter properly. | ||
17 | Please visit this link to view the newsletter on our website: http://www.go= | ||
18 | g.com/newsletter/ | ||
19 | |||
20 | |||
21 | ------=_Part_12345678_12345678 | ||
22 | Content-Transfer-Encoding: 7Bit | ||
23 | Content-Type: text/html; charset="windows-1252" | ||
24 | |||
25 | <html><body><p><span>HTML</span> text</p></body></html> | ||
26 | |||
27 | |||
28 | ------=_Part_12345678_12345678-- | ||
diff --git a/framework/src/domain/mimetreeparser/tests/data/cid-links.mbox b/framework/src/domain/mimetreeparser/tests/data/cid-links.mbox new file mode 100644 index 00000000..40ff5282 --- /dev/null +++ b/framework/src/domain/mimetreeparser/tests/data/cid-links.mbox | |||
@@ -0,0 +1,1384 @@ | |||
1 | Message-ID: <851f01d15e53$31734730$790bc9ad@info> | ||
2 | From: "OculusLab" <info@findermanze.co.ua> | ||
3 | To: <info@example.org> | ||
4 | Subject: CID links for images | ||
5 | Date: Wed, 03 Feb 2016 07:19:17 +0200 | ||
6 | MIME-Version: 1.0 | ||
7 | Content-Type: multipart/related; | ||
8 | type="multipart/alternative"; | ||
9 | boundary="----=_NextPart_000_000F_01D15E52.0BD654A0" | ||
10 | X-MSMail-Priority: Normal | ||
11 | X-Mailer: Microsoft Windows Live Mail 14.0.8117.416 | ||
12 | X-MimeOLE: Produced By Microsoft MimeOLE V14.0.8117.416 | ||
13 | |||
14 | This is a multi-part message in MIME format. | ||
15 | |||
16 | ------=_NextPart_000_000F_01D15E52.0BD654A0 | ||
17 | Content-Type: multipart/alternative; | ||
18 | boundary="----=_NextPart_000_0010_01D15E52.0BD654A0" | ||
19 | |||
20 | ------=_NextPart_000_0010_01D15E52.0BD654A0 | ||
21 | Content-Type: text/plain; | ||
22 | charset="windows-1251" | ||
23 | Content-Transfer-Encoding: quoted-printable | ||
24 | |||
25 | =0D=0A=0D=0A=0D=0A=0D=0ASuperkombipackung für nur 45 Euro=0D= | ||
26 | =0A=0D=0A | ||
27 | ------=_NextPart_000_0010_01D15E52.0BD654A0 | ||
28 | Content-Type: text/html; | ||
29 | charset="windows-1251" | ||
30 | Content-Transfer-Encoding: quoted-printable | ||
31 | |||
32 | <HTML><HEAD>=0D=0A<META http-equiv=3D"Content-Type" content=3D"te= | ||
33 | xt/html; charset=3Dwindows-1251">=0D=0A</HEAD>=0D=0A<BODY bgColor= | ||
34 | =3D#ffffff>=0D=0A<DIV align=3Dcenter><FONT size=3D2 face=3DArial>= | ||
35 | <A =0D=0Ahref=3D"http://intenices.co.ua/drugs-store/index.html"><= | ||
36 | STRONG><FONT =0D=0Asize=3D4>Superkombipackung für nur 45 Eur= | ||
37 | o</FONT></STRONG></A><BR><BR><A =0D=0Ahref=3D"http://intenices.co= | ||
38 | .ua/drugs-store/index.html"><IMG border=3D0 hspace=3D0 alt=3D""=20= | ||
39 | src=3D"cid:9359201d15e53f31a68c307b3369b6@info" width=3D650 heigh= | ||
40 | t=3D763></A></FONT></DIV></BODY></HTML> | ||
41 | |||
42 | ------=_NextPart_000_0010_01D15E52.0BD654A0-- | ||
43 | |||
44 | ------=_NextPart_000_000F_01D15E52.0BD654A0 | ||
45 | Content-Type: image/jpeg; | ||
46 | name="aqnaozisxya.jpeg" | ||
47 | Content-Transfer-Encoding: base64 | ||
48 | Content-ID: <9359201d15e53f31a68c307b3369b6@info> | ||
49 | |||
50 | /9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAA8AAD/4QMqaHR0cDov | ||
51 | L25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENl | ||
52 | aGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4 | ||
53 | OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjUtYzAxNCA3OS4xNTE0ODEsIDIwMTMvMDMvMTMtMTI6 | ||
54 | MDk6MTUgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5 | ||
55 | OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHht | ||
56 | bG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6 | ||
57 | Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUu | ||
58 | Y29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBo | ||
59 | b3Rvc2hvcCBDQyAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QjdCRTg5MTBD | ||
60 | OUNGMTFFNUJBOTdEMkQyNzU0ODI3RDciIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QjdCRTg5 | ||
61 | MTFDOUNGMTFFNUJBOTdEMkQyNzU0ODI3RDciPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5z | ||
62 | dGFuY2VJRD0ieG1wLmlpZDpCN0JFODkwRUM5Q0YxMUU1QkE5N0QyRDI3NTQ4MjdENyIgc3RSZWY6 | ||
63 | ZG9jdW1lbnRJRD0ieG1wLmRpZDpCN0JFODkwRkM5Q0YxMUU1QkE5N0QyRDI3NTQ4MjdENyIvPiA8 | ||
64 | L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0i | ||
65 | ciI/Pv/uAA5BZG9iZQBkwAAAAAH/2wCEAAYEBAQFBAYFBQYJBgUGCQsIBgYICwwKCgsKCgwQDAwM | ||
66 | DAwMEAwODxAPDgwTExQUExMcGxsbHB8fHx8fHx8fHx8BBwcHDQwNGBAQGBoVERUaHx8fHx8fHx8f | ||
67 | Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fH//AABEIAvsCigMBEQACEQED | ||
68 | EQH/xADJAAEAAgMBAQEAAAAAAAAAAAAAAwQBAgUGBwgBAQEBAQEBAQAAAAAAAAAAAAABAgMEBQYQ | ||
69 | AAEEAgEDAgQCBAgKCQMACwIAAQMEEQUSIRMGMUFRIjIUYXGBQhUHkaGxUiMzFhfB0WJy0+OkZZVW | ||
70 | 8ILSsyQ0lFU24UN1U4MlssJzhLQ1djcRAQABAgMEBQkFBwQBBAIDAAABEQIhMQNBUWEScYGRoQTw | ||
71 | scHRIjJSYhPhQpKyFfFygqIzUwXCI2Nzk9JDsxSDNPLD0//aAAwDAQACEQMRAD8A/VKAgICAgICA | ||
72 | gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA | ||
73 | gICAgICAgICAgICAgICAgICAg1y6Bl0DLoGXQMugZdBrIZMBOz9WZBpWkM68Zk+SIWd3QS8nQOTo | ||
74 | HJ0Dk6BydA5OgcnQOToGXQMugZdAy6Bl0DLoGXQMugOT4dAYnwyA7ughaWTPqgkIyaNnz16INQkN | ||
75 | 3bL+6BJIbHhn6INozJ2fL+6DbLoI5JDYmZn6KDJGTR8s9VQiM39Xyg1GWRy6v0ygkMiYXdn6sghG | ||
76 | aRxd8/yItGAnldupfxMoUZ70nx/kQO9J8f5EKAzSO3r/ABMhRnuyfH+RCg0snXqgNNI/v/Igd2T4 | ||
77 | /wAiDLSyfFCjDSyfH+RUZ7snx/kUBpTz6oMd2TPr/Igz3JPj/Igy0knx/kQY7smX6oMd2T4/yIMj | ||
78 | Kb+6B3T5O2eiVKHdP4oUO5J8UqtGHlk5Yz/IpUoz3ZPiqUO7J8UKDynluqIy8h/FA7h/FKlApDZv | ||
79 | VAaU/ilSjPcP4oDyHjOUGO4eG6oUZ5l8UGeZY9VUatIePVRR5Dz6pUoyxnn1QBkN3fqgy5l8UQYy | ||
80 | +KDcXd2fPxVGUBBqgICAgICDSb+qL8nQR0//ACkP+YyEpkBAQEBAQEBEEBAQEBAQEBAL0dAb0ZFH | ||
81 | 9HQQD6oJJP6pv0INY/Vvz/wIMS/1n8CCSL6X/N0G6CKT62UGS/qv0/4VQhQRh9Tfmglm/qyQQD9D | ||
82 | qNNY/pQbeyIN7ooHogyiDe6KwPoiMorLIA+6IOisj6ojA+roNnQG9UGG9XQY90VkcZwlUox+u/5I | ||
83 | CKZdBh/qUGVQZQZf1ZVGSRWGUGS9FUYZRWW9FRkvpRGG9kGXQBfogx7IMe7KKzyVQjz1yg2QZZEb | ||
84 | h6P+ao2QEGqAgICAgII5v6ovydBrU/8AKw/5jfyISlQEBAQEBAQEBEEBAQEBAQEGC9H/ACRWWQH9 | ||
85 | HQQA/VBJJ/Vt+hBrF6t+f+BBiX6/4EEkX0v+boNkEUn1fwINj/qkCH0QRx45t+aCWb+rJCFdiHi7 | ||
86 | Z6qNNQdmH1QZcm+KAxNh+qAJizdXQbcw+KIx3A69UATHHqgzzFAYx69UBjFAcx/FAaQW+KA0jdej | ||
87 | oM9xvg6VBpWz6Og1KVhZ3wpMqoWLcT5Zn5l/E36FiZbiEMcxM/yk7fBn9FKrRdrWib+s+b4EtRLM | ||
88 | wsjIxNkevxWmWWd3b0VSrHVy9FFqy7v8FSpyf4IlRyd39EByf4KByL4ItRyJ/ZEORfBFqMRfBBly | ||
89 | LGMKoMT/AAQZ5O/sgMRM2MIGS+CB83rhCo3L4IMixNnp6oM/N8EGW5fBESR+j/mqNkBBqgICAgIC | ||
90 | COb+pP8AJ0Ia1f8AysX+YP8AIhKVAQEBAQEBAQEQQEUQEBAQEGC+l/yQZZEH9HRUIt1dBvJ/Vsg1 | ||
91 | j9W/NBiX63QSR/S/5ug2QRyN8yDJf1aBH/gQaRt87fmiJJGyDsiq/bH4IHBvggcWQOLIHFkoM8UD | ||
92 | igcUDCDOEDCBhAw6BxQOKUEdiUII3kP0b0/F1JlYhxLV6WZ85wDensy5TLrEIBlZ/R/8ClFSx8fV | ||
93 | mf8AHDqKTW3hw7Pyz6P/AIHVhmUsV83wYu3zdOnXL/irWhRcr7VsfMPp649cfktRezNjoQyxzNzB | ||
94 | 8t7t8FuJYmEhMqjGEDCBhQMICAgICBhUZwgYUGcKjOEDCBhBnCAiNw9EVlAQaoCAgICAgjn/AKk/ | ||
95 | ydCGK3/lov8AMH+RQlIqCAgICAgICAgIgiqNvcU6zuJE5G36rLya3jdPTzl30/DX35OdN5KT9Yos | ||
96 | N/le/wDAvBd/l91r1W+A3ygfyW2xdAFm/HLrl+q37odP/oWpI/KDb+siZ/yddbf8tvhi7wG6V+p5 | ||
97 | DQndhd3jN/Yv/ovZo+P078MpebU8JfbxdHmJA7i+WdvVe2HmbMqD+joIvig3P6GQah6sgxJ9SDeP | ||
98 | 6f0ug2QaH9SDL/QyBH7/AJIjQPqb80EhfS6KjQYwqGEDCBhAwoGEGcIGEDCBhAx1QMIM4QV7dyGs | ||
99 | GTf5n+gPd1JlYhwLmxlmfMjtxz8oN6MuczV0iFXu8ny7cvglFqzgjdnF8v8AzSSglA3jJhJuLH9L | ||
100 | /AlKFUdkWlDmPQmd+Y/l7stQksRs4VzIX/ml/A6ysJ2L5mkF/mf6g+P4skrC3RtPDNkX+V/b8Pg/ | ||
101 | 5JEpdFXoGJiFib0fqy6w4yKjGEDCAgYQMICBhAwgygIMoggICDKAg2H0RWUBBqgICAgICCOdv6E/ | ||
102 | ydAgbEEbfAR/kUJbqggICAgICAgINZJAjBzN2EW9XdZuui2KzkttszNIed2O6nmJwhJwib3b1f8A | ||
103 | SvgeK/yF12FuEPq6HhItxuzcp+r9fV/dfLmXtho/RZmVRv6qVaY6f41ao1J2Z+nqt2osVNtcqP8A | ||
104 | 0Zu4+4P1b+NezQ8ZfZOE4PPq+Htvzeq1m4r3Y2diZpf1g92/hX6DQ8RbqRhm+TraM2Ti6OWcXwvQ | ||
105 | 4ovb9CI3P6WQah9TIof1Og2i+j9LoNkGhfUgy/0MiAe/5INA+pvzQSF6OitEGEBUEBQEBBlAwgYQ | ||
106 | EBkGUGHQeW2l0p7BOz/Kz8Rb4MucukKMZOTkzv6YVoDdxy+Qmz8EEwETfV8pe7/UP8HspKwnHq3t | ||
107 | +Wen54dZVGdaYMkP0/BnyrUo2qj8pRF6Y/l/xKStGQZhftn1cOjfiyC2Ds7YHHX3wpJDr6+bI9p/ | ||
108 | X1H/AAst2S53wuLowwgICAgICAgICAgyiCDKgwqMoCDYfRFZQEGqAgICAgII5/6k/wDNf+RAh/qY | ||
109 | /wDNH+RCW6AgICAgICAgP0Qeb2+xKxIUMf8AUg+Pzdl+f8f4qb55Yyh9XwuhyxzTm5jr5s2vbVo7 | ||
110 | sy5y0qlcjY+LO5P+DZUWjdiY8O36VlWCd8KwNHW6owTqxKNY5zhkaSN8EL5ZdtPVm2aw532xdFJe | ||
111 | w1O4C3E+ekjN8zL9N4fxEaltdr4utozZLoiTO36F6HBLJ9LIrUH+Zv0oBv8AMg2j+j9LoNkGhfUg | ||
112 | y/0MiAe/5INA+pvzQSF6IrTCBhAQEDCBhAQEBBlAwgICCtsZOFYm5cOXRy+De7qSsPKSNxFibrl/ | ||
113 | RYdIRkOBZh9/qZBhoHJ2cXw/4JUouhVnN26fN74UqtFmPXyZ9MP7/BRVptezD1UVUmrlCXJvRRUM | ||
114 | g8nGRs/B/wBCrKeFwd/Vs+7oLlWXjIz5y7Plnb3+KsZsy7LOztlvR12hykQYQHygxl0DqgdVAy6B | ||
115 | 1VGcugdUDqgZdAy6DPVARBBuPoisoCDVAQEBAQEEc/8AUn/mv/IgQ/1Mf+aP8iDdAQEBAQEBARHP | ||
116 | 3Nx69bAvg5OjLx+O1/p2cZenw2lz3cHm2Z36v1+Lr85ES+xLBP0d1LpIVZ2IgJh9XXCXWGkNeKMe | ||
117 | vQn9XZShUZwaTgz/ADerOpRWZGf1WkhC5deqojN3/QrA0zlnZ10iWaJKV+SpZA2L5c4Jvwdevwmv | ||
118 | yXxOxw8Rpc9r21G00sYkz5Ymyy/SxNYq+HMUmjom/wArKjAfUyDB/W6CQPpZBlQaF9SqMv8AQyAH | ||
119 | q/5INA+tkEr+iK0QEBBhBlAQYylRSsbWvE7iH9ITfD0/hWZuai1Rk3kz9G4h/G6zzNcqD9pXTfpI | ||
120 | X6FKytIavPZfryN3/N/8aVlaQnh2NyP15OzfqkyRdKTa61O7HZDI9Cb6hXSJq5zFFHcvKRRxsPR2 | ||
121 | LH4+nRZuatcEMmOP1gf0+LLLS1TrjIXpkVKtxDpxVIQfoLILIADejIiQWZ3VG+ESqvZrsUb49VJh | ||
122 | Ylxi5xScXZ/zZRUjkWMMwv8AFjRlmKRmPp0dvUX9WdUegrSc4RL8F1tcrkqqCIOisICAgICAgICD | ||
123 | KAgIggINx9EVlAQaoCAgICAgjn/qT/zX/kQZi/qg/wA1v5EGyAgICAgICBnCDyu7td638r/KPRl+ | ||
124 | e/yWrzXU2Pr+D06WqjE+MLwxk9NGkh9HbKxMrEObJscy9qEXMm9X9GZcrpdIg43JHzlh+KZKkgq9 | ||
125 | s3kM+cjthn9mWRrbsBG2P4FuiKJBen+YH4j6rFWm0LWhfjK7E3tj1Vi8olJnbC6RLKCTPwXSGZel | ||
126 | 8ftuVZgd+oPj9C/SeB1ObTjg+L4uyl/S9UBsUYr2PM3D6mQJPqdBtH9DINkGhfUgy/0MiAe/5INQ | ||
127 | +pkEj+iK1dBhUFAQYQRzTxwg5yPhm/h/QkyRDhbDcyzM4RC8cXu7+rrEy3EOY0sj9Gzj+BZo0lir | ||
128 | yk7Zd2z7e6EOpV1uWZzf9Cir8dSEPQWVoVSPFHjHFsJRKq0lTi/OAnik+LeiGbE5jbpSNM3GaFnJ | ||
129 | mHp1ZvVvzW82MnmwJiMWy+Xf0WZdLXaqRNGLN8errm6LguqjdlUbi6sIkZ1UZJmJsKI5luu+XfHV | ||
130 | lltVNmLDtjPuLoiM8i/p83t1/wASIvUdjLGLATfJnHxdbiWbrXbZ8tn1XRzEB0GEBBnCAgIggIog | ||
131 | IggICDKDYfRFZQEGqAgICAgII5/6mT/Nf+RBmP8Aqw/zW/kQbICAgINJJoomzIbA3xJ8KTMRmREy | ||
132 | oT+Q6uJnxKxu3sPX+NcLvE2RtdY0L52OdN5hE3SKHl+Lu/8AiXC7x0bIdo8JO2VKXy+6TOwxgOWf | ||
133 | 4v8A4Vxu8ddudY8Ja5El+U3cn9V4L7YumsvXbhk0a7Mz/V/IpyQtZay25TFxd8s/uPR1yv0NzcXp | ||
134 | acUEcWY25O/u/qvHdp0l05kss7DgePF0mGoakeWzlZVQiF7N92LqAN+j1S6dw6UztCPEWZmwpEJD | ||
135 | myzcLMbfzss6t0NQszC3b5M3srbKSpG/o/w9WXaJYdLx+V+5J16PjDL7X+Luzh83x8ZS9tUkyAsv | ||
136 | rvnLgev6EGD+p0RtH9DIrIkJDyF8t8WUGpeqqMv9DIAe6DUPqZBI6K1QYVBQEGpkwi5P6M2XSR5z | ||
137 | Y7J5ZHx0Fugs3q65zi6Rg5fM5C+DfH4K0KrEAuT4D9Jv7rMrDsUqrN19/d1FdEWZmWkbIjKg1cco | ||
138 | tVeUTB+Ytl/dvYm+CRJMPNHEMN5xb6eeR/Bn6s36PRanJLXbhZ3FlydloWZaZlvhEZHCsDdzEfV8 | ||
139 | IgEgF6E2USWZIhNuqUKuLYrnHO7M/R36LLTcK+XZnLDt9TejosQmKs4kwv8ATkWz/CrBdLtMzMzM | ||
140 | 3o3Rl1eeRUHUGFRlEEBFEBEEUQEQQEBBlQbD6KqygINUBAQEBBhBpP8A1Mn+a/8AIgzH/Vj+TfyI | ||
141 | NkGHdmbLvhm9XdQc63v9dWyzn3Db9UOq4anibLXazQuucO75ValyNce0L++cuvFqeNmcsHqs8LEZ | ||
142 | 4uPNZnmLlIbm/wCLrx3Xzdm9MWxGSF1lpjig1f4JVWEGroNX6JVG0Vh4ZAL/AO2b4L8HdYvsqtV5 | ||
143 | jHOH915LraOtstTJnbDLlLpCvQFo55M9Hfrlc7mk9qUM+uWXSIZceEjt32cG+SP39lL2odu0fGDg | ||
144 | 3q6zaztcmR+vT1XotSXS0L/PI+PTHVfZ/wAXGcvm+PnJ7Ki/ysvsPmulG+XQayt87u7+j9ERIH9W | ||
145 | osKEeulGJyKYnmw/04Yc/DClFqQfe8XITZ+HQoS9en4pAugYnEJN6OrCNh9/yVRoH1Mglf0RWqDC | ||
146 | Agwg5+7ttBUcR6yS/KLfh7upKw807devV/dZbZAOXT0b1J1KizA7MbMPRmWZWHbqvkG+CCcpBFsk | ||
147 | /wCTfFUac7ZP8kYiP+W/X+JFbhJZziQG/NnRKJuiqKs/3BE4g7AH871dRqHG2tQo5Ip+XP5mYnxj | ||
148 | 8sqlEoWbIj0YXb4O+Fh0mEkd8s4MW/MXZ1WV1pHIcs2UWivPLMw559tvgzdUqUQDKwOLuByufo7v | ||
149 | /gZVmV+I5W6vE4t7P0UF0Xy2fT8FpiVOxHylZsdfisy3CvPXflydvT3brn8OijdqefPCJ3yzthyb | ||
150 | 8vj/AArTFzpA+RZdHCWVUHUVhUZQEBAQEBARBAQEBAQbj6IrKAg1QEBAQEBBpN/Un/mv/IgyP0D+ | ||
151 | TIObst7Wp5Af6Sb+az9G/NebW8TbZ0u2loTd0PNXd3ftO7FJwD+aHRfO1PFX3cHvs8Pba5zu7vl/ | ||
152 | 4V55l2owpUY/FRWBMSLi3X8VmZWiYoHZst1SJEDt6qojL0VGH6IIzf0SBU2UhR0TNnwWWcfzyy1b | ||
153 | mk5OhSsfcVwlZ/qbquetp0TTuTu74wvFda9Nste2+WJuj+65zDVVS1RtTE/akdmf2WcYaii7Q18d | ||
154 | OLLvkv1nStUlFbsOZPjo3stxFEo5xG/J8fxLrCS7ujhIa/N2w8j/AMTdF+i/x2ny6dd743jL6303 | ||
155 | PW0mfiOF73kdCH6v0INJSfk7f5SgnD6GVBywzv1fHXp6qDn1JZAeUpYyBn5Ezuz/AByswrfWycoO | ||
156 | BZY85w/4q2pK6P6y0jQPqZBK/oitUGEBAQec8ikL7sG/VEG4/m6zLUOWTsLM3v7/AIu6y0kZ3wwM | ||
157 | /wCJOoqxTDnIwt6fFRYd6MWAWZlUH4i7mXt6P8GRVVr0k0rhGbRi3qRKkkdicLXZI+6D44mzdOqg | ||
158 | vAbuPVEogsA5Fg+Xb93H1RXIkhsN3YyYuy+Xjcuvo+WSWobx1g+o2d8tjPr/ABKVamGWqQsLsDP1 | ||
159 | 9/41ZlIhfoM7Bh+uOikEpZoBPLOyJEogiIH+XH4dFVWYhd/qfLqJKdui1DEsuIu+XbqqlWnAe7zx | ||
160 | 1ZsZWWnNOyMlp8FiIflbHv16ukpLsA2AZvddIcpbKoOisMgygICAgICIICAgICAg3H0RWUBBqgIC | ||
161 | AgICCOb+pPP81/5EHn9vvch9vVf0bByf4l83xHi9lr3aHhttzzxu5Pl3y/uvnTL2xDR2UlWr9PV1 | ||
162 | BkB7mePVKlFXYc4wZvTL9fyRYTtCI1wdv1my7rMCCC0dS00cju8Ev0u/s6Sua1ZjYT6fS/VlYRWL | ||
163 | qqjTLKjQsu7MyDj762Hy1xf6Op/mumnGNWb5dPSgUeviYujv1/hW9WKuVsujlfN1IeuxhzZmd/4l | ||
164 | wmHWGoz490iBpNZ6fgrEEqckuc9VeUq1pxPZnaIP1n6/k3q69Xh9Gb74iHHW1IstmZetpwMABGLf | ||
165 | KDMy/UW28sRD4N01mr0dePACtItRfV+hEaG/zl+aipg+hlRh3+V1Bq7/ACoDZx0SCWwO+CyqNQ+t | ||
166 | v0oiV/RFaoMIDoCDzvkMMjW45n/q3HDfg7dXWZahxhdykz7N1UaS8sNj4qKu6w2aVvxUWHc9lAIB | ||
167 | McF1b3ZFbRxRi2BFm/JsKo24Ogenyt6oAlERODuzu31Nlun5oNHhAmfGHF/ZFVhi7RcH9P1X/BSj | ||
168 | dWDcfRvV1BJW6O6sJKyWHbD+6rCvJLwd2cHw3q7KNwlhlAmyLqwzMLAuzsqyyiKliGSaQIhNwZ3c | ||
169 | pcfzW6Y/S6sRUmcE7Ua3RiFnwt8rHMs4w2PZGRAdFYZBlAQEBEEBAQEBAQEBBuPoisoCDVAQEBAQ | ||
170 | EHF8g2BxD9tG+CkH53/B14vGa/LHLG16vDaXNNZeZJfIl9GGjsorR2QVJ2OSYYvQH6u6krDexLJB | ||
171 | CRwvjh1x8UoN5Xa5rwMmwRs7LMSTFGmtnaau9aTpNF0/gVyVR27E4DGPWXORwkyQ6kpP9vGxdSZm | ||
172 | ykIqE/VbRE7sgguWxqwvIX1v0Bv8K1FtSZebijku3hj9XkLJl+Hq69NsUcLpexjZhFhH0ZsMuepK | ||
173 | WQ3InFvyXz9R67FdybGX9Vwl1aOWXSFaSyMwqooTWBAHz6v6LpbCTL0XjutKCu9iVv6aZujfzR9V | ||
174 | +h8F4b6dtZzl8fxWvzzSMoejpQO7s7svc8jv8OIig3i+r9CIiL6y/NRU4f1bKjUvpdQav9L/AJMo | ||
175 | Mj7/AJqwNh/WVGgfWP6URK/oisIMIDoCDnb0GOg7O+MEzrMrDzDC3V29HWatjtl+n8SLRd10Zd1n | ||
176 | duizMtRDuMXRRWwvlUSitMt/ZEQlG7k7sT9fVlFax14ImwAsOfgitmJh6t9Po7IILpMzCXwf+VlJ | ||
177 | atUSl+dibrj2UbosVrcb9H6P8HVhmYWnsM+GHGfirVmg7OTdWy7olVZxcJcj0+LLLVVxjdnD8c5S | ||
178 | rMwl5thaZo2gHqUj+pPhvyZdLYc7pbG+P0rbDbn8vT1UorbLe6UB+qlFYQZQEBARBAQEBAQEBAQb | ||
179 | j6IrKAg1QEBAQEGHfDIPHbKV5rsx/jhv0dF8LxF3NfMvraNtLYhTdlwdmpD1UGhD0QhETe6CjtDd | ||
180 | qpC3qbsLfpdSVhaYwr0YYy9cdGb1dSJWmKEqjmzWG/o5G/hwrWo2Do/Iup/FTlSrEkjl6utIhI/Z | ||
181 | WFQTTBEDyydAH+N/grEJLzl+5Lamz1d36AK9FltHK65f10D1I+bv/Tl1J/h+C78uDhN1ZXX2oi2D | ||
182 | Z/zZeXVtl2sbjtKsmG5dfg/RfPviXrtCtxYy5Nj82XOkt1QnsarZ/pG/QnJKVUZdm59I2x8Hf/Eu | ||
183 | 9mhMsXakOv4/opZ5Ru2x+RnyAF6l+OF9jwnhKYy+f4jxGyHs4IXL0ZfTfPdipXcRbog6EvoyDEf1 | ||
184 | P+SCIvrf83UROH9WyqtX+l1Bo7/L/AoMirBLcfQlRoH1j+lBK/ogwgwgIMIObuYpTqk4tlmdsv8A | ||
185 | BlmVteeYgEx5f1bOzO34ZWKOsOvBWFhN2Fvlf0x69PRR0WI4xdssPH0y3wypRiUrM+MINxVEjKoH | ||
186 | IwM7k+Gb3QoiEpZX+X5Q9cv6ujWEJhrM31yP+nC0zN6o2AuFGDuUZhyZ/wAfdZluZwRbN3CMP5vJ | ||
187 | md/hllKJEufM5xtzYOTN6spDVU9RzmBjCJnZ/wAfxwrQrC/BWsZ+kY2zh/d1aMzdBMwMDcpDJ3z0 | ||
188 | H3fOESqOKnxzKee4ePld3dhZvZlJKrOfnz8G6KKwxORsLer9Fq1m6VxnZmw3oy7xDhMtCd3LDe3q | ||
189 | qNm/gZQSNhQZwyDKgxhARREEBAQEBAQEBAQbj6IrKAg1QEBAQEGsn0F+T/yKTksZvFzN/Sm/xJ/5 | ||
190 | V+fvnGX2LckTssS01dkEZN0SgiNlFULkLyGD9flfOPxRU4t0EpG5GzYb8GWaLVsUjv0f0WqIhJ+q | ||
191 | CJz6qiGWQAF5DfiP8qqVce3LZtlhh4RN9LOvf4bwV9+UPHr+Ms085Zq14IGcjHuSv6F6M36F9jS/ | ||
192 | xUR70vj6v+Wr7sN5J3frxwvTH+N0+LzfqWpsorHxL4rjf/iNOdsutv8AltSNkKxws7vgnZ/ZePV/ | ||
193 | wc/du7Xr0v8ANR96ERBKLt6l+S+XreBv084fU0fGWamUrVPUbK4bdqEuL/ruzsP8izp6F12UOl+r | ||
194 | bGcvV6nxGvWdpbJd6X2Zugs6+hpeEi3GcXh1PEzOT08FbOGFsM3ozL1vM61apgcv8EFwQ4izIiaX | ||
195 | 2RWIvrf8kEUjuxM/s7uoJg/q2VGCYnF2H6sPjPplQhzq1y1MMuYwbtjy9X6v8P4lmGk1C1JYAjKP | ||
196 | tszszdc5fGVbUlbF8sf/AE9lpGsf1t+lBK/ogwgwgICCG0DnXlFvqcXZv4FJIeONsuTP8WZYdHS1 | ||
197 | N5s/bSvg2+gs+uPbr7qy3F1XWxxdzZ3diZsj+XusDDPn09FESMrA3F1QkATHDtnHoiK5w2M4aTp+ | ||
198 | WPVHSKNgruzfM7k/4qnMyFYI5Hkbo5dHZSUm7BsdcLISxH9JNjPwf2f9CsOUzRxHaWKQoJv6yPo/ | ||
199 | 4t7OszDtbODasBxlmM3Fs54t6ZSJbpEuiE85OzOX6cK1ZmyE8YCz8n+Yvi6MTLY8+ykogklZsv6J | ||
200 | ELKarG4j3C+ovpb8Piu1ttHG65K5dfyW2IA+Pu6itsug3Zi6IN2UVnqiM5QFBjLfFAQEBAQEBAQE | ||
201 | BBuPoisoCDVAQEBAQak2RdviySPH2o3CzILt6E6+DqW0umH19Oa2whdlzbauzpJVoTMoIjH2UmFQ | ||
202 | k3TqkqiJ/ZSgiI1RERfBFRFJGHU3/R7uvb4bwOpq5RhvePxHjdPSznHcpWJGlJiJug/SL+jL9B4b | ||
203 | /F6enjPtS+B4j/J6l+EezCEid/xX04ij5szVGT/BWjKMibL4dKFURt0z6ugiMnyzJQqxyf26Oykw | ||
204 | sXOlrvI9hTcRc3lib9QuvRcNTw1t3B6tLxV1vF7PS+Qam+4xvI0Nh/8A7cnTP5P6Lwamhda+hp+I | ||
205 | tuetrVBZmdsLi7rnBmB2ZkGjs/wdBvL7IMRfU/5IIjISd2Z+rF6KCZnZouT+jNlBD97WaJpHNmbG | ||
206 | eL+v5YUmViFSvLFFBJIZMzyfSHvjrjp+bqVFinEUVUGJsET8nb4dOitsEpw+k/8Ap7LTLEX1t+lF | ||
207 | Sv6IMIMICAgwg8zuaDwTEYN/RyPyZ/g/uyxMNxLmE3oT+/ukKta/YTRuwSG7xv8AF/RSYWJdwHZm | ||
208 | bHp7LDSZnVRuzoNmJBhBszqjJcWFJRxptpINs2jd+2PTLe5MtWQlza3cp3IRMv6G3G3yuX0k3uOW | ||
209 | WrrUtmiKuQvh/j7LjMO8TVejYfXKqSssQsLKsq89nt5b+BQa1YHldpD+n1Zv8a7W2Ucbrl4i9/Zv | ||
210 | RdHNqz4bPuXoitwbCzMq3ZFb+6iNsoMsgyoGeqoYb4IDqDCAgICAgICAg3H0RWUBBqgICAgIMOg8 | ||
211 | 7u6/C20rfTI3X8/RfK8Zp0u5t73+GvrbRzXZeR6WrsiozZSVQkyggkfopKq0hIqEyZhcnda09Ob5 | ||
212 | pGbN98WRWclOW27dBbr8V+i8H/iLbYrqYzufn/F/5a6cLMI3qpGRZyvtRbEYQ+LN0zjLV3Z/X2VR | ||
213 | G5dei0jR0Ro/v7INOr56YQau+Hxj093RUb49fioI3znD9H9lUaiZATOJOxN7qTCxL1Xj3nu01zhD | ||
214 | YxZrM+OJdDZvwJeTV8NF2T26Xi7rc8YfS9Pv9ftYnOqeXFm5g/q2fivBfpzbOL6OnqRfGDpLm6Co | ||
215 | YZvZBo8UbvlxbPxUAhZwcPRnZ2/hQVP2VU7Lhxybjx7pZIvTGevopRapItfVjdiYMm3oZO5P/GlC | ||
216 | ZWHFnZm+C0jDBhibP1IMBG4kz59EG7+iDCDCAgIMII5IwkHiY8h9cIOBvKnblaQRwB/BujOsTDcS | ||
217 | 4xM/DP8ANbCo7OputLH2if5x6foWLoaiXR5uyy0x9wzJUox92KVKMFeAfdKlEEm2Bm6MqUVZtlMb | ||
218 | dH4j7N7utxFUmaKvcjfrjr/jXaIcpmrcTZ+jOyqNxfD/AAf8OiTbVYuonG2UbfM3Jviy5XabpF7c | ||
219 | 9tAI5z1+DLnytcyatXKXhYkfPJuQD+q2euX+K72WUcL76pwjmGVziP1+ti+l2b2XRzbQXYbMhBHn | ||
220 | MfWT4N1+Ky0mjfm7m/p+qykkJ2boorYW6oNmQZUGzIMqAgKg6gwgICAgICAgINx9EVlAQaoCAgIC | ||
221 | DCCrsKo2ISF/qx8r/iueppxfFJb07+WavLyiUZvGbYJnXxr7Jtmkvp23RMVho7ssNoyyoqvK+FFV | ||
222 | CNnyoqvLJGA5kJh/D3dLbZmaQl0xEVlQsTOfp0b2Zfr/AAHgo0baz7z8n47xs6t2Huq/XHovovno | ||
223 | 3dBo7PjL+nxRGos3JmUmViGJn4E7OtMy14sMIuX1F1FlIxWcEYTuEgsfUS6O/wDIkwRc1njzN2m6 | ||
224 | uT4d1alGjsIP/Rt6dM+qlDmZ5NLGRO2DB8P+XxSFlXdmzn1dlWWnIv41JhYle1m6t660FuubtJF6 | ||
225 | iz/UPuL/AJrnfZF0Ul109SbJrD7H47v4NtrYLcb/AFtgx/mk3R2/hXyNSzlmj7WnfF1sS7LOzrLY | ||
226 | gKDDqjDoCDKAgIg/oitUBAQEGEGHQQzRhIDibZZ+jsoOaekrEx8XcWL0H4LMw1EuFLXs0bXRnyz5 | ||
227 | F29HZVXerzjNCMg/rerfB/dcphuJCDL9GUaY7XxQqglib4K0KqFh2Z+n6GW7bas3TRC+fV+rrvEU | ||
228 | cZYZ1pG7Y90EgnhuvUfj7sglf0/D4qqq2YRf+kboTfW3x/yv8a5zCuropymrHCZZeB+jf5L+itss | ||
229 | TCxdtcXaGP1dsm7ezN/jW2Uevi7VI8dCmPGfwZlGnSibAszfk36FiVTMg2b0/NBlkGVBlBsgICAo | ||
230 | DsisIggICAgICDcfRFZQEGqAgICAgwgwTZZEcjaa5phcg6G38a8+vo88cXfS1eWXnJxliJxJurL5 | ||
231 | V+lNsvo26kSrvbcccm/Nc+WWqwhmtMT5YXTllaudZtSgLsDMP8aRpnMoDzlldyfLsvsf4rQib5nc | ||
232 | +V/lNaYsiI2pCF3d8L9K/N0RuLs2fVkSjRxb4dEqjVwfD9VakwryfKYP6fMP8qSRmzeZxIn/AAyk | ||
233 | ZE5syC5Qxl6NxZ/4kiUuhSs/Q7fwKykLQixTRzO3VgZsfjhZbVjZ+WPitMNar5sSB7cHd1JzaiEL | ||
234 | u7M+VWUZP0fHqgrmbi/8qzKxL3X7sNqQvaov6C7TD/1mw/8A+6vn+LtxiX0/A3YTD6lVl5iy8b3r | ||
235 | CgIMIjCqiDKAgIg6K1QEBBhAQYQakyDQG6OoNJ4oHBylZuLdcupKw5w26swf+HF2ESdnJ2xn9Cxe | ||
236 | 6WJWf3WGgjZlRUsSdMMlVo5hFyJzf09B/JemyMHG6WjrTIiNmdUbM+EVvGWPlf09vwQDbLOyTAai | ||
237 | QoLsjexgX/0WYhJWx5GLn6nKT9f4v4luWIdKMGYIgb0ZndZlpbjWVSCyDZmQbIMt8VAZBlBlA90B | ||
238 | AUVhEEBAQEBAQbj6IrKAg1QEBAQYQEBBCbOTeiDnXKUcv1RsX4rF1kXZw1bdMZONY0sb545H9K89 | ||
239 | 3hLZydrfE3QqFpjboxf9P4Fj/wCnxb/+1waPoYjfMuS/Bnwt2+EtjNm7xM7FLcUYKrQDFGwMWcv8 | ||
240 | fT1X1fBWRbWj5Xj75upVy36dML6D5zV2H9CCN2b0ZlUo0MG/+qRKTCnaE2EcexD/ACq1SiW50fo2 | ||
241 | WdkgulAJMVZmd8HG7tx+LeysJKth5DZm+Z/1vwSSG8s5BI2PQXbLfglCJxaWCjZ3Jn+X19UqnKiq | ||
242 | OPGay+WZ/kjd/V/xUXYiIsMtJCAif1UqlFeV+j46ug9N+7Y3/a1x8ZxCDP8AnknXh8ZOEPo+AjGX | ||
243 | 1qhP0b5XdfPfSdJpmx9DoHeb+aSDHdb+a6A8vT6XQO7/AJLoHc/yX/hQZaTL44v/AAoN/m+H8aDP | ||
244 | VUYQEBAQYRGEVgvRBWntxVYnkkf3+UW9XdByStSWKVic3+Z+TM3szM3RmWLs27YwV9W3/g2f/KdY | ||
245 | 1M27F+I/b2WGpbHjCopz4wT/AAZ+qsZk5OeIGZMIC7u/oLNleqsPPELkemvm2XBg/wA5+v8AEpzw | ||
246 | tGT0t4GzwYv818pF5RUOIwJxJnYm9WdsOtRNUo1VDKDflkfxZVCGQY52N26Ozi7/AAypCSvRuzQg | ||
247 | TdcB0x8X6f4UmSjoxZI8N7MzKC4LdFFSN6N+Kg2ZBlmQZUGUBkBAUGVQQHUVhEEBAQEBBuPoisoC | ||
248 | DVAQEGEBAQERobZZBg48siqz12dn/NBAVNnJBgqTYQcHy+m40oZW9QJ/5F6vC3Ul5PF21teQaRjF | ||
249 | vive+dVq7tn1VRh3f2QMt6OghmAXfD9fdvzZVJRyE7s2f0KwzKF/RnZsfFVELmfF2ww5+CJVEXry | ||
250 | ygi5MxZduSUWJayzEfTDCLejIiCQvZBCRt/AoIJCZgKQn+Ufb4v7KVWj2H7sqpuNy4TY7hMDN+TZ | ||
251 | /wAK+d4q6svq+DspD6jrgbLLyvY6jA2PRQY7Y/BKB2x+CUDtj8P+mEDgPw/6YQZ4N8P+mUGWFvgg | ||
252 | k9lRhBhAQEBBhEEVqXog8vtrLy2ibPyxvxFvyWohGtGTlBZhf3ByH+DDrF8OlspdK7FVMfcS/lZc | ||
253 | tRuxc44f8FhuWSJ2bDqpCnKJyu0QeshMythc69OpDXDiDZJ/qP3dbuuq5xC0yg2VEFqpDYDiY9fY | ||
254 | vdlYmiPPXKckB4fq36pfFdbbqszCq/T8ltkYkGCdnQWKU5CDh7i/y/k6kj0VUBAG+OM/ioLDeiDb | ||
255 | OX6IjLOorZkGWUBBn1QYygZw3qgZQZZ0GUB1BhAQEBAQbj6IrKAg1QEBBhAQEBBqXogy/oiMCzYQ | ||
256 | a8W5ooYthBU22vG7Qkgx8ztkP87D4W7LuWasX280UfJbIy1bEgGLiQu7EHwdfUtuq+RdZSWI5gk6 | ||
257 | s62w25O+W9EKjt8EJam2cfFEo0J2bp6uqIiz7+iIryu2X+CqSqSOzv6dGVhlC5MOW9PwQRPI7+vX | ||
258 | KCKQ8e/p6KEqVi2A9PUvZm9XUmVtiZyR0q9zZXYq0TOcsj4AG9G+L/oZefU1MHp09LF9n8d1AazW | ||
259 | QVBb5hbMr/E36u6+dfdWavq2W0ij1lCN2ZndZbdDpj1QY6fFQMt8UDLf9PyVDogz+h/+joHX4INk | ||
260 | GEGHQEBBhAQEEcru0Ru3R2F3Z/0IPFyE7m7v7+v5rcI3pycLIO/o78X/ACLos3Rg1bms6h3CaeL4 | ||
261 | f4HwuV7djqt1bK5OjWQct0QhpSj/APEO7/qt0/T0VgudQFqGG7LSNkBBVuwDLG7OpWivNShwNxf2 | ||
262 | fH8C9Fs1hzuhFlaZbV68tiVog9X9Sf0ZvioPQUdVXrPzbMkr9HMvh+DKIvi36GQb4xhvf3RWW93R | ||
263 | GW/FQZZFZ6qDKDL9GQaO+PzVGvLqiNs/9PzRWWUGzIMqDCAgICAg3H0RWUBBqgICDCAgIDoIyJ2f | ||
264 | 0ZBr3X/msoHdf+ayB3f8lA7rfzUDut/N/jVHlPKvGmvu9mtgLDfUL+hf/Vd9LW5cJyefW0ebGM3z | ||
265 | 63UnrTlHKLxyD6r3WX1jB4L7KTijG4YdDbLfzmXSLocptlOFkDb5XVozVlj9firQaE+M4REZk2Py | ||
266 | QU5pPV3ZWGZVTkz+Te6qK0kjcuroKs1wQz7YUmSFGSzKfo/Efj7rlOpudY0t6zqNLf21loKcfJ84 | ||
267 | OQnwI/i7rhfqUzerT0q4Q+q+NeJ09JG5A/etGzNJM/T82ZvgvHfqTc92npRa9LVB3lAeP1Z6/kub | ||
268 | o9BBEwi3RFSoMszIMuyDCDHVAZBugIMIMOgICDGEBAQRzDzhkFvUhJm/SyDxR56P+h/zW0aM756e | ||
269 | rf4ElV2oeNln2lbLf9Zs/wAq43Rg3bm64vj8lxdWzoN6wYIi+PRWElcFluGW7MqjKAg5232AVYmZ | ||
270 | sPKf0D/hdItqVo8yUpm7kZZIny7/ABdd7Yo5zNWHfCqO7o6hRwlNI2Ck+ln+Deig7As+FBszKjb3 | ||
271 | /ldEZZlBsis4/iUGUBBoRKjXq79fX4KozxUGzMissoNkBBh1AQEBAQbj6IrKAg1QEBBhAQEGERqT | ||
272 | IqN2QYwoMYQEBBqYMTIORtdLUuDxniY/g/u35P6rdt8xkzdZF2bxW28OswuR08yB/wDo3+peqzxE | ||
273 | Tm8d/hpj3XmJ608EjhKBRG3s7OK9Nt+55brNkwx9zKA4+pvx9VuL97nNm5pJsMeziukTDndVo94c | ||
274 | O2WyrRKq0tsHbqTN+lKJVz577dWF+X4N1Um6Fi2ZU5J5jbP0N/GuU6m50t0tsta9SxZlaKtEc8xP | ||
275 | jiLOTrndfvd7bNz2vj/7tppXGbcE4R+rVgfBP/nP1Xlv19z16fh/ifQNdqqlKAa9OEYYh/VFv5X9 | ||
276 | 155mZzeqLYjJ1IaTu2XZRVuGBhsQdPd/5EZdZ2wyNMIMj6IDoMOgwiDeqK3RBBhFYdAQEBBhAQEg | ||
277 | eIsNxlNm9OT4/h6LcIib1b+BFSxlxlgk/mlxf/B/Kud0NQ7rOvO7M9XdmZIWVyIOIs38K1DMpxZa | ||
278 | ZbqoyqNTIRFyJ8CzZd/wUkeOu2XtWTmd3bL4FvgLei7Ww5zKB+i0i/Q09myQmbduDLO7l6k34MoP | ||
279 | TBGIthvZBJjogy3x/gQZZQZ9Onv7oNmUBBlBqZsP5qiPL/myqM9PZBlnb/GoNmd0VlQZZBlQHQYQ | ||
280 | EBAQbj6IrKAg1QEBBhAQEGERh0Vo7INXZAwgIMYQYQYcWdQQS1mJnVHLv6avYBwljYxf4szq23TG | ||
281 | TN1sTm8nsvBBdyKpLxf2A26fwsvRb4mdrz3eGjY83e8X3MGWKDuM36wPn+XC7261svPdoXQ4dmha | ||
282 | iLEkBi/+a7/yLpF0b3KbJ3Kv2NmQuMdcyL8Af/CpN/FYs4LdXxHyC07NHWcBf9aR2Fv4srnOrbDp | ||
283 | bo3Tseh1X7sW5c9lY5fCKJun6Xf/ABLjd4jc72eG3vba3TU6MAw1IRjAemWZmd/zwvPN0zm9NtsR | ||
284 | k6kNIiUadCvQwzZZBd7LCCCu7M1iD8y/kVZ2rz+ijTDojI+iKy6DV0RhBlvVFbIgisIMICAgIMIC | ||
285 | Ag8puKZV7Rvj+jN+QP8An1x+haqUc13wqJM9Hb4tlvzbqszCxLsVphkjZ2deeYd4XK48i5P6N/Kp | ||
286 | BK2LKwylFaRsqgqOT5BceKs0IP8APN6/5reqtsMzLzmWf8H+C6sJ6FYrNsI8ZFvmP8mQerjZxx8F | ||
287 | BOLfwMgz7oH8iDZkGUGVBlBGcjC/FurqxA14s/X+NVGMO3+FkGWJkB+nog29WUVszqDZkGVAdBhA | ||
288 | QEBBuPoisoCDVAQEGEBAQYdEYdFYdkGrsgxhAQEGMIMIGEGHBn9UEMlUC9kFWTXi/sgqSa0X9QZ/ | ||
289 | zZnQQ/s4RfIgzfiwsyVSjcaRP8UVPHr39cILsNAWx0QXI64C3oglYWb0QYk+h0RTf/zEH+c/8irK | ||
290 | 6/oo2w6Iy3oisug1QYRGW9UVsiCKwgwgICDCAgwgyyCnfiCRmYxYhJsOzrF00lu3Fw7Ol6u8J4b+ | ||
291 | aX+NI1Fmxz5YZIvkkbBN8PgukTViYom1sxMfa9Xd8MuV8OlsvSRBxFh+H8qwqYWVgSMqyytDUzYR | ||
292 | cnfDN1d/wZQeQ2Ft7Vo5f1fQG/yW9F2tjBiZVSdlWXpfH6ghSad2/pJny7v8GfDKDqY9kG3p0Qbe | ||
293 | 35oCDP4INmUBAd8MgrkOCz8VpGweuP4FBszoMOzKg3wRWRfOVEbCitmUGyAoMICAgINx9EVlAQao | ||
294 | CAgwgICDDogisIMOyDCDCBhBhAQYwgYQZwgccoMPGLoNeyPwZBloRb2QSMDMg2ZsIMogg1k+h0FM | ||
295 | v6+D/Of+R1Wdq6/oo2wiMt6IrLoNVRhRGW9UVsiCKwgwgwgygwgwgINZJBBsv1z6MpMrEOfanPPM | ||
296 | vpb1b4MuV0u1sNc8myyzRXK2kfUT/Q66acsakMaOtztFM/pE2G/zn/xLWpLNr0IsuTaRmWkbsqg6 | ||
297 | DjeQX+3C1YH+eX6/wD/6rdsJMudrNTLaxJJmOv8AH3L/ADf8a6ObuHq6nZEBiBhB+RM45d2/P1QW | ||
298 | w6C2McW6fL6N+hBI2PX+BQFUG9VFbfigMg2woDvhkGj/ABVRgmz/AIFRoPq6Df2yorHtj4KoIp+s | ||
299 | ojLP1RWzOg3ZQZ9lBrl1FMqh1QMug3D0/Sg2QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA | ||
300 | QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA | ||
301 | QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA | ||
302 | QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA | ||
303 | QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA | ||
304 | QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA | ||
305 | QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA | ||
306 | QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA | ||
307 | QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA | ||
308 | QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA | ||
309 | QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA | ||
310 | QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA | ||
311 | QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA | ||
312 | QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA | ||
313 | QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA | ||
314 | QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA | ||
315 | QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA | ||
316 | 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 | ||
484 | AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgION5nsL2u8V2l6g/G3Xrmc | ||
485 | R45cMN1k4v68GyWPwUwmYicIm62J6JmInuWK40is0mkb5phHXLx/kwjrquz19C/Zu07vjuxtWgs2 | ||
486 | JLeCjABhmE5SNwaRpDbiLsL46N0V1MromKcs29VZnDu27uLWh71k1xm7tjfwph2tNtJd00FiGnet | ||
487 | 8bfjF66by2JZHCzVGFo5YeRYhfEz9I+LenToumvnqcLre+bq+aHLwsf0p34T2W+XnN1sdx48xy62 | ||
488 | 3ZszWtBZvSNamksM1iucAtMAydwY8DOTkICwvj6VdSI5r4yiL7I6IuuuiceiOpnSmZtsuzmYu66W | ||
489 | xMd+HGubt+JU9/X2wzTzwfsu1UeRoW2tnaSSysYOE8b2YYe2HEiYmB+PUejJdSImJzrGzLOvHdnu | ||
490 | IrNJ8p9H7XK8mtWNLutvqKhPHL5dHC+rJv1bhENS0Tf5kJRy/od1zssi+OScou/kmt135buu6HW6 | ||
491 | 7kmNThT+KPd7a06LUVvlU1HkloL9ija8ZJq2mpxTyRwxxw14yrCdcSaOfvkX/wBwSznA4wt88zy3 | ||
492 | Zzffj+OlOHs0nfjXczZZETyThbbbGP8ADWbuqa8PZ6Vye92rXl+z2dq+0GreLs1q1iSNou7Qi59s | ||
493 | GIQzykd255ES+bo/VZiPZimMzfNvfbTy3LF2MTOyyLp6pvr5striW9hvtZftUCmlpwsOqtFD+07G | ||
494 | xkjaTYhHIRzTsJxscb4IGJwx+lb06TdSdl8Rlvtuw44xGbN2Vd9l89lMeGc5eh293a3F7yPyHXan | ||
495 | YM0kFfU4qlZKAXI5rBTwhIHJ4ZZohZuQjy9PwdYs92JnH257OS3zTjTpavwmNnsf6vKHm9luNpBs | ||
496 | aBaitdju0G2cF+G5O9+esDDSOc60hnL9w4RnzASP6untxWraYzM+zNueX36eeM8cMcUpNKRHtRfF | ||
497 | I2TPJdMfsw6s3U8kvxyUrRaK1bsNqNVFabZzbexVhYZBkOGZmBpfupC45LujwfDD8Vi+Zt5rqUpd | ||
498 | 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 | ||
680 | ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICDiec//AArf/wD463/3BrnqZdnnddD+ | ||
681 | pb0w8/O3llX93t2zNsaxhHpjOo1KrNWsRyNBkC7z2p8uLN7A3Xr0Xp8RMRfNfj/1YuHg4rFm6keZ | ||
682 | X8u3lkLUkdO+YkPi2zuOEMzs7SN2OzPgX+pvm4H+eFjUin1OF1nnur6GvD4/SrtntwhZCmUu41On | ||
683 | tX7wUblCW/LI1yxHLYtD2QcWmAwMBACc+3G4i+c4XS+I57/lpTrm6s8aUiMcq9DlZdM2WT8WfVEU | ||
684 | jrxnfPL0ut4bugm01YLt4ZppbNytr5ZjFpbUVaeQYzH07hdoGJ3Fuv1LE4xE7ZtiZ7se/vdJik3b | ||
685 | oup9nVNY6npFhRAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ | ||
686 | EBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBBpPBBYgkgnjGaCUXCWKRmIDAmwQkL9HZ29WdJh | ||
687 | YmmMObrvEvFdZO9jXaajSsOLg81etDEbgXqPIBF8P8FZumYozyxWrev4x43WjKKtqacEZhJEYR14 | ||
688 | gEo5sd0HYRbIycW5N746pMzMU8sF212pr+m1GxqjU2FGvcqA7OFexEEsbOLYZ2A2cWwpM412kYRS | ||
689 | Mkj67XuVY3qwuVLP2Zdscw5Hg/afHyZD5fl9uitZrXalMKbFhRRAQEBAQEBAQEBAQEBAQEBAQEBA | ||
690 | QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA | ||
691 | QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA | ||
692 | QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA | ||
693 | QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA | ||
694 | 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 | ||
834 | 0bkDbJqkdTZ3ZTijEasgQyWP/CyGfIjL5my4ceTlhlrTnGZjPl3fPuyypE8eLN9s8tNnNG35Lq49 | ||
835 | VY3Y0b2r22raDZbSK5Odo95PrnkntyxQQVD2Hbdmw0oRszNxaXtkQM/TDLFkVjTj4ox405qds0jj | ||
836 | ludL5pN8/Dy067bK9lZnvpnWeCPybVb7VQXLQwa+xsYWClHsrOxkHnUtcmlmsxwyPHIYA4AXJuTP | ||
837 | hbtmJmm2l2z93vjGd9GJieWZ/d/Nn30bQbq5L5IbR35JKY+TvUdhlJ42jbUs/Z6Px49/rx9Of4qa | ||
838 | cYW8bb/zzTu7l1MJu4fT9FUU+yuXtvPWi2Vhqp+UjScq85jiFtWxHAJC/wAo91nyzehdehdVNOKx | ||
839 | ZXbbqfmup9i3zSb+EaffNtW0Fu8+0fxqS9aDVftuWn9y9iX7nsjrwtx1/unLvfNKb/Nz5YbjlW2O | ||
840 | aImfhvnri/lj+XzY7Wb55ZmI32dVbce+Ij+Lodrw7YVarbuGzsikrQ7gqVKS5ZKZ8vDCwQDJMRET | ||
841 | 8ydmHOc/ipnZbvnm66XXeiOwmKX3bo5fy2+l65YaEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ | ||
842 | EBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBBpPBBYgkgnjGaCUXCWKRmIDAmwQkL9HZ29WdJh | ||
843 | YmmMIj1uukKsUlWEyp5+0Ioxd4cjwft5b5Pl6dPZWZrNdssxFIpsV6njvj9OCzXqaypXr3Mtbhig | ||
844 | jAJmJnZ+4IizHlnduqkzWKbFjCa7Vn7Cj3Hk+3i7jxdhz4Dnst17ecfR1+n0Sca1258SMKU2ZcPK | ||
845 | jiTeDaGzvW2VunVs14qcFOpRlrRmEPYkkMTj5ZYeknFmYWxhatvmKztma1SYikRsivfTzUdXY6TS | ||
846 | 7PtftKhWvdh+UH3MMcvAviHNi4v09lImk1jNdlNjYtRqT2AbI6UBbGMHijuvEDzCD5yDSY5sPX0y | ||
847 | pGFeOfEmMuGSGt4147VjmiraqnBFYMZbAR14gGSSMuQGbCLMRCTZZ39HVrOHDImMZnesTazWzvYe | ||
848 | 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 | ||
1072 | uuiminiqwxzwRfbwyjGLGEOWftCTNlgyLfK3TorWceKUwpuWFFEBAQEBAQEBAQEBAQEBAQEBAQEB | ||
1073 | AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB | ||
1074 | AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB | ||
1075 | AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB | ||
1076 | AQEBAQEBAQEBAQEBAQEBAQEBBX2Owqa6hYv3JGiqVYymnlfL8QBuRPhuvoykysRWXJi8qleGeezp | ||
1077 | b9KvFAdkJrL1AjMI25O3NrBDE7t1/puH44w61dFM/L0Jb7UxTb5dPcvyb/SQ2KtWxfrV7lwRKrUl | ||
1078 | miGWTl6cA5ZP/q5V5ZrMRsZi7CJ2Ss3b1KjWO1dsR1asTZlnmMY4xb0yRE7MyzVuIVD8l8cCpFcP | ||
1079 | a0xqTAUkNh7ETRmEbsJkJ8uLiLkzO7emVeWa02+vJImsVhuO+0ZSyQjsapSwjJJLG00bkAQlwlIm | ||
1080 | zlmAvlJ39H9VNldh6f2oYvJtNZjqy0LUF+vbsfahPWnrnG0nAjxl5B5PgfpDkX4Yy7XlmtOFexKx | ||
1081 | SZ3fsVdz5jqaNK1NVnr37FKevBbqRThziexOEGZGHm4ceecO3XCWxWbd100qs4V3xbM9kVdGpu9L | ||
1082 | cqzW6d+tZq13IZ7EM0ZxxuDZJjMXcR4t65UnCKzkRjNNqKLybxuamN2La05KZG8Q2QsRFE8gi5uD | ||
1083 | GxceTCLu7Z9FZiYzIxyW6GwobCqFuhZit1ZM9uxAYyxlh8PgwdxfDpMTGaRMS1/ams7Pf+7h7Pd+ | ||
1084 | 37vcDj3ufa7XLOOfc+Tj68unqkRlxWcK8EIeQaE7wUA2VUr0jmIVGnjeUiid2kZo+XJ3Bxdi6dEi | ||
1085 | K5E4Zsx77Ry7I9XFsap7OPLyURmjecWZsvmJn5t/AkRWKxkTNJpKz93U78lfvR9+IGllh5NzGMnd | ||
1086 | hMhzlhdxfD/g6lcK7Fp3qNjyjxqtFHNY21KGKUI5YpJLEQiUcue2Yu5MzifF+L+/sryzWm1K4V2K | ||
1087 | 5+ZeOx+Sf2dluxRbJ4opowkliHuPMRCMYM58yk+Xlx4+js/ults3Vps/b3bUumLaV+99nnrh0Sth | ||
1088 | 5F4+c9qANnUKeiJSXomnjc4AD6ilHlkGH3clNldi0xptbRb3STEIxbCtIRzPWARmjJ3nYO48TYfq | ||
1089 | fD5uPrjqryz5cM+xKx5u/Lt2LyiiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC | ||
1090 | AgICAgICAgICAgICAgICAgICAgICAgICAgp7mOrLqrcVuqV2rJEQT1AHmUgE2CFhy2css3RE5tWz | ||
1091 | MTg+eXK20OltaOgfb2dGeovR2KmzgsM4WHiYa0dV7cYWpCL5mdmchxj3wt31m2ebhTfWuOWym80a | ||
1092 | RfZTD2sd1P20yRj47dkl2ut21jcwR7Y4jgjoVa8sM0X28YAJWDqzFXkiKNx/pJQZuhMu3NHNhnbf | ||
1093 | M/zTdE8cKccKbnCyJi2K5TZEfy0mKdNZ3Y76vTeeUNjMekt15LYVKFspbhUYorFkGKE4wlCGWKw0 | ||
1094 | nAi6sMblh8t6LjZNL6z8Mx11j0RMfZV0mPYpG+OyK+mk9W+jl6XQDF5XqdhCOwuV5v2lamubGuEJ | ||
1095 | DNMFUGfthDX7PPtlhijEnfk/XOVu2aVjCPY/119KXYxX5o7rbo9XcgtaHbSeKbMK9eeGZ/IJr08c | ||
1096 | McbWJqwXnk5RjOJBI/BmIGIXYsYWbJpGnXZGPD3vTMTv2w3fFZviNsW0/DZWOvGN2/ahtaGzat09 | ||
1097 | jrrG2uWrV+EZ7mwqjV7XZqWwCTshXqGzCUosUhhh/lZn6KXRNKRSK239s2xHoS2YznGnL2RfbPrT | ||
1098 | WKr2f3f1NGGnsjsNf+z4bcB1ZOLOFqDv8JHHhMJcCMijcmx1LC63XROrbfHu80dUbursYtibbLrZ | ||
1099 | xu5buueWcevtxZ8u0e5s7ndHRhsDWcNLPI9eMHKYa1icpxhaYThkkAGAuLs+cM2OrLnpzSImf7l0 | ||
1100 | 9tkRE/i82GMNXxXCPgp/NWnXFY68cGv7CazttZtIS2uzebaVSuTbKoFZhGtWtMJ9ka1Qm4vILPIY | ||
1101 | Yf5WYui3ZPLdGUR7c9c2xHoScbZ30tj+eJ9b0/ilWzX2Xk3dhOGGbaPLWchcRMCqV+RhlvmZ5GLq | ||
1102 | 3vlYj+nbH7357lu9+Z4W+Z5axFsIta2kbXXZLgeSRXDkCvK8DVj2jWWmabHbIe2/zMLuQ/rMzM7r | ||
1103 | WjnZPw2zE9PJdHl6zWyv+aIp/KmfT3h0U3CjKNovKht9IiaR4f2oLvN0bPDs5fl6cfwU0p/p12W3 | ||
1104 | flv+w1cfqcYtp1RZ6YWNBFfp+Rw0deNqxqHs2rFuDY0DhemUvcNzr3XGMJecp8eLOZcS+rDJpzW3 | ||
1105 | HZbERv2YTHR5jU97DObsd3T5b9i9tTsa7yzYWypWrMOx1kNeo9WCSZnngknIozIGcYstMOCkcR/F | ||
1106 | cborp327Zx/lp5bdzrbPtWTsivnhyPDtJcjep95QkBx8UoUyeaImxKzy92H5m6G2W5B6+mV38XNY | ||
1107 | 1qbbsOOEuXh8J067Ju89rXxqttqRatrNe3BPe8ao0IrH280nZuQ9xyGdwEuy491nzJhvXqr4mOad | ||
1108 | WLc7prHZdtTRpbGnMxhbN1eubaeZyg8ctTeKfZznu5dvqdZbiDWyVII6wTSU5ICGOeKrF9wJkXys | ||
1109 | ExkT8XJvVTWuia3W7cONKxNKcKbqbmtCJi62Lpyuiftr1znNccX0FhpVKugrzaqSwbFHHWKOuJjT | ||
1110 | kaAv6U3fHZHDOHJvd8e6upNdWafNj6OvvcdOKaVtY+HDjv6ncXJ2EBAQEBAQEBAQEBAQEBAQEBAQ | ||
1111 | EBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ | ||
1112 | EBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ | ||
1113 | EBAQEBAQEBAQEBAQEHLk8m00E1kLdqGpHWk7Tzzz1xApBi70gt/SOQlGHzExiL46+nVemPCakxE2 | ||
1114 | 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 | ||
1156 | JnmtrdH71JiJxzzrtq+s0Pt/sa/2wFFW7QdmMwKIhDi3EXjNhMHZv1SZnZfmNSvNPNjNenv2uunE | ||
1157 | RbERknWGxAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE | ||
1158 | BAQEBAQEBAQEBAQEFbZavWbSlJQ2dSG9Rm496rZjCaI+JMY8gNiF8ELO2W9V00ta/Tui6yZtujbE | ||
1159 | 0nthJiubhf3Yfu1/5S03/D6v+jXt/WPGf3tX8d3rZ+nbug/uw/dr/wApab/h9X/Rp+seM/vav47v | ||
1160 | WfTt3Qf3Yfu1/wCUtN/w+r/o0/WPGf3tX8d3rPp27oP7sP3a/wDKWm/4fV/0afrHjP72r+O71n07 | ||
1161 | d0H92H7tf+UtN/w+r/o0/WPGf3tX8d3rPp27oP7sP3a/8pab/h9X/Rp+seM/vav47vWfTt3QP+6/ | ||
1162 | 92js7P4npsP06a+q38kafrHjP72r+O71n07d0Iav7pf3X1gIY/FNUTE+X7tSGZ8/g8gm7Ld/+b8b | ||
1163 | dnran4pjzH0rdyb+7D92v/KWm/4fV/0ax+seM/vav47vWfTt3Qf3Yfu1/wCUtN/w+r/o0/WPGf3t | ||
1164 | X8d3rPp27oP7sP3a/wDKWm/4fV/0afrHjP72r+O71n07d0H92H7tf+UtN/w+r/o0/WPGf3tX8d3r | ||
1165 | Pp27oP7sP3a/8pab/h9X/Rp+seM/vav47vWfTt3Qf3Yfu1/5S03/AA+r/o0/WPGf3tX8d3rPp27o | ||
1166 | P7sP3a/8pab/AIfV/wBGn6x4z+9q/ju9Z9O3dB/dh+7X/lLTf8Pq/wCjT9Y8Z/e1fx3es+nbuhf0 | ||
1167 | /h3iGlsla02j1+stGDxHPTqwwSPG7sTg5RiLuLuLPj8Fx1/H6+tHLqal98Z0uumfPKxbEZQ668jQ | ||
1168 | gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA | ||
1169 | gICAgICAgqjttUQAY3IHCScqoE0oOxWAJxKFnz1kEgJnH1yzrp9G/wCGcq5bN/RxJmnV5emGo7rT | ||
1170 | FLahG/XKWgPO9G0oOUAdfmlbOQb5C6l8HVnQ1KRPLNLssM+jeRjNNvl647QtzqAsVq5Xq42Lrcqc | ||
1171 | LygxzNxcsxDnJtxF3+X2SNDUmJnlmlueGXTuZ5opE1z8vTHa3baax2y1uF2ab7V37gf1+cdn1/rM | ||
1172 | 9OPqp9G/dOVctm/o4rMxHV6cvPHaj2m70uoiCba7Ctr4pTaOOS1NHCJG/oIvI4s7/gtaPh9TVmll | ||
1173 | t10xuiZ8xM0is5OZqPNtNd17Xrdivr4ZpZ2pd+xGLzV4p/twsDy4/LKXFx9fqHr1Xo1vAall3LbE | ||
1174 | 3TERWkThM283L0x6JZ+pGOOEV/l97snDv2uuO01hRRTDcgeKeV4IJGkBxOVncXjB84I+QE3FuvR1 | ||
1175 | 5fo31mKTWIrls39DXNHZ66efDpVv7TeN/tMtV+1af7UF2EqH3EX3DOX0s8XLnl/boun/ANTW5Ofk | ||
1176 | u5PipNO3Im6IwlvV8g0NvYz6yrsqtjZVmzZpRTxnPG2cfPGJOY9fiyl/htW2yL7rbosnKaTSeiSb | ||
1177 | oiabTa7/AEWoaJ9tsquvacuED25o4GMv5odwh5P19GU0fDaurX6dt11M6RM+YmYiKzk52p898T2u | ||
1178 | 62Wmo7KvLf1T4sxDNC7vgWKRwFjc3GLkwmXHDF09WdejW/xuvpadupdbMW35YT1bKY7N8YpzRzcu | ||
1179 | 39uHTgvVPJfHLlGe/U2tOzRqu42bcViI4onH6mkkEnEXb3y643+E1bLotusui67KJiaz0Qc8Y45Z | ||
1180 | 8GtryvxepUe5a3FGvUaV672JbMIRtML4KLmRMPNvcfVWzwetddy22XTdStItmtN/RxXmjHgvT3ad | ||
1181 | eodyxPHDUAe4diQxGMQxnk5u7CzfiuNundddyxEzdu2nNFK7EFHd6XYPZahfrW3pn27jQTRydk2b | ||
1182 | PGTg78Hx7Et6nh9SynNbdbzZViYr0byJrNNrjbb95Xg+t057c9zTsUgsBU517MBs88hMLR8ubAxC | ||
1183 | z8iyXQWd36MvXo/4nxOpqRpxZdF1K42zlvy6uM4JN0Umfh8qdM7HoadypdqxW6c8dmpODSQWISGS | ||
1184 | MwJsiQGLuJM7ejsvDqad1l023RMXRnE5rExOSVYUQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA | ||
1185 | QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQfH6+m8obU6vZx6yb7+LdWS1tKYH | ||
1186 | /ovvJ7MklywOP6MXOQer9Wjbp1NxX6m7X0ee6yb45Z0reaY28ttkRbbvnCf4uFtXGYml9fapfWOP | ||
1187 | t4dXLSInZ7U5UWLvht4bfkuvgqWD1oamAZrZA7lfsRjZn7YiLZkeSxZeSbDYd8D15Ozc9Px1vLpX | ||
1188 | zMc/1JpHwR7FteFLbaW9uyG9O2fqxWaxMRX8V3N2+zHC3D4Up6DyOSz4xsGpShubk5STTOLOOurx | ||
1189 | 0Za9cZXfLM8QWDPh15Sk7fT1aR4jSiNWzmj6dsfjmb7brqdPLEV2WRE5uFsXcls7ptinCLbs8vvU | ||
1190 | uu/DnEJ/HdbY081izZ1V2XXanaTwaanFC8k8012ftFedicfkCKT+tJ/R5SfPR35+J1Y1YiIvti+/ | ||
1191 | TibpmaREWxXk6ZmPdjdZDrdbSbt0Uu/enljt9c7OVJtru4jt+VlHrLtjyC0ceu0krVJTrxU5o444 | ||
1192 | 5Rn49ngM8hyzMxcunVsCymjp6c26VbrY0ordf7UVm6JmZimdeWIttwpunFZvm2+bqV5Y9njhXqmb | ||
1193 | sJ4RGaSppb2po+SbTV6wyt6jXjp/F6xA7SFBRruTPExMz/01g3bp9TAP4LOpr26t2nZfd7Opfz6k | ||
1194 | 8brtvRbHVWTR04tuiJxiyIjpnOZ68LZrtiXLvQ7UtRTq+Pa++NbxfTST66xYqywyzX5onqxFHFMw | ||
1195 | SFLFH3yISFnciH4r02XWfUuu1brK62rEXRF0TEWRPNNZjCkzyxExOUTuYtm7kikVuxumuFbojCJ/ | ||
1196 | eumu7DDhbafU2NjSipa/YBo/GKJTNwpWBuPav5rBIEJg1h5I4e8cmY+T8mLr78uW+2y6brrPqa11 | ||
1197 | Pet5eW32piZieWkzyxGNMKYLb92Ixp7c14ViK76zMz0wueF6HyAtTJrm2dqpqqIQQ6Da/YwU9l2W | ||
1198 | H+mjlhtwSiwZEG5FABE7P09Hfl4/xOl9Tn5Lbr7qzfbzzdZXZMTbdHH70xHcacTGEe7TbnWs16cN | ||
1199 | vGVeWvty8s7+pkv3LBtX12+rbfXca01OsUnOeK00deHlIxk7DE5C5O2QFs43F1n0KXxZbGN1k2X+ | ||
1200 | 1F00pE21unCmd1JpHvTtt0TExSfaikcKVxyymk9dIzVKB2S8V0Ut/W7D7Wzs5bnllT7G0c7TSDJY | ||
1201 | GJ4Gi7s0AWCjDlGBC4i36uV11LbY174tusrbpxbpzz20pFLa1rS26beaaTMTWd7M1mLsM7sf3dlN | ||
1202 | +EW2zStba5rl/QX9zvClsa+SGhv7lQLcJg7M2t1InOD2GZnESs2DYOBde30fqzi3LS8TbpadIuib | ||
1203 | tK26Y/f1KW+z+7bjWPvZbJm3xWsxti2zqrN108ImK2+qrWfVbS75lugtXt1rJZXCrrI6FKrNTPXj | ||
1204 | CD9LVmnZiiIpSk5g8oO+G+V8MrbrWWeHspbpXxndzXXRdz1n7tt9szhSk8s9OazMxfXKlOWnf0TX | ||
1205 | DZhEL1imO00ui8aqay7Bpo7rVbn30Tg70tS7uJH65CzLDGI8scwd3xhcbdT6epqa111s6nLzRyz9 | ||
1206 | 7U9NsTNae7MJFnJp/Ttrst6ts9FIm2u+cN7h+R6va2rdzYyQX6WrvbWOpckoVO/bHXayCRq7/anD | ||
1207 | ZeSOS65F0hLIOPTj1Xr8JrWW222RNl19unN0c11Lee+Yr7XNbSYs+aPartNW2azSPht/hxumY655 | ||
1208 | ZjGsRK4OkOr5ho7lqTb7OveN7E+wtVQd2mqRvDRhlio1oBhD/wAZLJzmAcODcnbouf8A9jm0NS22 | ||
1209 | NOybYpFsXbLprfMTfdPNPsWxS2ZwnDazdbldjNZiJw2W1ujClfemOzc+mL889AgICAgICAgICAgI | ||
1210 | CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgrbLV6 | ||
1211 | zaUpKGzqQ3qM3HvVbMYTRHxJjHkBsQvghZ2y3qumlrX6d0XWTNt0bYmk9sJMVzcL+7D92v8Aylpv | ||
1212 | +H1f9Gvb+seM/vav47vWz9O3dB/dh+7X/lLTf8Pq/wCjT9Y8Z/e1fx3es+nbug/uw/dr/wApab/h | ||
1213 | 9X/Rp+seM/vav47vWfTt3Qf3Yfu1/wCUtN/w+r/o0/WPGf3tX8d3rPp27oP7sP3a/wDKWm/4fV/0 | ||
1214 | afrHjP72r+O71n07d0H92H7tf+UtN/w+r/o0/WPGf3tX8d3rPp27oR2P3UfuxsRPFJ4pqRF/V46U | ||
1215 | ERdP8qMBL+Nat/zXjLZrGtqfiun0n07d0Nov3WfuzijGMfE9O4i2Gc6NYy/SRA5P+l1J/wAz4yZr | ||
1216 | 9bU/Hd6yNO3c2/uw/dr/AMpab/h9X/RqfrHjP72r+O71n07d0H92H7tf+UtN/wAPq/6NP1jxn97V | ||
1217 | /Hd6z6du6D+7D92v/KWm/wCH1f8ARp+seM/vav47vWfTt3Qf3Yfu1/5S03/D6v8Ao0/WPGf3tX8d | ||
1218 | 3rPp27oP7sP3a/8AKWm/4fV/0afrHjP72r+O71n07d0H92H7tf8AlLTf8Pq/6NP1jxn97V/Hd6z6 | ||
1219 | du6D+7D92v8Aylpv+H1f9Gn6x4z+9q/ju9Z9O3dCWr+7r931SzDaq+MamvarmMsE8VGsEkcgPyEw | ||
1220 | IQZxIXbLOyzf/lfFXRNt2rqTE4TE33Y968lu56FeBoQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE | ||
1221 | BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE | ||
1222 | BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE | ||
1223 | BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE | ||
1224 | BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE | ||
1225 | BAQEBAQEHwOx5Z5NPMcpbS0Lm7u4xzGAtn2YRdmZl+ijw+nEU5YfBnXvmc5R/wBpvJP/AHa5/wCo | ||
1226 | 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 | ||
1268 | 7M5dwc/wL6cf5KaY29750/4+K4Sj/ub/AN7/AOz/AOtV/Uvl7/sT9P8Am7vtP7m/97/7P/rU/Uvl | ||
1269 | 7/sP0/5u77T+5v8A3v8A7P8A61P1L5e/7D9P+bu+0/ub/wB7/wCz/wCtT9S+Xv8AsP0/5u77T+5v | ||
1270 | /e/+z/61P1L5e/7D9P8Am7vtP7m/97/7P/rU/Uvl7/sP0/5u77T+5v8A3v8A7P8A61P1L5e/7D9P | ||
1271 | +bu+0/ub/wB7/wCz/wCtT9S+Xv8AsP0/5u77T+5v/e/+z/61P1L5e/7D9P8Am7vtP7m/97/7P/rU | ||
1272 | /Uvl7/sP0/5u77T+5v8A3v8A7P8A61P1L5e/7D9P+bu+0/ub/wB7/wCz/wCtT9S+Xv8AsP0/5u77 | ||
1273 | T+5v/e/+z/61P1L5e/7D9P8Am7vtP7m/97/7P/rU/Uvl7/sP0/5u77T+5v8A3v8A7P8A61P1L5e/ | ||
1274 | 7D9P+bu+0/ub/wB7/wCz/wCtT9S+Xv8AsP0/5u77T+5v/e/+z/61P1L5e/7D9P8Am7vtdbxj928O | ||
1275 | k2obE7z2pIhJogaPtszmLi7v8x5+V3XHX8bOpby0o7aPg4surWr2a8L2CAgICAgICAgICAgICAgI | ||
1276 | CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI | ||
1277 | CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI | ||
1278 | CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI | ||
1279 | CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI | ||
1280 | CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI | ||
1281 | CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI | ||
1282 | CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI | ||
1283 | CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI | ||
1284 | CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI | ||
1285 | CAg81e2k4bO1Bc2MmpjEhGiXaB4ZGcWdzOWQDbPJ8cWIVLcemq3YTwWp/IJ42tzQ1WsUte/C3Y7n | ||
1286 | A3cWYpHij4kxcGfrkm/BWu2cImfs86RGzOaObb2F79sSDBMZB+0K4RxvIQg4lTc+D4zgSLq/T8VL | ||
1287 | axH/AJO6PQzM17LfzJaHk9uHSUbGxaD7i5gYJDnaICwzuRyu8YjGzM36vLK3dnEcPRDUbZ4+mU1b | ||
1288 | yyW5NDWp1Yp7EhzAZDYZ4WeFgLIyiBchIZG/Vz+CzGPZXvokzTy4OrrNi9/XDaGLhI/MShd84kjJ | ||
1289 | wIeXTLch9VLpwrG6rUZ0nZLj6baTWZoQtbSSHYvkrGrlhjib0fIxcgE34+vLmSXTERMxjgzPHDFF | ||
1290 | B5pG1ODrCU/24zzfd2Y4CflniIYjZjN+P80W9Fq7Du81ViPPPno7w3pbGthu0YwkaYBlAZzeJmAm | ||
1291 | 5dSEJerN+Cl8csls1UNV5FPdsV4pagwjaGY4ZQleQSCFxFjbIRvg+XT8PzVjHsie1JmnbRQs+TMN | ||
1292 | 3W2jY44j+9h+1AnJ5ZIpBijZm+VnIn9PhlZsmteNsU65W7CZj4bvRLo/tC5PalrSMdOavUaxIMRx | ||
1293 | yNyl5Mw5OJ+ocOjt0+LOpdNIun4f2/YtsVutjf8AsQQ+SXZghatSGUyox3jeSbhhj5Nw6Rvkvl+D | ||
1294 | N+S1dNOadlv7WbMaRtmvc5m122ztzy2qeQrQ6wb1dvuJInZz5FzMABxkceGOBPx/hUuw5uExC2zW | ||
1295 | kb6+h0ZfILNIaUuxHg0leaYxgNjEu2MbjnlEBcic8MzOzN+Ptu6PauiNn/qozZNYifLKq7T2189m | ||
1296 | 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 | ||
1366 | t3AM6tgZxaOHtg7CBDjDmZZfn1fl7eiRhNenvJisU8s6oz1uxr2DelNhrt0bFmTiP9HCMbCQMxcu | ||
1367 | Tk8bNlm91LcKRsx/Z39y3Y1nbSI+1Z22pi2TVRldu1XmaY4yHkxswEDg/VvXmpTGvCe82eW9zj8U | ||
1368 | kOAK5XnKKGGzWr8o8kMVgREWIuXzdvj+lJxrXOYjumpGE4ZVqtUtLZpSm1W2wVZnE54ii5F3GFhI | ||
1369 | oz5YFj4tlnEvwVv9qvGvekRSI6PMpQeGQQ1RrDYdopIPt7zCDD3uOXjkbr8hg/v16dFZn0dsUxX7 | ||
1370 | eydi3Y02yt0nq3L4yNmJxIIOGe3IMmTyZZJ+GPl4t19Eifaid01SYwpwXIdf29pZv9zP3McUfbxj | ||
1371 | j2nN85z1zz+CkYV6fQt2Mxwj0q2x1ezuR2Kz3gGlZZxMHg5SiBNghCTmw/k7g6lK5rWmSu/i7BaK | ||
1372 | 3Vs9mwMkclYnDmwMELQEBtybmxC3xZ1qs9899PUzEYU4R3V9br1orQRO1mZppSd3cgDtg34COSfH | ||
1373 | 5k6zdFYosOTrvHLFcKMNm4M9bXZKvGEXbdzcXFiMnM88WJ8MzMrdj00okx56+lnW+OT6+WqcFsS7 | ||
1374 | Ncathjid+4Am5s44NuBfM/ryVnGsbJp3RQp5575aweN2oKtWKO6LSa83KhN2eogTOxBK3PEjOL46 | ||
1375 | cUrOE7aU83qXf01dMfvYKkhzO92x1fhCIRZ9uICZ4b/rH+lZvisUIeaqUNhXq6aD9mXS/ZBEXL/w | ||
1376 | TdzlGcfp92/H68+6t/tT/Dy+b1GyY33V75lX1ms3mqCL9nU7YSdgILXdjpyBJ2nfgYi10HAmYnb6 | ||
1377 | nb8FZmvd5oj0H2981drRRXYtlemnp2o2vPGZSz/aswvFEMfXszSO7lxz0BmSMLacZntSmNeHr9bu | ||
1378 | qKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC | ||
1379 | AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC | ||
1380 | AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC | ||
1381 | D//Z | ||
1382 | ------=_NextPart_000_000F_01D15E52.0BD654A0-- | ||
1383 | |||
1384 | |||
diff --git a/framework/src/domain/mimetreeparser/tests/data/html.mbox b/framework/src/domain/mimetreeparser/tests/data/html.mbox new file mode 100644 index 00000000..bf5c685d --- /dev/null +++ b/framework/src/domain/mimetreeparser/tests/data/html.mbox | |||
@@ -0,0 +1,15 @@ | |||
1 | From foo@example.com Thu, 26 May 2011 01:16:54 +0100 | ||
2 | From: Thomas McGuire <foo@example.com> | ||
3 | Subject: HTML test | ||
4 | Date: Thu, 26 May 2011 01:16:54 +0100 | ||
5 | Message-ID: <1501334.pROlBb7MZF@herrwackelpudding.localhost> | ||
6 | X-KMail-Transport: GMX | ||
7 | X-KMail-Fcc: 28 | ||
8 | X-KMail-Drafts: 7 | ||
9 | X-KMail-Templates: 9 | ||
10 | User-Agent: KMail/4.6 beta5 (Linux/2.6.34.7-0.7-desktop; KDE/4.6.41; x86_64; git-0269848; 2011-04-19) | ||
11 | MIME-Version: 1.0 | ||
12 | Content-Transfer-Encoding: 7Bit | ||
13 | Content-Type: text/html; charset="windows-1252" | ||
14 | |||
15 | <html><body><p><span>HTML</span> text</p></body></html> \ No newline at end of file | ||
diff --git a/framework/src/domain/mimetreeparser/tests/data/openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox b/framework/src/domain/mimetreeparser/tests/data/openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox new file mode 100644 index 00000000..2d9726ea --- /dev/null +++ b/framework/src/domain/mimetreeparser/tests/data/openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox | |||
@@ -0,0 +1,115 @@ | |||
1 | From test@kolab.org Fri May 01 15:12:47 2015 | ||
2 | From: testkey <test@kolab.org> | ||
3 | To: you@you.com | ||
4 | Subject: enc & non enc attachment | ||
5 | Date: Fri, 01 May 2015 17:12:47 +0200 | ||
6 | Message-ID: <13897561.XENKdJMSlR@tabin.local> | ||
7 | X-KMail-Identity: 1197256126 | ||
8 | User-Agent: KMail/4.13.0.1 (Linux/3.19.1-towo.1-siduction-amd64; KDE/4.14.2; x86_64; git-cd33034; 2015-04-11) | ||
9 | MIME-Version: 1.0 | ||
10 | Content-Type: multipart/mixed; boundary="nextPart1939768.sIoLGH0PD8" | ||
11 | Content-Transfer-Encoding: 7Bit | ||
12 | |||
13 | This is a multi-part message in MIME format. | ||
14 | |||
15 | --nextPart1939768.sIoLGH0PD8 | ||
16 | Content-Type: multipart/encrypted; boundary="nextPart2814166.CHKktCGlQ3"; protocol="application/pgp-encrypted" | ||
17 | |||
18 | |||
19 | --nextPart2814166.CHKktCGlQ3 | ||
20 | Content-Type: application/pgp-encrypted | ||
21 | Content-Disposition: attachment | ||
22 | Content-Transfer-Encoding: 7Bit | ||
23 | |||
24 | Version: 1 | ||
25 | --nextPart2814166.CHKktCGlQ3 | ||
26 | Content-Type: application/octet-stream | ||
27 | Content-Disposition: inline; filename="msg.asc" | ||
28 | Content-Transfer-Encoding: 7Bit | ||
29 | |||
30 | -----BEGIN PGP MESSAGE----- | ||
31 | Version: GnuPG v2 | ||
32 | |||
33 | hIwDGJlthTT7oq0BA/9cXFQ6mN9Vxnc2B9M10odS3/6z1tsIY9oJdsiOjpfxqapX | ||
34 | P7nOzR/jNWdFQanXoG1SjAcY2FeZEN0c3SkxEM6R5QVF1vMh/Xsni1clI+peZyVT | ||
35 | Z4OSU74YCfYLg+cgDnPCF3kyNPVe6Z1pnfWOCZNCG3rpApw6UVLN63ScWC6eQIUB | ||
36 | DAMMzkNap8zaOwEIANKHn1svvj+hBOIZYf8R+q2Bw7cd4xEChiJ7uQLnD98j0Fh1 | ||
37 | 85v7/8JbZx6rEDDenPp1mCciDodb0aCmi0XLuzJz2ANGTVflfq+ZA+v1pwLksWCs | ||
38 | 0YcHLEjOJzjr3KKmvu6wqnun5J2yV69K3OW3qTTGhNvcYZulqQ617pPa48+sFCgh | ||
39 | nM8TMAD0ElVEwmMtrS3AWoJz52Af+R3YzpAnX8NzV317/JG+b6e2ksl3tR7TWp1q | ||
40 | 2FOqC1sXAxuv+DIz4GgRfaK1+xYr2ckkg+H/3HJqa5LmJ7rGCyv+Epfp9u+OvdBG | ||
41 | PBvuCtO3tm0crmnttMw57Gy35BKutRf/8MpBj/nS6QFX0t7XOLeL4Me7/a2H20wz | ||
42 | HZsuRGDXMCh0lL0FYCBAwdbbYvvy0gz/5iaNvoADtaIu+VtbFNrTUN0SwuL+AIFS | ||
43 | +WIiaSbFt4Ng3t9YmqL6pqB7fjxI10S+PK0s7ABqe4pgbzUWWt1yzBcxfk8l/47Q | ||
44 | JrlvcE7HuDOhNOHfZIgUP2Dbeu+pVvHIJbmLsNWpl4s+nHhoxc9HrVhYG/MTZtQ3 | ||
45 | kkUWviegO6mwEZjQvgBxjWib7090sCxkO847b8A93mfQNHnuy2ZEEJ+9xyk7nIWs | ||
46 | 4RsiNR8pYc/SMvdocyAvQMH/qSvmn/IFJ+jHhtT8UJlXJ0bHvXTHjHMqBp6fP69z | ||
47 | Jh1ERadWQdMaTkzQ+asl+kl/x3p6RZP8MEVbZIl/3pcV+xiFCYcFu2TETKMtbW+b | ||
48 | NYOlrltFxFDvyu3WeNNp0g9k0nFpD/T1OXHRBRcbUDWE4QF6NWTm6NO9wy2UYHCi | ||
49 | 7QTSecBWgMaw7cUdwvnW6chIVoov1pm69BI9D0PoV76zCI7KzpiDsTFxdilKwbQf | ||
50 | K/PDnv9Adx3ERh0/F8llBHrj2UGsRs4aHSEBDBJIHDCp8+lqtsRcINQBKEU3qIjt | ||
51 | wf5vizdaVIgQnsD2z8QmBQ7QCCipI0ur6GKl+YWDDOSDLDUs9dK4A6xo/4Q0bsnI | ||
52 | rH63ti5HslGq6uArfFkewH2MWff/8Li3uGEqzpK5NhP5UpbArelK+QaQQP5SdsmW | ||
53 | XFwUqDS4QTCKNJXw/5SQMl8UE10l2Xaav3TkiOYTcBcvPNDovYgnMyRff/tTeFa8 | ||
54 | 83STkvpGtkULkCntp22fydv5rg6DZ7eJrYfC2oZXdM87hHhUALUO6Y/VtVmNdNYw | ||
55 | F3Uim4PDuLIKt+mFqRtFqnWm+5X/AslC31qLkjH+Fbb83TY+mC9gbIn7CZGJRCjn | ||
56 | zzzMX2h15V/VHzNUgx9V/h28T0/z25FxoozZiJxpmhOtqoxMHp+y6nXXfMoIAD1D | ||
57 | 963Pc7u1HS0ny54A7bqc6KKd4W9IF7HkXn3SoBwCyn0IOPoKQTDD8mW3lbBI6+h9 | ||
58 | vP+MAQpfD8s+3VZ9r7OKYCVmUv47ViTRlf428Co6WT7rTHjGM09tqz826fTOXA== | ||
59 | =6Eu9 | ||
60 | -----END PGP MESSAGE----- | ||
61 | |||
62 | --nextPart2814166.CHKktCGlQ3-- | ||
63 | |||
64 | --nextPart1939768.sIoLGH0PD8 | ||
65 | Content-Disposition: attachment; filename="image.png" | ||
66 | Content-Transfer-Encoding: base64 | ||
67 | Content-Type: image/png; name="image.png" | ||
68 | |||
69 | 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 | ||
112 | o/0d59+Lh3Y1HHcAAAAASUVORK5CYII= | ||
113 | |||
114 | --nextPart1939768.sIoLGH0PD8-- | ||
115 | |||
diff --git a/framework/src/domain/mimetreeparser/tests/data/openpgp-inline-charset-encrypted.mbox b/framework/src/domain/mimetreeparser/tests/data/openpgp-inline-charset-encrypted.mbox new file mode 100644 index 00000000..8bd06910 --- /dev/null +++ b/framework/src/domain/mimetreeparser/tests/data/openpgp-inline-charset-encrypted.mbox | |||
@@ -0,0 +1,40 @@ | |||
1 | From test@example.com Thu, 17 Oct 2013 02:13:03 +0200 | ||
2 | Return-Path: <test@example.com> | ||
3 | Delivered-To: you@you.com | ||
4 | Received: from localhost (localhost [127.0.0.1]) | ||
5 | by test@example.com (Postfix) with ESMTP id B30D8120030 | ||
6 | for <you@you.com>; Thu, 17 Oct 2013 02:13:05 +0200 (CEST) | ||
7 | From: test <test@example.com> | ||
8 | To: you@you.com | ||
9 | Subject: charset | ||
10 | Date: Thu, 17 Oct 2013 02:13:03 +0200 | ||
11 | Message-ID: <4081645.yGjUJ4o4Se@example.local> | ||
12 | User-Agent: KMail/4.12 pre (Linux/3.11-4.towo-siduction-amd64; KDE/4.11.2; x86_64; git-f7f14e3; 2013-10-15) | ||
13 | MIME-Version: 1.0 | ||
14 | Content-Transfer-Encoding: 7Bit | ||
15 | Content-Type: text/plain; charset="ISO-8859-15" | ||
16 | |||
17 | -----BEGIN PGP MESSAGE----- | ||
18 | Version: GnuPG v2.0.22 (GNU/Linux) | ||
19 | |||
20 | hIwDGJlthTT7oq0BBACbaRZudMigMTetPZNRgkfEXv4QQowR1jborw0dcgKKqMQ1 | ||
21 | 6o67NkpxvmXKGJTfTVCLBX3nk6FKYo6NwlPCyU7X9X0DDk8hvaBdR9wGfrdm5YWX | ||
22 | GKOzcqJY1EypiMsspXeZvjzEW7O8I956c3vBb/2pM3xqYEK1kh8+d9bVH+cjf4UB | ||
23 | DAMMzkNap8zaOwEH/1rPShyYL8meJN+/GGgS8+Nf1BW5pSHdAPCg0dnX4QCLEx7u | ||
24 | GkBU6N4JGYayaCBofibOLacQPhYZdnR5Xb/Pvrx03GrzyzyDp0WyeI9nGNfkani7 | ||
25 | sCRWbzlMPsEvGEvJVnMLNRSk4xhPIWumL4APkw+Mgi6mf+Br8z0RhfnGwyMA53Mr | ||
26 | pG9VQKlq3v7/aaN40pMjAsxiytcHS515jXrb3Ko4pWbTlAr/eytOEfkLRJgSOpQT | ||
27 | BY7lWs+UQJqiG8Yn65vS9LMDNJgX9EOGx77Z4u9wvv4ZieOxzgbHGg5kYCoae7ba | ||
28 | hxZeNjYKscH+E6epbOxM/wlTdr4UTiiW9dMsH0zSwMUB891gToeXq+LDGEPTKVSX | ||
29 | tsJm4HS/kISJBwrCI4EUqWZML6xQ427NkZGmF2z/sD3kmL66GjspIKnb4zHmXacp | ||
30 | 84n2KrI9s7p6AnKnQjsxvB/4/lpXPCIY5GH7KjySEJiMsHECzeN1dJSL6keykBsx | ||
31 | DtmYDA+dhZ6UWbwzx/78+mjNREhyp/UiSAmLzlJh89OH/xelAPvKcIosYwz4cY9N | ||
32 | wjralTmL+Y0aHKeZJOeqPLaXADcPFiZrCNPCH65Ey5GEtDpjLpEbjVbykPV9+YkK | ||
33 | 7JKW6bwMraOl5zmAoR77PWMo3IoYb9q4GuqDr1V2ZGlb7eMH1gj1nfgfVintKC1X | ||
34 | 3jFfy7aK6LIQDVKEwbi0SxVXTKStuliVUy5oX4woDOxmTEotJf1QlKZpn5oF20UP | ||
35 | tumYrp0SPoP8Bo4EVRVaLupduI5cYce1q/kFj9Iho/wk56MoG9PxMMfsH7oKg3AA | ||
36 | CqQ6/kM4oJNdN5xIf1EH5HeaNFkDy1jlLznnhwVAZKPo/9ffpg== | ||
37 | =bPqu | ||
38 | -----END PGP MESSAGE----- | ||
39 | |||
40 | |||
diff --git a/framework/src/domain/mimetreeparser/tests/data/openpgp-inline-encrypted+nonenc.mbox b/framework/src/domain/mimetreeparser/tests/data/openpgp-inline-encrypted+nonenc.mbox new file mode 100644 index 00000000..b98dc336 --- /dev/null +++ b/framework/src/domain/mimetreeparser/tests/data/openpgp-inline-encrypted+nonenc.mbox | |||
@@ -0,0 +1,31 @@ | |||
1 | From test@kolab.org Wed, 25 May 2011 23:49:40 +0100 | ||
2 | From: OpenPGP Test <test@kolab.org> | ||
3 | To: test@kolab.org | ||
4 | Subject: inlinepgpencrypted + non enc text | ||
5 | Date: Wed, 25 May 2011 23:49:40 +0100 | ||
6 | Message-ID: <1786696.yKXrOjjflF@herrwackelpudding.localhost> | ||
7 | X-KMail-Transport: GMX | ||
8 | X-KMail-Fcc: 28 | ||
9 | X-KMail-Drafts: 7 | ||
10 | X-KMail-Templates: 9 | ||
11 | User-Agent: KMail/4.6 beta5 (Linux/2.6.34.7-0.7-desktop; KDE/4.6.41; x86_64; git-0269848; 2011-04-19) | ||
12 | MIME-Version: 1.0 | ||
13 | Content-Transfer-Encoding: 7Bit | ||
14 | Content-Type: text/plain; charset="us-ascii" | ||
15 | |||
16 | Not encrypted not signed :( | ||
17 | |||
18 | -----BEGIN PGP MESSAGE----- | ||
19 | Version: GnuPG v2.0.15 (GNU/Linux) | ||
20 | |||
21 | hQEMAwzOQ1qnzNo7AQf/a3aNTLpQBfcUr+4AKsZQLj4h6z7e7a5AaCW8AG0wrbxN | ||
22 | kBYB7E5jdZh45DX/99gvoZslthWryUCX2kKZ3LtIllxKVjqNuK5hSt+SAuKkwiMR | ||
23 | Xcbf1KFKENKupgGSO9B2NJRbjoExdJ+fC3mGXnO3dT7xJJAo3oLE8Nivu+Bj1peY | ||
24 | E1wCf+vcTwVHFrA7SV8eMRb9Z9wBXmU8Q8e9ekJ7ZsRX3tMeBs6jvscVvfMf6DYY | ||
25 | N14snZBZuGNKT9a3DPny7IC1S0lHcaam34ogWwMi3FxPGJt/Lg52kARlkF5TDhcP | ||
26 | N6H0EB/iqDRjOOUoEVm8um5XOSR1FpEiAdD0DON3y9JPATnrYq7sgYZz3BVImYY+ | ||
27 | N/jV8fEiN0a34pcOq8NQedMuOsJHNBS5MtbQH/kJLq0MXBpXekGlHo4MKw0trISc | ||
28 | Rw3pW6/BFfhPJLni29g9tw== | ||
29 | =fRFW | ||
30 | -----END PGP MESSAGE----- | ||
31 | |||
diff --git a/framework/src/domain/mimetreeparser/tests/data/plaintext.mbox b/framework/src/domain/mimetreeparser/tests/data/plaintext.mbox new file mode 100644 index 00000000..d185b1c1 --- /dev/null +++ b/framework/src/domain/mimetreeparser/tests/data/plaintext.mbox | |||
@@ -0,0 +1,13 @@ | |||
1 | Return-Path: <konqi@example.org> | ||
2 | Date: Wed, 8 Jun 2016 20:34:44 -0700 | ||
3 | From: Konqi <konqi@example.org> | ||
4 | To: konqi@kde.org | ||
5 | Subject: A random subject with alternative contenttype | ||
6 | MIME-Version: 1.0 | ||
7 | Content-Type: text/plain; charset=utf-8 | ||
8 | Content-Transfer-Encoding: quoted-printable | ||
9 | |||
10 | If you can see this text it means that your email client couldn't display o= | ||
11 | ur newsletter properly. | ||
12 | Please visit this link to view the newsletter on our website: http://www.go= | ||
13 | g.com/newsletter/ | ||
diff --git a/framework/src/domain/mimetreeparser/tests/data/smime-encrypted.mbox b/framework/src/domain/mimetreeparser/tests/data/smime-encrypted.mbox new file mode 100644 index 00000000..6b6d6a0d --- /dev/null +++ b/framework/src/domain/mimetreeparser/tests/data/smime-encrypted.mbox | |||
@@ -0,0 +1,22 @@ | |||
1 | From test@example.com Sat, 13 Apr 2013 01:54:30 +0200 | ||
2 | From: test <test@example.com> | ||
3 | To: you@you.com | ||
4 | Subject: test | ||
5 | Date: Sat, 13 Apr 2013 01:54:30 +0200 | ||
6 | Message-ID: <1576646.QQxzHWx8dA@tabin> | ||
7 | X-KMail-Identity: 505942601 | ||
8 | User-Agent: KMail/4.10.2 (Linux/3.9.0-rc4-experimental-amd64; KDE/4.10.60; x86_64; git-fc9b82c; 2013-04-11) | ||
9 | MIME-Version: 1.0 | ||
10 | Content-Type: application/pkcs7-mime; name="smime.p7m"; smime-type="enveloped-data" | ||
11 | Content-Transfer-Encoding: base64 | ||
12 | Content-Disposition: attachment; filename="smime.p7m" | ||
13 | |||
14 | MIAGCSqGSIb3DQEHA6CAMIACAQAxgfwwgfkCAQAwYjBVMQswCQYDVQQGEwJVUzENMAsGA1UECgwE | ||
15 | S0RBQjEWMBQGA1UEAwwNdW5pdHRlc3QgY2VydDEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxl | ||
16 | LmNvbQIJANNFIDoYY4XJMA0GCSqGSIb3DQEBAQUABIGAJwmmaOeidXUHSQGOf2OBIsPYafVqdORe | ||
17 | y54pEXbXiAfSVUWgI4a9CsiWwcDX8vlaX9ZLLr+L2VmOfr6Yc5214yxzausZVvnUFjy6LUXotuEX | ||
18 | tSar4EW7XI9DjaZc1l985naMsTx9JUa5GyQ9J6PGqhosAKpKMGgKkFAHaOwE1/IwgAYJKoZIhvcN | ||
19 | AQcBMBQGCCqGSIb3DQMHBAieDfmz3WGbN6CABHgEpsLrNn0PAZTDUfNomDypvSCl5bQH+9cKm80m | ||
20 | upMV2r8RBiXS7OaP4SpCxq18afDTTPatvboHIoEX92taTbq8soiAgEs6raSGtEYZNvFL0IYqm7MA | ||
21 | o5HCOmjiEcInyPf14lL3HnPk10FaP3hh58qTHUh4LPYtL7UECOZELYnUfUVhAAAAAAAAAAAAAA== | ||
22 | |||
diff --git a/framework/src/domain/mimetreeparser/tests/gnupg_home/CMakeLists.txt b/framework/src/domain/mimetreeparser/tests/gnupg_home/CMakeLists.txt new file mode 100644 index 00000000..9c64a008 --- /dev/null +++ b/framework/src/domain/mimetreeparser/tests/gnupg_home/CMakeLists.txt | |||
@@ -0,0 +1,10 @@ | |||
1 | configure_file( gpg-agent.conf.in | ||
2 | "${CMAKE_CURRENT_BINARY_DIR}/gpg-agent.conf" @ONLY ) | ||
3 | |||
4 | configure_file( gpgsm.conf.in | ||
5 | "${CMAKE_CURRENT_BINARY_DIR}/gpgsm.conf" @ONLY ) | ||
6 | |||
7 | file( COPY | ||
8 | ${CMAKE_CURRENT_SOURCE_DIR} | ||
9 | DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/../" | ||
10 | ) | ||
diff --git a/framework/src/domain/mimetreeparser/tests/gnupg_home/dirmngr-cache.d/DIR.txt b/framework/src/domain/mimetreeparser/tests/gnupg_home/dirmngr-cache.d/DIR.txt new file mode 100644 index 00000000..1a45a6b3 --- /dev/null +++ b/framework/src/domain/mimetreeparser/tests/gnupg_home/dirmngr-cache.d/DIR.txt | |||
@@ -0,0 +1,3 @@ | |||
1 | v:1: | ||
2 | c:4E31CEB57DDD4A7B9991AB05507B1ED4293FF952:CN=Test-ZS 7,O=Intevation GmbH,C=DE:ldap%3A//ca.intevation.org/cn=Test-ZS 7, o=Intevation GmbH, c=DE?certificateRevocationList:20100615T181523:20100707T181523:72FEF3FD88455A1D4C6796A6499D4422:::: | ||
3 | c:7F2A402CBB016A9146D613568C89D3596A4111AA:CN=Wurzel ZS 3,O=Intevation GmbH,C=DE:ldap%3A//ca.intevation.org/cn=Wurzel ZS 3, o=Intevation GmbH, c=DE?certificateRevocationList:20100625T102134:20100814T102134:44E60EEC02EF2FBF7A5C77E9BD565667:::: | ||
diff --git a/framework/src/domain/mimetreeparser/tests/gnupg_home/dirmngr-cache.d/crl-4E31CEB57DDD4A7B9991AB05507B1ED4293FF952.db b/framework/src/domain/mimetreeparser/tests/gnupg_home/dirmngr-cache.d/crl-4E31CEB57DDD4A7B9991AB05507B1ED4293FF952.db new file mode 100644 index 00000000..0b7e2dd4 --- /dev/null +++ b/framework/src/domain/mimetreeparser/tests/gnupg_home/dirmngr-cache.d/crl-4E31CEB57DDD4A7B9991AB05507B1ED4293FF952.db | |||
Binary files differ | |||
diff --git a/framework/src/domain/mimetreeparser/tests/gnupg_home/dirmngr-cache.d/crl-7F2A402CBB016A9146D613568C89D3596A4111AA.db b/framework/src/domain/mimetreeparser/tests/gnupg_home/dirmngr-cache.d/crl-7F2A402CBB016A9146D613568C89D3596A4111AA.db new file mode 100644 index 00000000..47474a26 --- /dev/null +++ b/framework/src/domain/mimetreeparser/tests/gnupg_home/dirmngr-cache.d/crl-7F2A402CBB016A9146D613568C89D3596A4111AA.db | |||
Binary files differ | |||
diff --git a/framework/src/domain/mimetreeparser/tests/gnupg_home/dirmngr.conf b/framework/src/domain/mimetreeparser/tests/gnupg_home/dirmngr.conf new file mode 100644 index 00000000..a17a0354 --- /dev/null +++ b/framework/src/domain/mimetreeparser/tests/gnupg_home/dirmngr.conf | |||
@@ -0,0 +1,8 @@ | |||
1 | |||
2 | ###+++--- GPGConf ---+++### | ||
3 | debug-level basic | ||
4 | log-file socket:///home/leo/kde/src/kdepim/messagecomposer/tests/gnupg_home/log-socket | ||
5 | ###+++--- GPGConf ---+++### Tue 29 Jun 2010 10:23:13 AM EDT | ||
6 | # GPGConf edited this configuration file. | ||
7 | # It will disable options before this marked block, but it will | ||
8 | # never change anything below these lines. | ||
diff --git a/framework/src/domain/mimetreeparser/tests/gnupg_home/gpg-agent.conf.in b/framework/src/domain/mimetreeparser/tests/gnupg_home/gpg-agent.conf.in new file mode 100644 index 00000000..ece69255 --- /dev/null +++ b/framework/src/domain/mimetreeparser/tests/gnupg_home/gpg-agent.conf.in | |||
@@ -0,0 +1,10 @@ | |||
1 | pinentry-program @CMAKE_CURRENT_BINARY_DIR@/pinentry-fake.sh | ||
2 | ###+++--- GPGConf ---+++### | ||
3 | allow-mark-trusted | ||
4 | debug-level basic | ||
5 | faked-system-time 20130110T154812 | ||
6 | log-file @CMAKE_CURRENT_BINARY_DIR@/gpg-agent.log | ||
7 | ###+++--- GPGConf ---+++### Tue 29 Jun 2010 10:23:13 AM EDT | ||
8 | # GPGConf edited this configuration file. | ||
9 | # It will disable options before this marked block, but it will | ||
10 | # never change anything below these lines. | ||
diff --git a/framework/src/domain/mimetreeparser/tests/gnupg_home/gpg.conf b/framework/src/domain/mimetreeparser/tests/gnupg_home/gpg.conf new file mode 100644 index 00000000..f1760823 --- /dev/null +++ b/framework/src/domain/mimetreeparser/tests/gnupg_home/gpg.conf | |||
@@ -0,0 +1,244 @@ | |||
1 | # Options for GnuPG | ||
2 | # Copyright 1998, 1999, 2000, 2001, 2002, 2003 Free Software Foundation, Inc. | ||
3 | # | ||
4 | # This file is free software; as a special exception the author gives | ||
5 | # unlimited permission to copy and/or distribute it, with or without | ||
6 | # modifications, as long as this notice is preserved. | ||
7 | # | ||
8 | # This file is distributed in the hope that it will be useful, but | ||
9 | # WITHOUT ANY WARRANTY, to the extent permitted by law; without even the | ||
10 | # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | ||
11 | # | ||
12 | # Unless you specify which option file to use (with the command line | ||
13 | # option "--options filename"), GnuPG uses the file ~/.gnupg/gpg.conf | ||
14 | # by default. | ||
15 | # | ||
16 | # An options file can contain any long options which are available in | ||
17 | # GnuPG. If the first non white space character of a line is a '#', | ||
18 | # this line is ignored. Empty lines are also ignored. | ||
19 | # | ||
20 | # See the man page for a list of options. | ||
21 | |||
22 | # Uncomment the following option to get rid of the copyright notice | ||
23 | |||
24 | #no-greeting | ||
25 | |||
26 | # If you have more than 1 secret key in your keyring, you may want to | ||
27 | # uncomment the following option and set your preferred keyid. | ||
28 | |||
29 | #default-key 621CC013 | ||
30 | |||
31 | # If you do not pass a recipient to gpg, it will ask for one. Using | ||
32 | # this option you can encrypt to a default key. Key validation will | ||
33 | # not be done in this case. The second form uses the default key as | ||
34 | # default recipient. | ||
35 | |||
36 | #default-recipient some-user-id | ||
37 | #default-recipient-self | ||
38 | |||
39 | # Use --encrypt-to to add the specified key as a recipient to all | ||
40 | # messages. This is useful, for example, when sending mail through a | ||
41 | # mail client that does not automatically encrypt mail to your key. | ||
42 | # In the example, this option allows you to read your local copy of | ||
43 | # encrypted mail that you've sent to others. | ||
44 | |||
45 | #encrypt-to some-key-id | ||
46 | |||
47 | # By default GnuPG creates version 3 signatures for data files. This | ||
48 | # is not strictly OpenPGP compliant but PGP 6 and most versions of PGP | ||
49 | # 7 require them. To disable this behavior, you may use this option | ||
50 | # or --openpgp. | ||
51 | |||
52 | #no-force-v3-sigs | ||
53 | |||
54 | # Because some mailers change lines starting with "From " to ">From " | ||
55 | # it is good to handle such lines in a special way when creating | ||
56 | # cleartext signatures; all other PGP versions do it this way too. | ||
57 | |||
58 | #no-escape-from-lines | ||
59 | |||
60 | # If you do not use the Latin-1 (ISO-8859-1) charset, you should tell | ||
61 | # GnuPG which is the native character set. Please check the man page | ||
62 | # for supported character sets. This character set is only used for | ||
63 | # metadata and not for the actual message which does not undergo any | ||
64 | # translation. Note that future version of GnuPG will change to UTF-8 | ||
65 | # as default character set. In most cases this option is not required | ||
66 | # as GnuPG is able to figure out the correct charset at runtime. | ||
67 | |||
68 | #charset utf-8 | ||
69 | |||
70 | # Group names may be defined like this: | ||
71 | # group mynames = paige 0x12345678 joe patti | ||
72 | # | ||
73 | # Any time "mynames" is a recipient (-r or --recipient), it will be | ||
74 | # expanded to the names "paige", "joe", and "patti", and the key ID | ||
75 | # "0x12345678". Note there is only one level of expansion - you | ||
76 | # cannot make an group that points to another group. Note also that | ||
77 | # if there are spaces in the recipient name, this will appear as two | ||
78 | # recipients. In these cases it is better to use the key ID. | ||
79 | |||
80 | #group mynames = paige 0x12345678 joe patti | ||
81 | |||
82 | # Lock the file only once for the lifetime of a process. If you do | ||
83 | # not define this, the lock will be obtained and released every time | ||
84 | # it is needed, which is usually preferable. | ||
85 | |||
86 | #lock-once | ||
87 | |||
88 | # GnuPG can send and receive keys to and from a keyserver. These | ||
89 | # servers can be HKP, email, or LDAP (if GnuPG is built with LDAP | ||
90 | # support). | ||
91 | # | ||
92 | # Example HKP keyserver: | ||
93 | # hkp://keys.gnupg.net | ||
94 | # hkp://subkeys.pgp.net | ||
95 | # | ||
96 | # Example email keyserver: | ||
97 | # mailto:pgp-public-keys@keys.pgp.net | ||
98 | # | ||
99 | # Example LDAP keyservers: | ||
100 | # ldap://keyserver.pgp.com | ||
101 | # | ||
102 | # Regular URL syntax applies, and you can set an alternate port | ||
103 | # through the usual method: | ||
104 | # hkp://keyserver.example.net:22742 | ||
105 | # | ||
106 | # Most users just set the name and type of their preferred keyserver. | ||
107 | # Note that most servers (with the notable exception of | ||
108 | # ldap://keyserver.pgp.com) synchronize changes with each other. Note | ||
109 | # also that a single server name may actually point to multiple | ||
110 | # servers via DNS round-robin. hkp://keys.gnupg.net is an example of | ||
111 | # such a "server", which spreads the load over a number of physical | ||
112 | # servers. To see the IP address of the server actually used, you may use | ||
113 | # the "--keyserver-options debug". | ||
114 | |||
115 | keyserver hkp://keys.gnupg.net | ||
116 | #keyserver mailto:pgp-public-keys@keys.nl.pgp.net | ||
117 | #keyserver ldap://keyserver.pgp.com | ||
118 | |||
119 | # Common options for keyserver functions: | ||
120 | # | ||
121 | # include-disabled : when searching, include keys marked as "disabled" | ||
122 | # on the keyserver (not all keyservers support this). | ||
123 | # | ||
124 | # no-include-revoked : when searching, do not include keys marked as | ||
125 | # "revoked" on the keyserver. | ||
126 | # | ||
127 | # verbose : show more information as the keys are fetched. | ||
128 | # Can be used more than once to increase the amount | ||
129 | # of information shown. | ||
130 | # | ||
131 | # use-temp-files : use temporary files instead of a pipe to talk to the | ||
132 | # keyserver. Some platforms (Win32 for one) always | ||
133 | # have this on. | ||
134 | # | ||
135 | # keep-temp-files : do not delete temporary files after using them | ||
136 | # (really only useful for debugging) | ||
137 | # | ||
138 | # http-proxy="proxy" : set the proxy to use for HTTP and HKP keyservers. | ||
139 | # This overrides the "http_proxy" environment variable, | ||
140 | # if any. | ||
141 | # | ||
142 | # auto-key-retrieve : automatically fetch keys as needed from the keyserver | ||
143 | # when verifying signatures or when importing keys that | ||
144 | # have been revoked by a revocation key that is not | ||
145 | # present on the keyring. | ||
146 | # | ||
147 | # no-include-attributes : do not include attribute IDs (aka "photo IDs") | ||
148 | # when sending keys to the keyserver. | ||
149 | |||
150 | #keyserver-options auto-key-retrieve | ||
151 | |||
152 | # Display photo user IDs in key listings | ||
153 | |||
154 | # list-options show-photos | ||
155 | |||
156 | # Display photo user IDs when a signature from a key with a photo is | ||
157 | # verified | ||
158 | |||
159 | # verify-options show-photos | ||
160 | |||
161 | # Use this program to display photo user IDs | ||
162 | # | ||
163 | # %i is expanded to a temporary file that contains the photo. | ||
164 | # %I is the same as %i, but the file isn't deleted afterwards by GnuPG. | ||
165 | # %k is expanded to the key ID of the key. | ||
166 | # %K is expanded to the long OpenPGP key ID of the key. | ||
167 | # %t is expanded to the extension of the image (e.g. "jpg"). | ||
168 | # %T is expanded to the MIME type of the image (e.g. "image/jpeg"). | ||
169 | # %f is expanded to the fingerprint of the key. | ||
170 | # %% is %, of course. | ||
171 | # | ||
172 | # If %i or %I are not present, then the photo is supplied to the | ||
173 | # viewer on standard input. If your platform supports it, standard | ||
174 | # input is the best way to do this as it avoids the time and effort in | ||
175 | # generating and then cleaning up a secure temp file. | ||
176 | # | ||
177 | # If no photo-viewer is provided, GnuPG will look for xloadimage, eog, | ||
178 | # or display (ImageMagick). On Mac OS X and Windows, the default is | ||
179 | # to use your regular JPEG image viewer. | ||
180 | # | ||
181 | # Some other viewers: | ||
182 | # photo-viewer "qiv %i" | ||
183 | # photo-viewer "ee %i" | ||
184 | # | ||
185 | # This one saves a copy of the photo ID in your home directory: | ||
186 | # photo-viewer "cat > ~/photoid-for-key-%k.%t" | ||
187 | # | ||
188 | # Use your MIME handler to view photos: | ||
189 | # photo-viewer "metamail -q -d -b -c %T -s 'KeyID 0x%k' -f GnuPG" | ||
190 | |||
191 | # Passphrase agent | ||
192 | # | ||
193 | # We support the old experimental passphrase agent protocol as well as | ||
194 | # the new Assuan based one (currently available in the "newpg" package | ||
195 | # at ftp.gnupg.org/gcrypt/alpha/aegypten/). To make use of the agent, | ||
196 | # you have to run an agent as daemon and use the option | ||
197 | # | ||
198 | # use-agent | ||
199 | # | ||
200 | # which tries to use the agent but will fallback to the regular mode | ||
201 | # if there is a problem connecting to the agent. The normal way to | ||
202 | # locate the agent is by looking at the environment variable | ||
203 | # GPG_AGENT_INFO which should have been set during gpg-agent startup. | ||
204 | # In certain situations the use of this variable is not possible, thus | ||
205 | # the option | ||
206 | # | ||
207 | # --gpg-agent-info=<path>:<pid>:1 | ||
208 | # | ||
209 | # may be used to override it. | ||
210 | |||
211 | # Automatic key location | ||
212 | # | ||
213 | # GnuPG can automatically locate and retrieve keys as needed using the | ||
214 | # auto-key-locate option. This happens when encrypting to an email | ||
215 | # address (in the "user@example.com" form), and there are no | ||
216 | # user@example.com keys on the local keyring. This option takes the | ||
217 | # following arguments, in the order they are to be tried: | ||
218 | # | ||
219 | # cert = locate a key using DNS CERT, as specified in RFC-4398. | ||
220 | # GnuPG can handle both the PGP (key) and IPGP (URL + fingerprint) | ||
221 | # CERT methods. | ||
222 | # | ||
223 | # pka = locate a key using DNS PKA. | ||
224 | # | ||
225 | # ldap = locate a key using the PGP Universal method of checking | ||
226 | # "ldap://keys.(thedomain)". For example, encrypting to | ||
227 | # user@example.com will check ldap://keys.example.com. | ||
228 | # | ||
229 | # keyserver = locate a key using whatever keyserver is defined using | ||
230 | # the keyserver option. | ||
231 | # | ||
232 | # You may also list arbitrary keyservers here by URL. | ||
233 | # | ||
234 | # Try CERT, then PKA, then LDAP, then hkp://subkeys.net: | ||
235 | #auto-key-locate cert pka ldap hkp://subkeys.pgp.net | ||
236 | |||
237 | ###+++--- GPGConf ---+++### | ||
238 | utf8-strings | ||
239 | #debug-level basic | ||
240 | #log-file socket:///home/leo/kde/src/kdepim/messagecomposer/tests/gnupg_home/log-socket | ||
241 | ###+++--- GPGConf ---+++### Tue 29 Jun 2010 10:23:13 AM EDT | ||
242 | # GPGConf edited this configuration file. | ||
243 | # It will disable options before this marked block, but it will | ||
244 | # never change anything below these lines. | ||
diff --git a/framework/src/domain/mimetreeparser/tests/gnupg_home/gpgsm.conf.in b/framework/src/domain/mimetreeparser/tests/gnupg_home/gpgsm.conf.in new file mode 100644 index 00000000..92b6119d --- /dev/null +++ b/framework/src/domain/mimetreeparser/tests/gnupg_home/gpgsm.conf.in | |||
@@ -0,0 +1,10 @@ | |||
1 | |||
2 | ###+++--- GPGConf ---+++### | ||
3 | disable-crl-checks | ||
4 | debug-level basic | ||
5 | faked-system-time 20130110T154812 | ||
6 | log-file @CMAKE_CURRENT_BINARY_DIR@/gpgsm.log | ||
7 | ###+++--- GPGConf ---+++### Tue 29 Jun 2010 10:23:13 AM EDT | ||
8 | # GPGConf edited this configuration file. | ||
9 | # It will disable options before this marked block, but it will | ||
10 | # never change anything below these lines. | ||
diff --git a/framework/src/domain/mimetreeparser/tests/gnupg_home/pinentry-fake.sh b/framework/src/domain/mimetreeparser/tests/gnupg_home/pinentry-fake.sh new file mode 100755 index 00000000..7135a942 --- /dev/null +++ b/framework/src/domain/mimetreeparser/tests/gnupg_home/pinentry-fake.sh | |||
@@ -0,0 +1,9 @@ | |||
1 | #!/bin/sh | ||
2 | |||
3 | echo "OK Your orders please" | ||
4 | while : | ||
5 | do | ||
6 | read cmd | ||
7 | echo "OK" | ||
8 | [ "$cmd" = "BYE" ] && break | ||
9 | done | ||
diff --git a/framework/src/domain/mimetreeparser/tests/gnupg_home/private-keys-v1.d/1AA8BA52430E51AE249AF0DA97D59F869E4101A8.key b/framework/src/domain/mimetreeparser/tests/gnupg_home/private-keys-v1.d/1AA8BA52430E51AE249AF0DA97D59F869E4101A8.key new file mode 100644 index 00000000..39ac307b --- /dev/null +++ b/framework/src/domain/mimetreeparser/tests/gnupg_home/private-keys-v1.d/1AA8BA52430E51AE249AF0DA97D59F869E4101A8.key | |||
Binary files differ | |||
diff --git a/framework/src/domain/mimetreeparser/tests/gnupg_home/pubring.gpg b/framework/src/domain/mimetreeparser/tests/gnupg_home/pubring.gpg new file mode 100644 index 00000000..2e00fa24 --- /dev/null +++ b/framework/src/domain/mimetreeparser/tests/gnupg_home/pubring.gpg | |||
Binary files differ | |||
diff --git a/framework/src/domain/mimetreeparser/tests/gnupg_home/pubring.kbx b/framework/src/domain/mimetreeparser/tests/gnupg_home/pubring.kbx new file mode 100644 index 00000000..0230f313 --- /dev/null +++ b/framework/src/domain/mimetreeparser/tests/gnupg_home/pubring.kbx | |||
Binary files differ | |||
diff --git a/framework/src/domain/mimetreeparser/tests/gnupg_home/scdaemon.conf b/framework/src/domain/mimetreeparser/tests/gnupg_home/scdaemon.conf new file mode 100644 index 00000000..a17a0354 --- /dev/null +++ b/framework/src/domain/mimetreeparser/tests/gnupg_home/scdaemon.conf | |||
@@ -0,0 +1,8 @@ | |||
1 | |||
2 | ###+++--- GPGConf ---+++### | ||
3 | debug-level basic | ||
4 | log-file socket:///home/leo/kde/src/kdepim/messagecomposer/tests/gnupg_home/log-socket | ||
5 | ###+++--- GPGConf ---+++### Tue 29 Jun 2010 10:23:13 AM EDT | ||
6 | # GPGConf edited this configuration file. | ||
7 | # It will disable options before this marked block, but it will | ||
8 | # never change anything below these lines. | ||
diff --git a/framework/src/domain/mimetreeparser/tests/gnupg_home/secring.gpg b/framework/src/domain/mimetreeparser/tests/gnupg_home/secring.gpg new file mode 100644 index 00000000..cfd3387d --- /dev/null +++ b/framework/src/domain/mimetreeparser/tests/gnupg_home/secring.gpg | |||
Binary files differ | |||
diff --git a/framework/src/domain/mimetreeparser/tests/gnupg_home/trustdb.gpg b/framework/src/domain/mimetreeparser/tests/gnupg_home/trustdb.gpg new file mode 100644 index 00000000..70089c15 --- /dev/null +++ b/framework/src/domain/mimetreeparser/tests/gnupg_home/trustdb.gpg | |||
Binary files differ | |||
diff --git a/framework/src/domain/mimetreeparser/tests/gnupg_home/trustlist.txt b/framework/src/domain/mimetreeparser/tests/gnupg_home/trustlist.txt new file mode 100644 index 00000000..bbb0442d --- /dev/null +++ b/framework/src/domain/mimetreeparser/tests/gnupg_home/trustlist.txt | |||
@@ -0,0 +1,11 @@ | |||
1 | 5E:7C:B2:F4:9F:70:05:43:42:32:5D:75:74:70:00:09:B9:D8:08:61 S | ||
2 | |||
3 | |||
4 | |||
5 | # CN=unittest cert | ||
6 | # O=KDAB | ||
7 | # C=US | ||
8 | # EMail=test@example.com | ||
9 | 24:D2:FC:A2:2E:B3:B8:0A:1E:37:71:D1:4C:C6:58:E3:21:2B:49:DC S | ||
10 | |||
11 | |||
diff --git a/framework/src/domain/mimetreeparser/tests/gpgerrortest.cpp b/framework/src/domain/mimetreeparser/tests/gpgerrortest.cpp new file mode 100644 index 00000000..4254d972 --- /dev/null +++ b/framework/src/domain/mimetreeparser/tests/gpgerrortest.cpp | |||
@@ -0,0 +1,214 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Sandro Knauß <knauss@kolabsystems.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | |||
20 | #include "interface.h" | ||
21 | #include "interface_p.h" | ||
22 | |||
23 | #include <QGpgME/Protocol> | ||
24 | #include <gpgme++/context.h> | ||
25 | #include <gpgme++/engineinfo.h> | ||
26 | #include <gpgme.h> | ||
27 | |||
28 | #include <QDebug> | ||
29 | #include <QDir> | ||
30 | #include <QProcess> | ||
31 | #include <QTest> | ||
32 | |||
33 | QByteArray readMailFromFile(const QString &mailFile) | ||
34 | { | ||
35 | QFile file(QLatin1String(MAIL_DATA_DIR) + QLatin1Char('/') + mailFile); | ||
36 | file.open(QIODevice::ReadOnly); | ||
37 | Q_ASSERT(file.isOpen()); | ||
38 | return file.readAll(); | ||
39 | } | ||
40 | |||
41 | void killAgent(const QString& dir) | ||
42 | { | ||
43 | QProcess proc; | ||
44 | proc.setProgram(QStringLiteral("gpg-connect-agent")); | ||
45 | QStringList arguments; | ||
46 | arguments << "-S " << dir + "/S.gpg-agent"; | ||
47 | proc.start(); | ||
48 | proc.waitForStarted(); | ||
49 | proc.write("KILLAGENT\n"); | ||
50 | proc.write("BYE\n"); | ||
51 | proc.closeWriteChannel(); | ||
52 | proc.waitForFinished(); | ||
53 | } | ||
54 | |||
55 | class GpgErrorTest : public QObject | ||
56 | { | ||
57 | Q_OBJECT | ||
58 | |||
59 | private slots: | ||
60 | |||
61 | void testGpgConfiguredCorrectly() | ||
62 | { | ||
63 | setEnv("GNUPGHOME", GNUPGHOME); | ||
64 | |||
65 | Parser parser(readMailFromFile("openpgp-inline-charset-encrypted.mbox")); | ||
66 | |||
67 | auto contentPartList = parser.collectContentParts(); | ||
68 | QCOMPARE(contentPartList.size(), 1); | ||
69 | auto contentPart = contentPartList[0]; | ||
70 | QCOMPARE(contentPart->availableContents(), QVector<QByteArray>() << "plaintext"); | ||
71 | auto contentList = contentPart->content("plaintext"); | ||
72 | QVERIFY(contentList[0]->content().startsWith("asdasd")); | ||
73 | QCOMPARE(contentList[0]->encryptions().size(), 1); | ||
74 | auto enc = contentList[0]->encryptions()[0]; | ||
75 | QCOMPARE(enc->errorType(), Encryption::NoError); | ||
76 | QCOMPARE(enc->errorString(), QString()); | ||
77 | QCOMPARE((int) enc->recipients().size(), 2); | ||
78 | } | ||
79 | |||
80 | void testNoGPGInstalled_data() | ||
81 | { | ||
82 | QTest::addColumn<QString>("mailFileName"); | ||
83 | |||
84 | QTest::newRow("openpgp-inline-charset-encrypted") << "openpgp-inline-charset-encrypted.mbox"; | ||
85 | QTest::newRow("openpgp-encrypted-attachment-and-non-encrypted-attachment") << "openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox"; | ||
86 | QTest::newRow("smime-encrypted") << "smime-encrypted.mbox"; | ||
87 | } | ||
88 | |||
89 | void testNoGPGInstalled() | ||
90 | { | ||
91 | QFETCH(QString, mailFileName); | ||
92 | |||
93 | setEnv("PATH", "/nonexististing"); | ||
94 | setGpgMEfname("/nonexisting/gpg", ""); | ||
95 | |||
96 | Parser parser(readMailFromFile(mailFileName)); | ||
97 | auto contentPartList = parser.collectContentParts(); | ||
98 | |||
99 | QCOMPARE(contentPartList.size(), 1); | ||
100 | auto contentPart = contentPartList[0]; | ||
101 | QCOMPARE(contentPart->availableContents(), QVector<QByteArray>() << "plaintext"); | ||
102 | auto contentList = contentPart->content("plaintext"); | ||
103 | QCOMPARE(contentList[0]->encryptions().size(), 1); | ||
104 | QVERIFY(contentList[0]->content().isEmpty()); | ||
105 | auto enc = contentList[0]->encryptions()[0]; | ||
106 | qDebug() << "HUHU"<< enc->errorType(); | ||
107 | QCOMPARE(enc->errorType(), Encryption::UnknownError); | ||
108 | QCOMPARE(enc->errorString(), QString("Crypto plug-in \"OpenPGP\" could not decrypt the data.<br />Error: No data")); | ||
109 | QCOMPARE((int) enc->recipients().size(), 0); | ||
110 | } | ||
111 | |||
112 | void testGpgIncorrectGPGHOME_data() | ||
113 | { | ||
114 | QTest::addColumn<QString>("mailFileName"); | ||
115 | |||
116 | QTest::newRow("openpgp-inline-charset-encrypted") << "openpgp-inline-charset-encrypted.mbox"; | ||
117 | QTest::newRow("openpgp-encrypted-attachment-and-non-encrypted-attachment") << "openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox"; | ||
118 | QTest::newRow("smime-encrypted") << "smime-encrypted.mbox"; | ||
119 | } | ||
120 | |||
121 | void testGpgIncorrectGPGHOME() | ||
122 | { | ||
123 | QFETCH(QString, mailFileName); | ||
124 | setEnv("GNUPGHOME", QByteArray(GNUPGHOME) + QByteArray("noexist")); | ||
125 | |||
126 | Parser parser(readMailFromFile(mailFileName)); | ||
127 | |||
128 | auto contentPartList = parser.collectContentParts(); | ||
129 | QCOMPARE(contentPartList.size(), 1); | ||
130 | auto contentPart = contentPartList[0]; | ||
131 | QCOMPARE(contentPart->availableContents(), QVector<QByteArray>() << "plaintext"); | ||
132 | auto contentList = contentPart->content("plaintext"); | ||
133 | QCOMPARE(contentList[0]->encryptions().size(), 1); | ||
134 | QCOMPARE(contentList[0]->signatures().size(), 0); | ||
135 | QVERIFY(contentList[0]->content().isEmpty()); | ||
136 | auto enc = contentList[0]->encryptions()[0]; | ||
137 | qDebug() << enc->errorType(); | ||
138 | QCOMPARE(enc->errorType(), Encryption::KeyMissing); | ||
139 | QCOMPARE(enc->errorString(), QString("Crypto plug-in \"OpenPGP\" could not decrypt the data.<br />Error: Decryption failed")); | ||
140 | QCOMPARE((int) enc->recipients().size(), 2); | ||
141 | } | ||
142 | |||
143 | public Q_SLOTS: | ||
144 | void init() | ||
145 | { | ||
146 | mResetGpgmeEngine = false; | ||
147 | mModifiedEnv.clear(); | ||
148 | { | ||
149 | QGpgME::openpgp(); // We need to intialize it, otherwise ctx will be a nullpointer | ||
150 | const GpgME::Context *ctx = GpgME::Context::createForProtocol(GpgME::Protocol::OpenPGP); | ||
151 | const auto engineinfo = ctx->engineInfo(); | ||
152 | mGpgmeEngine_fname = engineinfo.fileName(); | ||
153 | } | ||
154 | mEnv = QProcessEnvironment::systemEnvironment(); | ||
155 | unsetEnv("GNUPGHOME"); | ||
156 | } | ||
157 | |||
158 | void cleanup() | ||
159 | { | ||
160 | QCoreApplication::sendPostedEvents(); | ||
161 | |||
162 | const QString &gnupghome = qgetenv("GNUPGHOME"); | ||
163 | if (!gnupghome.isEmpty()) { | ||
164 | killAgent(gnupghome); | ||
165 | } | ||
166 | |||
167 | resetGpgMfname(); | ||
168 | resetEnv(); | ||
169 | } | ||
170 | private: | ||
171 | void unsetEnv(const QByteArray &name) | ||
172 | { | ||
173 | mModifiedEnv << name; | ||
174 | unsetenv(name); | ||
175 | } | ||
176 | |||
177 | void setEnv(const QByteArray &name, const QByteArray &value) | ||
178 | { | ||
179 | mModifiedEnv << name; | ||
180 | setenv(name, value , 1); | ||
181 | } | ||
182 | |||
183 | void resetEnv() | ||
184 | { | ||
185 | foreach(const auto &i, mModifiedEnv) { | ||
186 | if (mEnv.contains(i)) { | ||
187 | setenv(i, mEnv.value(i).toUtf8(), 1); | ||
188 | } else { | ||
189 | unsetenv(i); | ||
190 | } | ||
191 | } | ||
192 | } | ||
193 | |||
194 | void resetGpgMfname() | ||
195 | { | ||
196 | if (mResetGpgmeEngine) { | ||
197 | gpgme_set_engine_info (GPGME_PROTOCOL_OpenPGP, mGpgmeEngine_fname, NULL); | ||
198 | } | ||
199 | } | ||
200 | |||
201 | void setGpgMEfname(const QByteArray &fname, const QByteArray &homedir) | ||
202 | { | ||
203 | mResetGpgmeEngine = true; | ||
204 | gpgme_set_engine_info (GPGME_PROTOCOL_OpenPGP, fname, homedir); | ||
205 | } | ||
206 | |||
207 | QSet<QByteArray> mModifiedEnv; | ||
208 | QProcessEnvironment mEnv; | ||
209 | bool mResetGpgmeEngine; | ||
210 | QByteArray mGpgmeEngine_fname; | ||
211 | }; | ||
212 | |||
213 | QTEST_GUILESS_MAIN(GpgErrorTest) | ||
214 | #include "gpgerrortest.moc" | ||
diff --git a/framework/src/domain/mimetreeparser/tests/interfacetest.cpp b/framework/src/domain/mimetreeparser/tests/interfacetest.cpp new file mode 100644 index 00000000..3ae32a4a --- /dev/null +++ b/framework/src/domain/mimetreeparser/tests/interfacetest.cpp | |||
@@ -0,0 +1,310 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Sandro Knauß <knauss@kolabsystems.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | |||
20 | #include "interface.h" | ||
21 | #include "interface_p.h" | ||
22 | |||
23 | #include <QTest> | ||
24 | |||
25 | QByteArray readMailFromFile(const QString &mailFile) | ||
26 | { | ||
27 | QFile file(QLatin1String(MAIL_DATA_DIR) + QLatin1Char('/') + mailFile); | ||
28 | file.open(QIODevice::ReadOnly); | ||
29 | Q_ASSERT(file.isOpen()); | ||
30 | return file.readAll(); | ||
31 | } | ||
32 | |||
33 | QByteArray join(QVector<QByteArray> vec, QByteArray sep) | ||
34 | { | ||
35 | QByteArray ret; | ||
36 | bool bInit = true; | ||
37 | foreach(const auto &entry, vec) { | ||
38 | if (!bInit) { | ||
39 | ret += sep; | ||
40 | } | ||
41 | bInit = false; | ||
42 | ret += entry; | ||
43 | } | ||
44 | return ret; | ||
45 | } | ||
46 | |||
47 | class InterfaceTest : public QObject | ||
48 | { | ||
49 | Q_OBJECT | ||
50 | private: | ||
51 | void printTree(const Part::Ptr &start, QString pre) | ||
52 | { | ||
53 | foreach (const auto &part, start->subParts()) { | ||
54 | qWarning() << QStringLiteral("%1* %2(%3)") | ||
55 | .arg(pre) | ||
56 | .arg(QString::fromLatin1(part->type())) | ||
57 | .arg(QString::fromLatin1(join(part->availableContents(),", "))); | ||
58 | printTree(part,pre + QStringLiteral(" ")); | ||
59 | } | ||
60 | } | ||
61 | |||
62 | private slots: | ||
63 | |||
64 | void testTextMail() | ||
65 | { | ||
66 | Parser parser(readMailFromFile("plaintext.mbox")); | ||
67 | printTree(parser.d->mTree,QString()); | ||
68 | auto contentPartList = parser.collectContentParts(); | ||
69 | QCOMPARE(contentPartList.size(), 1); | ||
70 | auto contentPart = contentPartList[0]; | ||
71 | QVERIFY((bool)contentPart); | ||
72 | QCOMPARE(contentPart->availableContents(), QVector<QByteArray>() << "plaintext"); | ||
73 | auto contentList = contentPart->content("plaintext"); | ||
74 | QCOMPARE(contentList.size(), 1); | ||
75 | QCOMPARE(contentList[0]->content(), QStringLiteral("If you can see this text it means that your email client couldn't display our newsletter properly.\nPlease visit this link to view the newsletter on our website: http://www.gog.com/newsletter/").toLocal8Bit()); | ||
76 | QCOMPARE(contentList[0]->charset(), QStringLiteral("utf-8").toLocal8Bit()); | ||
77 | QCOMPARE(contentList[0]->encryptions().size(), 0); | ||
78 | QCOMPARE(contentList[0]->signatures().size(), 0); | ||
79 | |||
80 | contentList = contentPart->content("html"); | ||
81 | QCOMPARE(contentList.size(), 0); | ||
82 | auto contentAttachmentList = parser.collectAttachmentParts(); | ||
83 | QCOMPARE(contentAttachmentList.size(), 0); | ||
84 | } | ||
85 | |||
86 | void testTextAlternative() | ||
87 | { | ||
88 | Parser parser(readMailFromFile("alternative.mbox")); | ||
89 | printTree(parser.d->mTree,QString()); | ||
90 | auto contentPartList = parser.collectContentParts(); | ||
91 | QCOMPARE(contentPartList.size(), 1); | ||
92 | auto contentPart = contentPartList[0]; | ||
93 | QVERIFY((bool)contentPart); | ||
94 | QCOMPARE(contentPart->availableContents(), QVector<QByteArray>() << "html" << "plaintext"); | ||
95 | auto contentList = contentPart->content("plaintext"); | ||
96 | QCOMPARE(contentList.size(), 1); | ||
97 | QCOMPARE(contentList[0]->content(), QStringLiteral("If you can see this text it means that your email client couldn't display our newsletter properly.\nPlease visit this link to view the newsletter on our website: http://www.gog.com/newsletter/\n").toLocal8Bit()); | ||
98 | QCOMPARE(contentList[0]->charset(), QStringLiteral("utf-8").toLocal8Bit()); | ||
99 | QCOMPARE(contentList[0]->encryptions().size(), 0); | ||
100 | QCOMPARE(contentList[0]->signatures().size(), 0); | ||
101 | |||
102 | contentList = contentPart->content("html"); | ||
103 | QCOMPARE(contentList.size(), 1); | ||
104 | QCOMPARE(contentList[0]->content(), QStringLiteral("<html><body><p><span>HTML</span> text</p></body></html>\n\n").toLocal8Bit()); | ||
105 | QCOMPARE(contentList[0]->charset(), QStringLiteral("utf-8").toLocal8Bit()); | ||
106 | QCOMPARE(contentList[0]->encryptions().size(), 0); | ||
107 | QCOMPARE(contentList[0]->signatures().size(), 0); | ||
108 | auto contentAttachmentList = parser.collectAttachmentParts(); | ||
109 | QCOMPARE(contentAttachmentList.size(), 0); | ||
110 | } | ||
111 | |||
112 | void testTextHtml() | ||
113 | { | ||
114 | Parser parser(readMailFromFile("html.mbox")); | ||
115 | printTree(parser.d->mTree,QString()); | ||
116 | auto contentPartList = parser.collectContentParts(); | ||
117 | QCOMPARE(contentPartList.size(), 1); | ||
118 | auto contentPart = contentPartList[0]; | ||
119 | QVERIFY((bool)contentPart); | ||
120 | QCOMPARE(contentPart->availableContents(), QVector<QByteArray>() << "html"); | ||
121 | |||
122 | auto contentList = contentPart->content("plaintext"); | ||
123 | QCOMPARE(contentList.size(), 0); | ||
124 | |||
125 | contentList = contentPart->content("html"); | ||
126 | QCOMPARE(contentList.size(), 1); | ||
127 | QCOMPARE(contentList[0]->content(), QStringLiteral("<html><body><p><span>HTML</span> text</p></body></html>").toLocal8Bit()); | ||
128 | QCOMPARE(contentList[0]->charset(), QStringLiteral("utf-8").toLocal8Bit()); | ||
129 | QCOMPARE(contentList[0]->encryptions().size(), 0); | ||
130 | QCOMPARE(contentList[0]->signatures().size(), 0); | ||
131 | auto contentAttachmentList = parser.collectAttachmentParts(); | ||
132 | QCOMPARE(contentAttachmentList.size(), 0); | ||
133 | } | ||
134 | |||
135 | void testSMimeEncrypted() | ||
136 | { | ||
137 | Parser parser(readMailFromFile("smime-encrypted.mbox")); | ||
138 | printTree(parser.d->mTree,QString()); | ||
139 | auto contentPartList = parser.collectContentParts(); | ||
140 | QCOMPARE(contentPartList.size(), 1); | ||
141 | auto contentPart = contentPartList[0]; | ||
142 | QVERIFY((bool)contentPart); | ||
143 | QCOMPARE(contentPart->availableContents(), QVector<QByteArray>() << "plaintext"); | ||
144 | auto contentList = contentPart->content("plaintext"); | ||
145 | QCOMPARE(contentList.size(), 1); | ||
146 | QCOMPARE(contentList[0]->content(), QStringLiteral("The quick brown fox jumped over the lazy dog.").toLocal8Bit()); | ||
147 | QCOMPARE(contentList[0]->charset(), QStringLiteral("us-ascii").toLocal8Bit()); | ||
148 | QCOMPARE(contentList[0]->encryptions().size(), 1); | ||
149 | QCOMPARE(contentList[0]->signatures().size(), 0); | ||
150 | auto contentAttachmentList = parser.collectAttachmentParts(); | ||
151 | QCOMPARE(contentAttachmentList.size(), 0); | ||
152 | } | ||
153 | |||
154 | void testOpenPGPEncryptedAttachment() | ||
155 | { | ||
156 | Parser parser(readMailFromFile("openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox")); | ||
157 | printTree(parser.d->mTree,QString()); | ||
158 | auto contentPartList = parser.collectContentParts(); | ||
159 | QCOMPARE(contentPartList.size(), 1); | ||
160 | auto contentPart = contentPartList[0]; | ||
161 | QVERIFY((bool)contentPart); | ||
162 | QCOMPARE(contentPart->availableContents(), QVector<QByteArray>() << "plaintext"); | ||
163 | auto contentList = contentPart->content("plaintext"); | ||
164 | QCOMPARE(contentList.size(), 1); | ||
165 | QCOMPARE(contentList[0]->content(), QStringLiteral("test text").toLocal8Bit()); | ||
166 | QCOMPARE(contentList[0]->charset(), QStringLiteral("us-ascii").toLocal8Bit()); | ||
167 | QCOMPARE(contentList[0]->encryptions().size(), 1); | ||
168 | QCOMPARE(contentList[0]->signatures().size(), 1); | ||
169 | auto contentAttachmentList = parser.collectAttachmentParts(); | ||
170 | QCOMPARE(contentAttachmentList.size(), 2); | ||
171 | QCOMPARE(contentAttachmentList[0]->availableContents(), QVector<QByteArray>() << "text/plain"); | ||
172 | QCOMPARE(contentAttachmentList[0]->content().size(), 1); | ||
173 | QCOMPARE(contentAttachmentList[0]->encryptions().size(), 1); | ||
174 | QCOMPARE(contentAttachmentList[0]->signatures().size(), 1); | ||
175 | QCOMPARE(contentAttachmentList[1]->availableContents(), QVector<QByteArray>() << "image/png"); | ||
176 | QCOMPARE(contentAttachmentList[1]->content().size(), 1); | ||
177 | QCOMPARE(contentAttachmentList[1]->encryptions().size(), 0); | ||
178 | QCOMPARE(contentAttachmentList[1]->signatures().size(), 0); | ||
179 | } | ||
180 | |||
181 | void testOpenPGPInline() | ||
182 | { | ||
183 | Parser parser(readMailFromFile("openpgp-inline-charset-encrypted.mbox")); | ||
184 | printTree(parser.d->mTree,QString()); | ||
185 | auto contentPartList = parser.collectContentParts(); | ||
186 | QCOMPARE(contentPartList.size(), 1); | ||
187 | auto contentPart = contentPartList[0]; | ||
188 | QVERIFY((bool)contentPart); | ||
189 | QCOMPARE(contentPart->availableContents(), QVector<QByteArray>() << "plaintext"); | ||
190 | QCOMPARE(contentPart->encryptions().size(), 0); | ||
191 | QCOMPARE(contentPart->signatures().size(), 0); | ||
192 | auto contentList = contentPart->content("plaintext"); | ||
193 | QCOMPARE(contentList.size(), 1); | ||
194 | QCOMPARE(contentList[0]->content(), QStringLiteral("asdasd asd asd asdf sadf sdaf sadf äöü").toLocal8Bit()); | ||
195 | QCOMPARE(contentList[0]->charset(), QStringLiteral("ISO-8859-15").toLocal8Bit()); | ||
196 | QCOMPARE(contentList[0]->encryptions().size(), 1); | ||
197 | QCOMPARE(contentList[0]->signatures().size(), 1); | ||
198 | auto contentAttachmentList = parser.collectAttachmentParts(); | ||
199 | QCOMPARE(contentAttachmentList.size(), 0); | ||
200 | } | ||
201 | |||
202 | void testOpenPPGInlineWithNonEncText() | ||
203 | { | ||
204 | Parser parser(readMailFromFile("openpgp-inline-encrypted+nonenc.mbox")); | ||
205 | printTree(parser.d->mTree,QString()); | ||
206 | auto contentPartList = parser.collectContentParts(); | ||
207 | QCOMPARE(contentPartList.size(), 1); | ||
208 | auto contentPart = contentPartList[0]; | ||
209 | QVERIFY((bool)contentPart); | ||
210 | QCOMPARE(contentPart->availableContents(), QVector<QByteArray>() << "plaintext"); | ||
211 | QCOMPARE(contentPart->encryptions().size(), 0); | ||
212 | QCOMPARE(contentPart->signatures().size(), 0); | ||
213 | auto contentList = contentPart->content("plaintext"); | ||
214 | QCOMPARE(contentList.size(), 2); | ||
215 | QCOMPARE(contentList[0]->content(), QStringLiteral("Not encrypted not signed :(\n\n").toLocal8Bit()); | ||
216 | QCOMPARE(contentList[0]->charset(), QStringLiteral("us-ascii").toLocal8Bit()); | ||
217 | QCOMPARE(contentList[0]->encryptions().size(), 0); | ||
218 | QCOMPARE(contentList[0]->signatures().size(), 0); | ||
219 | QCOMPARE(contentList[1]->content(), QStringLiteral("some random text").toLocal8Bit()); | ||
220 | QCOMPARE(contentList[1]->charset(), QStringLiteral("us-ascii").toLocal8Bit()); | ||
221 | QCOMPARE(contentList[1]->encryptions().size(), 1); | ||
222 | QCOMPARE(contentList[1]->signatures().size(), 0); | ||
223 | auto contentAttachmentList = parser.collectAttachmentParts(); | ||
224 | QCOMPARE(contentAttachmentList.size(), 0); | ||
225 | } | ||
226 | |||
227 | void testEncryptionBlock() | ||
228 | { | ||
229 | Parser parser(readMailFromFile("openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox")); | ||
230 | auto contentPartList = parser.collectContentParts(); | ||
231 | auto contentPart = contentPartList[0]; | ||
232 | auto contentList = contentPart->content("plaintext"); | ||
233 | QCOMPARE(contentList.size(), 1); | ||
234 | QCOMPARE(contentList[0]->encryptions().size(), 1); | ||
235 | auto enc = contentList[0]->encryptions()[0]; | ||
236 | QCOMPARE((int) enc->recipients().size(), 2); | ||
237 | |||
238 | auto r = enc->recipients()[0]; | ||
239 | QCOMPARE(r->keyid(),QStringLiteral("14B79E26050467AA")); | ||
240 | QCOMPARE(r->name(),QStringLiteral("kdetest")); | ||
241 | QCOMPARE(r->email(),QStringLiteral("you@you.com")); | ||
242 | QCOMPARE(r->comment(),QStringLiteral("")); | ||
243 | |||
244 | r = enc->recipients()[1]; | ||
245 | QCOMPARE(r->keyid(),QStringLiteral("8D9860C58F246DE6")); | ||
246 | QCOMPARE(r->name(),QStringLiteral("unittest key")); | ||
247 | QCOMPARE(r->email(),QStringLiteral("test@kolab.org")); | ||
248 | QCOMPARE(r->comment(),QStringLiteral("no password")); | ||
249 | } | ||
250 | |||
251 | void testSignatureBlock() | ||
252 | { | ||
253 | Parser parser(readMailFromFile("openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox")); | ||
254 | auto contentPartList = parser.collectContentParts(); | ||
255 | auto contentPart = contentPartList[0]; | ||
256 | auto contentList = contentPart->content("plaintext"); | ||
257 | QCOMPARE(contentList.size(), 1); | ||
258 | QCOMPARE(contentList[0]->signatures().size(), 1); | ||
259 | auto sig = contentList[0]->signatures()[0]; | ||
260 | QCOMPARE(sig->creationDateTime(), QDateTime(QDate(2015,05,01),QTime(15,12,47))); | ||
261 | QCOMPARE(sig->expirationDateTime(), QDateTime()); | ||
262 | QCOMPARE(sig->neverExpires(), true); | ||
263 | |||
264 | auto key = sig->key(); | ||
265 | QCOMPARE(key->keyid(),QStringLiteral("8D9860C58F246DE6")); | ||
266 | QCOMPARE(key->name(),QStringLiteral("unittest key")); | ||
267 | QCOMPARE(key->email(),QStringLiteral("test@kolab.org")); | ||
268 | QCOMPARE(key->comment(),QStringLiteral("no password")); | ||
269 | } | ||
270 | |||
271 | void testRelatedAlternative() | ||
272 | { | ||
273 | Parser parser(readMailFromFile("cid-links.mbox")); | ||
274 | printTree(parser.d->mTree,QString()); | ||
275 | auto contentPartList = parser.collectContentParts(); | ||
276 | QCOMPARE(contentPartList.size(), 1); | ||
277 | auto contentPart = contentPartList[0]; | ||
278 | QVERIFY((bool)contentPart); | ||
279 | QCOMPARE(contentPart->availableContents(), QVector<QByteArray>() << "html" << "plaintext"); | ||
280 | QCOMPARE(contentPart->encryptions().size(), 0); | ||
281 | QCOMPARE(contentPart->signatures().size(), 0); | ||
282 | auto contentList = contentPart->content("plaintext"); | ||
283 | QCOMPARE(contentList.size(), 1); | ||
284 | auto contentAttachmentList = parser.collectAttachmentParts(); | ||
285 | QCOMPARE(contentAttachmentList.size(), 0); | ||
286 | } | ||
287 | |||
288 | void testAttachmentPart() | ||
289 | { | ||
290 | Parser parser(readMailFromFile("cid-links.mbox")); | ||
291 | const auto relatedImage = parser.d->mTree->subParts().at(1); | ||
292 | QCOMPARE(relatedImage->availableContents(), QVector<QByteArray>() << "image/jpeg"); | ||
293 | auto contentList = relatedImage->content(); | ||
294 | QCOMPARE(contentList.size(), 1); | ||
295 | contentList = relatedImage->content("image/jpeg"); | ||
296 | QCOMPARE(contentList.size(), 1); | ||
297 | } | ||
298 | |||
299 | void testCidLink() | ||
300 | { | ||
301 | Parser parser(readMailFromFile("cid-links.mbox")); | ||
302 | printTree(parser.d->mTree,QString()); | ||
303 | QCOMPARE(parser.getPart(QUrl("cid:9359201d15e53f31a68c307b3369b6@info")), parser.d->mTree->subParts().at(1)); | ||
304 | QVERIFY(!parser.getPart(QUrl("cid:"))); | ||
305 | QVERIFY(!parser.getPart(QUrl("cid:unknown"))); | ||
306 | } | ||
307 | }; | ||
308 | |||
309 | QTEST_GUILESS_MAIN(InterfaceTest) | ||
310 | #include "interfacetest.moc" | ||
diff --git a/framework/src/domain/mimetreeparser/tests/kdepim_add_gpg_crypto_test.cmake b/framework/src/domain/mimetreeparser/tests/kdepim_add_gpg_crypto_test.cmake new file mode 100644 index 00000000..ea0ab8d2 --- /dev/null +++ b/framework/src/domain/mimetreeparser/tests/kdepim_add_gpg_crypto_test.cmake | |||
@@ -0,0 +1,61 @@ | |||
1 | # Copyright (c) 2013 Sandro Knauß <mail@sandroknauss.de> | ||
2 | # | ||
3 | # Redistribution and use is allowed according to the terms of the BSD license. | ||
4 | # For details see the accompanying COPYING-CMAKE-SCRIPTS file. | ||
5 | |||
6 | set( MIMETREEPARSERRELPATH framework/src/domain/mimetreeparser) | ||
7 | set( GNUPGHOME ${CMAKE_BINARY_DIR}/${MIMETREEPARSERRELPATH}/tests/gnupg_home ) | ||
8 | add_definitions( -DGNUPGHOME="${GNUPGHOME}" ) | ||
9 | |||
10 | macro (ADD_GPG_CRYPTO_TEST _target _testname) | ||
11 | if (UNIX) | ||
12 | if (APPLE) | ||
13 | set(_library_path_variable "DYLD_LIBRARY_PATH") | ||
14 | elseif (CYGWIN) | ||
15 | set(_library_path_variable "PATH") | ||
16 | else (APPLE) | ||
17 | set(_library_path_variable "LD_LIBRARY_PATH") | ||
18 | endif (APPLE) | ||
19 | |||
20 | if (APPLE) | ||
21 | # DYLD_LIBRARY_PATH does not work like LD_LIBRARY_PATH | ||
22 | # OSX already has the RPATH in libraries and executables, putting runtime directories in | ||
23 | # DYLD_LIBRARY_PATH actually breaks things | ||
24 | set(_ld_library_path "${LIBRARY_OUTPUT_PATH}/${CMAKE_CFG_INTDIR}/") | ||
25 | else (APPLE) | ||
26 | set(_ld_library_path "${LIBRARY_OUTPUT_PATH}/${CMAKE_CFG_INTDIR}/:${LIB_INSTALL_DIR}:${QT_LIBRARY_DIR}") | ||
27 | endif (APPLE) | ||
28 | set(_executable "$<TARGET_FILE:${_target}>") | ||
29 | |||
30 | # use add_custom_target() to have the sh-wrapper generated during build time instead of cmake time | ||
31 | add_custom_command(TARGET ${_target} POST_BUILD | ||
32 | COMMAND ${CMAKE_COMMAND} | ||
33 | -D_filename=${_executable}.shell -D_library_path_variable=${_library_path_variable} | ||
34 | -D_ld_library_path="${_ld_library_path}" -D_executable=$<TARGET_FILE:${_target}> | ||
35 | -D_gnupghome="${GNUPGHOME}" | ||
36 | -P ${CMAKE_SOURCE_DIR}/${MIMETREEPARSERRELPATH}/tests/kdepim_generate_crypto_test_wrapper.cmake | ||
37 | ) | ||
38 | |||
39 | set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES "${_executable}.shell" ) | ||
40 | add_test(NAME ${_testname} COMMAND ${_executable}.shell) | ||
41 | |||
42 | else (UNIX) | ||
43 | # under windows, set the property WRAPPER_SCRIPT just to the name of the executable | ||
44 | # maybe later this will change to a generated batch file (for setting the PATH so that the Qt libs are found) | ||
45 | set(_ld_library_path "${LIBRARY_OUTPUT_PATH}/${CMAKE_CFG_INTDIR}\;${LIB_INSTALL_DIR}\;${QT_LIBRARY_DIR}") | ||
46 | set(_executable "$<TARGET_FILE:${_target}>") | ||
47 | |||
48 | # use add_custom_target() to have the batch-file-wrapper generated during build time instead of cmake time | ||
49 | add_custom_command(TARGET ${_target} POST_BUILD | ||
50 | COMMAND ${CMAKE_COMMAND} | ||
51 | -D_filename="${_executable}.bat" | ||
52 | -D_ld_library_path="${_ld_library_path}" -D_executable="${_executable}" | ||
53 | -D_gnupghome="${GNUPGHOME}" | ||
54 | -P ${CMAKE_SOURCE_DIR}/${MIMETREEPARSERRELPATH}/tests/kdepim_generate_crypto_test_wrapper.cmake | ||
55 | ) | ||
56 | |||
57 | add_test(NAME ${_testname} COMMAND ${_executable}.bat) | ||
58 | |||
59 | endif (UNIX) | ||
60 | endmacro (ADD_GPG_CRYPTO_TEST) | ||
61 | |||
diff --git a/framework/src/domain/mimetreeparser/tests/kdepim_generate_crypto_test_wrapper.cmake b/framework/src/domain/mimetreeparser/tests/kdepim_generate_crypto_test_wrapper.cmake new file mode 100644 index 00000000..e1412f37 --- /dev/null +++ b/framework/src/domain/mimetreeparser/tests/kdepim_generate_crypto_test_wrapper.cmake | |||
@@ -0,0 +1,45 @@ | |||
1 | # Copyright (c) 2006, Alexander Neundorf, <neundorf@kde.org> | ||
2 | # Copyright (c) 2013, Sandro Knauß <mail@sandroknauss.de> | ||
3 | # | ||
4 | # Redistribution and use is allowed according to the terms of the BSD license. | ||
5 | # For details see the accompanying COPYING-CMAKE-SCRIPTS file. | ||
6 | |||
7 | |||
8 | if (UNIX) | ||
9 | |||
10 | file(WRITE "${_filename}" | ||
11 | "#!/bin/sh | ||
12 | # created by cmake, don't edit, changes will be lost | ||
13 | |||
14 | # don't mess with a gpg-agent already running on the system | ||
15 | unset GPG_AGENT_INFO | ||
16 | |||
17 | ${_library_path_variable}=${_ld_library_path}\${${_library_path_variable}:+:\$${_library_path_variable}} GNUPGHOME=${_gnupghome} gpg-agent --daemon \"${_executable}\" \"$@\" | ||
18 | _result=$? | ||
19 | _pid=`echo GETINFO pid | GNUPGHOME=${_gnupghome} gpg-connect-agent | grep 'D' | cut -d' ' -f2` | ||
20 | if [ ! -z \"\$_pid\" ]; then | ||
21 | echo \"Waiting for gpg-agent to terminate (PID: $_pid)...\" | ||
22 | while kill -0 \"\$_pid\"; do | ||
23 | sleep 1 | ||
24 | done | ||
25 | fi | ||
26 | exit \$_result | ||
27 | ") | ||
28 | |||
29 | # make it executable | ||
30 | # since this is only executed on UNIX, it is safe to call chmod | ||
31 | exec_program(chmod ARGS ug+x \"${_filename}\" OUTPUT_VARIABLE _dummy ) | ||
32 | |||
33 | else (UNIX) | ||
34 | |||
35 | file(TO_NATIVE_PATH "${_ld_library_path}" win_path) | ||
36 | file(TO_NATIVE_PATH "${_gnupghome}" win_gnupghome) | ||
37 | |||
38 | file(WRITE "${_filename}" | ||
39 | " | ||
40 | set PATH=${win_path};$ENV{PATH} | ||
41 | set GNUPGHOME=${win_gnupghome};$ENV{GNUPGHOME} | ||
42 | gpg-agent --daemon \"${_executable}\" %* | ||
43 | ") | ||
44 | |||
45 | endif (UNIX) | ||
diff --git a/framework/src/domain/mimetreeparser/thoughts.txt b/framework/src/domain/mimetreeparser/thoughts.txt new file mode 100644 index 00000000..3340347a --- /dev/null +++ b/framework/src/domain/mimetreeparser/thoughts.txt | |||
@@ -0,0 +1,148 @@ | |||
1 | Usecases: | ||
2 | |||
3 | # plaintext msg + attachment | ||
4 | * ContentPart => cp1 | ||
5 | * AttachmentPart => ap1 | ||
6 | |||
7 | (cp1) == collect<ContentPart>(select=NoEncapsulatedMessages) | ||
8 | (ap1) == collect<AttachmentParts>(select=NoEncapsulatedMessages) | ||
9 | |||
10 | (PlainText) == cp1.availableContent() | ||
11 | |||
12 | # html msg + related attachment + normal attachment | ||
13 | * ContentPart => cp1 | ||
14 | * AttachmentPart(mimetype="*/related", cid="12345678") => ap1 | ||
15 | * AttachmentPart => ap2 | ||
16 | |||
17 | (cp1) == collect<ContentPart>(select=NoEncapsulatedMessages) | ||
18 | (ap1, ap2) == collect<AttachmentParts>(select=NoEncapsulatedMessages) | ||
19 | (ap2) == collect<AttachmentParts>(select=NoEncapsulatedMessages, filter=filterelated) | ||
20 | |||
21 | ap1 == getPart("cid:12345678") | ||
22 | |||
23 | (Html) == cp1.availableContent() | ||
24 | |||
25 | # alternative msg + attachment | ||
26 | * ContentPart(html=[Content("HTML"),], plaintext=[Content("Text"),]) => cp1 | ||
27 | * AttachmentPart => ap1 | ||
28 | |||
29 | (cp1) == collect<ContentPart>(select=NoEncapsulatedMessages) | ||
30 | (ap1) == collect<AttachmentParts>(select=NoEncapsulatedMessages) | ||
31 | |||
32 | (Html, PlainText) == cp1.availableContent() | ||
33 | [Content("HTML"),] == cp1.content(Html) | ||
34 | [Content("Text"),] == cp1.content(Plaintext) | ||
35 | |||
36 | # alternative msg with GPGInlin | ||
37 | * ContentPart( | ||
38 | plaintext=[Content("Text"), Content("foo", encryption=(enc1))], | ||
39 | html=[Content("HTML"),] | ||
40 | ) => cp1 | ||
41 | |||
42 | (Html, PlainText) == cp1.availableContent() | ||
43 | |||
44 | [Content("HTML"),] == cp1.content(Html) | ||
45 | [Content("Text"),Content("foo", encryption=(enc1))] == cp1.content(Plaintext) | ||
46 | |||
47 | |||
48 | # encrypted msg (not encrypted/error) with unencrypted attachment | ||
49 | * EncryptionErrorPart => cp1 | ||
50 | * AttachmentPart => ap1 | ||
51 | |||
52 | (cp1) == collect<ContentPart>(select=NoEncapsulatedMessages) | ||
53 | (ap1) == collect<AttachmentParts>(select=NoEncapsulatedMessages) | ||
54 | |||
55 | #encrypted msg (decrypted with attachment) + unencrypted attachment | ||
56 | * encrytion=(rec1,rec2) => enc1 | ||
57 | * ContentPart(encrytion = (enc1,)) => cp1 | ||
58 | * AttachmentPart(encryption = (enc1,)) => ap1 | ||
59 | * AttachmentPart => ap2 | ||
60 | |||
61 | (cp1) == collect<ContentPart>(select=NoEncapsulatedMessages) | ||
62 | (ap1, ap2) == collect<AttachmentParts>(select=NoEncapsulatedMessages) | ||
63 | |||
64 | #INLINE GPG encrypted msg + attachment | ||
65 | * ContentPart => cp1 with | ||
66 | plaintext=[Content, Content(encrytion = (enc1(rec1,rec2),)), Content(signed = (sig1,)), Content] | ||
67 | * AttachmentPart => ap1 | ||
68 | |||
69 | (cp1) == collect<ContentPart>(select=NoEncapsulatedMessages) | ||
70 | (ap1) == collect<AttachmentParts>(select=NoEncapsulatedMessages) | ||
71 | |||
72 | [Content, Content(encrytion = (enc1(rec1,rec2),)), Content(signed = (sig1,)), Content] == cp1.content(Plaintext) | ||
73 | |||
74 | #forwared encrypted msg + attachments | ||
75 | * ContentPart => cp1 | ||
76 | * EncapsulatedPart => ep1 | ||
77 | * Encrytion=(rec1,rec2) => enc1 | ||
78 | * Signature => sig1 | ||
79 | * ContentPart(encrytion = (enc1,), signature = (sig1,)) => cp2 | ||
80 | * Content(encrytion = (enc1,), signature = (sig1,)) | ||
81 | * Content(encrytion = (enc1, enc2(rec3,rec4),), signature = (sig1,)) | ||
82 | * AttachmentPart(encrytion = (enc1,), signature = (sig1,)) => ap1 | ||
83 | * AttachmentPart => ap2 | ||
84 | |||
85 | (cp1) = collect<ContentPart>(select=NoEncapsulatedMessages) | ||
86 | (ap2) = collect<AttachmentParts>(select=NoEncapsulatedMessages) | ||
87 | |||
88 | (cp2) = collect<ContentPart>(ep1, select=NoEncapsulatedMessages) | ||
89 | (ap1) = collect<AttachmentParts>(ep1, select=NoEncapsulatedMessages) | ||
90 | |||
91 | (cp1, cp2) == collect<ContentPart>() | ||
92 | (ap1, ap2) == collect<AttachmentParts>()[Content, Content(encrytion = (enc1(rec1,rec2),)), Content(signed = (sig1,)), Content] | ||
93 | |||
94 | |||
95 | # plaintext msg + attachment + cert | ||
96 | * ContentPart => cp1 | ||
97 | * AttachmentPart => ap1 | ||
98 | * CertPart => cep1 | ||
99 | |||
100 | (cp1) == collect<ContentPart>(select=NoEncapsulatedMessages) | ||
101 | (ap1, cep1) == collect<AttachmentPart>(select=NoEncapsulatedMessages) | ||
102 | (ap1) == collect<AttachmentPart>(select=NoEncapsulatedMessages, filter=filterSubAttachmentParts) | ||
103 | |||
104 | (cep1) == collect<CertPart>(select=NoEncapsulatedMessages) | ||
105 | |||
106 | |||
107 | collect function: | ||
108 | |||
109 | bool noEncapsulatedMessages(Part part) | ||
110 | { | ||
111 | if (is<EncapsulatedPart>(part)) { | ||
112 | return false; | ||
113 | } | ||
114 | return true; | ||
115 | } | ||
116 | |||
117 | bool filterRelated(T part) | ||
118 | { | ||
119 | if (part.mimetype == related && !part.cid.isEmpty()) { | ||
120 | return false; //filter out related parts | ||
121 | } | ||
122 | return true; | ||
123 | } | ||
124 | |||
125 | bool filterSubAttachmentParts(AttachmentPart part) | ||
126 | { | ||
127 | if (isSubPart<AttachmentPart>(part)) { | ||
128 | return false; // filter out CertPart f.ex. | ||
129 | } | ||
130 | return true; | ||
131 | } | ||
132 | |||
133 | List<T> collect<T>(Part start, std::function<bool(const Part &)> select, std::function<bool(const std::shared_ptr<T> &)> filter) { | ||
134 | List<T> col; | ||
135 | if (!select(start)) { | ||
136 | return col; | ||
137 | } | ||
138 | |||
139 | if(isOrSubTypeIs<T>(start) && filter(start.staticCast<T>)){ | ||
140 | col.append(p); | ||
141 | } | ||
142 | foreach(childs as child) { | ||
143 | if (select(child)) { | ||
144 | col.expand(collect(child,select,filter); | ||
145 | } | ||
146 | } | ||
147 | return col; | ||
148 | } \ No newline at end of file | ||
diff --git a/framework/src/domain/modeltest.cpp b/framework/src/domain/modeltest.cpp new file mode 100644 index 00000000..2bd3d27f --- /dev/null +++ b/framework/src/domain/modeltest.cpp | |||
@@ -0,0 +1,588 @@ | |||
1 | /**************************************************************************** | ||
2 | ** | ||
3 | ** Copyright (C) 2016 The Qt Company Ltd. | ||
4 | ** Contact: https://www.qt.io/licensing/ | ||
5 | ** | ||
6 | ** This file is part of the test suite of the Qt Toolkit. | ||
7 | ** | ||
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ | ||
9 | ** Commercial License Usage | ||
10 | ** Licensees holding valid commercial Qt licenses may use this file in | ||
11 | ** accordance with the commercial license agreement provided with the | ||
12 | ** Software or, alternatively, in accordance with the terms contained in | ||
13 | ** a written agreement between you and The Qt Company. For licensing terms | ||
14 | ** and conditions see https://www.qt.io/terms-conditions. For further | ||
15 | ** information use the contact form at https://www.qt.io/contact-us. | ||
16 | ** | ||
17 | ** GNU General Public License Usage | ||
18 | ** Alternatively, this file may be used under the terms of the GNU | ||
19 | ** General Public License version 3 as published by the Free Software | ||
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT | ||
21 | ** included in the packaging of this file. Please review the following | ||
22 | ** information to ensure the GNU General Public License requirements will | ||
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. | ||
24 | ** | ||
25 | ** $QT_END_LICENSE$ | ||
26 | ** | ||
27 | ****************************************************************************/ | ||
28 | |||
29 | #include "modeltest.h" | ||
30 | |||
31 | #include <QtCore/QtCore> | ||
32 | #include <QtTest/QtTest> | ||
33 | |||
34 | #include <QDebug> | ||
35 | /*! | ||
36 | Connect to all of the models signals. Whenever anything happens recheck everything. | ||
37 | */ | ||
38 | ModelTest::ModelTest ( QAbstractItemModel *_model, QObject *parent ) : QObject ( parent ), model ( _model ), fetchingMore ( false ) | ||
39 | { | ||
40 | if (!model) | ||
41 | qFatal("%s: model must not be null", Q_FUNC_INFO); | ||
42 | |||
43 | connect(model, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), | ||
44 | this, SLOT(runAllTests()) ); | ||
45 | connect(model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), | ||
46 | this, SLOT(runAllTests()) ); | ||
47 | connect(model, SIGNAL(columnsInserted(QModelIndex,int,int)), | ||
48 | this, SLOT(runAllTests()) ); | ||
49 | connect(model, SIGNAL(columnsRemoved(QModelIndex,int,int)), | ||
50 | this, SLOT(runAllTests()) ); | ||
51 | connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), | ||
52 | this, SLOT(runAllTests()) ); | ||
53 | connect(model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), | ||
54 | this, SLOT(runAllTests()) ); | ||
55 | connect(model, SIGNAL(layoutAboutToBeChanged()), this, SLOT(runAllTests()) ); | ||
56 | connect(model, SIGNAL(layoutChanged()), this, SLOT(runAllTests()) ); | ||
57 | connect(model, SIGNAL(modelReset()), this, SLOT(runAllTests()) ); | ||
58 | connect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), | ||
59 | this, SLOT(runAllTests()) ); | ||
60 | connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), | ||
61 | this, SLOT(runAllTests()) ); | ||
62 | connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), | ||
63 | this, SLOT(runAllTests()) ); | ||
64 | connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), | ||
65 | this, SLOT(runAllTests()) ); | ||
66 | |||
67 | // Special checks for changes | ||
68 | connect(model, SIGNAL(layoutAboutToBeChanged()), | ||
69 | this, SLOT(layoutAboutToBeChanged()) ); | ||
70 | connect(model, SIGNAL(layoutChanged()), | ||
71 | this, SLOT(layoutChanged()) ); | ||
72 | |||
73 | connect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), | ||
74 | this, SLOT(rowsAboutToBeInserted(QModelIndex,int,int)) ); | ||
75 | connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), | ||
76 | this, SLOT(rowsAboutToBeRemoved(QModelIndex,int,int)) ); | ||
77 | connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), | ||
78 | this, SLOT(rowsInserted(QModelIndex,int,int)) ); | ||
79 | connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), | ||
80 | this, SLOT(rowsRemoved(QModelIndex,int,int)) ); | ||
81 | connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), | ||
82 | this, SLOT(dataChanged(QModelIndex,QModelIndex)) ); | ||
83 | connect(model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), | ||
84 | this, SLOT(headerDataChanged(Qt::Orientation,int,int)) ); | ||
85 | |||
86 | runAllTests(); | ||
87 | } | ||
88 | |||
89 | void ModelTest::runAllTests() | ||
90 | { | ||
91 | if ( fetchingMore ) | ||
92 | return; | ||
93 | nonDestructiveBasicTest(); | ||
94 | rowCount(); | ||
95 | columnCount(); | ||
96 | hasIndex(); | ||
97 | index(); | ||
98 | parent(); | ||
99 | data(); | ||
100 | } | ||
101 | |||
102 | /*! | ||
103 | nonDestructiveBasicTest tries to call a number of the basic functions (not all) | ||
104 | to make sure the model doesn't outright segfault, testing the functions that makes sense. | ||
105 | */ | ||
106 | void ModelTest::nonDestructiveBasicTest() | ||
107 | { | ||
108 | QVERIFY( model->buddy ( QModelIndex() ) == QModelIndex() ); | ||
109 | model->canFetchMore ( QModelIndex() ); | ||
110 | QVERIFY( model->columnCount ( QModelIndex() ) >= 0 ); | ||
111 | QVERIFY( model->data ( QModelIndex() ) == QVariant() ); | ||
112 | fetchingMore = true; | ||
113 | model->fetchMore ( QModelIndex() ); | ||
114 | fetchingMore = false; | ||
115 | Qt::ItemFlags flags = model->flags ( QModelIndex() ); | ||
116 | QVERIFY( flags == Qt::ItemIsDropEnabled || flags == 0 ); | ||
117 | model->hasChildren ( QModelIndex() ); | ||
118 | model->hasIndex ( 0, 0 ); | ||
119 | model->headerData ( 0, Qt::Horizontal ); | ||
120 | model->index ( 0, 0 ); | ||
121 | model->itemData ( QModelIndex() ); | ||
122 | QVariant cache; | ||
123 | model->match ( QModelIndex(), -1, cache ); | ||
124 | model->mimeTypes(); | ||
125 | QVERIFY( model->parent ( QModelIndex() ) == QModelIndex() ); | ||
126 | QVERIFY( model->rowCount() >= 0 ); | ||
127 | QVariant variant; | ||
128 | model->setData ( QModelIndex(), variant, -1 ); | ||
129 | model->setHeaderData ( -1, Qt::Horizontal, QVariant() ); | ||
130 | model->setHeaderData ( 999999, Qt::Horizontal, QVariant() ); | ||
131 | QMap<int, QVariant> roles; | ||
132 | model->sibling ( 0, 0, QModelIndex() ); | ||
133 | model->span ( QModelIndex() ); | ||
134 | model->supportedDropActions(); | ||
135 | } | ||
136 | |||
137 | /*! | ||
138 | Tests model's implementation of QAbstractItemModel::rowCount() and hasChildren() | ||
139 | |||
140 | Models that are dynamically populated are not as fully tested here. | ||
141 | */ | ||
142 | void ModelTest::rowCount() | ||
143 | { | ||
144 | // qDebug() << "rc"; | ||
145 | // check top row | ||
146 | QModelIndex topIndex = model->index ( 0, 0, QModelIndex() ); | ||
147 | int rows = model->rowCount ( topIndex ); | ||
148 | QVERIFY( rows >= 0 ); | ||
149 | if ( rows > 0 ) | ||
150 | QVERIFY( model->hasChildren ( topIndex ) ); | ||
151 | |||
152 | QModelIndex secondLevelIndex = model->index ( 0, 0, topIndex ); | ||
153 | if ( secondLevelIndex.isValid() ) { // not the top level | ||
154 | // check a row count where parent is valid | ||
155 | rows = model->rowCount ( secondLevelIndex ); | ||
156 | QVERIFY( rows >= 0 ); | ||
157 | if ( rows > 0 ) | ||
158 | QVERIFY( model->hasChildren ( secondLevelIndex ) ); | ||
159 | } | ||
160 | |||
161 | // The models rowCount() is tested more extensively in checkChildren(), | ||
162 | // but this catches the big mistakes | ||
163 | } | ||
164 | |||
165 | /*! | ||
166 | Tests model's implementation of QAbstractItemModel::columnCount() and hasChildren() | ||
167 | */ | ||
168 | void ModelTest::columnCount() | ||
169 | { | ||
170 | // check top row | ||
171 | QModelIndex topIndex = model->index ( 0, 0, QModelIndex() ); | ||
172 | QVERIFY( model->columnCount ( topIndex ) >= 0 ); | ||
173 | |||
174 | // check a column count where parent is valid | ||
175 | QModelIndex childIndex = model->index ( 0, 0, topIndex ); | ||
176 | if ( childIndex.isValid() ) | ||
177 | QVERIFY( model->columnCount ( childIndex ) >= 0 ); | ||
178 | |||
179 | // columnCount() is tested more extensively in checkChildren(), | ||
180 | // but this catches the big mistakes | ||
181 | } | ||
182 | |||
183 | /*! | ||
184 | Tests model's implementation of QAbstractItemModel::hasIndex() | ||
185 | */ | ||
186 | void ModelTest::hasIndex() | ||
187 | { | ||
188 | // qDebug() << "hi"; | ||
189 | // Make sure that invalid values returns an invalid index | ||
190 | QVERIFY( !model->hasIndex ( -2, -2 ) ); | ||
191 | QVERIFY( !model->hasIndex ( -2, 0 ) ); | ||
192 | QVERIFY( !model->hasIndex ( 0, -2 ) ); | ||
193 | |||
194 | int rows = model->rowCount(); | ||
195 | int columns = model->columnCount(); | ||
196 | |||
197 | // check out of bounds | ||
198 | QVERIFY( !model->hasIndex ( rows, columns ) ); | ||
199 | QVERIFY( !model->hasIndex ( rows + 1, columns + 1 ) ); | ||
200 | |||
201 | if ( rows > 0 ) | ||
202 | QVERIFY( model->hasIndex ( 0, 0 ) ); | ||
203 | |||
204 | // hasIndex() is tested more extensively in checkChildren(), | ||
205 | // but this catches the big mistakes | ||
206 | } | ||
207 | |||
208 | /*! | ||
209 | Tests model's implementation of QAbstractItemModel::index() | ||
210 | */ | ||
211 | void ModelTest::index() | ||
212 | { | ||
213 | // qDebug() << "i"; | ||
214 | // Make sure that invalid values returns an invalid index | ||
215 | QVERIFY( model->index ( -2, -2 ) == QModelIndex() ); | ||
216 | QVERIFY( model->index ( -2, 0 ) == QModelIndex() ); | ||
217 | QVERIFY( model->index ( 0, -2 ) == QModelIndex() ); | ||
218 | |||
219 | int rows = model->rowCount(); | ||
220 | int columns = model->columnCount(); | ||
221 | |||
222 | if ( rows == 0 ) | ||
223 | return; | ||
224 | |||
225 | // Catch off by one errors | ||
226 | QVERIFY( model->index ( rows, columns ) == QModelIndex() ); | ||
227 | QVERIFY( model->index ( 0, 0 ).isValid() ); | ||
228 | |||
229 | // Make sure that the same index is *always* returned | ||
230 | QModelIndex a = model->index ( 0, 0 ); | ||
231 | QModelIndex b = model->index ( 0, 0 ); | ||
232 | QVERIFY( a == b ); | ||
233 | |||
234 | // index() is tested more extensively in checkChildren(), | ||
235 | // but this catches the big mistakes | ||
236 | } | ||
237 | |||
238 | /*! | ||
239 | Tests model's implementation of QAbstractItemModel::parent() | ||
240 | */ | ||
241 | void ModelTest::parent() | ||
242 | { | ||
243 | // qDebug() << "p"; | ||
244 | // Make sure the model won't crash and will return an invalid QModelIndex | ||
245 | // when asked for the parent of an invalid index. | ||
246 | QVERIFY( model->parent ( QModelIndex() ) == QModelIndex() ); | ||
247 | |||
248 | if ( model->rowCount() == 0 ) | ||
249 | return; | ||
250 | |||
251 | // Column 0 | Column 1 | | ||
252 | // QModelIndex() | | | ||
253 | // \- topIndex | topIndex1 | | ||
254 | // \- childIndex | childIndex1 | | ||
255 | |||
256 | // Common error test #1, make sure that a top level index has a parent | ||
257 | // that is a invalid QModelIndex. | ||
258 | QModelIndex topIndex = model->index ( 0, 0, QModelIndex() ); | ||
259 | QVERIFY( model->parent ( topIndex ) == QModelIndex() ); | ||
260 | |||
261 | // Common error test #2, make sure that a second level index has a parent | ||
262 | // that is the first level index. | ||
263 | if ( model->rowCount ( topIndex ) > 0 ) { | ||
264 | QModelIndex childIndex = model->index ( 0, 0, topIndex ); | ||
265 | QVERIFY( model->parent ( childIndex ) == topIndex ); | ||
266 | } | ||
267 | |||
268 | // Common error test #3, the second column should NOT have the same children | ||
269 | // as the first column in a row. | ||
270 | // Usually the second column shouldn't have children. | ||
271 | QModelIndex topIndex1 = model->index ( 0, 1, QModelIndex() ); | ||
272 | if ( model->rowCount ( topIndex1 ) > 0 ) { | ||
273 | QModelIndex childIndex = model->index ( 0, 0, topIndex ); | ||
274 | QModelIndex childIndex1 = model->index ( 0, 0, topIndex1 ); | ||
275 | QVERIFY( childIndex != childIndex1 ); | ||
276 | } | ||
277 | |||
278 | // Full test, walk n levels deep through the model making sure that all | ||
279 | // parent's children correctly specify their parent. | ||
280 | checkChildren ( QModelIndex() ); | ||
281 | } | ||
282 | |||
283 | /*! | ||
284 | Called from the parent() test. | ||
285 | |||
286 | A model that returns an index of parent X should also return X when asking | ||
287 | for the parent of the index. | ||
288 | |||
289 | This recursive function does pretty extensive testing on the whole model in an | ||
290 | effort to catch edge cases. | ||
291 | |||
292 | This function assumes that rowCount(), columnCount() and index() already work. | ||
293 | If they have a bug it will point it out, but the above tests should have already | ||
294 | found the basic bugs because it is easier to figure out the problem in | ||
295 | those tests then this one. | ||
296 | */ | ||
297 | void ModelTest::checkChildren ( const QModelIndex &parent, int currentDepth ) | ||
298 | { | ||
299 | // First just try walking back up the tree. | ||
300 | QModelIndex p = parent; | ||
301 | while ( p.isValid() ) | ||
302 | p = p.parent(); | ||
303 | |||
304 | // For models that are dynamically populated | ||
305 | if ( model->canFetchMore ( parent ) ) { | ||
306 | fetchingMore = true; | ||
307 | model->fetchMore ( parent ); | ||
308 | fetchingMore = false; | ||
309 | } | ||
310 | |||
311 | int rows = model->rowCount ( parent ); | ||
312 | int columns = model->columnCount ( parent ); | ||
313 | |||
314 | if ( rows > 0 ) | ||
315 | QVERIFY( model->hasChildren ( parent ) ); | ||
316 | |||
317 | // Some further testing against rows(), columns(), and hasChildren() | ||
318 | QVERIFY( rows >= 0 ); | ||
319 | QVERIFY( columns >= 0 ); | ||
320 | if ( rows > 0 ) | ||
321 | QVERIFY( model->hasChildren ( parent ) ); | ||
322 | |||
323 | qDebug() << "parent:" << model->data(parent).toString() << "rows:" << rows | ||
324 | << "columns:" << columns << "parent column:" << parent.column(); | ||
325 | |||
326 | const QModelIndex topLeftChild = model->index( 0, 0, parent ); | ||
327 | |||
328 | QVERIFY( !model->hasIndex ( rows + 1, 0, parent ) ); | ||
329 | for ( int r = 0; r < rows; ++r ) { | ||
330 | if ( model->canFetchMore ( parent ) ) { | ||
331 | fetchingMore = true; | ||
332 | model->fetchMore ( parent ); | ||
333 | fetchingMore = false; | ||
334 | } | ||
335 | QVERIFY( !model->hasIndex ( r, columns + 1, parent ) ); | ||
336 | for ( int c = 0; c < columns; ++c ) { | ||
337 | QVERIFY( model->hasIndex ( r, c, parent ) ); | ||
338 | QModelIndex index = model->index ( r, c, parent ); | ||
339 | // rowCount() and columnCount() said that it existed... | ||
340 | QVERIFY( index.isValid() ); | ||
341 | |||
342 | qDebug() << "\tchild("<< r <<", " << c << ", " << index.column() << "):" << model->data(index).toString() << index.internalPointer(); | ||
343 | |||
344 | // index() should always return the same index when called twice in a row | ||
345 | QModelIndex modifiedIndex = model->index ( r, c, parent ); | ||
346 | QVERIFY( index == modifiedIndex ); | ||
347 | |||
348 | // Make sure we get the same index if we request it twice in a row | ||
349 | QModelIndex a = model->index ( r, c, parent ); | ||
350 | QModelIndex b = model->index ( r, c, parent ); | ||
351 | QVERIFY( a == b ); | ||
352 | |||
353 | { | ||
354 | const QModelIndex sibling = model->sibling( r, c, topLeftChild ); | ||
355 | QVERIFY( index == sibling ); | ||
356 | } | ||
357 | { | ||
358 | const QModelIndex sibling = topLeftChild.sibling( r, c ); | ||
359 | QVERIFY( index == sibling ); | ||
360 | } | ||
361 | |||
362 | // Some basic checking on the index that is returned | ||
363 | QVERIFY( index.model() == model ); | ||
364 | QCOMPARE( index.row(), r ); | ||
365 | QCOMPARE( index.column(), c ); | ||
366 | // While you can technically return a QVariant usually this is a sign | ||
367 | // of a bug in data(). Disable if this really is ok in your model. | ||
368 | // QVERIFY( model->data ( index, Qt::DisplayRole ).isValid() ); | ||
369 | |||
370 | // If the next test fails here is some somewhat useful debug you play with. | ||
371 | |||
372 | if (model->parent(index) != parent) { | ||
373 | qDebug() << r << c << currentDepth << model->data(index).toString() | ||
374 | << model->data(parent).toString(); | ||
375 | qDebug() << index << parent << model->parent(index); | ||
376 | // And a view that you can even use to show the model. | ||
377 | // QTreeView view; | ||
378 | // view.setModel(model); | ||
379 | // view.show(); | ||
380 | } | ||
381 | |||
382 | // Check that we can get back our real parent. | ||
383 | QCOMPARE( model->parent ( index ), parent ); | ||
384 | |||
385 | // recursively go down the children | ||
386 | if ( model->hasChildren ( index ) && currentDepth < 10 ) { | ||
387 | qDebug() << r << c << "has children" << model->rowCount(index); | ||
388 | checkChildren ( index, ++currentDepth ); | ||
389 | }/* else { if (currentDepth >= 10) qDebug() << "checked 10 deep"; };*/ | ||
390 | |||
391 | // make sure that after testing the children that the index doesn't change. | ||
392 | QModelIndex newerIndex = model->index ( r, c, parent ); | ||
393 | QVERIFY( index == newerIndex ); | ||
394 | } | ||
395 | } | ||
396 | } | ||
397 | |||
398 | /*! | ||
399 | Tests model's implementation of QAbstractItemModel::data() | ||
400 | */ | ||
401 | void ModelTest::data() | ||
402 | { | ||
403 | // Invalid index should return an invalid qvariant | ||
404 | QVERIFY( !model->data ( QModelIndex() ).isValid() ); | ||
405 | |||
406 | if ( model->rowCount() == 0 ) | ||
407 | return; | ||
408 | |||
409 | // A valid index should have a valid QVariant data | ||
410 | QVERIFY( model->index ( 0, 0 ).isValid() ); | ||
411 | |||
412 | // shouldn't be able to set data on an invalid index | ||
413 | QVERIFY( !model->setData ( QModelIndex(), QLatin1String ( "foo" ), Qt::DisplayRole ) ); | ||
414 | |||
415 | // General Purpose roles that should return a QString | ||
416 | QVariant variant = model->data ( model->index ( 0, 0 ), Qt::ToolTipRole ); | ||
417 | if ( variant.isValid() ) { | ||
418 | QVERIFY( variant.canConvert<QString>() ); | ||
419 | } | ||
420 | variant = model->data ( model->index ( 0, 0 ), Qt::StatusTipRole ); | ||
421 | if ( variant.isValid() ) { | ||
422 | QVERIFY( variant.canConvert<QString>() ); | ||
423 | } | ||
424 | variant = model->data ( model->index ( 0, 0 ), Qt::WhatsThisRole ); | ||
425 | if ( variant.isValid() ) { | ||
426 | QVERIFY( variant.canConvert<QString>() ); | ||
427 | } | ||
428 | |||
429 | // General Purpose roles that should return a QSize | ||
430 | variant = model->data ( model->index ( 0, 0 ), Qt::SizeHintRole ); | ||
431 | if ( variant.isValid() ) { | ||
432 | QVERIFY( variant.canConvert<QSize>() ); | ||
433 | } | ||
434 | |||
435 | // General Purpose roles that should return a QFont | ||
436 | QVariant fontVariant = model->data ( model->index ( 0, 0 ), Qt::FontRole ); | ||
437 | if ( fontVariant.isValid() ) { | ||
438 | QVERIFY( fontVariant.canConvert<QFont>() ); | ||
439 | } | ||
440 | |||
441 | // Check that the alignment is one we know about | ||
442 | QVariant textAlignmentVariant = model->data ( model->index ( 0, 0 ), Qt::TextAlignmentRole ); | ||
443 | if ( textAlignmentVariant.isValid() ) { | ||
444 | int alignment = textAlignmentVariant.toInt(); | ||
445 | QCOMPARE( alignment, ( alignment & ( Qt::AlignHorizontal_Mask | Qt::AlignVertical_Mask ) ) ); | ||
446 | } | ||
447 | |||
448 | // General Purpose roles that should return a QColor | ||
449 | QVariant colorVariant = model->data ( model->index ( 0, 0 ), Qt::BackgroundColorRole ); | ||
450 | if ( colorVariant.isValid() ) { | ||
451 | QVERIFY( colorVariant.canConvert<QColor>() ); | ||
452 | } | ||
453 | |||
454 | colorVariant = model->data ( model->index ( 0, 0 ), Qt::TextColorRole ); | ||
455 | if ( colorVariant.isValid() ) { | ||
456 | QVERIFY( colorVariant.canConvert<QColor>() ); | ||
457 | } | ||
458 | |||
459 | // Check that the "check state" is one we know about. | ||
460 | QVariant checkStateVariant = model->data ( model->index ( 0, 0 ), Qt::CheckStateRole ); | ||
461 | if ( checkStateVariant.isValid() ) { | ||
462 | int state = checkStateVariant.toInt(); | ||
463 | QVERIFY( state == Qt::Unchecked || | ||
464 | state == Qt::PartiallyChecked || | ||
465 | state == Qt::Checked ); | ||
466 | } | ||
467 | } | ||
468 | |||
469 | /*! | ||
470 | Store what is about to be inserted to make sure it actually happens | ||
471 | |||
472 | \sa rowsInserted() | ||
473 | */ | ||
474 | void ModelTest::rowsAboutToBeInserted ( const QModelIndex &parent, int start, int /* end */) | ||
475 | { | ||
476 | // Q_UNUSED(end); | ||
477 | // qDebug() << "rowsAboutToBeInserted" << "start=" << start << "end=" << end << "parent=" << model->data ( parent ).toString() | ||
478 | // << "current count of parent=" << model->rowCount ( parent ); // << "display of last=" << model->data( model->index(start-1, 0, parent) ); | ||
479 | // qDebug() << model->index(start-1, 0, parent) << model->data( model->index(start-1, 0, parent) ); | ||
480 | Changing c; | ||
481 | c.parent = parent; | ||
482 | c.oldSize = model->rowCount ( parent ); | ||
483 | c.last = model->data ( model->index ( start - 1, 0, parent ) ); | ||
484 | c.next = model->data ( model->index ( start, 0, parent ) ); | ||
485 | insert.push ( c ); | ||
486 | } | ||
487 | |||
488 | /*! | ||
489 | Confirm that what was said was going to happen actually did | ||
490 | |||
491 | \sa rowsAboutToBeInserted() | ||
492 | */ | ||
493 | void ModelTest::rowsInserted ( const QModelIndex & parent, int start, int end ) | ||
494 | { | ||
495 | Changing c = insert.pop(); | ||
496 | QVERIFY( c.parent == parent ); | ||
497 | // qDebug() << "rowsInserted" << "start=" << start << "end=" << end << "oldsize=" << c.oldSize | ||
498 | // << "parent=" << model->data ( parent ).toString() << "current rowcount of parent=" << model->rowCount ( parent ); | ||
499 | |||
500 | // for (int ii=start; ii <= end; ii++) | ||
501 | // { | ||
502 | // qDebug() << "itemWasInserted:" << ii << model->data ( model->index ( ii, 0, parent )); | ||
503 | // } | ||
504 | // qDebug(); | ||
505 | |||
506 | QVERIFY( c.oldSize + ( end - start + 1 ) == model->rowCount ( parent ) ); | ||
507 | QVERIFY( c.last == model->data ( model->index ( start - 1, 0, c.parent ) ) ); | ||
508 | |||
509 | if (c.next != model->data(model->index(end + 1, 0, c.parent))) { | ||
510 | qDebug() << start << end; | ||
511 | for (int i=0; i < model->rowCount(); ++i) | ||
512 | qDebug() << model->index(i, 0).data().toString(); | ||
513 | qDebug() << c.next << model->data(model->index(end + 1, 0, c.parent)); | ||
514 | } | ||
515 | |||
516 | QVERIFY( c.next == model->data ( model->index ( end + 1, 0, c.parent ) ) ); | ||
517 | } | ||
518 | |||
519 | void ModelTest::layoutAboutToBeChanged() | ||
520 | { | ||
521 | for ( int i = 0; i < qBound ( 0, model->rowCount(), 100 ); ++i ) | ||
522 | changing.append ( QPersistentModelIndex ( model->index ( i, 0 ) ) ); | ||
523 | } | ||
524 | |||
525 | void ModelTest::layoutChanged() | ||
526 | { | ||
527 | for ( int i = 0; i < changing.count(); ++i ) { | ||
528 | QPersistentModelIndex p = changing[i]; | ||
529 | QVERIFY( p == model->index ( p.row(), p.column(), p.parent() ) ); | ||
530 | } | ||
531 | changing.clear(); | ||
532 | } | ||
533 | |||
534 | /*! | ||
535 | Store what is about to be inserted to make sure it actually happens | ||
536 | |||
537 | \sa rowsRemoved() | ||
538 | */ | ||
539 | void ModelTest::rowsAboutToBeRemoved ( const QModelIndex &parent, int start, int end ) | ||
540 | { | ||
541 | qDebug() << "ratbr" << parent << start << end; | ||
542 | Changing c; | ||
543 | c.parent = parent; | ||
544 | c.oldSize = model->rowCount ( parent ); | ||
545 | c.last = model->data ( model->index ( start - 1, 0, parent ) ); | ||
546 | c.next = model->data ( model->index ( end + 1, 0, parent ) ); | ||
547 | remove.push ( c ); | ||
548 | } | ||
549 | |||
550 | /*! | ||
551 | Confirm that what was said was going to happen actually did | ||
552 | |||
553 | \sa rowsAboutToBeRemoved() | ||
554 | */ | ||
555 | void ModelTest::rowsRemoved ( const QModelIndex & parent, int start, int end ) | ||
556 | { | ||
557 | qDebug() << "rr" << parent << start << end; | ||
558 | Changing c = remove.pop(); | ||
559 | QVERIFY( c.parent == parent ); | ||
560 | QVERIFY( c.oldSize - ( end - start + 1 ) == model->rowCount ( parent ) ); | ||
561 | QVERIFY( c.last == model->data ( model->index ( start - 1, 0, c.parent ) ) ); | ||
562 | QVERIFY( c.next == model->data ( model->index ( start, 0, c.parent ) ) ); | ||
563 | } | ||
564 | |||
565 | void ModelTest::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) | ||
566 | { | ||
567 | QVERIFY(topLeft.isValid()); | ||
568 | QVERIFY(bottomRight.isValid()); | ||
569 | QModelIndex commonParent = bottomRight.parent(); | ||
570 | QCOMPARE(topLeft.parent(), commonParent); | ||
571 | QVERIFY(topLeft.row() <= bottomRight.row()); | ||
572 | QVERIFY(topLeft.column() <= bottomRight.column()); | ||
573 | int rowCount = model->rowCount(commonParent); | ||
574 | int columnCount = model->columnCount(commonParent); | ||
575 | QVERIFY(bottomRight.row() < rowCount); | ||
576 | QVERIFY(bottomRight.column() < columnCount); | ||
577 | } | ||
578 | |||
579 | void ModelTest::headerDataChanged(Qt::Orientation orientation, int start, int end) | ||
580 | { | ||
581 | QVERIFY(start >= 0); | ||
582 | QVERIFY(end >= 0); | ||
583 | QVERIFY(start <= end); | ||
584 | int itemCount = orientation == Qt::Vertical ? model->rowCount() : model->columnCount(); | ||
585 | QVERIFY(start < itemCount); | ||
586 | QVERIFY(end < itemCount); | ||
587 | } | ||
588 | |||
diff --git a/framework/src/domain/modeltest.h b/framework/src/domain/modeltest.h new file mode 100644 index 00000000..735a4227 --- /dev/null +++ b/framework/src/domain/modeltest.h | |||
@@ -0,0 +1,83 @@ | |||
1 | /**************************************************************************** | ||
2 | ** | ||
3 | ** Copyright (C) 2016 The Qt Company Ltd. | ||
4 | ** Contact: https://www.qt.io/licensing/ | ||
5 | ** | ||
6 | ** This file is part of the test suite of the Qt Toolkit. | ||
7 | ** | ||
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ | ||
9 | ** Commercial License Usage | ||
10 | ** Licensees holding valid commercial Qt licenses may use this file in | ||
11 | ** accordance with the commercial license agreement provided with the | ||
12 | ** Software or, alternatively, in accordance with the terms contained in | ||
13 | ** a written agreement between you and The Qt Company. For licensing terms | ||
14 | ** and conditions see https://www.qt.io/terms-conditions. For further | ||
15 | ** information use the contact form at https://www.qt.io/contact-us. | ||
16 | ** | ||
17 | ** GNU General Public License Usage | ||
18 | ** Alternatively, this file may be used under the terms of the GNU | ||
19 | ** General Public License version 3 as published by the Free Software | ||
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT | ||
21 | ** included in the packaging of this file. Please review the following | ||
22 | ** information to ensure the GNU General Public License requirements will | ||
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. | ||
24 | ** | ||
25 | ** $QT_END_LICENSE$ | ||
26 | ** | ||
27 | ****************************************************************************/ | ||
28 | |||
29 | |||
30 | #ifndef MODELTEST_H | ||
31 | #define MODELTEST_H | ||
32 | |||
33 | #include <QtCore/QObject> | ||
34 | #include <QtCore/QAbstractItemModel> | ||
35 | #include <QtCore/QStack> | ||
36 | |||
37 | class ModelTest : public QObject | ||
38 | { | ||
39 | Q_OBJECT | ||
40 | |||
41 | public: | ||
42 | ModelTest( QAbstractItemModel *model, QObject *parent = 0 ); | ||
43 | |||
44 | private Q_SLOTS: | ||
45 | void nonDestructiveBasicTest(); | ||
46 | void rowCount(); | ||
47 | void columnCount(); | ||
48 | void hasIndex(); | ||
49 | void index(); | ||
50 | void parent(); | ||
51 | void data(); | ||
52 | |||
53 | protected Q_SLOTS: | ||
54 | void runAllTests(); | ||
55 | void layoutAboutToBeChanged(); | ||
56 | void layoutChanged(); | ||
57 | void rowsAboutToBeInserted( const QModelIndex &parent, int start, int end ); | ||
58 | void rowsInserted( const QModelIndex & parent, int start, int end ); | ||
59 | void rowsAboutToBeRemoved( const QModelIndex &parent, int start, int end ); | ||
60 | void rowsRemoved( const QModelIndex & parent, int start, int end ); | ||
61 | void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); | ||
62 | void headerDataChanged(Qt::Orientation orientation, int start, int end); | ||
63 | |||
64 | private: | ||
65 | void checkChildren( const QModelIndex &parent, int currentDepth = 0 ); | ||
66 | |||
67 | QAbstractItemModel *model; | ||
68 | |||
69 | struct Changing { | ||
70 | QModelIndex parent; | ||
71 | int oldSize; | ||
72 | QVariant last; | ||
73 | QVariant next; | ||
74 | }; | ||
75 | QStack<Changing> insert; | ||
76 | QStack<Changing> remove; | ||
77 | |||
78 | bool fetchingMore; | ||
79 | |||
80 | QList<QPersistentModelIndex> changing; | ||
81 | }; | ||
82 | |||
83 | #endif | ||
diff --git a/framework/src/domain/mouseproxy.cpp b/framework/src/domain/mouseproxy.cpp new file mode 100644 index 00000000..9944acb6 --- /dev/null +++ b/framework/src/domain/mouseproxy.cpp | |||
@@ -0,0 +1,39 @@ | |||
1 | /* | ||
2 | Copyright (c) 2017 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #include "mouseproxy.h" | ||
20 | |||
21 | MouseProxy::MouseProxy(QQuickItem *parent) | ||
22 | : QQuickItem(parent) | ||
23 | { | ||
24 | |||
25 | } | ||
26 | |||
27 | MouseProxy::~MouseProxy() | ||
28 | { | ||
29 | |||
30 | } | ||
31 | |||
32 | void MouseProxy::wheelEvent(QWheelEvent *event) | ||
33 | { | ||
34 | if (mTarget && mForwardWheelEvents) { | ||
35 | mTarget->event(event); | ||
36 | } else { | ||
37 | QQuickItem::wheelEvent(event); | ||
38 | } | ||
39 | } | ||
diff --git a/framework/src/domain/mouseproxy.h b/framework/src/domain/mouseproxy.h new file mode 100644 index 00000000..67b25d06 --- /dev/null +++ b/framework/src/domain/mouseproxy.h | |||
@@ -0,0 +1,39 @@ | |||
1 | /* | ||
2 | Copyright (c) 2017 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #include <QQuickItem> | ||
20 | |||
21 | class MouseProxy : public QQuickItem | ||
22 | { | ||
23 | Q_OBJECT | ||
24 | |||
25 | Q_PROPERTY(QObject* target MEMBER mTarget) | ||
26 | Q_PROPERTY(bool forwardWheelEvents MEMBER mForwardWheelEvents) | ||
27 | |||
28 | public: | ||
29 | MouseProxy(QQuickItem *parent=0); | ||
30 | ~MouseProxy(); | ||
31 | |||
32 | protected: | ||
33 | virtual void wheelEvent(QWheelEvent *event) override; | ||
34 | |||
35 | private: | ||
36 | QObject *mTarget = nullptr; | ||
37 | bool mForwardWheelEvents = false; | ||
38 | }; | ||
39 | |||
diff --git a/framework/src/domain/objecttreesource.cpp b/framework/src/domain/objecttreesource.cpp new file mode 100644 index 00000000..567f3516 --- /dev/null +++ b/framework/src/domain/objecttreesource.cpp | |||
@@ -0,0 +1,152 @@ | |||
1 | /* | ||
2 | Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net | ||
3 | Copyright (c) 2009 Andras Mantia <andras@kdab.net> | ||
4 | |||
5 | This program is free software; you can redistribute it and/or modify | ||
6 | it under the terms of the GNU General Public License as published by | ||
7 | the Free Software Foundation; either version 2 of the License, or | ||
8 | (at your option) any later version. | ||
9 | |||
10 | This program is distributed in the hope that it will be useful, | ||
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | GNU General Public License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU General Public License along | ||
16 | with this program; if not, write to the Free Software Foundation, Inc., | ||
17 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
18 | */ | ||
19 | |||
20 | #include "objecttreesource.h" | ||
21 | |||
22 | #include <MimeTreeParser/AttachmentStrategy> | ||
23 | #include <MimeTreeParser/BodyPartFormatterBaseFactory> | ||
24 | #include <MimeTreeParser/MessagePart> | ||
25 | #include <MimeTreeParser/MessagePartRenderer> | ||
26 | |||
27 | class ObjectSourcePrivate | ||
28 | { | ||
29 | public: | ||
30 | ObjectSourcePrivate() | ||
31 | : mWriter(0) | ||
32 | , mAllowDecryption(true) | ||
33 | , mHtmlLoadExternal(true) | ||
34 | , mPreferredMode(MimeTreeParser::Util::Html) | ||
35 | { | ||
36 | |||
37 | } | ||
38 | MimeTreeParser::HtmlWriter *mWriter; | ||
39 | MimeTreeParser::BodyPartFormatterBaseFactory mBodyPartFormatterBaseFactory; | ||
40 | bool mAllowDecryption; | ||
41 | bool mHtmlLoadExternal; | ||
42 | MimeTreeParser::Util::HtmlMode mPreferredMode; | ||
43 | }; | ||
44 | |||
45 | ObjectTreeSource::ObjectTreeSource(MimeTreeParser::HtmlWriter *writer) | ||
46 | : MimeTreeParser::Interface::ObjectTreeSource() | ||
47 | , d(new ObjectSourcePrivate) | ||
48 | { | ||
49 | d->mWriter = writer; | ||
50 | } | ||
51 | |||
52 | ObjectTreeSource::~ObjectTreeSource() | ||
53 | { | ||
54 | delete d; | ||
55 | } | ||
56 | |||
57 | void ObjectTreeSource::setAllowDecryption(bool allowDecryption) | ||
58 | { | ||
59 | d->mAllowDecryption = allowDecryption; | ||
60 | } | ||
61 | |||
62 | MimeTreeParser::HtmlWriter *ObjectTreeSource::htmlWriter() | ||
63 | { | ||
64 | return d->mWriter; | ||
65 | } | ||
66 | |||
67 | bool ObjectTreeSource::htmlLoadExternal() const | ||
68 | { | ||
69 | return d->mHtmlLoadExternal; | ||
70 | } | ||
71 | |||
72 | void ObjectTreeSource::setHtmlLoadExternal(bool loadExternal) | ||
73 | { | ||
74 | d->mHtmlLoadExternal = loadExternal; | ||
75 | } | ||
76 | |||
77 | bool ObjectTreeSource::decryptMessage() const | ||
78 | { | ||
79 | return d->mAllowDecryption; | ||
80 | } | ||
81 | |||
82 | bool ObjectTreeSource::showSignatureDetails() const | ||
83 | { | ||
84 | return true; | ||
85 | } | ||
86 | |||
87 | int ObjectTreeSource::levelQuote() const | ||
88 | { | ||
89 | return 1; | ||
90 | } | ||
91 | |||
92 | const QTextCodec *ObjectTreeSource::overrideCodec() | ||
93 | { | ||
94 | return Q_NULLPTR; | ||
95 | } | ||
96 | |||
97 | QString ObjectTreeSource::createMessageHeader(KMime::Message *message) | ||
98 | { | ||
99 | return QString(); | ||
100 | } | ||
101 | |||
102 | const MimeTreeParser::AttachmentStrategy *ObjectTreeSource::attachmentStrategy() | ||
103 | { | ||
104 | return MimeTreeParser::AttachmentStrategy::smart(); | ||
105 | } | ||
106 | |||
107 | QObject *ObjectTreeSource::sourceObject() | ||
108 | { | ||
109 | return Q_NULLPTR; | ||
110 | } | ||
111 | |||
112 | void ObjectTreeSource::setHtmlMode(MimeTreeParser::Util::HtmlMode mode, const QList<MimeTreeParser::Util::HtmlMode> &availableModes) | ||
113 | { | ||
114 | Q_UNUSED(mode); | ||
115 | Q_UNUSED(availableModes); | ||
116 | } | ||
117 | |||
118 | MimeTreeParser::Util::HtmlMode ObjectTreeSource::preferredMode() const | ||
119 | { | ||
120 | return d->mPreferredMode; | ||
121 | } | ||
122 | |||
123 | bool ObjectTreeSource::autoImportKeys() const | ||
124 | { | ||
125 | return false; | ||
126 | } | ||
127 | |||
128 | bool ObjectTreeSource::showEmoticons() const | ||
129 | { | ||
130 | return false; | ||
131 | } | ||
132 | |||
133 | bool ObjectTreeSource::showExpandQuotesMark() const | ||
134 | { | ||
135 | return false; | ||
136 | } | ||
137 | |||
138 | bool ObjectTreeSource::isPrinting() const | ||
139 | { | ||
140 | return false; | ||
141 | } | ||
142 | |||
143 | const MimeTreeParser::BodyPartFormatterBaseFactory *ObjectTreeSource::bodyPartFormatterFactory() | ||
144 | { | ||
145 | return &(d->mBodyPartFormatterBaseFactory); | ||
146 | } | ||
147 | |||
148 | MimeTreeParser::Interface::MessagePartRenderer::Ptr ObjectTreeSource::messagePartTheme(MimeTreeParser::Interface::MessagePart::Ptr msgPart) | ||
149 | { | ||
150 | Q_UNUSED(msgPart); | ||
151 | return MimeTreeParser::Interface::MessagePartRenderer::Ptr(); | ||
152 | } | ||
diff --git a/framework/src/domain/objecttreesource.h b/framework/src/domain/objecttreesource.h new file mode 100644 index 00000000..93812dc3 --- /dev/null +++ b/framework/src/domain/objecttreesource.h | |||
@@ -0,0 +1,57 @@ | |||
1 | /* | ||
2 | Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net | ||
3 | Copyright (c) 2009 Andras Mantia <andras@kdab.net> | ||
4 | |||
5 | This program is free software; you can redistribute it and/or modify | ||
6 | it under the terms of the GNU General Public License as published by | ||
7 | the Free Software Foundation; either version 2 of the License, or | ||
8 | (at your option) any later version. | ||
9 | |||
10 | This program is distributed in the hope that it will be useful, | ||
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | GNU General Public License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU General Public License along | ||
16 | with this program; if not, write to the Free Software Foundation, Inc., | ||
17 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
18 | */ | ||
19 | |||
20 | #ifndef MAILVIEWER_OBJECTTREEEMPTYSOURCE_H | ||
21 | #define MAILVIEWER_OBJECTTREEEMPTYSOURCE_H | ||
22 | |||
23 | #include <MimeTreeParser/ObjectTreeSource> | ||
24 | |||
25 | class QString; | ||
26 | |||
27 | class ObjectSourcePrivate; | ||
28 | class ObjectTreeSource : public MimeTreeParser::Interface::ObjectTreeSource | ||
29 | { | ||
30 | public: | ||
31 | ObjectTreeSource(MimeTreeParser::HtmlWriter *writer); | ||
32 | virtual ~ObjectTreeSource(); | ||
33 | void setHtmlLoadExternal(bool loadExternal); | ||
34 | bool decryptMessage() const Q_DECL_OVERRIDE; | ||
35 | bool htmlLoadExternal() const Q_DECL_OVERRIDE; | ||
36 | bool showSignatureDetails() const Q_DECL_OVERRIDE; | ||
37 | void setHtmlMode(MimeTreeParser::Util::HtmlMode mode, const QList<MimeTreeParser::Util::HtmlMode> &availableModes) Q_DECL_OVERRIDE; | ||
38 | MimeTreeParser::Util::HtmlMode preferredMode() const Q_DECL_OVERRIDE; | ||
39 | void setAllowDecryption(bool allowDecryption); | ||
40 | int levelQuote() const Q_DECL_OVERRIDE; | ||
41 | const QTextCodec *overrideCodec() Q_DECL_OVERRIDE; | ||
42 | QString createMessageHeader(KMime::Message *message) Q_DECL_OVERRIDE; | ||
43 | const MimeTreeParser::AttachmentStrategy *attachmentStrategy() Q_DECL_OVERRIDE; | ||
44 | MimeTreeParser::HtmlWriter *htmlWriter() Q_DECL_OVERRIDE; | ||
45 | QObject *sourceObject() Q_DECL_OVERRIDE; | ||
46 | bool autoImportKeys() const Q_DECL_OVERRIDE; | ||
47 | bool showEmoticons() const Q_DECL_OVERRIDE; | ||
48 | bool showExpandQuotesMark() const Q_DECL_OVERRIDE; | ||
49 | bool isPrinting() const Q_DECL_OVERRIDE; | ||
50 | const MimeTreeParser::BodyPartFormatterBaseFactory *bodyPartFormatterFactory() Q_DECL_OVERRIDE; | ||
51 | MimeTreeParser::Interface::MessagePartRendererPtr messagePartTheme(MimeTreeParser::Interface::MessagePartPtr msgPart) Q_DECL_OVERRIDE; | ||
52 | private: | ||
53 | ObjectSourcePrivate *const d; | ||
54 | }; | ||
55 | |||
56 | #endif | ||
57 | |||
diff --git a/framework/src/domain/outboxcontroller.cpp b/framework/src/domain/outboxcontroller.cpp new file mode 100644 index 00000000..590b6c49 --- /dev/null +++ b/framework/src/domain/outboxcontroller.cpp | |||
@@ -0,0 +1,47 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #include "outboxcontroller.h" | ||
20 | |||
21 | #include <sink/store.h> | ||
22 | #include <sink/log.h> | ||
23 | |||
24 | OutboxController::OutboxController() | ||
25 | : Kube::Controller(), | ||
26 | mSynchronizeOutboxAction{new Kube::ControllerAction{this, &OutboxController::sendOutbox}} | ||
27 | { | ||
28 | } | ||
29 | |||
30 | Kube::ControllerAction* OutboxController::sendOutboxAction() const | ||
31 | { | ||
32 | return mSynchronizeOutboxAction.data(); | ||
33 | } | ||
34 | |||
35 | void OutboxController::sendOutbox() | ||
36 | { | ||
37 | using namespace Sink; | ||
38 | using namespace Sink::ApplicationDomain; | ||
39 | Query query; | ||
40 | query.containsFilter<SinkResource::Capabilities>(ResourceCapabilities::Mail::transport); | ||
41 | auto job = Store::fetchAll<SinkResource>(query) | ||
42 | .each([=](const SinkResource::Ptr &resource) -> KAsync::Job<void> { | ||
43 | return Store::synchronize(SyncScope{}.resourceFilter(resource->identifier())); | ||
44 | }); | ||
45 | run(job); | ||
46 | } | ||
47 | |||
diff --git a/framework/src/domain/outboxcontroller.h b/framework/src/domain/outboxcontroller.h new file mode 100644 index 00000000..4c24ad59 --- /dev/null +++ b/framework/src/domain/outboxcontroller.h | |||
@@ -0,0 +1,39 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #pragma once | ||
20 | |||
21 | #include <QObject> | ||
22 | #include "controller.h" | ||
23 | |||
24 | class OutboxController : public Kube::Controller | ||
25 | { | ||
26 | Q_OBJECT | ||
27 | Q_PROPERTY (Kube::ControllerAction* sendOutboxAction READ sendOutboxAction CONSTANT) | ||
28 | |||
29 | public: | ||
30 | explicit OutboxController(); | ||
31 | |||
32 | Kube::ControllerAction* sendOutboxAction() const; | ||
33 | |||
34 | private slots: | ||
35 | void sendOutbox(); | ||
36 | |||
37 | private: | ||
38 | QScopedPointer<Kube::ControllerAction> mSynchronizeOutboxAction; | ||
39 | }; | ||
diff --git a/framework/src/domain/outboxmodel.cpp b/framework/src/domain/outboxmodel.cpp new file mode 100644 index 00000000..237648b1 --- /dev/null +++ b/framework/src/domain/outboxmodel.cpp | |||
@@ -0,0 +1,141 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Michael Bohlender <michael.bohlender@kdemail.net> | ||
3 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
4 | |||
5 | This library is free software; you can redistribute it and/or modify it | ||
6 | under the terms of the GNU Library General Public License as published by | ||
7 | the Free Software Foundation; either version 2 of the License, or (at your | ||
8 | option) any later version. | ||
9 | |||
10 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
12 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
13 | License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU Library General Public License | ||
16 | along with this library; see the file COPYING.LIB. If not, write to the | ||
17 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
18 | 02110-1301, USA. | ||
19 | */ | ||
20 | |||
21 | #include "outboxmodel.h" | ||
22 | |||
23 | #include <QFile> | ||
24 | #include <QDateTime> | ||
25 | #include <QString> | ||
26 | |||
27 | #include <sink/standardqueries.h> | ||
28 | #include <sink/notifier.h> | ||
29 | #include <sink/notification.h> | ||
30 | |||
31 | |||
32 | OutboxModel::OutboxModel(QObject *parent) | ||
33 | : QSortFilterProxyModel(), | ||
34 | mNotifier(new Sink::Notifier{Sink::Query{}.containsFilter<Sink::ApplicationDomain::SinkResource::Capabilities>(Sink::ApplicationDomain::ResourceCapabilities::Mail::transport)}), | ||
35 | mStatus(NoStatus) | ||
36 | { | ||
37 | setDynamicSortFilter(true); | ||
38 | sort(0, Qt::DescendingOrder); | ||
39 | |||
40 | using namespace Sink::ApplicationDomain; | ||
41 | auto query = Sink::StandardQueries::outboxMails(); | ||
42 | query.setFlags(Sink::Query::LiveQuery | Sink::Query::UpdateStatus); | ||
43 | query.request<Mail::Subject>(); | ||
44 | query.request<Mail::Date>(); | ||
45 | query.request<Mail::Folder>(); | ||
46 | runQuery(query); | ||
47 | connect(this, &QAbstractItemModel::rowsInserted, this, &OutboxModel::countChanged); | ||
48 | connect(this, &QAbstractItemModel::rowsRemoved, this, &OutboxModel::countChanged); | ||
49 | |||
50 | mNotifier->registerHandler([this] (const Sink::Notification &n) { | ||
51 | //TODO aggregate status from multiple resources | ||
52 | if (n.type == Sink::Notification::Status) { | ||
53 | switch (n.code) { | ||
54 | case Sink::ApplicationDomain::Status::ErrorStatus: | ||
55 | mStatus = ErrorStatus; | ||
56 | break; | ||
57 | case Sink::ApplicationDomain::Status::BusyStatus: | ||
58 | mStatus = InProgressStatus; | ||
59 | break; | ||
60 | default: | ||
61 | mStatus = NoStatus; | ||
62 | break; | ||
63 | } | ||
64 | emit statusChanged(); | ||
65 | } | ||
66 | |||
67 | }); | ||
68 | |||
69 | } | ||
70 | |||
71 | OutboxModel::~OutboxModel() | ||
72 | { | ||
73 | |||
74 | } | ||
75 | |||
76 | QHash< int, QByteArray > OutboxModel::roleNames() const | ||
77 | { | ||
78 | QHash<int, QByteArray> roles; | ||
79 | |||
80 | roles[Subject] = "subject"; | ||
81 | roles[Date] = "date"; | ||
82 | roles[Status] = "status"; | ||
83 | roles[Id] = "id"; | ||
84 | roles[MimeMessage] = "mimeMessage"; | ||
85 | roles[DomainObject] = "domainObject"; | ||
86 | |||
87 | return roles; | ||
88 | } | ||
89 | |||
90 | QVariant OutboxModel::data(const QModelIndex &idx, int role) const | ||
91 | { | ||
92 | auto srcIdx = mapToSource(idx); | ||
93 | auto mail = srcIdx.data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Mail::Ptr>(); | ||
94 | switch (role) { | ||
95 | case Subject: | ||
96 | return mail->getSubject(); | ||
97 | case Date: | ||
98 | return mail->getDate(); | ||
99 | case Status: { | ||
100 | const auto status = srcIdx.data(Sink::Store::StatusRole).toInt(); | ||
101 | if (status == Sink::ApplicationDomain::SyncStatus::SyncInProgress) { | ||
102 | return InProgressStatus; | ||
103 | } | ||
104 | if (status == Sink::ApplicationDomain::SyncStatus::SyncError) { | ||
105 | return ErrorStatus; | ||
106 | } | ||
107 | return PendingStatus; | ||
108 | } | ||
109 | case Id: | ||
110 | return mail->identifier(); | ||
111 | case DomainObject: | ||
112 | return QVariant::fromValue(mail); | ||
113 | case MimeMessage: { | ||
114 | return mail->getMimeMessage(); | ||
115 | } | ||
116 | } | ||
117 | return QSortFilterProxyModel::data(idx, role); | ||
118 | } | ||
119 | |||
120 | bool OutboxModel::lessThan(const QModelIndex &left, const QModelIndex &right) const | ||
121 | { | ||
122 | const auto leftDate = left.data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Mail::Ptr>()->getDate(); | ||
123 | const auto rightDate = right.data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Mail::Ptr>()->getDate(); | ||
124 | return leftDate < rightDate; | ||
125 | } | ||
126 | |||
127 | void OutboxModel::runQuery(const Sink::Query &query) | ||
128 | { | ||
129 | mModel = Sink::Store::loadModel<Sink::ApplicationDomain::Mail>(query); | ||
130 | setSourceModel(mModel.data()); | ||
131 | } | ||
132 | |||
133 | int OutboxModel::count() const | ||
134 | { | ||
135 | return rowCount(); | ||
136 | } | ||
137 | |||
138 | int OutboxModel::status() const | ||
139 | { | ||
140 | return mStatus; | ||
141 | } | ||
diff --git a/framework/src/domain/outboxmodel.h b/framework/src/domain/outboxmodel.h new file mode 100644 index 00000000..4be9c7f8 --- /dev/null +++ b/framework/src/domain/outboxmodel.h | |||
@@ -0,0 +1,77 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Michael Bohlender <michael.bohlender@kdemail.net> | ||
3 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
4 | |||
5 | This library is free software; you can redistribute it and/or modify it | ||
6 | under the terms of the GNU Library General Public License as published by | ||
7 | the Free Software Foundation; either version 2 of the License, or (at your | ||
8 | option) any later version. | ||
9 | |||
10 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
12 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
13 | License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU Library General Public License | ||
16 | along with this library; see the file COPYING.LIB. If not, write to the | ||
17 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
18 | 02110-1301, USA. | ||
19 | */ | ||
20 | |||
21 | #pragma once | ||
22 | |||
23 | #include <sink/store.h> | ||
24 | #include <sink/notifier.h> | ||
25 | |||
26 | #include <QSortFilterProxyModel> | ||
27 | #include <QSharedPointer> | ||
28 | #include <QStringList> | ||
29 | |||
30 | class OutboxModel : public QSortFilterProxyModel | ||
31 | { | ||
32 | Q_OBJECT | ||
33 | |||
34 | Q_PROPERTY (int count READ count NOTIFY countChanged) | ||
35 | Q_PROPERTY (int status READ status NOTIFY statusChanged) | ||
36 | |||
37 | public: | ||
38 | enum Status { | ||
39 | NoStatus, | ||
40 | PendingStatus, | ||
41 | InProgressStatus, | ||
42 | ErrorStatus | ||
43 | }; | ||
44 | Q_ENUMS(Status) | ||
45 | |||
46 | OutboxModel(QObject *parent = Q_NULLPTR); | ||
47 | ~OutboxModel(); | ||
48 | |||
49 | QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; | ||
50 | |||
51 | bool lessThan(const QModelIndex &left, const QModelIndex &right) const Q_DECL_OVERRIDE; | ||
52 | |||
53 | enum Roles { | ||
54 | Subject = Qt::UserRole + 1, | ||
55 | Date, | ||
56 | Status, | ||
57 | Id, | ||
58 | MimeMessage, | ||
59 | DomainObject | ||
60 | }; | ||
61 | |||
62 | QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE; | ||
63 | |||
64 | void runQuery(const Sink::Query &query); | ||
65 | |||
66 | int count() const; | ||
67 | int status() const; | ||
68 | |||
69 | signals: | ||
70 | void statusChanged(); | ||
71 | void countChanged(); | ||
72 | |||
73 | private: | ||
74 | QSharedPointer<QAbstractItemModel> mModel; | ||
75 | QSharedPointer<Sink::Notifier> mNotifier; | ||
76 | int mStatus; | ||
77 | }; | ||
diff --git a/framework/src/domain/peoplemodel.cpp b/framework/src/domain/peoplemodel.cpp new file mode 100644 index 00000000..c9e7248c --- /dev/null +++ b/framework/src/domain/peoplemodel.cpp | |||
@@ -0,0 +1,127 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Michael Bohlender <michael.bohlender@kdemail.net> | ||
3 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
4 | |||
5 | This library is free software; you can redistribute it and/or modify it | ||
6 | under the terms of the GNU Library General Public License as published by | ||
7 | the Free Software Foundation; either version 2 of the License, or (at your | ||
8 | option) any later version. | ||
9 | |||
10 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
12 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
13 | License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU Library General Public License | ||
16 | along with this library; see the file COPYING.LIB. If not, write to the | ||
17 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
18 | 02110-1301, USA. | ||
19 | */ | ||
20 | #include "peoplemodel.h" | ||
21 | |||
22 | #include <sink/standardqueries.h> | ||
23 | #include <sink/store.h> | ||
24 | |||
25 | PeopleModel::PeopleModel(QObject *parent) | ||
26 | : QSortFilterProxyModel() | ||
27 | { | ||
28 | using namespace Sink::ApplicationDomain; | ||
29 | |||
30 | setDynamicSortFilter(true); | ||
31 | sort(0, Qt::DescendingOrder); | ||
32 | setFilterCaseSensitivity(Qt::CaseInsensitive); | ||
33 | Sink::Query query; | ||
34 | query.setFlags(Sink::Query::LiveQuery); | ||
35 | query.request<Contact::Fn>(); | ||
36 | query.request<Contact::Emails>(); | ||
37 | query.request<Contact::Addressbook>(); | ||
38 | query.request<Contact::Vcard>(); | ||
39 | query.request<Contact::Firstname>(); | ||
40 | query.request<Contact::Lastname>(); | ||
41 | runQuery(query); | ||
42 | } | ||
43 | |||
44 | PeopleModel::~PeopleModel() | ||
45 | { | ||
46 | |||
47 | } | ||
48 | |||
49 | void PeopleModel::setFilter(const QString &filter) | ||
50 | { | ||
51 | setFilterWildcard(filter); | ||
52 | } | ||
53 | |||
54 | QString PeopleModel::filter() const | ||
55 | { | ||
56 | return {}; | ||
57 | } | ||
58 | |||
59 | QHash< int, QByteArray > PeopleModel::roleNames() const | ||
60 | { | ||
61 | static QHash<int, QByteArray> roles = { | ||
62 | {Name, "name"}, | ||
63 | {Emails, "emails"}, | ||
64 | {Addressbook, "addressbook"}, | ||
65 | {Type, "type"}, | ||
66 | {DomainObject, "domainObject"}, | ||
67 | {FirstName, "firstName"}, | ||
68 | {LastName, "lastName"} | ||
69 | }; | ||
70 | return roles; | ||
71 | } | ||
72 | |||
73 | static QStringList toStringList(const QList<Sink::ApplicationDomain::Contact::Email> &list) | ||
74 | { | ||
75 | QStringList out; | ||
76 | std::transform(list.constBegin(), list.constEnd(), std::back_inserter(out), [] (const Sink::ApplicationDomain::Contact::Email &s) { return s.email; }); | ||
77 | return out; | ||
78 | } | ||
79 | |||
80 | QVariant PeopleModel::data(const QModelIndex &idx, int role) const | ||
81 | { | ||
82 | auto srcIdx = mapToSource(idx); | ||
83 | auto contact = srcIdx.data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Contact::Ptr>(); | ||
84 | switch (role) { | ||
85 | case Name: | ||
86 | return contact->getFn(); | ||
87 | case Emails: | ||
88 | return QVariant::fromValue(toStringList(contact->getEmails())); | ||
89 | case Addressbook: | ||
90 | return contact->getAddressbook(); | ||
91 | case Type: | ||
92 | return "contact"; | ||
93 | case DomainObject: | ||
94 | return QVariant::fromValue(contact); | ||
95 | case FirstName: | ||
96 | return contact->getFirstname(); | ||
97 | case LastName: | ||
98 | return contact->getLastname(); | ||
99 | } | ||
100 | return QSortFilterProxyModel::data(idx, role); | ||
101 | } | ||
102 | |||
103 | bool PeopleModel::lessThan(const QModelIndex &left, const QModelIndex &right) const | ||
104 | { | ||
105 | const auto leftName = left.data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Contact::Ptr>()->getFn(); | ||
106 | const auto rightName = right.data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Contact::Ptr>()->getFn(); | ||
107 | return leftName < rightName; | ||
108 | } | ||
109 | |||
110 | void PeopleModel::runQuery(const Sink::Query &query) | ||
111 | { | ||
112 | mModel = Sink::Store::loadModel<Sink::ApplicationDomain::Contact>(query); | ||
113 | setSourceModel(mModel.data()); | ||
114 | } | ||
115 | |||
116 | void PeopleModel::setAddressbook(const QVariant &parentFolder) | ||
117 | { | ||
118 | //TODO filter query by addressbook | ||
119 | qWarning() << "The addressbook filter is not yet implemented"; | ||
120 | } | ||
121 | |||
122 | QVariant PeopleModel::addressbook() const | ||
123 | { | ||
124 | return QVariant(); | ||
125 | } | ||
126 | |||
127 | |||
diff --git a/framework/src/domain/peoplemodel.h b/framework/src/domain/peoplemodel.h new file mode 100644 index 00000000..a59e752c --- /dev/null +++ b/framework/src/domain/peoplemodel.h | |||
@@ -0,0 +1,69 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Michael Bohlender <michael.bohlender@kdemail.net> | ||
3 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
4 | |||
5 | This library is free software; you can redistribute it and/or modify it | ||
6 | under the terms of the GNU Library General Public License as published by | ||
7 | the Free Software Foundation; either version 2 of the License, or (at your | ||
8 | option) any later version. | ||
9 | |||
10 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
12 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
13 | License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU Library General Public License | ||
16 | along with this library; see the file COPYING.LIB. If not, write to the | ||
17 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
18 | 02110-1301, USA. | ||
19 | */ | ||
20 | |||
21 | #pragma once | ||
22 | |||
23 | #include <QSortFilterProxyModel> | ||
24 | #include <QSharedPointer> | ||
25 | |||
26 | namespace Sink { | ||
27 | class Query; | ||
28 | }; | ||
29 | |||
30 | /** | ||
31 | * A model that mixes addressbooks, contact groups and contacts | ||
32 | */ | ||
33 | class PeopleModel : public QSortFilterProxyModel | ||
34 | { | ||
35 | Q_OBJECT | ||
36 | Q_PROPERTY (QVariant addressbook READ addressbook WRITE setAddressbook) | ||
37 | Q_PROPERTY (QString filter READ filter WRITE setFilter) | ||
38 | |||
39 | public: | ||
40 | PeopleModel(QObject *parent = Q_NULLPTR); | ||
41 | ~PeopleModel(); | ||
42 | |||
43 | QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; | ||
44 | |||
45 | bool lessThan(const QModelIndex &left, const QModelIndex &right) const Q_DECL_OVERRIDE; | ||
46 | |||
47 | enum Roles { | ||
48 | Name = Qt::UserRole + 1, | ||
49 | Type, | ||
50 | Emails, | ||
51 | Addressbook, | ||
52 | DomainObject, | ||
53 | FirstName, | ||
54 | LastName | ||
55 | }; | ||
56 | |||
57 | QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE; | ||
58 | |||
59 | void runQuery(const Sink::Query &query); | ||
60 | |||
61 | void setAddressbook(const QVariant &parentFolder); | ||
62 | QVariant addressbook() const; | ||
63 | |||
64 | void setFilter(const QString &mail); | ||
65 | QString filter() const; | ||
66 | |||
67 | private: | ||
68 | QSharedPointer<QAbstractItemModel> mModel; | ||
69 | }; | ||
diff --git a/framework/src/domain/recepientautocompletionmodel.cpp b/framework/src/domain/recepientautocompletionmodel.cpp new file mode 100644 index 00000000..2f52db07 --- /dev/null +++ b/framework/src/domain/recepientautocompletionmodel.cpp | |||
@@ -0,0 +1,134 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #include "recepientautocompletionmodel.h" | ||
20 | |||
21 | #include <QStandardItemModel> | ||
22 | #include <QSettings> | ||
23 | #include <QStandardPaths> | ||
24 | #include <QSet> | ||
25 | #include <QDebug> | ||
26 | #include <QTimer> | ||
27 | #include <sink/store.h> | ||
28 | #include <sink/applicationdomaintype.h> | ||
29 | |||
30 | using namespace Sink::ApplicationDomain; | ||
31 | |||
32 | RecipientAutocompletionModel::RecipientAutocompletionModel(QObject *parent) | ||
33 | : QSortFilterProxyModel(), | ||
34 | mSourceModel(new QStandardItemModel), | ||
35 | mTimer(new QTimer) | ||
36 | { | ||
37 | setSourceModel(mSourceModel.data()); | ||
38 | setDynamicSortFilter(true); | ||
39 | setFilterCaseSensitivity(Qt::CaseInsensitive); | ||
40 | mTimer->setSingleShot(true); | ||
41 | QObject::connect(mTimer.data(), &QTimer::timeout, this, &RecipientAutocompletionModel::save); | ||
42 | |||
43 | load(); | ||
44 | } | ||
45 | |||
46 | RecipientAutocompletionModel::~RecipientAutocompletionModel() | ||
47 | { | ||
48 | save(); | ||
49 | } | ||
50 | |||
51 | static QString getPath() | ||
52 | { | ||
53 | return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/kube/recepientautocompletion.ini"; | ||
54 | } | ||
55 | |||
56 | void RecipientAutocompletionModel::save() | ||
57 | { | ||
58 | QSet<QString> list; | ||
59 | for (int row = 0; row < mSourceModel->rowCount(); row++) { | ||
60 | list << mSourceModel->item(row)->data(Text).toString(); | ||
61 | } | ||
62 | |||
63 | qWarning() << "Path " << getPath(); | ||
64 | QSettings settings(getPath(), QSettings::IniFormat); | ||
65 | settings.setValue("list", QStringList{list.toList()}); | ||
66 | } | ||
67 | |||
68 | void RecipientAutocompletionModel::load() | ||
69 | { | ||
70 | qWarning() << "Path " << getPath(); | ||
71 | QSettings settings(getPath(), QSettings::IniFormat); | ||
72 | auto list = settings.value("list").toStringList(); | ||
73 | auto add = [] (const QString &n) { | ||
74 | auto item = new QStandardItem{n}; | ||
75 | item->setData(n, Text); | ||
76 | return item; | ||
77 | }; | ||
78 | for (const auto &entry : list) { | ||
79 | mSourceModel->appendRow(add(entry)); | ||
80 | } | ||
81 | Sink::Query query; | ||
82 | query.request<Contact::Fn>(); | ||
83 | query.request<Contact::Emails>(); | ||
84 | Sink::Store::fetchAll<Contact>(query) | ||
85 | .then([this] (const QList<Contact::Ptr> &list) { | ||
86 | for (const auto &c : list) { | ||
87 | for (const auto &email : c->getEmails()) { | ||
88 | addToModel(email.email, c->getFn()); | ||
89 | } | ||
90 | } | ||
91 | }).exec(); | ||
92 | } | ||
93 | |||
94 | QHash< int, QByteArray > RecipientAutocompletionModel::roleNames() const | ||
95 | { | ||
96 | QHash<int, QByteArray> roles; | ||
97 | roles[Text] = "text"; | ||
98 | roles[Color] = "color"; | ||
99 | return roles; | ||
100 | } | ||
101 | |||
102 | |||
103 | bool RecipientAutocompletionModel::addToModel(const QString &address, const QString &name) | ||
104 | { | ||
105 | auto add = [] (const QString &n) { | ||
106 | auto item = new QStandardItem{n}; | ||
107 | item->setData(n, Text); | ||
108 | return item; | ||
109 | }; | ||
110 | auto formattedName = [&] () { | ||
111 | if (name.isEmpty()) { | ||
112 | return QString(address); | ||
113 | } | ||
114 | return QString("%1 <%2>").arg(QString(address), QString(name)); | ||
115 | }(); | ||
116 | auto matches = mSourceModel->findItems(formattedName); | ||
117 | if (matches.isEmpty()) { | ||
118 | mSourceModel->appendRow(add(formattedName)); | ||
119 | return true; | ||
120 | } | ||
121 | return false; | ||
122 | } | ||
123 | |||
124 | void RecipientAutocompletionModel::addEntry(const QByteArray &address, const QByteArray &name) | ||
125 | { | ||
126 | if (addToModel(address, name)) { | ||
127 | mTimer->start(100); | ||
128 | } | ||
129 | } | ||
130 | |||
131 | void RecipientAutocompletionModel::setFilter(const QString &filter) | ||
132 | { | ||
133 | setFilterWildcard("*" + filter +"*"); | ||
134 | } | ||
diff --git a/framework/src/domain/recepientautocompletionmodel.h b/framework/src/domain/recepientautocompletionmodel.h new file mode 100644 index 00000000..22c6f108 --- /dev/null +++ b/framework/src/domain/recepientautocompletionmodel.h | |||
@@ -0,0 +1,56 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | |||
20 | #pragma once | ||
21 | |||
22 | #include <QSortFilterProxyModel> | ||
23 | #include <QScopedPointer> | ||
24 | |||
25 | class QStandardItemModel; | ||
26 | class QTimer; | ||
27 | |||
28 | class RecipientAutocompletionModel : public QSortFilterProxyModel | ||
29 | { | ||
30 | Q_OBJECT | ||
31 | |||
32 | public: | ||
33 | RecipientAutocompletionModel(QObject *parent = Q_NULLPTR); | ||
34 | ~RecipientAutocompletionModel(); | ||
35 | |||
36 | enum Roles { | ||
37 | Text = Qt::UserRole + 1, | ||
38 | Color | ||
39 | }; | ||
40 | Q_ENUMS(Roles) | ||
41 | |||
42 | QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE; | ||
43 | |||
44 | bool addToModel(const QString &address, const QString &name); | ||
45 | void addEntry(const QByteArray &address, const QByteArray &name); | ||
46 | void setFilter(const QString &); | ||
47 | |||
48 | private slots: | ||
49 | void save(); | ||
50 | |||
51 | private: | ||
52 | void load(); | ||
53 | |||
54 | QScopedPointer<QStandardItemModel> mSourceModel; | ||
55 | QScopedPointer<QTimer> mTimer; | ||
56 | }; | ||
diff --git a/framework/src/domain/retriever.cpp b/framework/src/domain/retriever.cpp new file mode 100644 index 00000000..b8e29523 --- /dev/null +++ b/framework/src/domain/retriever.cpp | |||
@@ -0,0 +1,55 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsystems.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | |||
20 | |||
21 | #include "retriever.h" | ||
22 | |||
23 | Retriever::Retriever(QObject *parent) : QObject(parent) | ||
24 | { | ||
25 | } | ||
26 | |||
27 | QAbstractItemModel* Retriever::model() const | ||
28 | { | ||
29 | return mModel; | ||
30 | } | ||
31 | |||
32 | void Retriever::setModel(QAbstractItemModel* model) | ||
33 | { | ||
34 | mValue = QVariant(); | ||
35 | mModel = model; | ||
36 | connect(model, &QAbstractItemModel::rowsInserted, this, &Retriever::onRowsInserted); | ||
37 | connect(model, &QAbstractItemModel::modelReset, this, &Retriever::onModelReset); | ||
38 | if (model->rowCount(QModelIndex())) { | ||
39 | mValue = model->index(0, 0, QModelIndex()).data(mModel->roleNames().key(mPropertyName.toLatin1())); | ||
40 | emit valueChanged(); | ||
41 | } | ||
42 | } | ||
43 | |||
44 | void Retriever::onRowsInserted(const QModelIndex &parent, int first, int last) | ||
45 | { | ||
46 | if (!mValue.isValid()) { | ||
47 | mValue = mModel->index(0, 0, QModelIndex()).data(mModel->roleNames().key(mPropertyName.toLatin1())); | ||
48 | emit valueChanged(); | ||
49 | } | ||
50 | } | ||
51 | |||
52 | void Retriever::onModelReset() | ||
53 | { | ||
54 | mValue = QVariant(); | ||
55 | } | ||
diff --git a/framework/src/domain/retriever.h b/framework/src/domain/retriever.h new file mode 100644 index 00000000..e454532d --- /dev/null +++ b/framework/src/domain/retriever.h | |||
@@ -0,0 +1,55 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsystems.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | |||
20 | #pragma once | ||
21 | |||
22 | #include <QObject> | ||
23 | #include <QAbstractItemModel> | ||
24 | #include <QVariant> | ||
25 | |||
26 | /** | ||
27 | * A wrapper for a QAbstractItemModel to retrieve a value from a single index via property binding | ||
28 | * | ||
29 | * Assign a model that retrieves the index, set the property your interested in, and propery-bind "value". | ||
30 | */ | ||
31 | class Retriever : public QObject | ||
32 | { | ||
33 | Q_OBJECT | ||
34 | Q_PROPERTY(QAbstractItemModel* model READ model WRITE setModel) | ||
35 | Q_PROPERTY(QString propertyName MEMBER mPropertyName) | ||
36 | Q_PROPERTY(QVariant value MEMBER mValue NOTIFY valueChanged) | ||
37 | |||
38 | public: | ||
39 | explicit Retriever(QObject *parent = Q_NULLPTR); | ||
40 | |||
41 | QAbstractItemModel* model() const; | ||
42 | void setModel(QAbstractItemModel* model); | ||
43 | |||
44 | signals: | ||
45 | void valueChanged(); | ||
46 | |||
47 | private slots: | ||
48 | void onRowsInserted(const QModelIndex &parent, int first, int last); | ||
49 | void onModelReset(); | ||
50 | |||
51 | private: | ||
52 | QString mPropertyName; | ||
53 | QVariant mValue; | ||
54 | QAbstractItemModel *mModel; | ||
55 | }; | ||
diff --git a/framework/src/domain/selector.cpp b/framework/src/domain/selector.cpp new file mode 100644 index 00000000..d021095b --- /dev/null +++ b/framework/src/domain/selector.cpp | |||
@@ -0,0 +1,31 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekofp <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #include "selector.h" | ||
20 | |||
21 | #include <QQmlEngine> | ||
22 | |||
23 | Selector::Selector(QAbstractItemModel *model) : mModel{model} | ||
24 | { | ||
25 | QQmlEngine::setObjectOwnership(mModel, QQmlEngine::CppOwnership); | ||
26 | } | ||
27 | |||
28 | void Selector::reapplyCurrentIndex() | ||
29 | { | ||
30 | setCurrentIndex(currentIndex()); | ||
31 | } | ||
diff --git a/framework/src/domain/selector.h b/framework/src/domain/selector.h new file mode 100644 index 00000000..358eaa9a --- /dev/null +++ b/framework/src/domain/selector.h | |||
@@ -0,0 +1,56 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekofp <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #pragma once | ||
20 | |||
21 | #include <QAbstractItemModel> | ||
22 | #include <QString> | ||
23 | |||
24 | /** | ||
25 | * Exposes a model and maintains a current index selection. | ||
26 | */ | ||
27 | class Selector : public QObject { | ||
28 | Q_OBJECT | ||
29 | Q_PROPERTY (int currentIndex READ currentIndex WRITE setCurrentIndex) | ||
30 | Q_PROPERTY (QAbstractItemModel* model READ model CONSTANT) | ||
31 | |||
32 | public: | ||
33 | Selector(QAbstractItemModel *model); | ||
34 | |||
35 | virtual QAbstractItemModel *model() { return mModel; } | ||
36 | |||
37 | void setCurrentIndex(int i) { | ||
38 | mCurrentIndex = i; | ||
39 | Q_ASSERT(mModel); | ||
40 | if (i >= 0) { | ||
41 | setCurrent(mModel->index(mCurrentIndex, 0)); | ||
42 | } else { | ||
43 | setCurrent(QModelIndex()); | ||
44 | } | ||
45 | } | ||
46 | |||
47 | void reapplyCurrentIndex(); | ||
48 | |||
49 | int currentIndex() { return mCurrentIndex; } | ||
50 | |||
51 | virtual void setCurrent(const QModelIndex &) = 0; | ||
52 | private: | ||
53 | QAbstractItemModel *mModel = nullptr; | ||
54 | int mCurrentIndex = 0; | ||
55 | }; | ||
56 | |||
diff --git a/framework/src/domain/settings/CMakeLists.txt b/framework/src/domain/settings/CMakeLists.txt new file mode 100644 index 00000000..dc9d01b1 --- /dev/null +++ b/framework/src/domain/settings/CMakeLists.txt | |||
@@ -0,0 +1,6 @@ | |||
1 | include_directories(${CMAKE_CURRENT_BINARY_DIR}) | ||
2 | cmake_policy(SET CMP0063 NEW) | ||
3 | add_executable(sinkactiontest sinkactiontest.cpp) | ||
4 | add_test(sinkactiontest sinkactiontest) | ||
5 | qt5_use_modules(sinkactiontest Core Test Concurrent) | ||
6 | target_link_libraries(sinkactiontest sink actionplugin KF5::Mime mailplugin) | ||
diff --git a/framework/src/domain/settings/accountsettings.cpp b/framework/src/domain/settings/accountsettings.cpp new file mode 100644 index 00000000..d1019e1f --- /dev/null +++ b/framework/src/domain/settings/accountsettings.cpp | |||
@@ -0,0 +1,391 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #include "accountsettings.h" | ||
20 | |||
21 | #include <sink/store.h> | ||
22 | #include <sink/log.h> | ||
23 | #include <QDebug> | ||
24 | #include <QDir> | ||
25 | #include <QUrl> | ||
26 | |||
27 | using namespace Sink; | ||
28 | using namespace Sink::ApplicationDomain; | ||
29 | |||
30 | SINK_DEBUG_AREA("accountsettings") | ||
31 | |||
32 | AccountSettings::AccountSettings(QObject *parent) | ||
33 | : QObject(parent) | ||
34 | { | ||
35 | } | ||
36 | |||
37 | void AccountSettings::setAccountType(const QByteArray &type) | ||
38 | { | ||
39 | mAccountType = type; | ||
40 | } | ||
41 | |||
42 | QByteArray AccountSettings::accountType() const | ||
43 | { | ||
44 | return mAccountType; | ||
45 | } | ||
46 | |||
47 | void AccountSettings::setAccountIdentifier(const QByteArray &id) | ||
48 | { | ||
49 | if (id.isEmpty()) { | ||
50 | return; | ||
51 | } | ||
52 | mAccountIdentifier = id; | ||
53 | |||
54 | //Clear | ||
55 | mIcon = QString(); | ||
56 | mName = QString(); | ||
57 | mImapServer = QString(); | ||
58 | mImapUsername = QString(); | ||
59 | mImapPassword = QString(); | ||
60 | mSmtpServer = QString(); | ||
61 | mSmtpUsername = QString(); | ||
62 | mSmtpPassword = QString(); | ||
63 | mCardDavServer = QString(); | ||
64 | mCardDavUsername = QString(); | ||
65 | mCardDavPassword = QString(); | ||
66 | emit changed(); | ||
67 | emit imapResourceChanged(); | ||
68 | emit smtpResourceChanged(); | ||
69 | emit cardDavResourceChanged(); | ||
70 | |||
71 | load(); | ||
72 | |||
73 | } | ||
74 | |||
75 | QByteArray AccountSettings::accountIdentifier() const | ||
76 | { | ||
77 | return mAccountIdentifier; | ||
78 | } | ||
79 | |||
80 | void AccountSettings::setPath(const QUrl &path) | ||
81 | { | ||
82 | auto normalizedPath = path.path(); | ||
83 | if (mPath != normalizedPath) { | ||
84 | mPath = normalizedPath; | ||
85 | emit pathChanged(); | ||
86 | } | ||
87 | } | ||
88 | |||
89 | QUrl AccountSettings::path() const | ||
90 | { | ||
91 | return QUrl(mPath); | ||
92 | } | ||
93 | |||
94 | QValidator *AccountSettings::pathValidator() const | ||
95 | { | ||
96 | class PathValidator : public QValidator { | ||
97 | State validate(QString &input, int &pos) const { | ||
98 | Q_UNUSED(pos); | ||
99 | if (!input.isEmpty() && QDir(input).exists()) { | ||
100 | return Acceptable; | ||
101 | } else { | ||
102 | return Intermediate; | ||
103 | } | ||
104 | } | ||
105 | }; | ||
106 | static PathValidator *pathValidator = new PathValidator; | ||
107 | return pathValidator; | ||
108 | } | ||
109 | |||
110 | QValidator *AccountSettings::imapServerValidator() const | ||
111 | { | ||
112 | class ImapServerValidator : public QValidator { | ||
113 | State validate(QString &input, int &pos) const { | ||
114 | Q_UNUSED(pos); | ||
115 | // imaps://mainserver.example.net:475 | ||
116 | const QUrl url(input); | ||
117 | static QSet<QString> validProtocols = QSet<QString>() << "imap" << "imaps"; | ||
118 | if (url.isValid() && validProtocols.contains(url.scheme().toLower())) { | ||
119 | return Acceptable; | ||
120 | } else { | ||
121 | return Intermediate; | ||
122 | } | ||
123 | } | ||
124 | }; | ||
125 | static ImapServerValidator *validator = new ImapServerValidator; | ||
126 | return validator; | ||
127 | } | ||
128 | |||
129 | QValidator *AccountSettings::smtpServerValidator() const | ||
130 | { | ||
131 | class SmtpServerValidator : public QValidator { | ||
132 | State validate(QString &input, int &pos) const { | ||
133 | Q_UNUSED(pos); | ||
134 | // smtps://mainserver.example.net:475 | ||
135 | const QUrl url(input); | ||
136 | static QSet<QString> validProtocols = QSet<QString>() << "smtp" << "smtps"; | ||
137 | if (url.isValid() && validProtocols.contains(url.scheme().toLower())) { | ||
138 | return Acceptable; | ||
139 | } else { | ||
140 | return Intermediate; | ||
141 | } | ||
142 | } | ||
143 | }; | ||
144 | static SmtpServerValidator *validator = new SmtpServerValidator; | ||
145 | return validator; | ||
146 | } | ||
147 | |||
148 | void AccountSettings::saveAccount() | ||
149 | { | ||
150 | if (mAccountIdentifier.isEmpty()) { | ||
151 | auto account = ApplicationDomainType::createEntity<SinkAccount>(); | ||
152 | mAccountIdentifier = account.identifier(); | ||
153 | Q_ASSERT(!mAccountType.isEmpty()); | ||
154 | account.setAccountType(mAccountType); | ||
155 | account.setName(mName); | ||
156 | account.setIcon(mIcon); | ||
157 | Store::create(account) | ||
158 | .onError([](const KAsync::Error &error) { | ||
159 | qWarning() << "Error while creating account: " << error.errorMessage;; | ||
160 | }) | ||
161 | .exec(); | ||
162 | } else { | ||
163 | qDebug() << "Saving account " << mAccountIdentifier << mMailtransportIdentifier; | ||
164 | Q_ASSERT(!mAccountIdentifier.isEmpty()); | ||
165 | SinkAccount account(mAccountIdentifier); | ||
166 | account.setAccountType(mAccountType); | ||
167 | account.setName(mName); | ||
168 | account.setIcon(mIcon); | ||
169 | Q_ASSERT(!account.identifier().isEmpty()); | ||
170 | Store::modify(account) | ||
171 | .onError([](const KAsync::Error &error) { | ||
172 | qWarning() << "Error while creating account: " << error.errorMessage;; | ||
173 | }) | ||
174 | .exec(); | ||
175 | } | ||
176 | } | ||
177 | |||
178 | void AccountSettings::loadAccount() | ||
179 | { | ||
180 | Q_ASSERT(!mAccountIdentifier.isEmpty()); | ||
181 | Store::fetchOne<SinkAccount>(Query().filter(mAccountIdentifier).request<SinkAccount::Icon>().request<SinkAccount::Name>().request<SinkAccount::AccountType>()) | ||
182 | .then([this](const SinkAccount &account) { | ||
183 | mAccountType = account.getAccountType().toLatin1(); | ||
184 | mIcon = account.getIcon(); | ||
185 | mName = account.getName(); | ||
186 | emit changed(); | ||
187 | }).exec(); | ||
188 | } | ||
189 | |||
190 | void AccountSettings::loadImapResource() | ||
191 | { | ||
192 | Store::fetchOne<SinkResource>(Query().filter<SinkResource::Account>(mAccountIdentifier).containsFilter<SinkResource::Capabilities>(ResourceCapabilities::Mail::storage)) | ||
193 | .then([this](const SinkResource &resource) { | ||
194 | mImapIdentifier = resource.identifier(); | ||
195 | mImapServer = resource.getProperty("server").toString(); | ||
196 | mImapUsername = resource.getProperty("username").toString(); | ||
197 | mImapPassword = resource.getProperty("password").toString(); | ||
198 | emit imapResourceChanged(); | ||
199 | }).onError([](const KAsync::Error &error) { | ||
200 | qWarning() << "Failed to find the imap resource: " << error.errorMessage; | ||
201 | }).exec(); | ||
202 | } | ||
203 | |||
204 | void AccountSettings::loadMaildirResource() | ||
205 | { | ||
206 | Store::fetchOne<SinkResource>(Query().filter<SinkResource::Account>(mAccountIdentifier).containsFilter<SinkResource::Capabilities>(ResourceCapabilities::Mail::storage)) | ||
207 | .then([this](const SinkResource &resource) { | ||
208 | mMaildirIdentifier = resource.identifier(); | ||
209 | auto path = resource.getProperty("path").toString(); | ||
210 | if (mPath != path) { | ||
211 | mPath = path; | ||
212 | emit pathChanged(); | ||
213 | } | ||
214 | }).onError([](const KAsync::Error &error) { | ||
215 | SinkWarning() << "Failed to find the maildir resource: " << error.errorMessage; | ||
216 | }).exec(); | ||
217 | } | ||
218 | |||
219 | void AccountSettings::loadMailtransportResource() | ||
220 | { | ||
221 | Store::fetchOne<SinkResource>(Query().filter<SinkResource::Account>(mAccountIdentifier).containsFilter<SinkResource::Capabilities>(ResourceCapabilities::Mail::transport)) | ||
222 | .then([this](const SinkResource &resource) { | ||
223 | mMailtransportIdentifier = resource.identifier(); | ||
224 | mSmtpServer = resource.getProperty("server").toString(); | ||
225 | mSmtpUsername = resource.getProperty("username").toString(); | ||
226 | mSmtpPassword = resource.getProperty("password").toString(); | ||
227 | emit smtpResourceChanged(); | ||
228 | }).onError([](const KAsync::Error &error) { | ||
229 | SinkWarning() << "Failed to find the smtp resource: " << error.errorMessage; | ||
230 | }).exec(); | ||
231 | } | ||
232 | |||
233 | void AccountSettings::loadIdentity() | ||
234 | { | ||
235 | //FIXME this assumes that we only ever have one identity per account | ||
236 | Store::fetchOne<Identity>(Query().filter<Identity::Account>(mAccountIdentifier)) | ||
237 | .then([this](const Identity &identity) { | ||
238 | mIdentityIdentifier = identity.identifier(); | ||
239 | mUsername = identity.getName(); | ||
240 | mEmailAddress = identity.getAddress(); | ||
241 | emit identityChanged(); | ||
242 | }).onError([](const KAsync::Error &error) { | ||
243 | SinkWarning() << "Failed to find the identity resource: " << error.errorMessage; | ||
244 | }).exec(); | ||
245 | } | ||
246 | |||
247 | void AccountSettings::loadCardDavResource() | ||
248 | { | ||
249 | Store::fetchOne<SinkResource>(Query().filter<SinkResource::Account>(mAccountIdentifier).containsFilter<SinkResource::Capabilities>(ResourceCapabilities::Mail::storage)) | ||
250 | .then([this](const SinkResource &resource) { | ||
251 | mCardDavIdentifier = resource.identifier(); | ||
252 | mCardDavServer = resource.getProperty("server").toString(); | ||
253 | mCardDavUsername = resource.getProperty("username").toString(); | ||
254 | mCardDavPassword = resource.getProperty("password").toString(); | ||
255 | emit cardDavResourceChanged(); | ||
256 | }).onError([](const KAsync::Error &error) { | ||
257 | qWarning() << "Failed to find the CardDAV resource: " << error.errorMessage; | ||
258 | }).exec(); | ||
259 | } | ||
260 | |||
261 | |||
262 | template<typename ResourceType> | ||
263 | static QByteArray saveResource(const QByteArray &accountIdentifier, const QByteArray &identifier, const std::map<QByteArray, QVariant> &properties) | ||
264 | { | ||
265 | if (!identifier.isEmpty()) { | ||
266 | SinkResource resource(identifier); | ||
267 | for (const auto &pair : properties) { | ||
268 | resource.setProperty(pair.first, pair.second); | ||
269 | } | ||
270 | Store::modify(resource) | ||
271 | .onError([](const KAsync::Error &error) { | ||
272 | SinkWarning() << "Error while modifying resource: " << error.errorMessage; | ||
273 | }) | ||
274 | .exec(); | ||
275 | } else { | ||
276 | auto resource = ResourceType::create(accountIdentifier); | ||
277 | auto newIdentifier = resource.identifier(); | ||
278 | for (const auto &pair : properties) { | ||
279 | resource.setProperty(pair.first, pair.second); | ||
280 | } | ||
281 | Store::create(resource) | ||
282 | .onError([](const KAsync::Error &error) { | ||
283 | SinkWarning() << "Error while creating resource: " << error.errorMessage; | ||
284 | }) | ||
285 | .exec(); | ||
286 | return newIdentifier; | ||
287 | } | ||
288 | return identifier; | ||
289 | } | ||
290 | |||
291 | void AccountSettings::saveImapResource() | ||
292 | { | ||
293 | mImapIdentifier = saveResource<ImapResource>(mAccountIdentifier, mImapIdentifier, { | ||
294 | {"server", mImapServer}, | ||
295 | {"username", mImapUsername}, | ||
296 | {"password", mImapPassword}, | ||
297 | }); | ||
298 | } | ||
299 | |||
300 | void AccountSettings::saveCardDavResource() | ||
301 | { | ||
302 | mCardDavIdentifier = saveResource<CardDavResource>(mAccountIdentifier, mCardDavIdentifier, { | ||
303 | {"server", mCardDavServer}, | ||
304 | {"username", mCardDavUsername}, | ||
305 | {"password", mCardDavPassword}, | ||
306 | }); | ||
307 | } | ||
308 | |||
309 | void AccountSettings::saveMaildirResource() | ||
310 | { | ||
311 | mMaildirIdentifier = saveResource<MaildirResource>(mAccountIdentifier, mMaildirIdentifier, { | ||
312 | {"path", mPath}, | ||
313 | }); | ||
314 | } | ||
315 | |||
316 | void AccountSettings::saveMailtransportResource() | ||
317 | { | ||
318 | mMailtransportIdentifier = saveResource<MailtransportResource>(mAccountIdentifier, mMailtransportIdentifier, { | ||
319 | {"server", mSmtpServer}, | ||
320 | {"username", mSmtpUsername}, | ||
321 | {"password", mSmtpPassword}, | ||
322 | }); | ||
323 | } | ||
324 | |||
325 | void AccountSettings::saveIdentity() | ||
326 | { | ||
327 | if (!mIdentityIdentifier.isEmpty()) { | ||
328 | Identity identity(mMailtransportIdentifier); | ||
329 | identity.setName(mUsername); | ||
330 | identity.setAddress(mEmailAddress); | ||
331 | Store::modify(identity) | ||
332 | .onError([](const KAsync::Error &error) { | ||
333 | SinkWarning() << "Error while modifying identity: " << error.errorMessage; | ||
334 | }) | ||
335 | .exec(); | ||
336 | } else { | ||
337 | auto identity = ApplicationDomainType::createEntity<Identity>(); | ||
338 | mIdentityIdentifier = identity.identifier(); | ||
339 | identity.setAccount(mAccountIdentifier); | ||
340 | identity.setName(mUsername); | ||
341 | identity.setAddress(mEmailAddress); | ||
342 | Store::create(identity) | ||
343 | .onError([](const KAsync::Error &error) { | ||
344 | SinkWarning() << "Error while creating identity: " << error.errorMessage; | ||
345 | }) | ||
346 | .exec(); | ||
347 | } | ||
348 | } | ||
349 | |||
350 | void AccountSettings::removeResource(const QByteArray &identifier) | ||
351 | { | ||
352 | if (identifier.isEmpty()) { | ||
353 | SinkWarning() << "We're missing an identifier"; | ||
354 | } else { | ||
355 | SinkResource resource(identifier); | ||
356 | Store::remove(resource) | ||
357 | .onError([](const KAsync::Error &error) { | ||
358 | SinkWarning() << "Error while removing resource: " << error.errorMessage; | ||
359 | }) | ||
360 | .exec(); | ||
361 | } | ||
362 | } | ||
363 | |||
364 | void AccountSettings::removeAccount() | ||
365 | { | ||
366 | if (mAccountIdentifier.isEmpty()) { | ||
367 | SinkWarning() << "We're missing an identifier"; | ||
368 | } else { | ||
369 | SinkAccount account(mAccountIdentifier); | ||
370 | Store::remove(account) | ||
371 | .onError([](const KAsync::Error &error) { | ||
372 | SinkWarning() << "Error while removing account: " << error.errorMessage; | ||
373 | }) | ||
374 | .exec(); | ||
375 | } | ||
376 | } | ||
377 | |||
378 | void AccountSettings::removeIdentity() | ||
379 | { | ||
380 | if (mIdentityIdentifier.isEmpty()) { | ||
381 | SinkWarning() << "We're missing an identifier"; | ||
382 | } else { | ||
383 | Identity identity(mIdentityIdentifier); | ||
384 | Store::remove(identity) | ||
385 | .onError([](const KAsync::Error &error) { | ||
386 | SinkWarning() << "Error while removing identity: " << error.errorMessage; | ||
387 | }) | ||
388 | .exec(); | ||
389 | } | ||
390 | } | ||
391 | |||
diff --git a/framework/src/domain/settings/accountsettings.h b/framework/src/domain/settings/accountsettings.h new file mode 100644 index 00000000..077b7784 --- /dev/null +++ b/framework/src/domain/settings/accountsettings.h | |||
@@ -0,0 +1,123 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #pragma once | ||
20 | |||
21 | #include <QObject> | ||
22 | #include <QValidator> | ||
23 | |||
24 | class AccountSettings : public QObject | ||
25 | { | ||
26 | Q_OBJECT | ||
27 | Q_PROPERTY(QByteArray accountIdentifier READ accountIdentifier WRITE setAccountIdentifier) | ||
28 | Q_PROPERTY(QByteArray accountType READ accountType WRITE setAccountType) | ||
29 | Q_PROPERTY(QString icon MEMBER mIcon NOTIFY changed) | ||
30 | Q_PROPERTY(QString accountName MEMBER mName NOTIFY changed) | ||
31 | |||
32 | Q_PROPERTY(QString userName MEMBER mUsername NOTIFY identityChanged) | ||
33 | Q_PROPERTY(QString emailAddress MEMBER mEmailAddress NOTIFY identityChanged) | ||
34 | |||
35 | Q_PROPERTY(QString imapServer MEMBER mImapServer NOTIFY imapResourceChanged) | ||
36 | Q_PROPERTY(QValidator* imapServerValidator READ imapServerValidator CONSTANT) | ||
37 | Q_PROPERTY(QString imapUsername MEMBER mImapUsername NOTIFY imapResourceChanged) | ||
38 | Q_PROPERTY(QString imapPassword MEMBER mImapPassword NOTIFY imapResourceChanged) | ||
39 | |||
40 | Q_PROPERTY(QString smtpServer MEMBER mSmtpServer NOTIFY smtpResourceChanged) | ||
41 | Q_PROPERTY(QValidator* smtpServerValidator READ smtpServerValidator CONSTANT) | ||
42 | Q_PROPERTY(QString smtpUsername MEMBER mSmtpUsername NOTIFY smtpResourceChanged) | ||
43 | Q_PROPERTY(QString smtpPassword MEMBER mSmtpPassword NOTIFY smtpResourceChanged) | ||
44 | |||
45 | Q_PROPERTY(QUrl path READ path WRITE setPath NOTIFY pathChanged) | ||
46 | Q_PROPERTY(QValidator* pathValidator READ pathValidator CONSTANT) | ||
47 | |||
48 | public: | ||
49 | AccountSettings(QObject *parent = 0); | ||
50 | |||
51 | void setAccountIdentifier(const QByteArray &); | ||
52 | QByteArray accountIdentifier() const; | ||
53 | |||
54 | void setAccountType(const QByteArray &); | ||
55 | QByteArray accountType() const; | ||
56 | |||
57 | void setPath(const QUrl &); | ||
58 | QUrl path() const; | ||
59 | |||
60 | virtual QValidator *imapServerValidator() const; | ||
61 | virtual QValidator *smtpServerValidator() const; | ||
62 | virtual QValidator *pathValidator() const; | ||
63 | |||
64 | Q_INVOKABLE virtual void load() = 0; | ||
65 | Q_INVOKABLE virtual void save() = 0; | ||
66 | Q_INVOKABLE virtual void remove() = 0; | ||
67 | |||
68 | signals: | ||
69 | void imapResourceChanged(); | ||
70 | void smtpResourceChanged(); | ||
71 | void identityChanged(); | ||
72 | void pathChanged(); | ||
73 | void changed(); | ||
74 | void cardDavResourceChanged(); | ||
75 | |||
76 | protected: | ||
77 | void saveAccount(); | ||
78 | void saveImapResource(); | ||
79 | void saveMaildirResource(); | ||
80 | void saveMailtransportResource(); | ||
81 | void saveIdentity(); | ||
82 | void saveCardDavResource(); | ||
83 | |||
84 | void loadAccount(); | ||
85 | void loadImapResource(); | ||
86 | void loadMaildirResource(); | ||
87 | void loadMailtransportResource(); | ||
88 | void loadIdentity(); | ||
89 | void loadCardDavResource(); | ||
90 | |||
91 | void removeAccount(); | ||
92 | void removeResource(const QByteArray &identifier); | ||
93 | |||
94 | void removeIdentity(); | ||
95 | |||
96 | QByteArray mAccountIdentifier; | ||
97 | QByteArray mAccountType; | ||
98 | QString mIcon; | ||
99 | QString mName; | ||
100 | |||
101 | QByteArray mImapIdentifier; | ||
102 | QString mImapServer; | ||
103 | QString mImapUsername; | ||
104 | QString mImapPassword; | ||
105 | |||
106 | QByteArray mMaildirIdentifier; | ||
107 | QString mPath; | ||
108 | |||
109 | QByteArray mMailtransportIdentifier; | ||
110 | QString mSmtpServer; | ||
111 | QString mSmtpUsername; | ||
112 | QString mSmtpPassword; | ||
113 | |||
114 | QByteArray mIdentityIdentifier; | ||
115 | QString mUsername; | ||
116 | QString mEmailAddress; | ||
117 | |||
118 | QByteArray mCardDavIdentifier; | ||
119 | QString mCardDavServer; | ||
120 | QString mCardDavUsername; | ||
121 | QString mCardDavPassword; | ||
122 | }; | ||
123 | |||
diff --git a/framework/src/domain/stringhtmlwriter.cpp b/framework/src/domain/stringhtmlwriter.cpp new file mode 100644 index 00000000..88034492 --- /dev/null +++ b/framework/src/domain/stringhtmlwriter.cpp | |||
@@ -0,0 +1,150 @@ | |||
1 | /* -*- c++ -*- | ||
2 | filehtmlwriter.cpp | ||
3 | |||
4 | This file is part of KMail, the KDE mail client. | ||
5 | Copyright (c) 2003 Marc Mutz <mutz@kde.org> | ||
6 | |||
7 | KMail is free software; you can redistribute it and/or modify it | ||
8 | under the terms of the GNU General Public License, version 2, as | ||
9 | published by the Free Software Foundation. | ||
10 | |||
11 | KMail is distributed in the hope that it will be useful, but | ||
12 | WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
14 | General Public License for more details. | ||
15 | |||
16 | You should have received a copy of the GNU General Public License | ||
17 | along with this program; if not, write to the Free Software | ||
18 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
19 | |||
20 | In addition, as a special exception, the copyright holders give | ||
21 | permission to link the code of this program with any edition of | ||
22 | the Qt library by Trolltech AS, Norway (or with modified versions | ||
23 | of Qt that use the same license as Qt), and distribute linked | ||
24 | combinations including the two. You must obey the GNU General | ||
25 | Public License in all respects for all of the code used other than | ||
26 | Qt. If you modify this file, you may extend this exception to | ||
27 | your version of the file, but you are not obligated to do so. If | ||
28 | you do not wish to do so, delete this exception statement from | ||
29 | your version. | ||
30 | */ | ||
31 | |||
32 | #include "stringhtmlwriter.h" | ||
33 | |||
34 | #include <QDebug> | ||
35 | #include <QTextStream> | ||
36 | #include <QUrl> | ||
37 | |||
38 | StringHtmlWriter::StringHtmlWriter() | ||
39 | : MimeTreeParser::HtmlWriter() | ||
40 | , mState(Ended) | ||
41 | { | ||
42 | } | ||
43 | |||
44 | StringHtmlWriter::~StringHtmlWriter() | ||
45 | { | ||
46 | } | ||
47 | |||
48 | void StringHtmlWriter::begin(const QString &css) | ||
49 | { | ||
50 | if (mState != Ended) { | ||
51 | qWarning() << "begin() called on non-ended session!"; | ||
52 | reset(); | ||
53 | } | ||
54 | |||
55 | mState = Begun; | ||
56 | mExtraHead.clear(); | ||
57 | mHtml.clear(); | ||
58 | |||
59 | if (!css.isEmpty()) { | ||
60 | write(QLatin1String("<!-- CSS Definitions \n") + css + QLatin1String("-->\n")); | ||
61 | } | ||
62 | } | ||
63 | |||
64 | void StringHtmlWriter::end() | ||
65 | { | ||
66 | if (mState != Begun) { | ||
67 | qWarning() << "Called on non-begun or queued session!"; | ||
68 | } | ||
69 | |||
70 | if (!mExtraHead.isEmpty()) { | ||
71 | insertExtraHead(); | ||
72 | mExtraHead.clear(); | ||
73 | } | ||
74 | resolveCidUrls(); | ||
75 | mState = Ended; | ||
76 | } | ||
77 | |||
78 | void StringHtmlWriter::reset() | ||
79 | { | ||
80 | if (mState != Ended) { | ||
81 | mHtml.clear(); | ||
82 | mExtraHead.clear(); | ||
83 | mState = Begun; // don't run into end()'s warning | ||
84 | end(); | ||
85 | mState = Ended; | ||
86 | } | ||
87 | } | ||
88 | |||
89 | void StringHtmlWriter::write(const QString &str) | ||
90 | { | ||
91 | if (mState != Begun) { | ||
92 | qWarning() << "Called in Ended or Queued state!"; | ||
93 | } | ||
94 | mHtml.append(str); | ||
95 | } | ||
96 | |||
97 | void StringHtmlWriter::queue(const QString &str) | ||
98 | { | ||
99 | write(str); | ||
100 | } | ||
101 | |||
102 | void StringHtmlWriter::flush() | ||
103 | { | ||
104 | mState = Begun; // don't run into end()'s warning | ||
105 | end(); | ||
106 | } | ||
107 | |||
108 | void StringHtmlWriter::embedPart(const QByteArray &contentId, const QString &url) | ||
109 | { | ||
110 | write("<!-- embedPart(contentID=" + contentId + ", url=" + url + ") -->\n"); | ||
111 | mEmbeddedPartMap.insert(contentId, url); | ||
112 | } | ||
113 | |||
114 | void StringHtmlWriter::resolveCidUrls() | ||
115 | { | ||
116 | for (const auto &cid : mEmbeddedPartMap.keys()) { | ||
117 | mHtml.replace(QString("src=\"cid:%1\"").arg(QString(cid)), QString("src=\"%1\"").arg(mEmbeddedPartMap.value(cid).toString())); | ||
118 | } | ||
119 | } | ||
120 | |||
121 | void StringHtmlWriter::extraHead(const QString &extraHead) | ||
122 | { | ||
123 | if (mState != Ended) { | ||
124 | qWarning() << "Called on non-started session!"; | ||
125 | } | ||
126 | mExtraHead.append(extraHead); | ||
127 | } | ||
128 | |||
129 | |||
130 | void StringHtmlWriter::insertExtraHead() | ||
131 | { | ||
132 | const QString headTag(QStringLiteral("<head>")); | ||
133 | const int index = mHtml.indexOf(headTag); | ||
134 | if (index != -1) { | ||
135 | mHtml.insert(index + headTag.length(), mExtraHead); | ||
136 | } | ||
137 | } | ||
138 | |||
139 | QMap<QByteArray, QUrl> StringHtmlWriter::embeddedParts() const | ||
140 | { | ||
141 | return mEmbeddedPartMap; | ||
142 | } | ||
143 | |||
144 | QString StringHtmlWriter::html() const | ||
145 | { | ||
146 | if (mState != Ended) { | ||
147 | qWarning() << "Called on non-ended session!"; | ||
148 | } | ||
149 | return mHtml; | ||
150 | } | ||
diff --git a/framework/src/domain/stringhtmlwriter.h b/framework/src/domain/stringhtmlwriter.h new file mode 100644 index 00000000..fa5b760e --- /dev/null +++ b/framework/src/domain/stringhtmlwriter.h | |||
@@ -0,0 +1,71 @@ | |||
1 | /* -*- c++ -*- | ||
2 | |||
3 | Copyright (c) 2016 Sandro Knauß <sknauss@kde.org> | ||
4 | |||
5 | Kube is free software; you can redistribute it and/or modify it | ||
6 | under the terms of the GNU General Public License, version 2, as | ||
7 | published by the Free Software Foundation. | ||
8 | |||
9 | Kube is distributed in the hope that it will be useful, but | ||
10 | WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
12 | General Public License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU General Public License | ||
15 | along with this program; if not, write to the Free Software | ||
16 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
17 | |||
18 | In addition, as a special exception, the copyright holders give | ||
19 | permission to link the code of this program with any edition of | ||
20 | the Qt library by Trolltech AS, Norway (or with modified versions | ||
21 | of Qt that use the same license as Qt), and distribute linked | ||
22 | combinations including the two. You must obey the GNU General | ||
23 | Public License in all respects for all of the code used other than | ||
24 | Qt. If you modify this file, you may extend this exception to | ||
25 | your version of the file, but you are not obligated to do so. If | ||
26 | you do not wish to do so, delete this exception statement from | ||
27 | your version. | ||
28 | */ | ||
29 | |||
30 | #ifndef __KUBE_FRAMEWORK_MAIL_STRINGHTMLWRITER_H__ | ||
31 | #define __KUBE_FRAMEWORK_MAIL_STRINGHTMLWRITER_H__ | ||
32 | |||
33 | #include <MimeTreeParser/HtmlWriter> | ||
34 | |||
35 | #include <QFile> | ||
36 | #include <QTextStream> | ||
37 | |||
38 | class QString; | ||
39 | |||
40 | class StringHtmlWriter : public MimeTreeParser::HtmlWriter | ||
41 | { | ||
42 | public: | ||
43 | explicit StringHtmlWriter(); | ||
44 | virtual ~StringHtmlWriter(); | ||
45 | |||
46 | void begin(const QString &cssDefs) Q_DECL_OVERRIDE; | ||
47 | void end() Q_DECL_OVERRIDE; | ||
48 | void reset() Q_DECL_OVERRIDE; | ||
49 | void write(const QString &str) Q_DECL_OVERRIDE; | ||
50 | void queue(const QString &str) Q_DECL_OVERRIDE; | ||
51 | void flush() Q_DECL_OVERRIDE; | ||
52 | void embedPart(const QByteArray &contentId, const QString &url) Q_DECL_OVERRIDE; | ||
53 | void extraHead(const QString &str) Q_DECL_OVERRIDE; | ||
54 | |||
55 | QString html() const; | ||
56 | QMap<QByteArray, QUrl> embeddedParts() const; | ||
57 | private: | ||
58 | void insertExtraHead(); | ||
59 | void resolveCidUrls(); | ||
60 | |||
61 | QString mHtml; | ||
62 | QString mExtraHead; | ||
63 | enum State { | ||
64 | Begun, | ||
65 | Queued, | ||
66 | Ended | ||
67 | } mState; | ||
68 | QMap<QByteArray, QUrl> mEmbeddedPartMap; | ||
69 | }; | ||
70 | |||
71 | #endif // __MESSAGEVIEWER_FILEHTMLWRITER_H__ | ||
diff --git a/framework/src/frameworkplugin.cpp b/framework/src/frameworkplugin.cpp new file mode 100644 index 00000000..f491bae7 --- /dev/null +++ b/framework/src/frameworkplugin.cpp | |||
@@ -0,0 +1,72 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Michael Bohlender <michael.bohlender@kdemail.net> | ||
3 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
4 | |||
5 | This library is free software; you can redistribute it and/or modify it | ||
6 | under the terms of the GNU Library General Public License as published by | ||
7 | the Free Software Foundation; either version 2 of the License, or (at your | ||
8 | option) any later version. | ||
9 | |||
10 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
12 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
13 | License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU Library General Public License | ||
16 | along with this library; see the file COPYING.LIB. If not, write to the | ||
17 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
18 | 02110-1301, USA. | ||
19 | */ | ||
20 | |||
21 | #include "frameworkplugin.h" | ||
22 | |||
23 | #include "domain/maillistmodel.h" | ||
24 | #include "domain/folderlistmodel.h" | ||
25 | #include "domain/composercontroller.h" | ||
26 | #include "domain/messageparser.h" | ||
27 | #include "domain/retriever.h" | ||
28 | #include "domain/outboxmodel.h" | ||
29 | #include "domain/outboxcontroller.h" | ||
30 | #include "domain/mailcontroller.h" | ||
31 | #include "domain/foldercontroller.h" | ||
32 | #include "domain/mouseproxy.h" | ||
33 | #include "domain/contactcontroller.h" | ||
34 | #include "domain/peoplemodel.h" | ||
35 | #include "accounts/accountsmodel.h" | ||
36 | #include "accounts/accountfactory.h" | ||
37 | #include "settings/settings.h" | ||
38 | #include "notifications/notificationhandler.h" | ||
39 | #include "actions/action.h" | ||
40 | #include "actions/context.h" | ||
41 | #include "actions/actionhandler.h" | ||
42 | #include "actions/actionresult.h" | ||
43 | |||
44 | #include <QtQml> | ||
45 | |||
46 | void FrameworkPlugin::registerTypes (const char *uri) | ||
47 | { | ||
48 | qmlRegisterType<FolderListModel>(uri, 1, 0, "FolderListModel"); | ||
49 | qmlRegisterType<MailListModel>(uri, 1, 0, "MailListModel"); | ||
50 | qmlRegisterType<ComposerController>(uri, 1, 0, "ComposerController"); | ||
51 | qmlRegisterType<MessageParser>(uri, 1, 0, "MessageParser"); | ||
52 | qmlRegisterType<Retriever>(uri, 1, 0, "Retriever"); | ||
53 | qmlRegisterType<OutboxController>(uri, 1, 0, "OutboxController"); | ||
54 | qmlRegisterType<OutboxModel>(uri, 1, 0, "OutboxModel"); | ||
55 | qmlRegisterType<MailController>(uri, 1, 0, "MailController"); | ||
56 | qmlRegisterType<FolderController>(uri, 1, 0, "FolderController"); | ||
57 | qmlRegisterType<MouseProxy>(uri, 1, 0, "MouseProxy"); | ||
58 | qmlRegisterType<ContactController>(uri, 1, 0,"ContactController"); | ||
59 | qmlRegisterType<PeopleModel>(uri, 1, 0,"PeopleModel"); | ||
60 | |||
61 | qmlRegisterType<AccountFactory>(uri, 1, 0, "AccountFactory"); | ||
62 | qmlRegisterType<AccountsModel>(uri, 1, 0, "AccountsModel"); | ||
63 | |||
64 | qmlRegisterType<Kube::Settings>(uri, 1, 0, "Settings"); | ||
65 | qmlRegisterType<Kube::NotificationHandler>(uri, 1, 0, "NotificationHandler"); | ||
66 | qmlRegisterType<Kube::Notification>(uri, 1, 0, "Notification"); | ||
67 | |||
68 | qmlRegisterType<Kube::Context>(uri, 1, 0, "Context"); | ||
69 | qmlRegisterType<Kube::Action>(uri, 1, 0, "Action"); | ||
70 | qmlRegisterType<Kube::ActionHandler>(uri, 1, 0, "ActionHandler"); | ||
71 | qmlRegisterType<Kube::ActionResult>(uri, 1, 0, "ActionResult"); | ||
72 | } | ||
diff --git a/framework/src/frameworkplugin.h b/framework/src/frameworkplugin.h new file mode 100644 index 00000000..519e0ba9 --- /dev/null +++ b/framework/src/frameworkplugin.h | |||
@@ -0,0 +1,33 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Michael Bohlender <michael.bohlender@kdemail.net> | ||
3 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
4 | |||
5 | This library is free software; you can redistribute it and/or modify it | ||
6 | under the terms of the GNU Library General Public License as published by | ||
7 | the Free Software Foundation; either version 2 of the License, or (at your | ||
8 | option) any later version. | ||
9 | |||
10 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
12 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
13 | License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU Library General Public License | ||
16 | along with this library; see the file COPYING.LIB. If not, write to the | ||
17 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
18 | 02110-1301, USA. | ||
19 | */ | ||
20 | |||
21 | #pragma once | ||
22 | |||
23 | #include <QQmlEngine> | ||
24 | #include <QQmlExtensionPlugin> | ||
25 | |||
26 | class FrameworkPlugin : public QQmlExtensionPlugin | ||
27 | { | ||
28 | Q_OBJECT | ||
29 | Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") | ||
30 | |||
31 | public: | ||
32 | virtual void registerTypes(const char *uri); | ||
33 | }; | ||
diff --git a/framework/src/notifications/notificationhandler.cpp b/framework/src/notifications/notificationhandler.cpp new file mode 100644 index 00000000..093d638a --- /dev/null +++ b/framework/src/notifications/notificationhandler.cpp | |||
@@ -0,0 +1,80 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | |||
20 | #include "notificationhandler.h" | ||
21 | |||
22 | #include <QDebug> | ||
23 | |||
24 | #include <sink/notifier.h> | ||
25 | #include <sink/notification.h> | ||
26 | #include <sink/query.h> | ||
27 | |||
28 | using namespace Kube; | ||
29 | |||
30 | NotificationHandler::NotificationHandler(QObject *parent) | ||
31 | : QObject(parent) | ||
32 | { | ||
33 | Sink::Query query{Sink::Query::LiveQuery}; | ||
34 | mNotifier.reset(new Sink::Notifier{query}); | ||
35 | mNotifier->registerHandler([this] (const Sink::Notification ¬ification) { | ||
36 | notify(notification); | ||
37 | }); | ||
38 | |||
39 | } | ||
40 | |||
41 | void NotificationHandler::notify(const Sink::Notification ¬ification) | ||
42 | { | ||
43 | Notification n; | ||
44 | qWarning() << "Received notification: " << notification; | ||
45 | if (notification.type == Sink::Notification::Warning) { | ||
46 | n.mType = Notification::Warning; | ||
47 | if (notification.code == Sink::ApplicationDomain::TransmissionError) { | ||
48 | n.mMessage = "Failed to send message."; | ||
49 | } else { | ||
50 | return; | ||
51 | } | ||
52 | } else if (notification.type == Sink::Notification::Status) { | ||
53 | if (notification.code == Sink::ApplicationDomain::ErrorStatus) { | ||
54 | //A resource entered error status | ||
55 | n.mType = Notification::Warning; | ||
56 | n.mMessage = "A resource experienced an error."; | ||
57 | } else { | ||
58 | return; | ||
59 | } | ||
60 | } else if (notification.type == Sink::Notification::Error) { | ||
61 | if (notification.code == Sink::ApplicationDomain::ConnectionError) { | ||
62 | n.mType = Notification::Warning; | ||
63 | n.mMessage = "Failed to connect to server."; | ||
64 | } else { | ||
65 | return; | ||
66 | } | ||
67 | } else if (notification.type == Sink::Notification::Info) { | ||
68 | n.mType = Notification::Info; | ||
69 | if (notification.code == Sink::ApplicationDomain::TransmissionSuccess) { | ||
70 | n.mMessage = "A message has been sent."; | ||
71 | } else { | ||
72 | return; | ||
73 | } | ||
74 | } else { | ||
75 | return; | ||
76 | } | ||
77 | //The base implementation to call the handler in QML | ||
78 | QMetaObject::invokeMethod(this, "handler", Q_ARG(QVariant, QVariant::fromValue(&n))); | ||
79 | } | ||
80 | |||
diff --git a/framework/src/notifications/notificationhandler.h b/framework/src/notifications/notificationhandler.h new file mode 100644 index 00000000..fa992e87 --- /dev/null +++ b/framework/src/notifications/notificationhandler.h | |||
@@ -0,0 +1,80 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #pragma once | ||
20 | |||
21 | #include <QObject> | ||
22 | #include <QVariant> | ||
23 | #include <functional> | ||
24 | #include <sink/notifier.h> | ||
25 | |||
26 | namespace Sink { | ||
27 | class Notification; | ||
28 | } | ||
29 | |||
30 | namespace Kube { | ||
31 | |||
32 | class Notification : public QObject | ||
33 | { | ||
34 | Q_OBJECT | ||
35 | |||
36 | public: | ||
37 | enum Type { | ||
38 | Info, | ||
39 | Warning | ||
40 | }; | ||
41 | Q_ENUMS(Type) | ||
42 | |||
43 | Q_PROPERTY(Type type MEMBER mType CONSTANT) | ||
44 | Q_PROPERTY(QString message MEMBER mMessage CONSTANT) | ||
45 | |||
46 | Notification() = default; | ||
47 | ~Notification() = default; | ||
48 | |||
49 | Type mType; | ||
50 | QString mMessage; | ||
51 | }; | ||
52 | |||
53 | /** | ||
54 | * A notification handler. | ||
55 | * | ||
56 | * Can beinstantiated from QML like so: | ||
57 | * NotificationHandler { | ||
58 | * function handler(notification) { | ||
59 | * ...do something | ||
60 | * } | ||
61 | * } | ||
62 | * | ||
63 | * The handler will listen for all notifications by default. | ||
64 | */ | ||
65 | class NotificationHandler : public QObject | ||
66 | { | ||
67 | Q_OBJECT | ||
68 | |||
69 | public: | ||
70 | NotificationHandler(QObject *parent = 0); | ||
71 | |||
72 | virtual void notify(const Sink::Notification ¬ification); | ||
73 | |||
74 | private: | ||
75 | QScopedPointer<Sink::Notifier> mNotifier; | ||
76 | }; | ||
77 | |||
78 | } | ||
79 | |||
80 | Q_DECLARE_METATYPE(Kube::Notification*); | ||
diff --git a/framework/src/settings/settings.cpp b/framework/src/settings/settings.cpp new file mode 100644 index 00000000..00d1b9c5 --- /dev/null +++ b/framework/src/settings/settings.cpp | |||
@@ -0,0 +1,174 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #include "settings.h" | ||
20 | |||
21 | #include <QDebug> | ||
22 | #include <QStandardPaths> | ||
23 | #include <QMetaObject> | ||
24 | #include <QMetaProperty> | ||
25 | #include <QFile> | ||
26 | |||
27 | using namespace Kube; | ||
28 | |||
29 | Settings::Settings(QObject *parent) | ||
30 | : QObject(parent), | ||
31 | mLoaded(false) | ||
32 | { | ||
33 | |||
34 | } | ||
35 | |||
36 | Settings::Settings(const QByteArray &id, QObject *parent) | ||
37 | : QObject(parent), | ||
38 | mIdentifier(id), | ||
39 | mLoaded(false) | ||
40 | { | ||
41 | load(); | ||
42 | } | ||
43 | |||
44 | Settings::Settings(const Settings &other) | ||
45 | : QObject(other.parent()), | ||
46 | mIdentifier(other.mIdentifier), | ||
47 | mLoaded(false) | ||
48 | { | ||
49 | load(); | ||
50 | } | ||
51 | |||
52 | Settings::~Settings() | ||
53 | { | ||
54 | } | ||
55 | |||
56 | QSharedPointer<QSettings> Settings::getSettings() | ||
57 | { | ||
58 | return QSharedPointer<QSettings>::create(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QString("/kube/%1.ini").arg(QString::fromLatin1(mIdentifier)), QSettings::IniFormat); | ||
59 | } | ||
60 | |||
61 | void Settings::save() | ||
62 | { | ||
63 | qWarning() << "Saving" << mIdentifier; | ||
64 | auto settings = getSettings(); | ||
65 | |||
66 | for (const auto &p : dynamicPropertyNames()) { | ||
67 | qWarning() << "setting " << p << property(p); | ||
68 | if (p == "identifier" || p == "name") { | ||
69 | continue; | ||
70 | } | ||
71 | settings->setValue(p, property(p)); | ||
72 | } | ||
73 | for (int i = metaObject()->propertyOffset(); i < metaObject()->propertyCount(); i++) { | ||
74 | const auto p = metaObject()->property(i).name(); | ||
75 | if (p == QByteArray("identifier") || p == QByteArray("name")) { | ||
76 | continue; | ||
77 | } | ||
78 | qWarning() << "setting " << p << property(p); | ||
79 | settings->setValue(p, property(p)); | ||
80 | } | ||
81 | settings->sync(); | ||
82 | } | ||
83 | |||
84 | void Settings::remove() | ||
85 | { | ||
86 | QFile::remove(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QString("/kube/%1.ini").arg(QString::fromLatin1(mIdentifier))); | ||
87 | } | ||
88 | |||
89 | void Settings::load() | ||
90 | { | ||
91 | if (mLoaded || mIdentifier.isEmpty() || mIdentifier.endsWith(".")) { | ||
92 | return; | ||
93 | } | ||
94 | mLoaded = true; | ||
95 | for (int i = metaObject()->propertyOffset(); i < metaObject()->propertyCount(); i++) { | ||
96 | auto p = metaObject()->property(i).name(); | ||
97 | setProperty(p, QVariant()); | ||
98 | } | ||
99 | auto settings = getSettings(); | ||
100 | for (const auto &p : settings->allKeys()) { | ||
101 | qWarning() << "loading " << p << settings->value(p); | ||
102 | setProperty(p.toLatin1(), settings->value(p)); | ||
103 | } | ||
104 | } | ||
105 | |||
106 | void Settings::setIdentifier(const QByteArray &id) | ||
107 | { | ||
108 | mIdentifier = id; | ||
109 | load(); | ||
110 | } | ||
111 | |||
112 | QByteArray Settings::identifier() const | ||
113 | { | ||
114 | return mIdentifier; | ||
115 | } | ||
116 | |||
117 | ApplicationContext::ApplicationContext() | ||
118 | : Settings("applicationcontext") | ||
119 | { | ||
120 | |||
121 | } | ||
122 | |||
123 | Account ApplicationContext::currentAccount() const | ||
124 | { | ||
125 | return Account(property("currentAccountId").toByteArray()); | ||
126 | } | ||
127 | |||
128 | Account::Account(const QByteArray &identifier) | ||
129 | : Settings("account." + identifier) | ||
130 | { | ||
131 | |||
132 | } | ||
133 | |||
134 | Identity Account::primaryIdentity() const | ||
135 | { | ||
136 | return Identity(property("primaryIdentityId").toByteArray()); | ||
137 | } | ||
138 | |||
139 | QByteArray Account::type() const | ||
140 | { | ||
141 | return property("type").toByteArray(); | ||
142 | } | ||
143 | |||
144 | Identity::Identity(const QByteArray &identifier) | ||
145 | : Settings("identity." + identifier) | ||
146 | { | ||
147 | |||
148 | } | ||
149 | |||
150 | Transport Identity::transport() const | ||
151 | { | ||
152 | return Transport(property("transportId").toByteArray()); | ||
153 | } | ||
154 | |||
155 | Transport::Transport(const QByteArray &identifier) | ||
156 | : Settings("transport." + identifier) | ||
157 | { | ||
158 | |||
159 | } | ||
160 | |||
161 | QByteArray Transport::username() const | ||
162 | { | ||
163 | return property("username").toByteArray(); | ||
164 | } | ||
165 | |||
166 | QByteArray Transport::password() const | ||
167 | { | ||
168 | return property("password").toByteArray(); | ||
169 | } | ||
170 | |||
171 | QByteArray Transport::server() const | ||
172 | { | ||
173 | return property("server").toByteArray(); | ||
174 | } | ||
diff --git a/framework/src/settings/settings.h b/framework/src/settings/settings.h new file mode 100644 index 00000000..c5cff76b --- /dev/null +++ b/framework/src/settings/settings.h | |||
@@ -0,0 +1,92 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | #pragma once | ||
20 | |||
21 | #include <QObject> | ||
22 | #include <QByteArray> | ||
23 | #include <QSettings> | ||
24 | #include <QSharedPointer> | ||
25 | |||
26 | namespace Kube { | ||
27 | |||
28 | class Settings : public QObject { | ||
29 | Q_OBJECT | ||
30 | Q_PROPERTY(QByteArray identifier READ identifier WRITE setIdentifier) | ||
31 | public: | ||
32 | Settings(QObject *parent = 0); | ||
33 | Settings(const QByteArray &id, QObject *parent = 0); | ||
34 | virtual ~Settings(); | ||
35 | Settings(const Settings&); | ||
36 | |||
37 | void setIdentifier(const QByteArray &id); | ||
38 | QByteArray identifier() const; | ||
39 | |||
40 | Q_INVOKABLE void save(); | ||
41 | Q_INVOKABLE void remove(); | ||
42 | private: | ||
43 | void load(); | ||
44 | QSharedPointer<QSettings> getSettings(); | ||
45 | QByteArray mIdentifier; | ||
46 | bool mLoaded; | ||
47 | }; | ||
48 | |||
49 | class Account; | ||
50 | class Identity; | ||
51 | class Transport; | ||
52 | |||
53 | class ApplicationContext : public Settings | ||
54 | { | ||
55 | Q_OBJECT | ||
56 | public: | ||
57 | ApplicationContext(); | ||
58 | Account currentAccount() const; | ||
59 | |||
60 | }; | ||
61 | |||
62 | class Account : public Settings | ||
63 | { | ||
64 | Q_OBJECT | ||
65 | public: | ||
66 | Account(const QByteArray &identifier); | ||
67 | Identity primaryIdentity() const; | ||
68 | QByteArray type() const; | ||
69 | }; | ||
70 | |||
71 | class Identity : public Settings | ||
72 | { | ||
73 | Q_OBJECT | ||
74 | public: | ||
75 | Identity(const QByteArray &identifier); | ||
76 | Transport transport() const; | ||
77 | }; | ||
78 | |||
79 | class Transport : public Settings | ||
80 | { | ||
81 | Q_OBJECT | ||
82 | public: | ||
83 | Transport(const QByteArray &identifier); | ||
84 | QByteArray username() const; | ||
85 | QByteArray password() const; | ||
86 | QByteArray server() const; | ||
87 | }; | ||
88 | |||
89 | } | ||
90 | |||
91 | Q_DECLARE_METATYPE(Kube::Settings*); | ||
92 | |||