diff options
author | Christian Mollekopf <chrigi_1@fastmail.fm> | 2016-05-22 13:10:39 +0200 |
---|---|---|
committer | Christian Mollekopf <chrigi_1@fastmail.fm> | 2016-05-22 13:10:39 +0200 |
commit | 6864e4accaafa4fa90332719bff5a85a0e92b242 (patch) | |
tree | 31c0d6df98112674e55ea5ad636c2ad780add49c | |
parent | de52c17a7a08e72affc4c182fb1650d18d8b3b2b (diff) | |
download | sink-6864e4accaafa4fa90332719bff5a85a0e92b242.tar.gz sink-6864e4accaafa4fa90332719bff5a85a0e92b242.zip |
ImapResource prototype
-rw-r--r-- | common/domain/applicationdomaintype.cpp | 11 | ||||
-rw-r--r-- | common/domain/applicationdomaintype.h | 3 | ||||
-rw-r--r-- | examples/CMakeLists.txt | 1 | ||||
-rw-r--r-- | examples/imapresource/CMakeLists.txt | 18 | ||||
-rw-r--r-- | examples/imapresource/domainadaptor.cpp | 35 | ||||
-rw-r--r-- | examples/imapresource/domainadaptor.h | 38 | ||||
-rw-r--r-- | examples/imapresource/facade.cpp | 44 | ||||
-rw-r--r-- | examples/imapresource/facade.h | 36 | ||||
-rw-r--r-- | examples/imapresource/imapresource.cpp | 277 | ||||
-rw-r--r-- | examples/imapresource/imapresource.h | 71 | ||||
-rw-r--r-- | examples/imapresource/imapserverproxy.cpp | 209 | ||||
-rw-r--r-- | examples/imapresource/imapserverproxy.h | 62 | ||||
-rw-r--r-- | examples/imapresource/tests/CMakeLists.txt | 21 | ||||
-rw-r--r-- | examples/imapresource/tests/imapresourcetest.cpp | 162 | ||||
-rw-r--r-- | examples/imapresource/tests/resetmailbox.sh | 10 |
15 files changed, 998 insertions, 0 deletions
diff --git a/common/domain/applicationdomaintype.cpp b/common/domain/applicationdomaintype.cpp index 166d3e6..7be0295 100644 --- a/common/domain/applicationdomaintype.cpp +++ b/common/domain/applicationdomaintype.cpp | |||
@@ -261,6 +261,17 @@ namespace MailtransportResource { | |||
261 | } | 261 | } |
262 | } | 262 | } |
263 | 263 | ||
264 | namespace ImapResource { | ||
265 | SinkResource create(const QByteArray &account) | ||
266 | { | ||
267 | auto &&resource = ApplicationDomainType::createEntity<SinkResource>(); | ||
268 | resource.setProperty("type", "org.kde.imap"); | ||
269 | resource.setProperty("account", account); | ||
270 | resource.setProperty("capabilities", QVariant::fromValue(QByteArrayList() << "storage" << "drafts")); | ||
271 | return resource; | ||
272 | } | ||
273 | } | ||
274 | |||
264 | template<> | 275 | template<> |
265 | QByteArray getTypeName<Event>() | 276 | QByteArray getTypeName<Event>() |
266 | { | 277 | { |
diff --git a/common/domain/applicationdomaintype.h b/common/domain/applicationdomaintype.h index 4eec4a3..1b0ae9d 100644 --- a/common/domain/applicationdomaintype.h +++ b/common/domain/applicationdomaintype.h | |||
@@ -245,6 +245,9 @@ namespace MaildirResource { | |||
245 | namespace MailtransportResource { | 245 | namespace MailtransportResource { |
246 | SinkResource SINK_EXPORT create(const QByteArray &account); | 246 | SinkResource SINK_EXPORT create(const QByteArray &account); |
247 | }; | 247 | }; |
248 | namespace ImapResource { | ||
249 | SinkResource SINK_EXPORT create(const QByteArray &account); | ||
250 | }; | ||
248 | 251 | ||
249 | /** | 252 | /** |
250 | * All types need to be registered here an MUST return a different name. | 253 | * All types need to be registered here an MUST return a different name. |
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index d5fcacf..59753bb 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt | |||
@@ -7,5 +7,6 @@ add_subdirectory(dummyresource) | |||
7 | if (BUILD_MAILDIR) | 7 | if (BUILD_MAILDIR) |
8 | # a maildir resource implementation | 8 | # a maildir resource implementation |
9 | add_subdirectory(maildirresource) | 9 | add_subdirectory(maildirresource) |
10 | add_subdirectory(imapresource) | ||
10 | endif() | 11 | endif() |
11 | add_subdirectory(mailtransportresource) | 12 | add_subdirectory(mailtransportresource) |
diff --git a/examples/imapresource/CMakeLists.txt b/examples/imapresource/CMakeLists.txt new file mode 100644 index 0000000..d5320a6 --- /dev/null +++ b/examples/imapresource/CMakeLists.txt | |||
@@ -0,0 +1,18 @@ | |||
1 | project(sink_resource_imap) | ||
2 | |||
3 | add_definitions(-DQT_PLUGIN) | ||
4 | include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) | ||
5 | |||
6 | find_package(KF5 COMPONENTS REQUIRED Mime IMAP KIO) | ||
7 | find_package(KF5CoreAddons REQUIRED) | ||
8 | |||
9 | include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) | ||
10 | |||
11 | add_library(${PROJECT_NAME} SHARED facade.cpp imapresource.cpp domainadaptor.cpp imapserverproxy.cpp) | ||
12 | qt5_use_modules(${PROJECT_NAME} Core Network) | ||
13 | #We need CoreAddons for KJob and KIOCore for KTcpSocket. Both used in KIMAP | ||
14 | target_link_libraries(${PROJECT_NAME} sink KF5::Mime KF5::IMAP KF5::CoreAddons KF5::KIOCore) | ||
15 | |||
16 | install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${SINK_RESOURCE_PLUGINS_PATH}) | ||
17 | |||
18 | add_subdirectory(tests) | ||
diff --git a/examples/imapresource/domainadaptor.cpp b/examples/imapresource/domainadaptor.cpp new file mode 100644 index 0000000..4e74ad2 --- /dev/null +++ b/examples/imapresource/domainadaptor.cpp | |||
@@ -0,0 +1,35 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastmail.fm> | ||
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 | ||
15 | * along with this program; if not, write to the | ||
16 | * Free Software Foundation, Inc., | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | */ | ||
19 | |||
20 | #include "domainadaptor.h" | ||
21 | |||
22 | using namespace flatbuffers; | ||
23 | |||
24 | ImapMailAdaptorFactory::ImapMailAdaptorFactory() | ||
25 | : DomainTypeAdaptorFactory() | ||
26 | { | ||
27 | |||
28 | } | ||
29 | |||
30 | ImapFolderAdaptorFactory::ImapFolderAdaptorFactory() | ||
31 | : DomainTypeAdaptorFactory() | ||
32 | { | ||
33 | |||
34 | } | ||
35 | |||
diff --git a/examples/imapresource/domainadaptor.h b/examples/imapresource/domainadaptor.h new file mode 100644 index 0000000..06a513c --- /dev/null +++ b/examples/imapresource/domainadaptor.h | |||
@@ -0,0 +1,38 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastmail.fm> | ||
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 | ||
15 | * along with this program; if not, write to the | ||
16 | * Free Software Foundation, Inc., | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | */ | ||
19 | #pragma once | ||
20 | |||
21 | #include <common/domainadaptor.h> | ||
22 | #include "mail_generated.h" | ||
23 | #include "folder_generated.h" | ||
24 | #include "dummy_generated.h" | ||
25 | |||
26 | class ImapMailAdaptorFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Mail, Sink::ApplicationDomain::Buffer::Dummy, Sink::ApplicationDomain::Buffer::DummyBuilder> | ||
27 | { | ||
28 | public: | ||
29 | ImapMailAdaptorFactory(); | ||
30 | virtual ~ImapMailAdaptorFactory() {}; | ||
31 | }; | ||
32 | |||
33 | class ImapFolderAdaptorFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Folder, Sink::ApplicationDomain::Buffer::Dummy, Sink::ApplicationDomain::Buffer::DummyBuilder> | ||
34 | { | ||
35 | public: | ||
36 | ImapFolderAdaptorFactory(); | ||
37 | virtual ~ImapFolderAdaptorFactory() {}; | ||
38 | }; | ||
diff --git a/examples/imapresource/facade.cpp b/examples/imapresource/facade.cpp new file mode 100644 index 0000000..d338b01 --- /dev/null +++ b/examples/imapresource/facade.cpp | |||
@@ -0,0 +1,44 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014 Christian Mollekopf <chrigi_1@fastmail.fm> | ||
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 | ||
15 | * along with this program; if not, write to the | ||
16 | * Free Software Foundation, Inc., | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | */ | ||
19 | |||
20 | #include "facade.h" | ||
21 | |||
22 | #include <QDir> | ||
23 | #include <QFileInfo> | ||
24 | |||
25 | #include "domainadaptor.h" | ||
26 | #include "queryrunner.h" | ||
27 | |||
28 | ImapResourceMailFacade::ImapResourceMailFacade(const QByteArray &instanceIdentifier) | ||
29 | : Sink::GenericFacade<Sink::ApplicationDomain::Mail>(instanceIdentifier, QSharedPointer<ImapMailAdaptorFactory>::create()) | ||
30 | { | ||
31 | } | ||
32 | |||
33 | ImapResourceMailFacade::~ImapResourceMailFacade() | ||
34 | { | ||
35 | } | ||
36 | |||
37 | ImapResourceFolderFacade::ImapResourceFolderFacade(const QByteArray &instanceIdentifier) | ||
38 | : Sink::GenericFacade<Sink::ApplicationDomain::Folder>(instanceIdentifier, QSharedPointer<ImapFolderAdaptorFactory>::create()) | ||
39 | { | ||
40 | } | ||
41 | |||
42 | ImapResourceFolderFacade::~ImapResourceFolderFacade() | ||
43 | { | ||
44 | } | ||
diff --git a/examples/imapresource/facade.h b/examples/imapresource/facade.h new file mode 100644 index 0000000..479ad96 --- /dev/null +++ b/examples/imapresource/facade.h | |||
@@ -0,0 +1,36 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014 Christian Mollekopf <chrigi_1@fastmail.fm> | ||
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 | ||
15 | * along with this program; if not, write to the | ||
16 | * Free Software Foundation, Inc., | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | */ | ||
19 | |||
20 | #pragma once | ||
21 | |||
22 | #include "common/facade.h" | ||
23 | |||
24 | class ImapResourceMailFacade : public Sink::GenericFacade<Sink::ApplicationDomain::Mail> | ||
25 | { | ||
26 | public: | ||
27 | ImapResourceMailFacade(const QByteArray &instanceIdentifier); | ||
28 | virtual ~ImapResourceMailFacade(); | ||
29 | }; | ||
30 | |||
31 | class ImapResourceFolderFacade : public Sink::GenericFacade<Sink::ApplicationDomain::Folder> | ||
32 | { | ||
33 | public: | ||
34 | ImapResourceFolderFacade(const QByteArray &instanceIdentifier); | ||
35 | virtual ~ImapResourceFolderFacade(); | ||
36 | }; | ||
diff --git a/examples/imapresource/imapresource.cpp b/examples/imapresource/imapresource.cpp new file mode 100644 index 0000000..baa88b9 --- /dev/null +++ b/examples/imapresource/imapresource.cpp | |||
@@ -0,0 +1,277 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastmail.fm> | ||
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 | ||
15 | * along with this program; if not, write to the | ||
16 | * Free Software Foundation, Inc., | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | */ | ||
19 | |||
20 | #include "imapresource.h" | ||
21 | #include "facade.h" | ||
22 | #include "entitybuffer.h" | ||
23 | #include "pipeline.h" | ||
24 | #include "mail_generated.h" | ||
25 | #include "createentity_generated.h" | ||
26 | #include "modifyentity_generated.h" | ||
27 | #include "deleteentity_generated.h" | ||
28 | #include "domainadaptor.h" | ||
29 | #include "resourceconfig.h" | ||
30 | #include "commands.h" | ||
31 | #include "index.h" | ||
32 | #include "log.h" | ||
33 | #include "domain/mail.h" | ||
34 | #include "definitions.h" | ||
35 | #include "facadefactory.h" | ||
36 | #include "indexupdater.h" | ||
37 | #include "inspection.h" | ||
38 | #include <QDate> | ||
39 | #include <QUuid> | ||
40 | #include <QDir> | ||
41 | #include <QDirIterator> | ||
42 | |||
43 | #include "imapserverproxy.h" | ||
44 | |||
45 | //This is the resources entity type, and not the domain type | ||
46 | #define ENTITY_TYPE_MAIL "mail" | ||
47 | #define ENTITY_TYPE_FOLDER "folder" | ||
48 | |||
49 | #undef DEBUG_AREA | ||
50 | #define DEBUG_AREA "resource.imap" | ||
51 | |||
52 | |||
53 | ImapResource::ImapResource(const QByteArray &instanceIdentifier, const QSharedPointer<Sink::Pipeline> &pipeline) | ||
54 | : Sink::GenericResource(instanceIdentifier, pipeline), | ||
55 | mMailAdaptorFactory(QSharedPointer<ImapMailAdaptorFactory>::create()), | ||
56 | mFolderAdaptorFactory(QSharedPointer<ImapFolderAdaptorFactory>::create()) | ||
57 | { | ||
58 | auto config = ResourceConfig::getConfiguration(instanceIdentifier); | ||
59 | mServer = config.value("server").toString(); | ||
60 | mPort = config.value("port").toInt(); | ||
61 | |||
62 | // auto folderUpdater = new FolderUpdater(QByteArray()); | ||
63 | addType(ENTITY_TYPE_MAIL, mMailAdaptorFactory, | ||
64 | QVector<Sink::Preprocessor*>() << new DefaultIndexUpdater<Sink::ApplicationDomain::Mail>); | ||
65 | addType(ENTITY_TYPE_FOLDER, mFolderAdaptorFactory, | ||
66 | QVector<Sink::Preprocessor*>() << new DefaultIndexUpdater<Sink::ApplicationDomain::Folder>); | ||
67 | } | ||
68 | |||
69 | QByteArray ImapResource::createFolder(const QString &folderPath, const QByteArray &icon, Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction) | ||
70 | { | ||
71 | auto remoteId = folderPath.toUtf8(); | ||
72 | auto bufferType = ENTITY_TYPE_FOLDER; | ||
73 | Sink::ApplicationDomain::Folder folder; | ||
74 | folder.setProperty("name", folderPath.split('/').last()); | ||
75 | folder.setProperty("icon", icon); | ||
76 | |||
77 | // if (!md.isRoot()) { | ||
78 | // folder.setProperty("parent", resolveRemoteId(ENTITY_TYPE_FOLDER, md.parent().path().toUtf8(), synchronizationTransaction)); | ||
79 | // } | ||
80 | createOrModify(transaction, synchronizationTransaction, *mFolderAdaptorFactory, bufferType, remoteId, folder); | ||
81 | return remoteId; | ||
82 | } | ||
83 | |||
84 | void ImapResource::synchronizeFolders(const QStringList &folderList, Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction) | ||
85 | { | ||
86 | const QByteArray bufferType = ENTITY_TYPE_FOLDER; | ||
87 | Trace() << "Found folders " << folderList; | ||
88 | |||
89 | scanForRemovals(transaction, synchronizationTransaction, bufferType, | ||
90 | [&bufferType, &transaction](const std::function<void(const QByteArray &)> &callback) { | ||
91 | //TODO Instead of iterating over all entries in the database, which can also pick up the same item multiple times, | ||
92 | //we should rather iterate over an index that contains every uid exactly once. The remoteId index would be such an index, | ||
93 | //but we currently fail to iterate over all entries in an index it seems. | ||
94 | // auto remoteIds = synchronizationTransaction.openDatabase("rid.mapping." + bufferType, std::function<void(const Sink::Storage::Error &)>(), true); | ||
95 | auto mainDatabase = Sink::Storage::mainDatabase(transaction, bufferType); | ||
96 | mainDatabase.scan("", [&](const QByteArray &key, const QByteArray &) { | ||
97 | callback(key); | ||
98 | return true; | ||
99 | }); | ||
100 | }, | ||
101 | [&folderList](const QByteArray &remoteId) -> bool { | ||
102 | return folderList.contains(remoteId); | ||
103 | } | ||
104 | ); | ||
105 | |||
106 | for (const auto folderPath : folderList) { | ||
107 | createFolder(folderPath, "folder", transaction, synchronizationTransaction); | ||
108 | } | ||
109 | } | ||
110 | |||
111 | void ImapResource::synchronizeMails(Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction, const QString &path, const QVector<Message> &messages) | ||
112 | { | ||
113 | auto time = QSharedPointer<QTime>::create(); | ||
114 | time->start(); | ||
115 | const QByteArray bufferType = ENTITY_TYPE_MAIL; | ||
116 | |||
117 | |||
118 | Trace() << "Importing new mail."; | ||
119 | |||
120 | // Trace() << "Looking into " << listingPath; | ||
121 | |||
122 | const auto folderLocalId = resolveRemoteId(ENTITY_TYPE_FOLDER, path.toUtf8(), synchronizationTransaction); | ||
123 | |||
124 | //This is not a full listing | ||
125 | // auto property = "folder"; | ||
126 | // scanForRemovals(transaction, synchronizationTransaction, bufferType, | ||
127 | // [&](const std::function<void(const QByteArray &)> &callback) { | ||
128 | // Index index(bufferType + ".index." + property, transaction); | ||
129 | // index.lookup(folderLocalId, [&](const QByteArray &sinkId) { | ||
130 | // callback(sinkId); | ||
131 | // }, | ||
132 | // [&](const Index::Error &error) { | ||
133 | // Warning() << "Error in index: " << error.message << property; | ||
134 | // }); | ||
135 | // }, | ||
136 | // [](const QByteArray &remoteId) -> bool { | ||
137 | // return QFile(remoteId).exists(); | ||
138 | // } | ||
139 | // ); | ||
140 | |||
141 | mSynchronizerQueue.startTransaction(); | ||
142 | int count = 0; | ||
143 | for (const auto &message : messages) { | ||
144 | count++; | ||
145 | const auto remoteId = path.toUtf8() + "/" + QByteArray::number(message.uid); | ||
146 | |||
147 | Trace() << "Found a mail " << remoteId << message.msg->subject(true)->asUnicodeString() << message.flags; | ||
148 | |||
149 | Sink::ApplicationDomain::Mail mail; | ||
150 | mail.setFolder(folderLocalId); | ||
151 | //FIXME this should come from the mime message, extracted in the pipeline | ||
152 | mail.setExtractedSubject(message.msg->subject(true)->asUnicodeString()); | ||
153 | |||
154 | auto filePath = Sink::resourceStorageLocation(mResourceInstanceIdentifier) + "/" + remoteId; | ||
155 | QDir().mkpath(Sink::resourceStorageLocation(mResourceInstanceIdentifier) + "/" + path.toUtf8()); | ||
156 | QFile file(filePath); | ||
157 | if (!file.open(QIODevice::WriteOnly)) { | ||
158 | Warning() << "Failed to open file for writing: " << file.errorString(); | ||
159 | } | ||
160 | const auto content = message.msg->encodedContent(); | ||
161 | file.write(content); | ||
162 | mail.setMimeMessagePath(filePath); | ||
163 | //FIXME Not sure if these are the actual flags | ||
164 | mail.setUnread(message.flags.contains("\\SEEN")); | ||
165 | mail.setImportant(message.flags.contains("\\FLAGGED")); | ||
166 | |||
167 | createOrModify(transaction, synchronizationTransaction, *mMailAdaptorFactory, bufferType, remoteId, mail); | ||
168 | } | ||
169 | mSynchronizerQueue.commit(); | ||
170 | const auto elapsed = time->elapsed(); | ||
171 | Log() << "Synchronized " << count << " mails in " << path << Sink::Log::TraceTime(elapsed) << " " << elapsed/qMax(count, 1) << " [ms/mail]"; | ||
172 | } | ||
173 | |||
174 | KAsync::Job<void> ImapResource::synchronizeWithSource(Sink::Storage &mainStore, Sink::Storage &synchronizationStore) | ||
175 | { | ||
176 | Log() << " Synchronizing"; | ||
177 | return KAsync::start<void>([this, &mainStore, &synchronizationStore](KAsync::Future<void> future) { | ||
178 | ImapServerProxy imap(mServer, mPort); | ||
179 | QStringList folderList; | ||
180 | // QList<KAsync::Future<void>> waitCondition; | ||
181 | auto folderFuture = imap.fetchFolders([this, &imap, &mainStore, &synchronizationStore, &folderList](const QStringList &folders) { | ||
182 | auto transaction = mainStore.createTransaction(Sink::Storage::ReadOnly); | ||
183 | auto syncTransaction = synchronizationStore.createTransaction(Sink::Storage::ReadWrite); | ||
184 | synchronizeFolders(folders, transaction, syncTransaction); | ||
185 | transaction.commit(); | ||
186 | syncTransaction.commit(); | ||
187 | folderList << folders; | ||
188 | |||
189 | }); | ||
190 | folderFuture.waitForFinished(); | ||
191 | if (folderFuture.errorCode()) { | ||
192 | future.setError(1, "Folder list sync failed"); | ||
193 | return; | ||
194 | } | ||
195 | |||
196 | for (const auto &folder : folderList) { | ||
197 | // auto transaction = mainStore.createTransaction(Sink::Storage::ReadOnly); | ||
198 | // auto syncTransaction = synchronizationStore.createTransaction(Sink::Storage::ReadOnly); | ||
199 | |||
200 | //TODO load entity to read sync settings should we have some (if the folder is existing already) | ||
201 | //Note that this will not work if we change any of those settings in the pipeline | ||
202 | // | ||
203 | // auto mainDatabase = Sink::Storage::mainDatabase(transaction, ENTITY_TYPE_FOLDER); | ||
204 | // const auto sinkId = resolveRemoteId(ENTITY_TYPE_FOLDER, folder.toUtf8(), syncTransaction); | ||
205 | // const auto found = mainDatabase.contains(sinkId); | ||
206 | // if (found) { | ||
207 | // if (auto current = getLatest(mainDatabase, sinkId, mFolderAdaptorFactory)) { | ||
208 | // | ||
209 | // } | ||
210 | // } | ||
211 | |||
212 | // transaction.commit(); | ||
213 | // syncTransaction.commit(); | ||
214 | |||
215 | auto messagesFuture = imap.fetchMessages(folder, [this, &mainStore, &synchronizationStore, folder](const QVector<Message> &messages) { | ||
216 | auto transaction = mainStore.createTransaction(Sink::Storage::ReadOnly); | ||
217 | auto syncTransaction = synchronizationStore.createTransaction(Sink::Storage::ReadWrite); | ||
218 | Trace() << "Synchronizing mails" << folder; | ||
219 | synchronizeMails(transaction, syncTransaction, folder, messages); | ||
220 | transaction.commit(); | ||
221 | syncTransaction.commit(); | ||
222 | }); | ||
223 | messagesFuture.waitForFinished(); | ||
224 | if (messagesFuture.errorCode()) { | ||
225 | future.setError(1, "Folder sync failed: " + folder); | ||
226 | return; | ||
227 | } | ||
228 | } | ||
229 | |||
230 | |||
231 | // auto transaction = mainStore.createTransaction(Sink::Storage::ReadWrite); | ||
232 | // auto mainDatabase = Sink::Storage::mainDatabase(transaction, ENTITY_TYPE_FOLDER); | ||
233 | // mainDatabase.scan("", [&](const QByteArray &key, const QByteArray &data) { | ||
234 | // return true; | ||
235 | // }); | ||
236 | //TODO now fetch all folders and iterate over them and synchronize each one | ||
237 | |||
238 | Log() << "Done Synchronizing"; | ||
239 | future.setFinished(); | ||
240 | }); | ||
241 | } | ||
242 | |||
243 | KAsync::Job<void> ImapResource::replay(Sink::Storage &synchronizationStore, const QByteArray &type, const QByteArray &key, const QByteArray &value) | ||
244 | { | ||
245 | //TODO implement | ||
246 | return KAsync::null<void>(); | ||
247 | } | ||
248 | |||
249 | void ImapResource::removeFromDisk(const QByteArray &instanceIdentifier) | ||
250 | { | ||
251 | GenericResource::removeFromDisk(instanceIdentifier); | ||
252 | Sink::Storage(Sink::storageLocation(), instanceIdentifier + ".synchronization", Sink::Storage::ReadWrite).removeFromDisk(); | ||
253 | } | ||
254 | |||
255 | KAsync::Job<void> ImapResource::inspect(int inspectionType, const QByteArray &inspectionId, const QByteArray &domainType, const QByteArray &entityId, const QByteArray &property, const QVariant &expectedValue) | ||
256 | { | ||
257 | //TODO | ||
258 | return KAsync::null<void>(); | ||
259 | } | ||
260 | |||
261 | ImapResourceFactory::ImapResourceFactory(QObject *parent) | ||
262 | : Sink::ResourceFactory(parent) | ||
263 | { | ||
264 | |||
265 | } | ||
266 | |||
267 | Sink::Resource *ImapResourceFactory::createResource(const QByteArray &instanceIdentifier) | ||
268 | { | ||
269 | return new ImapResource(instanceIdentifier); | ||
270 | } | ||
271 | |||
272 | void ImapResourceFactory::registerFacades(Sink::FacadeFactory &factory) | ||
273 | { | ||
274 | factory.registerFacade<Sink::ApplicationDomain::Mail, ImapResourceMailFacade>(PLUGIN_NAME); | ||
275 | factory.registerFacade<Sink::ApplicationDomain::Folder, ImapResourceFolderFacade>(PLUGIN_NAME); | ||
276 | } | ||
277 | |||
diff --git a/examples/imapresource/imapresource.h b/examples/imapresource/imapresource.h new file mode 100644 index 0000000..6fe15dd --- /dev/null +++ b/examples/imapresource/imapresource.h | |||
@@ -0,0 +1,71 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastmail.fm> | ||
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 | ||
15 | * along with this program; if not, write to the | ||
16 | * Free Software Foundation, Inc., | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | */ | ||
19 | |||
20 | #pragma once | ||
21 | |||
22 | #include "common/genericresource.h" | ||
23 | |||
24 | #include <Async/Async> | ||
25 | |||
26 | #include <flatbuffers/flatbuffers.h> | ||
27 | |||
28 | //TODO: a little ugly to have this in two places, once here and once in Q_PLUGIN_METADATA | ||
29 | #define PLUGIN_NAME "org.kde.imap" | ||
30 | |||
31 | class ImapMailAdaptorFactory; | ||
32 | class ImapFolderAdaptorFactory; | ||
33 | struct Message; | ||
34 | |||
35 | /** | ||
36 | * An imap resource. | ||
37 | */ | ||
38 | class ImapResource : public Sink::GenericResource | ||
39 | { | ||
40 | public: | ||
41 | ImapResource(const QByteArray &instanceIdentifier, const QSharedPointer<Sink::Pipeline> &pipeline = QSharedPointer<Sink::Pipeline>()); | ||
42 | KAsync::Job<void> synchronizeWithSource(Sink::Storage &mainStore, Sink::Storage &synchronizationStore) Q_DECL_OVERRIDE; | ||
43 | KAsync::Job<void> inspect(int inspectionType, const QByteArray &inspectionId, const QByteArray &domainType, const QByteArray &entityId, const QByteArray &property, const QVariant &expectedValue) Q_DECL_OVERRIDE; | ||
44 | static void removeFromDisk(const QByteArray &instanceIdentifier); | ||
45 | private: | ||
46 | KAsync::Job<void> replay(Sink::Storage &synchronizationStore, const QByteArray &type, const QByteArray &key, const QByteArray &value) Q_DECL_OVERRIDE; | ||
47 | |||
48 | QByteArray createFolder(const QString &folderPath, const QByteArray &icon, Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction); | ||
49 | void synchronizeFolders(const QStringList &folderList, Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction); | ||
50 | void synchronizeMails(Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction, const QString &path, const QVector<Message> &messages); | ||
51 | |||
52 | QSharedPointer<ImapMailAdaptorFactory> mMailAdaptorFactory; | ||
53 | QSharedPointer<ImapFolderAdaptorFactory> mFolderAdaptorFactory; | ||
54 | private: | ||
55 | QString mServer; | ||
56 | int mPort; | ||
57 | }; | ||
58 | |||
59 | class ImapResourceFactory : public Sink::ResourceFactory | ||
60 | { | ||
61 | Q_OBJECT | ||
62 | Q_PLUGIN_METADATA(IID "org.kde.imap") | ||
63 | Q_INTERFACES(Sink::ResourceFactory) | ||
64 | |||
65 | public: | ||
66 | ImapResourceFactory(QObject *parent = 0); | ||
67 | |||
68 | Sink::Resource *createResource(const QByteArray &instanceIdentifier) Q_DECL_OVERRIDE; | ||
69 | void registerFacades(Sink::FacadeFactory &factory) Q_DECL_OVERRIDE; | ||
70 | }; | ||
71 | |||
diff --git a/examples/imapresource/imapserverproxy.cpp b/examples/imapresource/imapserverproxy.cpp new file mode 100644 index 0000000..9630096 --- /dev/null +++ b/examples/imapresource/imapserverproxy.cpp | |||
@@ -0,0 +1,209 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastmail.fm> | ||
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 | ||
15 | * along with this program; if not, write to the | ||
16 | * Free Software Foundation, Inc., | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | */ | ||
19 | #include "imapserverproxy.h" | ||
20 | |||
21 | #include <QDir> | ||
22 | #include <QFile> | ||
23 | #include <KIMAP/KIMAP/LoginJob> | ||
24 | #include <KIMAP/KIMAP/SelectJob> | ||
25 | #include <KIMAP/KIMAP/AppendJob> | ||
26 | |||
27 | #include <KIMAP/KIMAP/SessionUiProxy> | ||
28 | #include <KCoreAddons/KJob> | ||
29 | |||
30 | #include "log.h" | ||
31 | |||
32 | static KAsync::Job<void> runJob(KJob *job) | ||
33 | { | ||
34 | return KAsync::start<void>([job](KAsync::Future<void> &future) { | ||
35 | QObject::connect(job, &KJob::result, job, [&future](KJob *job) { | ||
36 | if (job->error()) { | ||
37 | Warning() << "Job failed: " << job->errorString(); | ||
38 | future.setError(job->error(), job->errorString()); | ||
39 | } else { | ||
40 | future.setFinished(); | ||
41 | } | ||
42 | }); | ||
43 | job->start(); | ||
44 | }); | ||
45 | } | ||
46 | |||
47 | class SessionUiProxy : public KIMAP::SessionUiProxy { | ||
48 | public: | ||
49 | bool ignoreSslError( const KSslErrorUiData &errorData ) { | ||
50 | return true; | ||
51 | } | ||
52 | }; | ||
53 | |||
54 | ImapServerProxy::ImapServerProxy(const QString &serverUrl, int port) : mSession(new KIMAP::Session(serverUrl, port)) | ||
55 | { | ||
56 | mSession->setUiProxy(SessionUiProxy::Ptr(new SessionUiProxy)); | ||
57 | mSession->setTimeout(10); | ||
58 | } | ||
59 | |||
60 | KAsync::Job<void> ImapServerProxy::login(const QString &username, const QString &password) | ||
61 | { | ||
62 | if (mSession->state() == KIMAP::Session::State::Authenticated || mSession->state() == KIMAP::Session::State::Selected) { | ||
63 | return KAsync::null<void>(); | ||
64 | } | ||
65 | auto loginJob = new KIMAP::LoginJob(mSession); | ||
66 | loginJob->setUserName(username); | ||
67 | loginJob->setPassword(password); | ||
68 | loginJob->setAuthenticationMode(KIMAP::LoginJob::Plain); | ||
69 | loginJob->setEncryptionMode(KIMAP::LoginJob::EncryptionMode::AnySslVersion); | ||
70 | return runJob(loginJob); | ||
71 | } | ||
72 | |||
73 | KAsync::Job<void> ImapServerProxy::select(const QString &mailbox) | ||
74 | { | ||
75 | if (mSession->state() == KIMAP::Session::State::Disconnected) { | ||
76 | return KAsync::error<void>(1, "Not connected"); | ||
77 | } | ||
78 | auto select = new KIMAP::SelectJob(mSession); | ||
79 | select->setMailBox(mailbox); | ||
80 | // select->setCondstoreEnabled(serverSupportsCondstore()); | ||
81 | return runJob(select); | ||
82 | } | ||
83 | |||
84 | KAsync::Job<void> ImapServerProxy::append(const QString &mailbox, const QByteArray &content, const QList<QByteArray> &flags, const QDateTime &internalDate) | ||
85 | { | ||
86 | if (mSession->state() == KIMAP::Session::State::Disconnected) { | ||
87 | return KAsync::error<void>(1, "Not connected"); | ||
88 | } | ||
89 | auto append = new KIMAP::AppendJob(mSession); | ||
90 | append->setMailBox(mailbox); | ||
91 | append->setContent(content); | ||
92 | append->setFlags(flags); | ||
93 | append->setInternalDate(internalDate); | ||
94 | return runJob(append); | ||
95 | } | ||
96 | |||
97 | KAsync::Job<void> ImapServerProxy::fetch(const KIMAP::ImapSet &set, KIMAP::FetchJob::FetchScope scope, FetchCallback callback) | ||
98 | { | ||
99 | if (mSession->state() == KIMAP::Session::State::Disconnected) { | ||
100 | return KAsync::error<void>(1, "Not connected"); | ||
101 | } | ||
102 | auto fetch = new KIMAP::FetchJob(mSession); | ||
103 | fetch->setSequenceSet(set); | ||
104 | fetch->setUidBased(true); | ||
105 | fetch->setScope(scope); | ||
106 | QObject::connect(fetch, static_cast<void(KIMAP::FetchJob::*)(const QString &, | ||
107 | const QMap<qint64,qint64> &, | ||
108 | const QMap<qint64,qint64> &, | ||
109 | const QMap<qint64,KIMAP::MessageAttribute> &, | ||
110 | const QMap<qint64,KIMAP::MessageFlags> &, | ||
111 | const QMap<qint64,KIMAP::MessagePtr> &)>(&KIMAP::FetchJob::headersReceived), | ||
112 | callback); | ||
113 | return runJob(fetch); | ||
114 | } | ||
115 | |||
116 | KAsync::Job<QList<qint64>> ImapServerProxy::fetchHeaders(const QString &mailbox) | ||
117 | { | ||
118 | auto list = QSharedPointer<QList<qint64>>::create(); | ||
119 | KIMAP::FetchJob::FetchScope scope; | ||
120 | scope.parts.clear(); | ||
121 | scope.mode = KIMAP::FetchJob::FetchScope::Headers; | ||
122 | |||
123 | //Fetch headers of all messages | ||
124 | return fetch(KIMAP::ImapSet(1, 0), scope, | ||
125 | [list](const QString &mailbox, | ||
126 | const QMap<qint64,qint64> &uids, | ||
127 | const QMap<qint64,qint64> &sizes, | ||
128 | const QMap<qint64,KIMAP::MessageAttribute> &attrs, | ||
129 | const QMap<qint64,KIMAP::MessageFlags> &flags, | ||
130 | const QMap<qint64,KIMAP::MessagePtr> &messages) { | ||
131 | Trace() << "Received " << uids.size() << " headers from " << mailbox; | ||
132 | Trace() << uids.size() << sizes.size() << attrs.size() << flags.size() << messages.size(); | ||
133 | |||
134 | //TODO based on the data available here, figure out which messages to actually fetch | ||
135 | //(we only fetched headers and structure so far) | ||
136 | //We could i.e. build chunks to fetch based on the size | ||
137 | |||
138 | for (const auto &id : uids.keys()) { | ||
139 | list->append(uids.value(id)); | ||
140 | } | ||
141 | }) | ||
142 | .then<QList<qint64>>([list](){ | ||
143 | return *list; | ||
144 | }); | ||
145 | } | ||
146 | |||
147 | KAsync::Job<void> ImapServerProxy::list(KIMAP::ListJob::Option option, const std::function<void(const QList<KIMAP::MailBoxDescriptor> &mailboxes,const QList<QList<QByteArray> > &flags)> &callback) | ||
148 | { | ||
149 | auto listJob = new KIMAP::ListJob(mSession); | ||
150 | listJob->setOption(option); | ||
151 | // listJob->setQueriedNamespaces(serverNamespaces()); | ||
152 | QObject::connect(listJob, &KIMAP::ListJob::mailBoxesReceived, | ||
153 | listJob, callback); | ||
154 | return runJob(listJob); | ||
155 | } | ||
156 | |||
157 | KAsync::Future<void> ImapServerProxy::fetchFolders(std::function<void(const QStringList &)> callback) | ||
158 | { | ||
159 | Trace() << "Fetching folders"; | ||
160 | auto job = login("doe", "doe").then<void>(list(KIMAP::ListJob::IncludeUnsubscribed, [callback](const QList<KIMAP::MailBoxDescriptor> &mailboxes, const QList<QList<QByteArray> > &flags){ | ||
161 | QStringList list; | ||
162 | for (const auto &mailbox : mailboxes) { | ||
163 | Trace() << "Found mailbox: " << mailbox.name; | ||
164 | list << mailbox.name; | ||
165 | } | ||
166 | callback(list); | ||
167 | }), | ||
168 | [](int errorCode, const QString &errorString) { | ||
169 | Warning() << "Failed to list folders: " << errorCode << errorString; | ||
170 | }); | ||
171 | return job.exec(); | ||
172 | } | ||
173 | |||
174 | KAsync::Future<void> ImapServerProxy::fetchMessages(const QString &folder, std::function<void(const QVector<Message> &)> callback) | ||
175 | { | ||
176 | auto job = login("doe", "doe").then<void>(select(folder)).then<void, KAsync::Job<void>>([this, callback, folder]() -> KAsync::Job<void> { | ||
177 | return fetchHeaders(folder).then<void, KAsync::Job<void>, QList<qint64>>([this, callback](const QList<qint64> &uidsToFetch){ | ||
178 | Trace() << "Uids to fetch: " << uidsToFetch; | ||
179 | if (uidsToFetch.isEmpty()) { | ||
180 | Trace() << "Nothing to fetch"; | ||
181 | return KAsync::null<void>(); | ||
182 | } | ||
183 | KIMAP::FetchJob::FetchScope scope; | ||
184 | scope.parts.clear(); | ||
185 | scope.mode = KIMAP::FetchJob::FetchScope::Full; | ||
186 | |||
187 | KIMAP::ImapSet set; | ||
188 | set.add(uidsToFetch.toVector()); | ||
189 | return fetch(set, scope, | ||
190 | [callback](const QString &mailbox, | ||
191 | const QMap<qint64,qint64> &uids, | ||
192 | const QMap<qint64,qint64> &sizes, | ||
193 | const QMap<qint64,KIMAP::MessageAttribute> &attrs, | ||
194 | const QMap<qint64,KIMAP::MessageFlags> &flags, | ||
195 | const QMap<qint64,KIMAP::MessagePtr> &messages) { | ||
196 | Trace() << "Received " << uids.size() << " messages from " << mailbox; | ||
197 | Trace() << uids.size() << sizes.size() << attrs.size() << flags.size() << messages.size(); | ||
198 | |||
199 | QVector<Message> list; | ||
200 | for (const auto &id : uids.keys()) { | ||
201 | list << Message{uids.value(id), sizes.value(id), attrs.value(id), flags.value(id), messages.value(id)}; | ||
202 | } | ||
203 | callback(list); | ||
204 | }); | ||
205 | }); | ||
206 | |||
207 | }); | ||
208 | return job.exec(); | ||
209 | } | ||
diff --git a/examples/imapresource/imapserverproxy.h b/examples/imapresource/imapserverproxy.h new file mode 100644 index 0000000..2beb28c --- /dev/null +++ b/examples/imapresource/imapserverproxy.h | |||
@@ -0,0 +1,62 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastmail.fm> | ||
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 | ||
15 | * along with this program; if not, write to the | ||
16 | * Free Software Foundation, Inc., | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | */ | ||
19 | |||
20 | #pragma once | ||
21 | |||
22 | #include <Async/Async> | ||
23 | |||
24 | #include <KMime/KMime/KMimeMessage> | ||
25 | #include <KIMAP/KIMAP/ListJob> | ||
26 | #include <KIMAP/KIMAP/Session> | ||
27 | #include <KIMAP/KIMAP/FetchJob> | ||
28 | |||
29 | struct Message { | ||
30 | qint64 uid; | ||
31 | qint64 size; | ||
32 | QPair<QByteArray, QVariant> attributes; | ||
33 | QList<QByteArray> flags; | ||
34 | KMime::Message::Ptr msg; | ||
35 | }; | ||
36 | |||
37 | class ImapServerProxy { | ||
38 | KIMAP::Session *mSession; | ||
39 | public: | ||
40 | ImapServerProxy(const QString &serverUrl, int port); | ||
41 | |||
42 | //Standard IMAP calls | ||
43 | KAsync::Job<void> login(const QString &username, const QString &password); | ||
44 | KAsync::Job<void> select(const QString &mailbox); | ||
45 | KAsync::Job<void> append(const QString &mailbox, const QByteArray &content, const QList<QByteArray> &flags = QList<QByteArray>(), const QDateTime &internalDate = QDateTime()); | ||
46 | |||
47 | typedef std::function<void(const QString &, | ||
48 | const QMap<qint64,qint64> &, | ||
49 | const QMap<qint64,qint64> &, | ||
50 | const QMap<qint64,KIMAP::MessageAttribute> &, | ||
51 | const QMap<qint64,KIMAP::MessageFlags> &, | ||
52 | const QMap<qint64,KIMAP::MessagePtr> &)> FetchCallback; | ||
53 | |||
54 | KAsync::Job<void> fetch(const KIMAP::ImapSet &set, KIMAP::FetchJob::FetchScope scope, FetchCallback callback); | ||
55 | KAsync::Job<void> list(KIMAP::ListJob::Option option, const std::function<void(const QList<KIMAP::MailBoxDescriptor> &mailboxes,const QList<QList<QByteArray> > &flags)> &callback); | ||
56 | |||
57 | //Composed calls that do login etc. | ||
58 | KAsync::Job<QList<qint64>> fetchHeaders(const QString &mailbox); | ||
59 | |||
60 | KAsync::Future<void> fetchFolders(std::function<void(const QStringList &)> callback); | ||
61 | KAsync::Future<void> fetchMessages(const QString &folder, std::function<void(const QVector<Message> &)> callback); | ||
62 | }; | ||
diff --git a/examples/imapresource/tests/CMakeLists.txt b/examples/imapresource/tests/CMakeLists.txt new file mode 100644 index 0000000..cdd5fcb --- /dev/null +++ b/examples/imapresource/tests/CMakeLists.txt | |||
@@ -0,0 +1,21 @@ | |||
1 | set(CMAKE_AUTOMOC ON) | ||
2 | include_directories( | ||
3 | ${CMAKE_CURRENT_BINARY_DIR} | ||
4 | ) | ||
5 | |||
6 | macro(auto_tests) | ||
7 | foreach(_testname ${ARGN}) | ||
8 | add_executable(${_testname} ${_testname}.cpp) | ||
9 | # generate_flatbuffers(${_testname} calendar) | ||
10 | add_test(${_testname} ${_testname}) | ||
11 | qt5_use_modules(${_testname} Core Test Concurrent) | ||
12 | target_link_libraries(${_testname} sink libhawd) | ||
13 | endforeach(_testname) | ||
14 | endmacro(auto_tests) | ||
15 | |||
16 | auto_tests ( | ||
17 | imapresourcetest | ||
18 | ) | ||
19 | target_link_libraries(imapresourcetest sink_resource_imap) | ||
20 | |||
21 | install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/resetmailbox.sh DESTINATION bin PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ) | ||
diff --git a/examples/imapresource/tests/imapresourcetest.cpp b/examples/imapresource/tests/imapresourcetest.cpp new file mode 100644 index 0000000..27d7d6d --- /dev/null +++ b/examples/imapresource/tests/imapresourcetest.cpp | |||
@@ -0,0 +1,162 @@ | |||
1 | #include <QtTest> | ||
2 | |||
3 | #include <QString> | ||
4 | #include <KMime/Message> | ||
5 | |||
6 | // #include "imapresource/imapresource.h" | ||
7 | #include "store.h" | ||
8 | #include "resourcecontrol.h" | ||
9 | #include "commands.h" | ||
10 | #include "entitybuffer.h" | ||
11 | #include "resourceconfig.h" | ||
12 | #include "modelresult.h" | ||
13 | #include "pipeline.h" | ||
14 | #include "log.h" | ||
15 | #include "test.h" | ||
16 | #include "../imapresource.h" | ||
17 | #include "../imapserverproxy.h" | ||
18 | |||
19 | #define ASYNCCOMPARE(actual, expected) \ | ||
20 | do {\ | ||
21 | if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__))\ | ||
22 | return KAsync::error<void>(1, "Comparison failed.");\ | ||
23 | } while (0) | ||
24 | |||
25 | #define ASYNCVERIFY(statement) \ | ||
26 | do {\ | ||
27 | if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__))\ | ||
28 | return KAsync::error<void>(1, "Verify failed.");\ | ||
29 | } while (0) | ||
30 | |||
31 | #define VERIFYEXEC(statement) \ | ||
32 | do {\ | ||
33 | auto result = statement.exec(); \ | ||
34 | result.waitForFinished(); \ | ||
35 | if (!QTest::qVerify(!result.errorCode(), #statement, "", __FILE__, __LINE__))\ | ||
36 | return;\ | ||
37 | } while (0) | ||
38 | |||
39 | using namespace Sink; | ||
40 | using namespace Sink::ApplicationDomain; | ||
41 | |||
42 | /** | ||
43 | * Test of complete system using the imap resource. | ||
44 | * | ||
45 | * This test requires the imap resource installed. | ||
46 | */ | ||
47 | class ImapResourceTest : public QObject | ||
48 | { | ||
49 | Q_OBJECT | ||
50 | |||
51 | QTemporaryDir tempDir; | ||
52 | QString targetPath; | ||
53 | private slots: | ||
54 | void initTestCase() | ||
55 | { | ||
56 | |||
57 | //FIXME initTest only works for the current process, | ||
58 | //we also have to start resources in test-mode | ||
59 | // Sink::Test::initTest(); | ||
60 | Sink::Log::setDebugOutputLevel(Sink::Log::Trace); | ||
61 | ::ImapResource::removeFromDisk("org.kde.imap.instance1"); | ||
62 | system("resetmailbox.sh"); | ||
63 | // auto resource = ApplicationDomain::ImapResource::create("account1"); | ||
64 | Sink::ApplicationDomain::SinkResource resource; | ||
65 | resource.setProperty("identifier", "org.kde.imap.instance1"); | ||
66 | resource.setProperty("type", "org.kde.imap"); | ||
67 | resource.setProperty("server", "localhost"); | ||
68 | resource.setProperty("port", 993); | ||
69 | Sink::Store::create(resource).exec().waitForFinished(); | ||
70 | } | ||
71 | |||
72 | void cleanup() | ||
73 | { | ||
74 | Sink::ResourceControl::shutdown(QByteArray("org.kde.imap.instance1")).exec().waitForFinished(); | ||
75 | ::ImapResource::removeFromDisk("org.kde.imap.instance1"); | ||
76 | } | ||
77 | |||
78 | void init() | ||
79 | { | ||
80 | qDebug(); | ||
81 | qDebug() << "-----------------------------------------"; | ||
82 | qDebug(); | ||
83 | Sink::ResourceControl::start(QByteArray("org.kde.imap.instance1")).exec().waitForFinished(); | ||
84 | } | ||
85 | |||
86 | void testListFolders() | ||
87 | { | ||
88 | Sink::Query query; | ||
89 | query.resources << "org.kde.imap.instance1"; | ||
90 | query.request<Folder::Name>(); | ||
91 | |||
92 | // Ensure all local data is processed | ||
93 | VERIFYEXEC(Store::synchronize(query)); | ||
94 | ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished(); | ||
95 | |||
96 | auto job = Store::fetchAll<Folder>(query).then<void, QList<Folder::Ptr>>([](const QList<Folder::Ptr> &folders) { | ||
97 | QCOMPARE(folders.size(), 2); | ||
98 | QStringList names; | ||
99 | for (const auto &folder : folders) { | ||
100 | names << folder->getName(); | ||
101 | } | ||
102 | QVERIFY(names.contains("INBOX")); | ||
103 | QVERIFY(names.contains("INBOX.test")); | ||
104 | }); | ||
105 | VERIFYEXEC(job); | ||
106 | } | ||
107 | |||
108 | void testListMails() | ||
109 | { | ||
110 | Sink::Query query; | ||
111 | query.resources << "org.kde.imap.instance1"; | ||
112 | query.request<Mail::Subject>().request<Mail::MimeMessage>(); | ||
113 | |||
114 | // Ensure all local data is processed | ||
115 | VERIFYEXEC(Store::synchronize(query)); | ||
116 | ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished(); | ||
117 | |||
118 | auto job = Store::fetchAll<Mail>(query).then<void, QList<Mail::Ptr>>([](const QList<Mail::Ptr> &mails) { | ||
119 | QCOMPARE(mails.size(), 1); | ||
120 | QVERIFY(mails.first()->getSubject().startsWith(QString("[Nepomuk] Jenkins build is still unstable"))); | ||
121 | const auto data = mails.first()->getMimeMessage(); | ||
122 | QVERIFY(!data.isEmpty()); | ||
123 | |||
124 | KMime::Message m; | ||
125 | m.setContent(data); | ||
126 | m.parse(); | ||
127 | QCOMPARE(mails.first()->getSubject(), m.subject(true)->asUnicodeString()); | ||
128 | }); | ||
129 | VERIFYEXEC(job); | ||
130 | } | ||
131 | |||
132 | void testFetchNewMessages() | ||
133 | { | ||
134 | Sink::Query query; | ||
135 | query.resources << "org.kde.imap.instance1"; | ||
136 | query.request<Mail::Subject>().request<Mail::MimeMessage>(); | ||
137 | |||
138 | // Ensure all local data is processed | ||
139 | VERIFYEXEC(Store::synchronize(query)); | ||
140 | ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished(); | ||
141 | |||
142 | ImapServerProxy imap("localhost", 993); | ||
143 | imap.login("doe", "doe").exec().waitForFinished(); | ||
144 | |||
145 | auto msg = KMime::Message::Ptr::create(); | ||
146 | msg->subject(true)->fromUnicodeString("Foobar", "utf8"); | ||
147 | msg->assemble(); | ||
148 | |||
149 | VERIFYEXEC(imap.append("INBOX.test", msg->encodedContent(true))); | ||
150 | |||
151 | Store::synchronize(query).exec().waitForFinished(); | ||
152 | ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished(); | ||
153 | |||
154 | auto job = Store::fetchAll<Mail>(query).then<void, QList<Mail::Ptr>>([](const QList<Mail::Ptr> &mails) { | ||
155 | QCOMPARE(mails.size(), 2); | ||
156 | }); | ||
157 | VERIFYEXEC(job); | ||
158 | } | ||
159 | }; | ||
160 | |||
161 | QTEST_MAIN(ImapResourceTest) | ||
162 | #include "imapresourcetest.moc" | ||
diff --git a/examples/imapresource/tests/resetmailbox.sh b/examples/imapresource/tests/resetmailbox.sh new file mode 100644 index 0000000..966115a --- /dev/null +++ b/examples/imapresource/tests/resetmailbox.sh | |||
@@ -0,0 +1,10 @@ | |||
1 | #!/bin/bash | ||
2 | |||
3 | sudo echo "dm user.doe.test" | cyradm --auth PLAIN -u cyrus -w admin localhost | ||
4 | sudo echo "cm user.doe.test" | cyradm --auth PLAIN -u cyrus -w admin localhost | ||
5 | sudo echo "sam user.doe cyrus c" | cyradm --auth PLAIN -u cyrus -w admin localhost | ||
6 | sudo echo "sam user.doe.test cyrus c" | cyradm --auth PLAIN -u cyrus -w admin localhost | ||
7 | # sudo rm -R /var/spool/imap/d/user/doe/* | ||
8 | sudo cp /work/source/Sink/tests/data/maildir1/cur/1365777830.R28.localhost.localdomain\:2\,S /var/spool/imap/d/user/doe/test/1. | ||
9 | sudo chown cyrus:mail /var/spool/imap/d/user/doe/test/1. | ||
10 | sudo /usr/lib/cyrus-imapd/reconstruct "user.doe.test" | ||