summaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
Diffstat (limited to 'examples')
-rw-r--r--examples/davresource/CMakeLists.txt4
-rw-r--r--examples/davresource/davresource.cpp47
-rw-r--r--examples/davresource/davresource.h4
-rw-r--r--examples/dummyresource/domainadaptor.cpp3
-rw-r--r--examples/dummyresource/domainadaptor.h8
-rw-r--r--examples/dummyresource/resourcefactory.cpp2
-rw-r--r--examples/imapresource/CMakeLists.txt2
-rw-r--r--examples/imapresource/imapresource.cpp126
-rw-r--r--examples/imapresource/imapserverproxy.cpp88
-rw-r--r--examples/imapresource/imapserverproxy.h30
-rw-r--r--examples/imapresource/tests/imapmailsyncbenchmark.cpp2
-rw-r--r--examples/imapresource/tests/imapserverproxytest.cpp2
-rw-r--r--examples/imapresource/tests/populatemailbox.sh11
-rw-r--r--examples/imapresource/tests/resetmailbox.sh3
-rw-r--r--examples/maildirresource/maildirresource.cpp26
-rw-r--r--examples/mailtransportresource/mailtransport.cpp2
-rw-r--r--examples/mailtransportresource/mailtransportresource.cpp2
-rw-r--r--examples/mailtransportresource/tests/mailtransporttest.cpp4
18 files changed, 240 insertions, 126 deletions
diff --git a/examples/davresource/CMakeLists.txt b/examples/davresource/CMakeLists.txt
index 28829d5..7091edc 100644
--- a/examples/davresource/CMakeLists.txt
+++ b/examples/davresource/CMakeLists.txt
@@ -3,10 +3,10 @@ project(sink_resource_dav)
3add_definitions(-DQT_PLUGIN) 3add_definitions(-DQT_PLUGIN)
4include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) 4include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
5 5
6find_package(KPimKDAV REQUIRED) 6find_package(KPimKDAV2 REQUIRED)
7 7
8add_library(${PROJECT_NAME} SHARED davresource.cpp) 8add_library(${PROJECT_NAME} SHARED davresource.cpp)
9qt5_use_modules(${PROJECT_NAME} Core Network) 9qt5_use_modules(${PROJECT_NAME} Core Network)
10target_link_libraries(${PROJECT_NAME} sink KPim::KDAV) 10target_link_libraries(${PROJECT_NAME} sink KPim::KDAV2)
11 11
12install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${SINK_RESOURCE_PLUGINS_PATH}) 12install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${SINK_RESOURCE_PLUGINS_PATH})
diff --git a/examples/davresource/davresource.cpp b/examples/davresource/davresource.cpp
index 50471ed..465220f 100644
--- a/examples/davresource/davresource.cpp
+++ b/examples/davresource/davresource.cpp
@@ -31,19 +31,17 @@
31 31
32#include "contactpreprocessor.h" 32#include "contactpreprocessor.h"
33 33
34#include <KDAV/DavCollection> 34#include <KDAV2/DavCollection>
35#include <KDAV/DavCollectionsFetchJob> 35#include <KDAV2/DavCollectionsFetchJob>
36#include <KDAV/DavItem> 36#include <KDAV2/DavItem>
37#include <KDAV/DavItemsListJob> 37#include <KDAV2/DavItemsListJob>
38#include <KDAV/DavItemFetchJob> 38#include <KDAV2/DavItemFetchJob>
39#include <KDAV/EtagCache> 39#include <KDAV2/EtagCache>
40 40
41//This is the resources entity type, and not the domain type 41//This is the resources entity type, and not the domain type
42#define ENTITY_TYPE_CONTACT "contact" 42#define ENTITY_TYPE_CONTACT "contact"
43#define ENTITY_TYPE_ADDRESSBOOK "addressbook" 43#define ENTITY_TYPE_ADDRESSBOOK "addressbook"
44 44
45SINK_DEBUG_AREA("davresource")
46
47using namespace Sink; 45using namespace Sink;
48 46
49static KAsync::Job<void> runJob(KJob *job) 47static KAsync::Job<void> runJob(KJob *job)
@@ -87,7 +85,7 @@ public:
87 return remoteId; 85 return remoteId;
88 } 86 }
89 87
90 void synchronizeAddressbooks(const KDAV::DavCollection::List &addressbookList) 88 void synchronizeAddressbooks(const KDAV2::DavCollection::List &addressbookList)
91 { 89 {
92 const QByteArray bufferType = ENTITY_TYPE_ADDRESSBOOK; 90 const QByteArray bufferType = ENTITY_TYPE_ADDRESSBOOK;
93 SinkTrace() << "Found addressbooks " << addressbookList.size(); 91 SinkTrace() << "Found addressbooks " << addressbookList.size();
@@ -121,12 +119,12 @@ public:
121 return list; 119 return list;
122 } 120 }
123 121
124 static QByteArray getRid(const KDAV::DavItem &item) 122 static QByteArray getRid(const KDAV2::DavItem &item)
125 { 123 {
126 return item.url().toDisplayString().toUtf8(); 124 return item.url().toDisplayString().toUtf8();
127 } 125 }
128 126
129 static QByteArray getRid(const KDAV::DavCollection &item) 127 static QByteArray getRid(const KDAV2::DavCollection &item)
130 { 128 {
131 return item.url().toDisplayString().toUtf8(); 129 return item.url().toDisplayString().toUtf8();
132 } 130 }
@@ -135,7 +133,7 @@ public:
135 { 133 {
136 if (query.type() == ApplicationDomain::getTypeName<ApplicationDomain::Addressbook>()) { 134 if (query.type() == ApplicationDomain::getTypeName<ApplicationDomain::Addressbook>()) {
137 SinkLogCtx(mLogCtx) << "Synchronizing addressbooks:" << mResourceUrl.url(); 135 SinkLogCtx(mLogCtx) << "Synchronizing addressbooks:" << mResourceUrl.url();
138 auto collectionsFetchJob = new KDAV::DavCollectionsFetchJob(mResourceUrl); 136 auto collectionsFetchJob = new KDAV2::DavCollectionsFetchJob(mResourceUrl);
139 auto job = runJob(collectionsFetchJob).then([this, collectionsFetchJob] (const KAsync::Error &error) { 137 auto job = runJob(collectionsFetchJob).then([this, collectionsFetchJob] (const KAsync::Error &error) {
140 if (error) { 138 if (error) {
141 SinkWarningCtx(mLogCtx) << "Failed to synchronize addressbooks." << collectionsFetchJob->errorString(); 139 SinkWarningCtx(mLogCtx) << "Failed to synchronize addressbooks." << collectionsFetchJob->errorString();
@@ -147,29 +145,29 @@ public:
147 } else if (query.type() == ApplicationDomain::getTypeName<ApplicationDomain::Contact>()) { 145 } else if (query.type() == ApplicationDomain::getTypeName<ApplicationDomain::Contact>()) {
148 SinkLogCtx(mLogCtx) << "Synchronizing contacts."; 146 SinkLogCtx(mLogCtx) << "Synchronizing contacts.";
149 auto ridList = QSharedPointer<QByteArrayList>::create(); 147 auto ridList = QSharedPointer<QByteArrayList>::create();
150 auto collectionsFetchJob = new KDAV::DavCollectionsFetchJob(mResourceUrl); 148 auto collectionsFetchJob = new KDAV2::DavCollectionsFetchJob(mResourceUrl);
151 auto job = runJob(collectionsFetchJob).then([this, collectionsFetchJob] { 149 auto job = runJob(collectionsFetchJob).then([this, collectionsFetchJob] {
152 synchronizeAddressbooks(collectionsFetchJob ->collections()); 150 synchronizeAddressbooks(collectionsFetchJob ->collections());
153 return collectionsFetchJob->collections(); 151 return collectionsFetchJob->collections();
154 }) 152 })
155 .serialEach([this, ridList](const KDAV::DavCollection &collection) { 153 .serialEach([this, ridList](const KDAV2::DavCollection &collection) {
156 auto collId = getRid(collection); 154 auto collId = getRid(collection);
157 const auto addressbookLocalId = syncStore().resolveRemoteId(ENTITY_TYPE_ADDRESSBOOK, collId); 155 const auto addressbookLocalId = syncStore().resolveRemoteId(ENTITY_TYPE_ADDRESSBOOK, collId);
158 auto ctag = collection.CTag().toLatin1(); 156 auto ctag = collection.CTag().toLatin1();
159 if (ctag != syncStore().readValue(collId + "_ctagXX")) { 157 if (ctag != syncStore().readValue(collId + "_ctagXX")) {
160 SinkTraceCtx(mLogCtx) << "Syncing " << collId; 158 SinkTraceCtx(mLogCtx) << "Syncing " << collId;
161 auto cache = std::shared_ptr<KDAV::EtagCache>(new KDAV::EtagCache()); 159 auto cache = std::shared_ptr<KDAV2::EtagCache>(new KDAV2::EtagCache());
162 auto davItemsListJob = new KDAV::DavItemsListJob(collection.url(), cache); 160 auto davItemsListJob = new KDAV2::DavItemsListJob(collection.url(), cache);
163 const QByteArray bufferType = ENTITY_TYPE_CONTACT; 161 const QByteArray bufferType = ENTITY_TYPE_CONTACT;
164 QHash<QByteArray, Query::Comparator> mergeCriteria; 162 QHash<QByteArray, Query::Comparator> mergeCriteria;
165 auto colljob = runJob(davItemsListJob).then([davItemsListJob] { 163 auto colljob = runJob(davItemsListJob).then([davItemsListJob] {
166 return KAsync::value(davItemsListJob->items()); 164 return KAsync::value(davItemsListJob->items());
167 }) 165 })
168 .serialEach([=] (const KDAV::DavItem &item) { 166 .serialEach([=] (const KDAV2::DavItem &item) {
169 QByteArray rid = getRid(item); 167 QByteArray rid = getRid(item);
170 if (item.etag().toLatin1() != syncStore().readValue(rid + "_etag")){ 168 if (item.etag().toLatin1() != syncStore().readValue(rid + "_etag")){
171 SinkTrace() << "Updating " << rid; 169 SinkTrace() << "Updating " << rid;
172 auto davItemFetchJob = new KDAV::DavItemFetchJob(item); 170 auto davItemFetchJob = new KDAV2::DavItemFetchJob(item);
173 auto itemjob = runJob(davItemFetchJob) 171 auto itemjob = runJob(davItemFetchJob)
174 .then([=] { 172 .then([=] {
175 const auto item = davItemFetchJob->item(); 173 const auto item = davItemFetchJob->item();
@@ -180,7 +178,7 @@ public:
180 createOrModify(bufferType, rid, contact, mergeCriteria); 178 createOrModify(bufferType, rid, contact, mergeCriteria);
181 return item; 179 return item;
182 }) 180 })
183 .then([this, ridList] (const KDAV::DavItem &item) { 181 .then([this, ridList] (const KDAV2::DavItem &item) {
184 const auto rid = getRid(item); 182 const auto rid = getRid(item);
185 syncStore().writeValue(rid + "_etag", item.etag().toLatin1()); 183 syncStore().writeValue(rid + "_etag", item.etag().toLatin1());
186 ridList->append(rid); 184 ridList->append(rid);
@@ -227,24 +225,19 @@ KAsync::Job<QByteArray> replay(const ApplicationDomain::Contact &contact, Sink::
227 } 225 }
228 226
229public: 227public:
230 KDAV::DavUrl mResourceUrl; 228 KDAV2::DavUrl mResourceUrl;
231}; 229};
232 230
233 231
234DavResource::DavResource(const Sink::ResourceContext &resourceContext) 232DavResource::DavResource(const Sink::ResourceContext &resourceContext)
235 : Sink::GenericResource(resourceContext) 233 : Sink::GenericResource(resourceContext)
236{ 234{
237 /*
238 * Fork KIO slaves (used in kdav), instead of starting them via klauncher.
239 * Otherwise we have yet another runtime dependency that will i.e. not work in the docker container.
240 */
241 qputenv("KDE_FORK_SLAVES", "TRUE");
242 auto config = ResourceConfig::getConfiguration(resourceContext.instanceId()); 235 auto config = ResourceConfig::getConfiguration(resourceContext.instanceId());
243 auto resourceUrl = QUrl::fromUserInput(config.value("server").toString()); 236 auto resourceUrl = QUrl::fromUserInput(config.value("server").toString());
244 resourceUrl.setUserName(config.value("username").toString()); 237 resourceUrl.setUserName(config.value("username").toString());
245 resourceUrl.setPassword(config.value("password").toString()); 238 resourceUrl.setPassword(config.value("password").toString());
246 239
247 mResourceUrl = KDAV::DavUrl(resourceUrl, KDAV::CardDav); 240 mResourceUrl = KDAV2::DavUrl(resourceUrl, KDAV2::CardDav);
248 241
249 auto synchronizer = QSharedPointer<ContactSynchronizer>::create(resourceContext); 242 auto synchronizer = QSharedPointer<ContactSynchronizer>::create(resourceContext);
250 synchronizer->mResourceUrl = mResourceUrl; 243 synchronizer->mResourceUrl = mResourceUrl;
@@ -271,7 +264,7 @@ Sink::Resource *DavResourceFactory::createResource(const ResourceContext &contex
271void DavResourceFactory::registerFacades(const QByteArray &name, Sink::FacadeFactory &factory) 264void DavResourceFactory::registerFacades(const QByteArray &name, Sink::FacadeFactory &factory)
272{ 265{
273 factory.registerFacade<ApplicationDomain::Contact, DefaultFacade<ApplicationDomain::Contact>>(name); 266 factory.registerFacade<ApplicationDomain::Contact, DefaultFacade<ApplicationDomain::Contact>>(name);
274 factory.registerFacade<ApplicationDomain::Addressbook, DefaultFacade<ApplicationDomain::Contact>>(name); 267 factory.registerFacade<ApplicationDomain::Addressbook, DefaultFacade<ApplicationDomain::Addressbook>>(name);
275} 268}
276 269
277void DavResourceFactory::registerAdaptorFactories(const QByteArray &name, Sink::AdaptorFactoryRegistry &registry) 270void DavResourceFactory::registerAdaptorFactories(const QByteArray &name, Sink::AdaptorFactoryRegistry &registry)
diff --git a/examples/davresource/davresource.h b/examples/davresource/davresource.h
index 1ce66ea..db175a4 100644
--- a/examples/davresource/davresource.h
+++ b/examples/davresource/davresource.h
@@ -21,7 +21,7 @@
21 21
22#include "common/genericresource.h" 22#include "common/genericresource.h"
23 23
24#include <KDAV/DavUrl> 24#include <KDAV2/DavUrl>
25#include <KAsync/Async> 25#include <KAsync/Async>
26 26
27#include <flatbuffers/flatbuffers.h> 27#include <flatbuffers/flatbuffers.h>
@@ -48,7 +48,7 @@ public:
48private: 48private:
49 QStringList listAvailableFolders(); 49 QStringList listAvailableFolders();
50 50
51 KDAV::DavUrl mResourceUrl; 51 KDAV2::DavUrl mResourceUrl;
52}; 52};
53 53
54class DavResourceFactory : public Sink::ResourceFactory 54class DavResourceFactory : public Sink::ResourceFactory
diff --git a/examples/dummyresource/domainadaptor.cpp b/examples/dummyresource/domainadaptor.cpp
index dcc08c7..e7a20da 100644
--- a/examples/dummyresource/domainadaptor.cpp
+++ b/examples/dummyresource/domainadaptor.cpp
@@ -28,9 +28,6 @@ using namespace flatbuffers;
28DummyEventAdaptorFactory::DummyEventAdaptorFactory() 28DummyEventAdaptorFactory::DummyEventAdaptorFactory()
29 : DomainTypeAdaptorFactory() 29 : DomainTypeAdaptorFactory()
30{ 30{
31 //TODO turn this into initializeReadPropertyMapper as well?
32 mResourceMapper->addMapping<Sink::ApplicationDomain::Event::Summary, DummyEvent>(&DummyEvent::summary);
33 mResourceWriteMapper->addMapping<Sink::ApplicationDomain::Event::Summary>(&DummyEventBuilder::add_summary);
34} 31}
35 32
36DummyMailAdaptorFactory::DummyMailAdaptorFactory() 33DummyMailAdaptorFactory::DummyMailAdaptorFactory()
diff --git a/examples/dummyresource/domainadaptor.h b/examples/dummyresource/domainadaptor.h
index e7098e9..3faaa63 100644
--- a/examples/dummyresource/domainadaptor.h
+++ b/examples/dummyresource/domainadaptor.h
@@ -22,25 +22,23 @@
22#include "event_generated.h" 22#include "event_generated.h"
23#include "mail_generated.h" 23#include "mail_generated.h"
24#include "folder_generated.h" 24#include "folder_generated.h"
25#include "dummy_generated.h"
26#include "dummycalendar_generated.h"
27#include "entity_generated.h" 25#include "entity_generated.h"
28 26
29class DummyEventAdaptorFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Event, DummyCalendar::DummyEvent, DummyCalendar::DummyEventBuilder> 27class DummyEventAdaptorFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Event>
30{ 28{
31public: 29public:
32 DummyEventAdaptorFactory(); 30 DummyEventAdaptorFactory();
33 virtual ~DummyEventAdaptorFactory() {}; 31 virtual ~DummyEventAdaptorFactory() {};
34}; 32};
35 33
36class DummyMailAdaptorFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Mail, Sink::ApplicationDomain::Buffer::Dummy, Sink::ApplicationDomain::Buffer::DummyBuilder> 34class DummyMailAdaptorFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Mail>
37{ 35{
38public: 36public:
39 DummyMailAdaptorFactory(); 37 DummyMailAdaptorFactory();
40 virtual ~DummyMailAdaptorFactory() {}; 38 virtual ~DummyMailAdaptorFactory() {};
41}; 39};
42 40
43class DummyFolderAdaptorFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Folder, Sink::ApplicationDomain::Buffer::Dummy, Sink::ApplicationDomain::Buffer::DummyBuilder> 41class DummyFolderAdaptorFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Folder>
44{ 42{
45public: 43public:
46 DummyFolderAdaptorFactory(); 44 DummyFolderAdaptorFactory();
diff --git a/examples/dummyresource/resourcefactory.cpp b/examples/dummyresource/resourcefactory.cpp
index c1f536e..dffdfc9 100644
--- a/examples/dummyresource/resourcefactory.cpp
+++ b/examples/dummyresource/resourcefactory.cpp
@@ -42,8 +42,6 @@
42#define ENTITY_TYPE_MAIL "mail" 42#define ENTITY_TYPE_MAIL "mail"
43#define ENTITY_TYPE_FOLDER "folder" 43#define ENTITY_TYPE_FOLDER "folder"
44 44
45SINK_DEBUG_AREA("dummyresource")
46
47using namespace Sink; 45using namespace Sink;
48 46
49class DummySynchronizer : public Sink::Synchronizer { 47class DummySynchronizer : public Sink::Synchronizer {
diff --git a/examples/imapresource/CMakeLists.txt b/examples/imapresource/CMakeLists.txt
index 46a8b08..5d2d38b 100644
--- a/examples/imapresource/CMakeLists.txt
+++ b/examples/imapresource/CMakeLists.txt
@@ -4,7 +4,7 @@ add_definitions(-DQT_PLUGIN)
4include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) 4include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
5 5
6find_package(KF5 COMPONENTS REQUIRED Mime) 6find_package(KF5 COMPONENTS REQUIRED Mime)
7find_package(KIMAP2 0.0.1 REQUIRED) 7find_package(KIMAP2 0.2 REQUIRED)
8 8
9include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) 9include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
10 10
diff --git a/examples/imapresource/imapresource.cpp b/examples/imapresource/imapresource.cpp
index 0579dae..81c808b 100644
--- a/examples/imapresource/imapresource.cpp
+++ b/examples/imapresource/imapresource.cpp
@@ -46,8 +46,6 @@
46#define ENTITY_TYPE_MAIL "mail" 46#define ENTITY_TYPE_MAIL "mail"
47#define ENTITY_TYPE_FOLDER "folder" 47#define ENTITY_TYPE_FOLDER "folder"
48 48
49SINK_DEBUG_AREA("imapresource")
50
51Q_DECLARE_METATYPE(QSharedPointer<Imap::ImapServerProxy>) 49Q_DECLARE_METATYPE(QSharedPointer<Imap::ImapServerProxy>)
52 50
53using namespace Imap; 51using namespace Imap;
@@ -87,6 +85,25 @@ static QByteArray parentRid(const Imap::Folder &folder)
87 return folder.parentPath().toUtf8(); 85 return folder.parentPath().toUtf8();
88} 86}
89 87
88static QByteArray getSpecialPurposeType(const QByteArrayList &flags)
89{
90 if (Imap::flagsContain(Imap::FolderFlags::Trash, flags)) {
91 return ApplicationDomain::SpecialPurpose::Mail::trash;
92 }
93 if (Imap::flagsContain(Imap::FolderFlags::Drafts, flags)) {
94 return ApplicationDomain::SpecialPurpose::Mail::drafts;
95 }
96 if (Imap::flagsContain(Imap::FolderFlags::Sent, flags)) {
97 return ApplicationDomain::SpecialPurpose::Mail::sent;
98 }
99 return {};
100}
101
102static bool hasSpecialPurposeFlag(const QByteArrayList &flags)
103{
104 return !getSpecialPurposeType(flags).isEmpty();
105}
106
90 107
91class ImapSynchronizer : public Sink::Synchronizer { 108class ImapSynchronizer : public Sink::Synchronizer {
92 Q_OBJECT 109 Q_OBJECT
@@ -100,22 +117,29 @@ public:
100 QByteArray createFolder(const Imap::Folder &f) 117 QByteArray createFolder(const Imap::Folder &f)
101 { 118 {
102 const auto parentFolderRid = parentRid(f); 119 const auto parentFolderRid = parentRid(f);
103 SinkTraceCtx(mLogCtx) << "Creating folder: " << f.name() << parentFolderRid; 120 bool isToplevel = parentFolderRid.isEmpty();
121
122 SinkTraceCtx(mLogCtx) << "Creating folder: " << f.name() << parentFolderRid << f.flags;
104 123
105 const auto remoteId = folderRid(f); 124 const auto remoteId = folderRid(f);
106 Sink::ApplicationDomain::Folder folder; 125 Sink::ApplicationDomain::Folder folder;
107 folder.setName(f.name()); 126 folder.setName(f.name());
108 folder.setIcon("folder"); 127 folder.setIcon("folder");
109 folder.setEnabled(f.subscribed); 128 folder.setEnabled(f.subscribed);
110 QHash<QByteArray, Query::Comparator> mergeCriteria; 129 auto specialPurpose = [&] {
111 if (SpecialPurpose::isSpecialPurposeFolderName(f.name()) && parentFolderRid.isEmpty()) { 130 if (hasSpecialPurposeFlag(f.flags)) {
112 auto type = SpecialPurpose::getSpecialPurposeType(f.name()); 131 return getSpecialPurposeType(f.flags);
113 folder.setSpecialPurpose(QByteArrayList() << type); 132 } else if (SpecialPurpose::isSpecialPurposeFolderName(f.name()) && isToplevel) {
114 mergeCriteria.insert(ApplicationDomain::Folder::SpecialPurpose::name, Query::Comparator(type, Query::Comparator::Contains)); 133 return SpecialPurpose::getSpecialPurposeType(f.name());
134 }
135 return QByteArray{};
136 }();
137 if (!specialPurpose.isEmpty()) {
138 folder.setSpecialPurpose(QByteArrayList() << specialPurpose);
115 } 139 }
116 140
117 if (!parentFolderRid.isEmpty()) { 141 if (!isToplevel) {
118 folder.setParent(syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, parentFolderRid)); 142 folder.setParent(syncStore().resolveRemoteId(ApplicationDomain::Folder::name, parentFolderRid));
119 } 143 }
120 createOrModify(ApplicationDomain::getTypeName<ApplicationDomain::Folder>(), remoteId, folder); 144 createOrModify(ApplicationDomain::getTypeName<ApplicationDomain::Folder>(), remoteId, folder);
121 return remoteId; 145 return remoteId;
@@ -160,22 +184,20 @@ public:
160 return flags; 184 return flags;
161 } 185 }
162 186
163 void synchronizeMails(const QByteArray &folderRid, const Message &message) 187 void synchronizeMails(const QByteArray &folderRid, const QByteArray &folderLocalId, const Message &message)
164 { 188 {
165 auto time = QSharedPointer<QTime>::create(); 189 auto time = QSharedPointer<QTime>::create();
166 time->start(); 190 time->start();
167 SinkTraceCtx(mLogCtx) << "Importing new mail." << folderRid; 191 SinkTraceCtx(mLogCtx) << "Importing new mail." << folderRid;
168 192
169 const auto folderLocalId = syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, folderRid);
170
171 const auto remoteId = assembleMailRid(folderLocalId, message.uid); 193 const auto remoteId = assembleMailRid(folderLocalId, message.uid);
172 194
173 Q_ASSERT(message.msg); 195 Q_ASSERT(message.msg);
174 SinkTraceCtx(mLogCtx) << "Found a mail " << remoteId << message.msg->subject(true)->asUnicodeString() << message.flags; 196 SinkTraceCtx(mLogCtx) << "Found a mail " << remoteId << message.flags;
175 197
176 auto mail = Sink::ApplicationDomain::Mail::create(mResourceInstanceIdentifier); 198 auto mail = Sink::ApplicationDomain::Mail::create(mResourceInstanceIdentifier);
177 mail.setFolder(folderLocalId); 199 mail.setFolder(folderLocalId);
178 mail.setMimeMessage(message.msg->encodedContent()); 200 mail.setMimeMessage(message.msg->encodedContent(true));
179 mail.setExtractedFullPayloadAvailable(message.fullPayload); 201 mail.setExtractedFullPayloadAvailable(message.fullPayload);
180 setFlags(mail, message.flags); 202 setFlags(mail, message.flags);
181 203
@@ -291,14 +313,15 @@ public:
291 SinkTraceCtx(mLogCtx) << "Uids to fetch: " << filteredAndSorted; 313 SinkTraceCtx(mLogCtx) << "Uids to fetch: " << filteredAndSorted;
292 314
293 bool headersOnly = false; 315 bool headersOnly = false;
316 const auto folderLocalId = syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, folderRemoteId);
294 return imap->fetchMessages(folder, filteredAndSorted, headersOnly, [=](const Message &m) { 317 return imap->fetchMessages(folder, filteredAndSorted, headersOnly, [=](const Message &m) {
295 if (*maxUid < m.uid) { 318 if (*maxUid < m.uid) {
296 *maxUid = m.uid; 319 *maxUid = m.uid;
297 } 320 }
298 synchronizeMails(folderRemoteId, m); 321 synchronizeMails(folderRemoteId, folderLocalId, m);
299 }, 322 },
300 [this, maxUid, folder](int progress, int total) { 323 [=](int progress, int total) {
301 SinkLog() << "Progress: " << progress << " out of " << total; 324 reportProgress(progress, total, QByteArrayList{} << folderLocalId);
302 //commit every 10 messages 325 //commit every 10 messages
303 if ((progress % 10) == 0) { 326 if ((progress % 10) == 0) {
304 commit(); 327 commit();
@@ -335,11 +358,12 @@ public:
335 SinkLogCtx(mLogCtx) << "Fetching headers for: " << toFetch; 358 SinkLogCtx(mLogCtx) << "Fetching headers for: " << toFetch;
336 359
337 bool headersOnly = true; 360 bool headersOnly = true;
361 const auto folderLocalId = syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, folderRemoteId);
338 return imap->fetchMessages(folder, toFetch, headersOnly, [=](const Message &m) { 362 return imap->fetchMessages(folder, toFetch, headersOnly, [=](const Message &m) {
339 synchronizeMails(folderRemoteId, m); 363 synchronizeMails(folderRemoteId, folderLocalId, m);
340 }, 364 },
341 [=](int progress, int total) { 365 [=](int progress, int total) {
342 SinkLogCtx(mLogCtx) << "Progress: " << progress << " out of " << total; 366 reportProgress(progress, total, QByteArrayList{} << folderLocalId);
343 //commit every 100 messages 367 //commit every 100 messages
344 if ((progress % 100) == 0) { 368 if ((progress % 100) == 0) {
345 commit(); 369 commit();
@@ -466,7 +490,7 @@ public:
466 //Otherwise fetch full payload for daterange 490 //Otherwise fetch full payload for daterange
467 auto folderList = QSharedPointer<QVector<Folder>>::create(); 491 auto folderList = QSharedPointer<QVector<Folder>>::create();
468 return imap->fetchFolders([folderList](const Folder &folder) { 492 return imap->fetchFolders([folderList](const Folder &folder) {
469 if (!folder.noselect) { 493 if (!folder.noselect && folder.subscribed) {
470 *folderList << folder; 494 *folderList << folder;
471 } 495 }
472 }) 496 })
@@ -480,12 +504,16 @@ public:
480 KAsync::Error getError(const KAsync::Error &error) 504 KAsync::Error getError(const KAsync::Error &error)
481 { 505 {
482 if (error) { 506 if (error) {
483 if (error.errorCode == Imap::CouldNotConnectError) { 507 switch(error.errorCode) {
484 return {ApplicationDomain::ConnectionError, error.errorMessage}; 508 case Imap::CouldNotConnectError:
485 } else if (error.errorCode == Imap::SslHandshakeError) { 509 return {ApplicationDomain::ConnectionError, error.errorMessage};
486 return {ApplicationDomain::LoginError, error.errorMessage}; 510 case Imap::SslHandshakeError:
511 return {ApplicationDomain::LoginError, error.errorMessage};
512 case Imap::HostNotFoundError:
513 return {ApplicationDomain::NoServerError, error.errorMessage};
514 default:
515 return {ApplicationDomain::UnknownError, error.errorMessage};
487 } 516 }
488 return {ApplicationDomain::UnknownError, error.errorMessage};
489 } 517 }
490 return {}; 518 return {};
491 } 519 }
@@ -539,11 +567,12 @@ public:
539 } 567 }
540 SinkLog() << "Fetching messages: " << toFetch << folderRemoteId; 568 SinkLog() << "Fetching messages: " << toFetch << folderRemoteId;
541 bool headersOnly = false; 569 bool headersOnly = false;
570 const auto folderLocalId = syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, folderRemoteId);
542 return imap->fetchMessages(Folder{folderRemoteId}, toFetch, headersOnly, [=](const Message &m) { 571 return imap->fetchMessages(Folder{folderRemoteId}, toFetch, headersOnly, [=](const Message &m) {
543 synchronizeMails(folderRemoteId, m); 572 synchronizeMails(folderRemoteId, folderLocalId, m);
544 }, 573 },
545 [=](int progress, int total) { 574 [=](int progress, int total) {
546 reportProgress(progress, total); 575 reportProgress(progress, total, QByteArrayList{} << folderLocalId);
547 //commit every 100 messages 576 //commit every 100 messages
548 if ((progress % 100) == 0) { 577 if ((progress % 100) == 0) {
549 commit(); 578 commit();
@@ -554,10 +583,7 @@ public:
554 bool syncHeaders = query.hasFilter<ApplicationDomain::Mail::Folder>(); 583 bool syncHeaders = query.hasFilter<ApplicationDomain::Mail::Folder>();
555 //FIXME If we were able to to flush in between we could just query the local store for the folder list. 584 //FIXME If we were able to to flush in between we could just query the local store for the folder list.
556 return getFolderList(imap, query) 585 return getFolderList(imap, query)
557 .then([=] (const QVector<Folder> &folders) { 586 .serialEach([=](const Folder &folder) {
558 //Synchronize folders
559 return KAsync::value(folders)
560 .serialEach<void>([=](const Folder &folder) {
561 SinkLog() << "Syncing folder " << folder.path(); 587 SinkLog() << "Syncing folder " << folder.path();
562 //Emit notification that the folder is being synced. 588 //Emit notification that the folder is being synced.
563 //The synchronizer can't do that because it has no concept of the folder filter on a mail sync scope meaning that the folder is being synchronized. 589 //The synchronizer can't do that because it has no concept of the folder filter on a mail sync scope meaning that the folder is being synchronized.
@@ -572,7 +598,6 @@ public:
572 SinkWarning() << "Failed to sync folder: " << folder.path() << "Error: " << error.errorMessage; 598 SinkWarning() << "Failed to sync folder: " << folder.path() << "Error: " << error.errorMessage;
573 }); 599 });
574 }); 600 });
575 });
576 } 601 }
577 }) 602 })
578 .then([=] (const KAsync::Error &error) { 603 .then([=] (const KAsync::Error &error) {
@@ -582,6 +607,15 @@ public:
582 } 607 }
583 return KAsync::error<void>("Nothing to do"); 608 return KAsync::error<void>("Nothing to do");
584 } 609 }
610 static QByteArray ensureCRLF(const QByteArray &data) {
611 auto index = data.indexOf('\n');
612 if (index > 0 && data.at(index - 1) == '\r') { //First line is LF-only terminated
613 //Convert back and forth in case there's a mix. We don't want to expand CRLF into CRCRLF.
614 return KMime::LFtoCRLF(KMime::CRLFtoLF(data));
615 } else {
616 return data;
617 }
618 }
585 619
586 KAsync::Job<QByteArray> replay(const ApplicationDomain::Mail &mail, Sink::Operation operation, const QByteArray &oldRemoteId, const QList<QByteArray> &changedProperties) Q_DECL_OVERRIDE 620 KAsync::Job<QByteArray> replay(const ApplicationDomain::Mail &mail, Sink::Operation operation, const QByteArray &oldRemoteId, const QList<QByteArray> &changedProperties) Q_DECL_OVERRIDE
587 { 621 {
@@ -589,10 +623,10 @@ public:
589 auto login = imap->login(mUser, mPassword); 623 auto login = imap->login(mUser, mPassword);
590 KAsync::Job<QByteArray> job = KAsync::null<QByteArray>(); 624 KAsync::Job<QByteArray> job = KAsync::null<QByteArray>();
591 if (operation == Sink::Operation_Creation) { 625 if (operation == Sink::Operation_Creation) {
592 QString mailbox = syncStore().resolveLocalId(ENTITY_TYPE_FOLDER, mail.getFolder()); 626 const QString mailbox = syncStore().resolveLocalId(ENTITY_TYPE_FOLDER, mail.getFolder());
593 QByteArray content = KMime::LFtoCRLF(mail.getMimeMessage()); 627 const auto content = ensureCRLF(mail.getMimeMessage());
594 auto flags = getFlags(mail); 628 const auto flags = getFlags(mail);
595 QDateTime internalDate = mail.getDate(); 629 const QDateTime internalDate = mail.getDate();
596 job = login.then(imap->append(mailbox, content, flags, internalDate)) 630 job = login.then(imap->append(mailbox, content, flags, internalDate))
597 .addToContext(imap) 631 .addToContext(imap)
598 .then([mail](qint64 uid) { 632 .then([mail](qint64 uid) {
@@ -623,11 +657,11 @@ public:
623 const bool messageMoved = changedProperties.contains(ApplicationDomain::Mail::Folder::name); 657 const bool messageMoved = changedProperties.contains(ApplicationDomain::Mail::Folder::name);
624 const bool messageChanged = changedProperties.contains(ApplicationDomain::Mail::MimeMessage::name); 658 const bool messageChanged = changedProperties.contains(ApplicationDomain::Mail::MimeMessage::name);
625 if (messageChanged || messageMoved) { 659 if (messageChanged || messageMoved) {
626 SinkTrace() << "Replacing message.";
627 const auto folderId = folderIdFromMailRid(oldRemoteId); 660 const auto folderId = folderIdFromMailRid(oldRemoteId);
628 const QString oldMailbox = syncStore().resolveLocalId(ENTITY_TYPE_FOLDER, folderId); 661 const QString oldMailbox = syncStore().resolveLocalId(ENTITY_TYPE_FOLDER, folderId);
629 QByteArray content = KMime::LFtoCRLF(mail.getMimeMessage()); 662 const auto content = ensureCRLF(mail.getMimeMessage());
630 QDateTime internalDate = mail.getDate(); 663 const QDateTime internalDate = mail.getDate();
664 SinkTrace() << "Replacing message. Old mailbox: " << oldMailbox << "New mailbox: " << mailbox << "Flags: " << flags << "Content: " << content;
631 KIMAP2::ImapSet set; 665 KIMAP2::ImapSet set;
632 set.add(uid); 666 set.add(uid);
633 job = login.then(imap->append(mailbox, content, flags, internalDate)) 667 job = login.then(imap->append(mailbox, content, flags, internalDate))
@@ -655,7 +689,7 @@ public:
655 if (error) { 689 if (error) {
656 SinkWarning() << "Error during changereplay: " << error.errorMessage; 690 SinkWarning() << "Error during changereplay: " << error.errorMessage;
657 return imap->logout() 691 return imap->logout()
658 .then(KAsync::error<QByteArray>(error)); 692 .then(KAsync::error<QByteArray>(getError(error)));
659 } 693 }
660 return imap->logout() 694 return imap->logout()
661 .then(KAsync::value(remoteId)); 695 .then(KAsync::value(remoteId));
@@ -664,6 +698,12 @@ public:
664 698
665 KAsync::Job<QByteArray> replay(const ApplicationDomain::Folder &folder, Sink::Operation operation, const QByteArray &oldRemoteId, const QList<QByteArray> &changedProperties) Q_DECL_OVERRIDE 699 KAsync::Job<QByteArray> replay(const ApplicationDomain::Folder &folder, Sink::Operation operation, const QByteArray &oldRemoteId, const QList<QByteArray> &changedProperties) Q_DECL_OVERRIDE
666 { 700 {
701 if (operation != Sink::Operation_Creation) {
702 if(oldRemoteId.isEmpty()) {
703 Q_ASSERT(false);
704 return KAsync::error<QByteArray>("Tried to replay modification without old remoteId.");
705 }
706 }
667 auto imap = QSharedPointer<ImapServerProxy>::create(mServer, mPort, &mSessionCache); 707 auto imap = QSharedPointer<ImapServerProxy>::create(mServer, mPort, &mSessionCache);
668 auto login = imap->login(mUser, mPassword); 708 auto login = imap->login(mUser, mPassword);
669 if (operation == Sink::Operation_Creation) { 709 if (operation == Sink::Operation_Creation) {
@@ -793,6 +833,10 @@ protected:
793 .then(imap->select(folderRemoteId)) 833 .then(imap->select(folderRemoteId))
794 .then([](Imap::SelectResult){}) 834 .then([](Imap::SelectResult){})
795 .then(imap->fetch(set, scope, [imap, messageByUid](const Imap::Message &message) { 835 .then(imap->fetch(set, scope, [imap, messageByUid](const Imap::Message &message) {
836 //We avoid parsing normally, so we have to do it explicitly here
837 if (message.msg) {
838 message.msg->parse();
839 }
796 messageByUid->insert(message.uid, message); 840 messageByUid->insert(message.uid, message);
797 })); 841 }));
798 842
diff --git a/examples/imapresource/imapserverproxy.cpp b/examples/imapresource/imapserverproxy.cpp
index 0cc43b8..538105c 100644
--- a/examples/imapresource/imapserverproxy.cpp
+++ b/examples/imapresource/imapserverproxy.cpp
@@ -37,8 +37,6 @@
37#include "log.h" 37#include "log.h"
38#include "test.h" 38#include "test.h"
39 39
40SINK_DEBUG_AREA("imapserverproxy")
41
42using namespace Imap; 40using namespace Imap;
43 41
44const char* Imap::Flags::Seen = "\\Seen"; 42const char* Imap::Flags::Seen = "\\Seen";
@@ -57,6 +55,7 @@ const char* Imap::FolderFlags::Trash = "\\Trash";
57const char* Imap::FolderFlags::Archive = "\\Archive"; 55const char* Imap::FolderFlags::Archive = "\\Archive";
58const char* Imap::FolderFlags::Junk = "\\Junk"; 56const char* Imap::FolderFlags::Junk = "\\Junk";
59const char* Imap::FolderFlags::Flagged = "\\Flagged"; 57const char* Imap::FolderFlags::Flagged = "\\Flagged";
58const char* Imap::FolderFlags::Drafts = "\\Drafts";
60 59
61const char* Imap::Capabilities::Namespace = "NAMESPACE"; 60const char* Imap::Capabilities::Namespace = "NAMESPACE";
62const char* Imap::Capabilities::Uidplus = "UIDPLUS"; 61const char* Imap::Capabilities::Uidplus = "UIDPLUS";
@@ -98,17 +97,25 @@ static KAsync::Job<void> runJob(KJob *job)
98 }); 97 });
99} 98}
100 99
101ImapServerProxy::ImapServerProxy(const QString &serverUrl, int port, SessionCache *sessionCache) : mSession(new KIMAP2::Session(serverUrl, qint16(port))), mSessionCache(sessionCache) 100KIMAP2::Session *createNewSession(const QString &serverUrl, int port)
102{ 101{
103 QObject::connect(mSession, &KIMAP2::Session::sslErrors, [this](const QList<QSslError> &errors) { 102 auto newSession = new KIMAP2::Session(serverUrl, qint16(port));
103 if (Sink::Test::testModeEnabled()) {
104 newSession->setTimeout(1);
105 } else {
106 newSession->setTimeout(40);
107 }
108 QObject::connect(newSession, &KIMAP2::Session::sslErrors, [=](const QList<QSslError> &errors) {
104 SinkLog() << "Received ssl error: " << errors; 109 SinkLog() << "Received ssl error: " << errors;
105 mSession->ignoreErrors(errors); 110 newSession->ignoreErrors(errors);
106 }); 111 });
112 return newSession;
113}
107 114
108 if (Sink::Test::testModeEnabled()) { 115ImapServerProxy::ImapServerProxy(const QString &serverUrl, int port, SessionCache *sessionCache) : mSessionCache(sessionCache), mSession(nullptr)
109 mSession->setTimeout(1); 116{
110 } else { 117 if (!mSessionCache || mSessionCache->isEmpty()) {
111 mSession->setTimeout(40); 118 mSession = createNewSession(serverUrl, port);
112 } 119 }
113} 120}
114 121
@@ -161,12 +168,16 @@ KAsync::Job<void> ImapServerProxy::login(const QString &username, const QString
161 // SinkTrace() << "Found user namespaces: " << mNamespaces.user; 168 // SinkTrace() << "Found user namespaces: " << mNamespaces.user;
162 }).then([=] (const KAsync::Error &error) { 169 }).then([=] (const KAsync::Error &error) {
163 if (error) { 170 if (error) {
164 if (error.errorCode == KIMAP2::LoginJob::ErrorCode::ERR_COULD_NOT_CONNECT) { 171 switch (error.errorCode) {
172 case KIMAP2::LoginJob::ErrorCode::ERR_HOST_NOT_FOUND:
173 return KAsync::error(HostNotFoundError, "Host not found: " + error.errorMessage);
174 case KIMAP2::LoginJob::ErrorCode::ERR_COULD_NOT_CONNECT:
165 return KAsync::error(CouldNotConnectError, "Failed to connect: " + error.errorMessage); 175 return KAsync::error(CouldNotConnectError, "Failed to connect: " + error.errorMessage);
166 } else if (error.errorCode == KIMAP2::LoginJob::ErrorCode::ERR_SSL_HANDSHAKE_FAILED) { 176 case KIMAP2::LoginJob::ErrorCode::ERR_SSL_HANDSHAKE_FAILED:
167 return KAsync::error(SslHandshakeError, "Ssl handshake failed: " + error.errorMessage); 177 return KAsync::error(SslHandshakeError, "Ssl handshake failed: " + error.errorMessage);
178 default:
179 return KAsync::error(error);
168 } 180 }
169 return KAsync::error(error);
170 } 181 }
171 return KAsync::null(); 182 return KAsync::null();
172 }); 183 });
@@ -188,6 +199,12 @@ KAsync::Job<void> ImapServerProxy::logout()
188 } 199 }
189} 200}
190 201
202bool ImapServerProxy::isGmail() const
203{
204 //Magic capability that only gmail has
205 return mCapabilities.contains("X-GM-EXT-1");
206}
207
191KAsync::Job<SelectResult> ImapServerProxy::select(const QString &mailbox) 208KAsync::Job<SelectResult> ImapServerProxy::select(const QString &mailbox)
192{ 209{
193 auto select = new KIMAP2::SelectJob(mSession); 210 auto select = new KIMAP2::SelectJob(mSession);
@@ -297,6 +314,7 @@ KAsync::Job<void> ImapServerProxy::fetch(const KIMAP2::ImapSet &set, KIMAP2::Fet
297 fetch->setSequenceSet(set); 314 fetch->setSequenceSet(set);
298 fetch->setUidBased(true); 315 fetch->setUidBased(true);
299 fetch->setScope(scope); 316 fetch->setScope(scope);
317 fetch->setAvoidParsing(true);
300 QObject::connect(fetch, &KIMAP2::FetchJob::resultReceived, callback); 318 QObject::connect(fetch, &KIMAP2::FetchJob::resultReceived, callback);
301 return runJob(fetch); 319 return runJob(fetch);
302} 320}
@@ -437,18 +455,60 @@ QString ImapServerProxy::getNamespace(const QString &name)
437 return ns.name; 455 return ns.name;
438} 456}
439 457
458static bool caseInsensitiveContains(const QByteArray &f, const QByteArrayList &list) {
459 return list.contains(f) || list.contains(f.toLower());
460}
461
462bool Imap::flagsContain(const QByteArray &f, const QByteArrayList &flags)
463{
464 return caseInsensitiveContains(f, flags);
465}
466
467static void reportFolder(const Folder &f, QSharedPointer<QSet<QString>> reportedList, std::function<void(const Folder &)> callback) {
468 if (!reportedList->contains(f.path())) {
469 reportedList->insert(f.path());
470 auto c = f;
471 c.noselect = true;
472 callback(c);
473 if (!f.parentPath().isEmpty()){
474 reportFolder(f.parentFolder(), reportedList, callback);
475 }
476 }
477}
478
440KAsync::Job<void> ImapServerProxy::fetchFolders(std::function<void(const Folder &)> callback) 479KAsync::Job<void> ImapServerProxy::fetchFolders(std::function<void(const Folder &)> callback)
441{ 480{
442 SinkTrace() << "Fetching folders"; 481 SinkTrace() << "Fetching folders";
443 auto subscribedList = QSharedPointer<QSet<QString>>::create() ; 482 auto subscribedList = QSharedPointer<QSet<QString>>::create() ;
483 auto reportedList = QSharedPointer<QSet<QString>>::create() ;
444 return list(KIMAP2::ListJob::NoOption, [=](const KIMAP2::MailBoxDescriptor &mailbox, const QList<QByteArray> &){ 484 return list(KIMAP2::ListJob::NoOption, [=](const KIMAP2::MailBoxDescriptor &mailbox, const QList<QByteArray> &){
445 *subscribedList << mailbox.name; 485 *subscribedList << mailbox.name;
446 }).then(list(KIMAP2::ListJob::IncludeUnsubscribed, [=](const KIMAP2::MailBoxDescriptor &mailbox, const QList<QByteArray> &flags) { 486 }).then(list(KIMAP2::ListJob::IncludeUnsubscribed, [=](const KIMAP2::MailBoxDescriptor &mailbox, const QList<QByteArray> &flags) {
447 bool noselect = flags.contains(QByteArray(FolderFlags::Noselect).toLower()) || flags.contains(QByteArray(FolderFlags::Noselect)); 487 bool noselect = caseInsensitiveContains(FolderFlags::Noselect, flags);
448 bool subscribed = subscribedList->contains(mailbox.name); 488 bool subscribed = subscribedList->contains(mailbox.name);
489 if (isGmail()) {
490 bool inbox = mailbox.name.toLower() == "inbox";
491 bool sent = caseInsensitiveContains(FolderFlags::Sent, flags);
492 bool drafts = caseInsensitiveContains(FolderFlags::Drafts, flags);
493 bool trash = caseInsensitiveContains(FolderFlags::Trash, flags);
494 /**
495 * Because gmail duplicates messages all over the place we only support a few selected folders for now that should be mostly exclusive.
496 */
497 if (!(inbox || sent || drafts || trash)) {
498 return;
499 }
500 }
449 SinkLog() << "Found mailbox: " << mailbox.name << flags << FolderFlags::Noselect << noselect << " sub: " << subscribed; 501 SinkLog() << "Found mailbox: " << mailbox.name << flags << FolderFlags::Noselect << noselect << " sub: " << subscribed;
450 auto ns = getNamespace(mailbox.name); 502 auto ns = getNamespace(mailbox.name);
451 callback(Folder{mailbox.name, ns, mailbox.separator, noselect, subscribed, flags}); 503 auto folder = Folder{mailbox.name, ns, mailbox.separator, noselect, subscribed, flags};
504
505 //call callback for parents if that didn't already happen.
506 //This is necessary because we can have missing bits in the hierarchy in IMAP, but this will not work in sink because we'd end up with an incomplete tree.
507 if (!folder.parentPath().isEmpty() && !reportedList->contains(folder.parentPath())) {
508 reportFolder(folder.parentFolder(), reportedList, callback);
509 }
510 reportedList->insert(folder.path());
511 callback(folder);
452 })); 512 }));
453} 513}
454 514
diff --git a/examples/imapresource/imapserverproxy.h b/examples/imapresource/imapserverproxy.h
index 872f032..82f4f58 100644
--- a/examples/imapresource/imapserverproxy.h
+++ b/examples/imapresource/imapserverproxy.h
@@ -31,6 +31,7 @@ namespace Imap {
31 31
32enum ErrorCode { 32enum ErrorCode {
33 NoError, 33 NoError,
34 HostNotFoundError,
34 CouldNotConnectError, 35 CouldNotConnectError,
35 SslHandshakeError 36 SslHandshakeError
36}; 37};
@@ -60,6 +61,7 @@ namespace FolderFlags
60 extern const char* Junk; 61 extern const char* Junk;
61 extern const char* Flagged; 62 extern const char* Flagged;
62 extern const char* All; 63 extern const char* All;
64 extern const char* Drafts;
63} 65}
64 66
65namespace Capabilities 67namespace Capabilities
@@ -78,6 +80,8 @@ struct Message {
78 bool fullPayload; 80 bool fullPayload;
79}; 81};
80 82
83bool flagsContain(const QByteArray &f, const QByteArrayList &flags);
84
81struct Folder { 85struct Folder {
82 Folder() = default; 86 Folder() = default;
83 Folder(const QString &path, const QString &ns, const QChar &separator, bool noselect_, bool subscribed_, const QByteArrayList &flags_) 87 Folder(const QString &path, const QString &ns, const QChar &separator, bool noselect_, bool subscribed_, const QByteArrayList &flags_)
@@ -114,6 +118,15 @@ struct Folder {
114 return parentPath; 118 return parentPath;
115 } 119 }
116 120
121 Folder parentFolder() const
122 {
123 Folder parent;
124 parent.mPath = parentPath();
125 parent.mNamespace = mNamespace;
126 parent.mSeparator = mSeparator;
127 return parent;
128 }
129
117 QString name() const 130 QString name() const
118 { 131 {
119 auto pathParts = mPath.split(mSeparator); 132 auto pathParts = mPath.split(mSeparator);
@@ -218,18 +231,18 @@ public:
218 return session; 231 return session;
219 } 232 }
220 } 233 }
221 return CachedSession{}; 234 return {};
235 }
236
237 bool isEmpty() const
238 {
239 return mSessions.isEmpty();
222 } 240 }
223private: 241private:
224 QList<CachedSession> mSessions; 242 QList<CachedSession> mSessions;
225}; 243};
226 244
227class ImapServerProxy { 245class ImapServerProxy {
228 KIMAP2::Session *mSession;
229 QStringList mCapabilities;
230 Namespaces mNamespaces;
231
232
233public: 246public:
234 ImapServerProxy(const QString &serverUrl, int port, SessionCache *sessionCache = nullptr); 247 ImapServerProxy(const QString &serverUrl, int port, SessionCache *sessionCache = nullptr);
235 248
@@ -279,9 +292,14 @@ public:
279 KAsync::Job<QVector<qint64>> fetchUids(const Folder &folder); 292 KAsync::Job<QVector<qint64>> fetchUids(const Folder &folder);
280 293
281private: 294private:
295 bool isGmail() const;
296
282 QString getNamespace(const QString &name); 297 QString getNamespace(const QString &name);
283 QObject mGuard; 298 QObject mGuard;
284 SessionCache *mSessionCache; 299 SessionCache *mSessionCache;
300 KIMAP2::Session *mSession;
301 QStringList mCapabilities;
302 Namespaces mNamespaces;
285}; 303};
286 304
287} 305}
diff --git a/examples/imapresource/tests/imapmailsyncbenchmark.cpp b/examples/imapresource/tests/imapmailsyncbenchmark.cpp
index a53c148..814e325 100644
--- a/examples/imapresource/tests/imapmailsyncbenchmark.cpp
+++ b/examples/imapresource/tests/imapmailsyncbenchmark.cpp
@@ -31,8 +31,6 @@
31using namespace Sink; 31using namespace Sink;
32using namespace Sink::ApplicationDomain; 32using namespace Sink::ApplicationDomain;
33 33
34SINK_DEBUG_AREA("ImapMailSyncBenchmark")
35
36/** 34/**
37 * Test of complete system using the imap resource. 35 * Test of complete system using the imap resource.
38 * 36 *
diff --git a/examples/imapresource/tests/imapserverproxytest.cpp b/examples/imapresource/tests/imapserverproxytest.cpp
index 476066d..271b3d9 100644
--- a/examples/imapresource/tests/imapserverproxytest.cpp
+++ b/examples/imapresource/tests/imapserverproxytest.cpp
@@ -12,8 +12,6 @@
12 12
13using namespace Imap; 13using namespace Imap;
14 14
15// SINK_DEBUG_AREA("imapserverproxytest")
16
17/** 15/**
18 */ 16 */
19class ImapServerProxyTest : public QObject 17class ImapServerProxyTest : public QObject
diff --git a/examples/imapresource/tests/populatemailbox.sh b/examples/imapresource/tests/populatemailbox.sh
index a435df7..800e2e7 100644
--- a/examples/imapresource/tests/populatemailbox.sh
+++ b/examples/imapresource/tests/populatemailbox.sh
@@ -1,12 +1,23 @@
1#!/bin/bash 1#!/bin/bash
2 2
3sudo echo "sam user.doe.* cyrus c" | cyradm --auth PLAIN -u cyrus -w admin localhost 3sudo echo "sam user.doe.* cyrus c" | cyradm --auth PLAIN -u cyrus -w admin localhost
4#Delete all mailboxes
4sudo echo "dm user.doe.*" | cyradm --auth PLAIN -u cyrus -w admin localhost 5sudo echo "dm user.doe.*" | cyradm --auth PLAIN -u cyrus -w admin localhost
6#Create mailboxes
5sudo echo "cm user.doe.test" | cyradm --auth PLAIN -u cyrus -w admin localhost 7sudo echo "cm user.doe.test" | cyradm --auth PLAIN -u cyrus -w admin localhost
6sudo echo "cm user.doe.Drafts" | cyradm --auth PLAIN -u cyrus -w admin localhost 8sudo echo "cm user.doe.Drafts" | cyradm --auth PLAIN -u cyrus -w admin localhost
7sudo echo "cm user.doe.Trash" | cyradm --auth PLAIN -u cyrus -w admin localhost 9sudo echo "cm user.doe.Trash" | cyradm --auth PLAIN -u cyrus -w admin localhost
10
11#Set acls so we can create in INBOX
8sudo echo "sam user.doe cyrus c" | cyradm --auth PLAIN -u cyrus -w admin localhost 12sudo echo "sam user.doe cyrus c" | cyradm --auth PLAIN -u cyrus -w admin localhost
9 13
14#Subscribe to mailboxes
15sudo echo "sub INBOX" | cyradm --auth PLAIN -u doe -w doe localhost
16sudo echo "sub INBOX.test" | cyradm --auth PLAIN -u doe -w doe localhost
17sudo echo "sub INBOX.Drafts" | cyradm --auth PLAIN -u doe -w doe localhost
18sudo echo "sub INBOX.Trash" | cyradm --auth PLAIN -u doe -w doe localhost
19
20#Create a bunch of test messages in the test folder
10# for i in `seq 1 5000`; 21# for i in `seq 1 5000`;
11# do 22# do
12# # sudo cp /work/source/Sink/examples/imapresource/tests/data/1365777830.R28.localhost.localdomain\:2\,S /var/spool/imap/d/user/doe/test/$i. 23# # sudo cp /work/source/Sink/examples/imapresource/tests/data/1365777830.R28.localhost.localdomain\:2\,S /var/spool/imap/d/user/doe/test/$i.
diff --git a/examples/imapresource/tests/resetmailbox.sh b/examples/imapresource/tests/resetmailbox.sh
index 6ed198e..63d3478 100644
--- a/examples/imapresource/tests/resetmailbox.sh
+++ b/examples/imapresource/tests/resetmailbox.sh
@@ -3,8 +3,11 @@
3sudo echo "sam user.doe.* cyrus c" | cyradm --auth PLAIN -u cyrus -w admin localhost 3sudo echo "sam user.doe.* cyrus c" | cyradm --auth PLAIN -u cyrus -w admin localhost
4sudo echo "dm user.doe.*" | cyradm --auth PLAIN -u cyrus -w admin localhost 4sudo echo "dm user.doe.*" | cyradm --auth PLAIN -u cyrus -w admin localhost
5sudo echo "cm user.doe.test" | cyradm --auth PLAIN -u cyrus -w admin localhost 5sudo echo "cm user.doe.test" | cyradm --auth PLAIN -u cyrus -w admin localhost
6sudo echo "subscribe INBOX.test" | cyradm --auth PLAIN -u doe -w doe localhost
6sudo echo "cm user.doe.Drafts" | cyradm --auth PLAIN -u cyrus -w admin localhost 7sudo echo "cm user.doe.Drafts" | cyradm --auth PLAIN -u cyrus -w admin localhost
8sudo echo "subscribe INBOX.Drafts" | cyradm --auth PLAIN -u doe -w doe localhost
7sudo echo "cm user.doe.Trash" | cyradm --auth PLAIN -u cyrus -w admin localhost 9sudo echo "cm user.doe.Trash" | cyradm --auth PLAIN -u cyrus -w admin localhost
10sudo echo "subscribe INBOX.Trash" | cyradm --auth PLAIN -u doe -w doe localhost
8sudo echo "sam user.doe cyrus c" | cyradm --auth PLAIN -u cyrus -w admin localhost 11sudo echo "sam user.doe cyrus c" | cyradm --auth PLAIN -u cyrus -w admin localhost
9sudo cp /work/source/Sink/examples/imapresource/tests/data/1365777830.R28.localhost.localdomain\:2\,S /var/spool/imap/d/user/doe/test/1. 12sudo cp /work/source/Sink/examples/imapresource/tests/data/1365777830.R28.localhost.localdomain\:2\,S /var/spool/imap/d/user/doe/test/1.
10sudo chown cyrus:mail /var/spool/imap/d/user/doe/test/1. 13sudo chown cyrus:mail /var/spool/imap/d/user/doe/test/1.
diff --git a/examples/maildirresource/maildirresource.cpp b/examples/maildirresource/maildirresource.cpp
index 40bab37..b406f63 100644
--- a/examples/maildirresource/maildirresource.cpp
+++ b/examples/maildirresource/maildirresource.cpp
@@ -43,8 +43,6 @@
43#define ENTITY_TYPE_MAIL "mail" 43#define ENTITY_TYPE_MAIL "mail"
44#define ENTITY_TYPE_FOLDER "folder" 44#define ENTITY_TYPE_FOLDER "folder"
45 45
46SINK_DEBUG_AREA("maildirresource")
47
48using namespace Sink; 46using namespace Sink;
49 47
50static QString getFilePathFromMimeMessagePath(const QString &mimeMessagePath) 48static QString getFilePathFromMimeMessagePath(const QString &mimeMessagePath)
@@ -555,18 +553,20 @@ MaildirResource::MaildirResource(const Sink::ResourceContext &resourceContext)
555 setupPreprocessors(ENTITY_TYPE_FOLDER, QVector<Sink::Preprocessor*>() << new FolderPreprocessor(mMaildirPath)); 553 setupPreprocessors(ENTITY_TYPE_FOLDER, QVector<Sink::Preprocessor*>() << new FolderPreprocessor(mMaildirPath));
556 554
557 KPIM::Maildir dir(mMaildirPath, true); 555 KPIM::Maildir dir(mMaildirPath, true);
558 SinkTrace() << "Started maildir resource for maildir: " << mMaildirPath; 556 if (dir.isValid(false)) {
559 { 557 {
560 auto draftsFolder = dir.addSubFolder("Drafts"); 558 auto draftsFolder = dir.addSubFolder("Drafts");
561 auto remoteId = synchronizer->createFolder(draftsFolder, "folder", QByteArrayList() << "drafts"); 559 auto remoteId = synchronizer->createFolder(draftsFolder, "folder", QByteArrayList() << "drafts");
562 auto draftsFolderLocalId = synchronizer->syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, remoteId); 560 auto draftsFolderLocalId = synchronizer->syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, remoteId);
563 } 561 }
564 { 562 {
565 auto trashFolder = dir.addSubFolder("Trash"); 563 auto trashFolder = dir.addSubFolder("Trash");
566 auto remoteId = synchronizer->createFolder(trashFolder, "folder", QByteArrayList() << "trash"); 564 auto remoteId = synchronizer->createFolder(trashFolder, "folder", QByteArrayList() << "trash");
567 auto trashFolderLocalId = synchronizer->syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, remoteId); 565 auto trashFolderLocalId = synchronizer->syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, remoteId);
566 }
567 synchronizer->commit();
568 } 568 }
569 synchronizer->commit(); 569 SinkTrace() << "Started maildir resource for maildir: " << mMaildirPath;
570} 570}
571 571
572 572
diff --git a/examples/mailtransportresource/mailtransport.cpp b/examples/mailtransportresource/mailtransport.cpp
index 84c1556..afe0257 100644
--- a/examples/mailtransportresource/mailtransport.cpp
+++ b/examples/mailtransportresource/mailtransport.cpp
@@ -23,8 +23,6 @@
23#include <QDebug> 23#include <QDebug>
24#include <common/log.h> 24#include <common/log.h>
25 25
26SINK_DEBUG_AREA("mailtransport")
27
28extern "C" { 26extern "C" {
29 27
30#include <stdio.h> 28#include <stdio.h>
diff --git a/examples/mailtransportresource/mailtransportresource.cpp b/examples/mailtransportresource/mailtransportresource.cpp
index c73219f..3d6f8e4 100644
--- a/examples/mailtransportresource/mailtransportresource.cpp
+++ b/examples/mailtransportresource/mailtransportresource.cpp
@@ -39,8 +39,6 @@
39 39
40#define ENTITY_TYPE_MAIL "mail" 40#define ENTITY_TYPE_MAIL "mail"
41 41
42SINK_DEBUG_AREA("mailtransportresource")
43
44using namespace Sink; 42using namespace Sink;
45 43
46class MailtransportPreprocessor : public Sink::Preprocessor 44class MailtransportPreprocessor : public Sink::Preprocessor
diff --git a/examples/mailtransportresource/tests/mailtransporttest.cpp b/examples/mailtransportresource/tests/mailtransporttest.cpp
index e4cc447..2a831ed 100644
--- a/examples/mailtransportresource/tests/mailtransporttest.cpp
+++ b/examples/mailtransportresource/tests/mailtransporttest.cpp
@@ -64,7 +64,7 @@ private slots:
64 message->assemble(); 64 message->assemble();
65 65
66 auto mail = ApplicationDomain::Mail::create(mResourceInstanceIdentifier); 66 auto mail = ApplicationDomain::Mail::create(mResourceInstanceIdentifier);
67 mail.setMimeMessage(message->encodedContent()); 67 mail.setMimeMessage(message->encodedContent(true));
68 68
69 VERIFYEXEC(Store::create(mail)); 69 VERIFYEXEC(Store::create(mail));
70 VERIFYEXEC(ResourceControl::flushMessageQueue(QByteArrayList() << mResourceInstanceIdentifier)); 70 VERIFYEXEC(ResourceControl::flushMessageQueue(QByteArrayList() << mResourceInstanceIdentifier));
@@ -92,7 +92,7 @@ private slots:
92 message->assemble(); 92 message->assemble();
93 93
94 auto mail = ApplicationDomain::Mail::create(mResourceInstanceIdentifier); 94 auto mail = ApplicationDomain::Mail::create(mResourceInstanceIdentifier);
95 mail.setMimeMessage(message->encodedContent()); 95 mail.setMimeMessage(message->encodedContent(true));
96 96
97 VERIFYEXEC(Store::create(mail)); 97 VERIFYEXEC(Store::create(mail));
98 VERIFYEXEC(ResourceControl::flushMessageQueue(QByteArrayList() << mResourceInstanceIdentifier)); 98 VERIFYEXEC(ResourceControl::flushMessageQueue(QByteArrayList() << mResourceInstanceIdentifier));