diff options
Diffstat (limited to 'examples/imapresource/imapresource.cpp')
-rw-r--r-- | examples/imapresource/imapresource.cpp | 126 |
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 | ||
49 | SINK_DEBUG_AREA("imapresource") | ||
50 | |||
51 | Q_DECLARE_METATYPE(QSharedPointer<Imap::ImapServerProxy>) | 49 | Q_DECLARE_METATYPE(QSharedPointer<Imap::ImapServerProxy>) |
52 | 50 | ||
53 | using namespace Imap; | 51 | using 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 | ||
88 | static 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 | |||
102 | static bool hasSpecialPurposeFlag(const QByteArrayList &flags) | ||
103 | { | ||
104 | return !getSpecialPurposeType(flags).isEmpty(); | ||
105 | } | ||
106 | |||
90 | 107 | ||
91 | class ImapSynchronizer : public Sink::Synchronizer { | 108 | class 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 | ||