summaryrefslogtreecommitdiffstats
path: root/examples/imapresource/imapresource.cpp
diff options
context:
space:
mode:
authorChristian Mollekopf <chrigi_1@fastmail.fm>2017-07-03 14:02:27 +0200
committerChristian Mollekopf <chrigi_1@fastmail.fm>2017-07-03 14:02:27 +0200
commit55fe06979ceebe67553135b43aa47e70d931304b (patch)
tree16b10a744879cc1872d6c07624b59ae64469ddbf /examples/imapresource/imapresource.cpp
parent56fae95f49a1ca8ca614bd9f89b0ea5f872765e9 (diff)
parent288946f1694c2abe1d2c5800c87339d1e8780e4b (diff)
downloadsink-55fe06979ceebe67553135b43aa47e70d931304b.tar.gz
sink-55fe06979ceebe67553135b43aa47e70d931304b.zip
Merge branch 'develop'
Diffstat (limited to 'examples/imapresource/imapresource.cpp')
-rw-r--r--examples/imapresource/imapresource.cpp126
1 files changed, 85 insertions, 41 deletions
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