From b5789da647bfdba9e3cc9b0595271b4d8c42bb8c Mon Sep 17 00:00:00 2001 From: Christian Mollekopf Date: Fri, 3 Jun 2016 09:40:31 +0200 Subject: The imap resource can write-back changes --- examples/imapresource/imapresource.cpp | 148 +++++++++++++++++++++++++++++- examples/imapresource/imapserverproxy.cpp | 53 ++++++++++- examples/imapresource/imapserverproxy.h | 5 +- 3 files changed, 198 insertions(+), 8 deletions(-) (limited to 'examples') diff --git a/examples/imapresource/imapresource.cpp b/examples/imapresource/imapresource.cpp index 4122a69..2dfb2ea 100644 --- a/examples/imapresource/imapresource.cpp +++ b/examples/imapresource/imapresource.cpp @@ -330,6 +330,76 @@ public: KAsync::Job replay(const ApplicationDomain::Mail &mail, Sink::Operation operation, const QByteArray &oldRemoteId) { + auto imap = QSharedPointer::create(mServer, mPort); + auto login = imap->login(mUser, mPassword); + if (operation == Sink::Operation_Creation) { + auto parentRemoteId = syncStore().resolveLocalId("folder", mail.getFolder()); + QString mailbox = parentRemoteId; + QByteArray content = KMime::LFtoCRLF(mail.getMimeMessage()); + QByteArrayList flags; + if (!mail.getUnread()) { + flags << Imap::Flags::Seen; + } + if (mail.getImportant()) { + flags << Imap::Flags::Flagged; + } + QDateTime internalDate = mail.getDate(); + auto rid = QSharedPointer::create(); + return login.then(imap->append(mailbox, content, flags, internalDate)) + .then([imap, mailbox, rid](qint64 uid) { + const auto remoteId = mailbox.toUtf8() + "/" + QByteArray::number(uid); + //FIXME this get's called after the final error ahndler? WTF? + Trace() << "Finished creating a new mail: " << remoteId; + *rid = remoteId; + }).then([rid, imap]() { //FIXME fix KJob so we don't need this extra clause + return *rid; + }); + } else if (operation == Sink::Operation_Removal) { + auto ridParts = oldRemoteId.split('/'); + auto uid = ridParts.takeLast().toLongLong(); + //FIXME don't hardcode the separator + auto mailbox = ridParts.join('.'); + Trace() << "Removing a mail: " << oldRemoteId << "in the mailbox: " << mailbox; + KIMAP::ImapSet set; + set.add(uid); + return login.then(imap->remove(mailbox, set)) + .then([imap, oldRemoteId]() { + Trace() << "Finished removing a mail: " << oldRemoteId; + return QByteArray(); + }); + } else if (operation == Sink::Operation_Modification) { + auto ridParts = oldRemoteId.split('/'); + auto uid = ridParts.takeLast().toLongLong(); + //FIXME don't hardcode the separator + auto mailbox = ridParts.join('.'); + Trace() << "Modifying a mail: " << oldRemoteId << " in the mailbox: " << mailbox; + //TODO if the message changed, remove old message and create a new one, + //otherwise only change flags + + QByteArrayList flags; + if (!mail.getUnread()) { + flags << Imap::Flags::Seen; + } + if (mail.getImportant()) { + flags << Imap::Flags::Flagged; + } + + QByteArray content = KMime::LFtoCRLF(mail.getMimeMessage()); + QDateTime internalDate = mail.getDate(); + auto rid = QSharedPointer::create(); + KIMAP::ImapSet set; + set.add(uid); + return login.then(imap->append(mailbox, content, flags, internalDate)) + .then([imap, mailbox, rid](qint64 uid) { + const auto remoteId = mailbox + "/" + QByteArray::number(uid); + Trace() << "Finished creating a modified mail: " << remoteId; + *rid = remoteId; + }) + .then(imap->remove(mailbox, set)) + .then([rid, imap]() { + return *rid; + }); + } return KAsync::null(); } @@ -439,19 +509,61 @@ KAsync::Job ImapResource::inspect(int inspectionType, const QByteArray &in const auto folderRemoteId = syncStore->resolveLocalId(ENTITY_TYPE_FOLDER, mail.getFolder()); const auto mailRemoteId = syncStore->resolveLocalId(ENTITY_TYPE_MAIL, mail.identifier()); if (mailRemoteId.isEmpty() || folderRemoteId.isEmpty()) { - KAsync::error(); + Warning() << "Missing remote id for folder or mail. " << mailRemoteId << folderRemoteId; + return KAsync::error(); } + auto uid = mailRemoteId.split('/').last().toLongLong(); Trace() << "Mail remote id: " << folderRemoteId << mailRemoteId << mail.identifier() << folder.identifier(); + KIMAP::ImapSet set; + set.add(uid); + if (set.isEmpty()) { + return KAsync::error(1, "Couldn't determine uid of mail."); + } + KIMAP::FetchJob::FetchScope scope; + scope.mode = KIMAP::FetchJob::FetchScope::Full; + auto imap = QSharedPointer::create(mServer, mPort); + auto messageByUid = QSharedPointer>::create(); + auto inspectionJob = imap->login(mUser, mPassword) + .then(imap->select(folderRemoteId)) + .then(imap->fetch(set, scope, [imap, messageByUid](const QVector &messages) { + for (const auto &m : messages) { + messageByUid->insert(m.uid, m); + } + })); + if (inspectionType == Sink::ResourceControl::Inspection::PropertyInspectionType) { if (property == "unread") { - return KAsync::null(); + return inspectionJob.then>([=]() { + auto msg = messageByUid->value(uid); + if (expectedValue.toBool() && msg.flags.contains(Imap::Flags::Seen)) { + return KAsync::error(1, "Expected unread but couldn't find it."); + } + if (!expectedValue.toBool() && !msg.flags.contains(Imap::Flags::Seen)) { + return KAsync::error(1, "Expected read but couldn't find it."); + } + return KAsync::null(); + }); } if (property == "subject") { - return KAsync::null(); + return inspectionJob.then>([=]() { + auto msg = messageByUid->value(uid); + if (msg.msg->subject(true)->asUnicodeString() != expectedValue.toString()) { + return KAsync::error(1, "Subject not as expected: " + msg.msg->subject(true)->asUnicodeString()); + } + return KAsync::null(); + }); } } if (inspectionType == Sink::ResourceControl::Inspection::ExistenceInspectionType) { + return inspectionJob.then>([=]() { + if (!messageByUid->contains(uid)) { + Warning() << "Existing messages are: " << messageByUid->keys(); + Warning() << "We're looking for: " << uid; + return KAsync::error(1, "Couldn't find message: " + mailRemoteId); + } + return KAsync::null(); + }); } } if (domainType == ENTITY_TYPE_FOLDER) { @@ -459,6 +571,36 @@ KAsync::Job ImapResource::inspect(int inspectionType, const QByteArray &in const auto folder = entityStore->read(entityId); if (inspectionType == Sink::ResourceControl::Inspection::CacheIntegrityInspectionType) { + Warning() << "Inspecting cache integrity" << remoteId; + + int expectedCount = 0; + Index index("mail.index.folder", transaction); + index.lookup(entityId, [&](const QByteArray &sinkId) { + expectedCount++; + }, + [&](const Index::Error &error) { + Warning() << "Error in index: " << error.message << property; + }); + + auto set = KIMAP::ImapSet::fromImapSequenceSet("1:*"); + KIMAP::FetchJob::FetchScope scope; + scope.mode = KIMAP::FetchJob::FetchScope::Headers; + auto imap = QSharedPointer::create(mServer, mPort); + auto messageByUid = QSharedPointer>::create(); + auto inspectionJob = imap->login(mUser, mPassword) + .then(imap->select(remoteId)) + .then(imap->fetch(set, scope, [=](const QVector &messages) { + for (const auto &m : messages) { + messageByUid->insert(m.uid, m); + } + })) + .then>([imap, messageByUid, expectedCount]() { + if (messageByUid->size() != expectedCount) { + return KAsync::error(1, QString("Wrong number of files; found %1 instead of %2.").arg(messageByUid->size()).arg(expectedCount)); + } + return KAsync::null(); + }); + return inspectionJob; } if (inspectionType == Sink::ResourceControl::Inspection::ExistenceInspectionType) { auto folderByPath = QSharedPointer>::create(); diff --git a/examples/imapresource/imapserverproxy.cpp b/examples/imapresource/imapserverproxy.cpp index 18b8dcf..1414dbe 100644 --- a/examples/imapresource/imapserverproxy.cpp +++ b/examples/imapresource/imapserverproxy.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -41,6 +42,22 @@ const char* Imap::Flags::Deleted = "\\Deleted"; const char* Imap::Flags::Answered = "\\Answered"; const char* Imap::Flags::Flagged = "\\Flagged"; +template +static KAsync::Job runJob(KJob *job, const std::function &f) +{ + return KAsync::start([job, f](KAsync::Future &future) { + QObject::connect(job, &KJob::result, [&future, f](KJob *job) { + if (job->error()) { + future.setError(job->error(), job->errorString()); + } else { + future.setValue(f(job)); + future.setFinished(); + } + }); + job->start(); + }); +} + static KAsync::Job runJob(KJob *job) { return KAsync::start([job](KAsync::Future &future) { @@ -76,7 +93,21 @@ KAsync::Job ImapServerProxy::login(const QString &username, const QString loginJob->setPassword(password); loginJob->setAuthenticationMode(KIMAP::LoginJob::Plain); loginJob->setEncryptionMode(KIMAP::LoginJob::EncryptionMode::AnySslVersion); - return runJob(loginJob); + + auto capabilitiesJob = new KIMAP::CapabilitiesJob(mSession); + QObject::connect(capabilitiesJob, &KIMAP::CapabilitiesJob::capabilitiesReceived, [this](const QStringList &capabilities) { + mCapabilities = capabilities; + }); + return runJob(loginJob).then(runJob(capabilitiesJob)).then([this](){ + Trace() << "Supported capabilities: " << mCapabilities; + QStringList requiredExtensions = QStringList() << "UIDPLUS"; + for (const auto &requiredExtension : requiredExtensions) { + if (!mCapabilities.contains(requiredExtension)) { + Warning() << "Server doesn't support required capability: " << requiredExtension; + //TODO fail the job + } + } + }); } KAsync::Job ImapServerProxy::select(const QString &mailbox) @@ -87,14 +118,16 @@ KAsync::Job ImapServerProxy::select(const QString &mailbox) return runJob(select); } -KAsync::Job ImapServerProxy::append(const QString &mailbox, const QByteArray &content, const QList &flags, const QDateTime &internalDate) +KAsync::Job ImapServerProxy::append(const QString &mailbox, const QByteArray &content, const QList &flags, const QDateTime &internalDate) { auto append = new KIMAP::AppendJob(mSession); append->setMailBox(mailbox); append->setContent(content); append->setFlags(flags); append->setInternalDate(internalDate); - return runJob(append); + return runJob(append, [](KJob *job) -> qint64{ + return static_cast(job)->uid(); + }); } KAsync::Job ImapServerProxy::store(const KIMAP::ImapSet &set, const QList &flags) @@ -135,6 +168,13 @@ KAsync::Job ImapServerProxy::expunge() return runJob(job); } +KAsync::Job ImapServerProxy::expunge(const KIMAP::ImapSet &set) +{ + //FIXME implement UID EXPUNGE + auto job = new KIMAP::ExpungeJob(mSession); + return runJob(job); +} + KAsync::Job ImapServerProxy::fetch(const KIMAP::ImapSet &set, KIMAP::FetchJob::FetchScope scope, FetchCallback callback) { auto fetch = new KIMAP::FetchJob(mSession); @@ -219,10 +259,15 @@ KAsync::Job ImapServerProxy::list(KIMAP::ListJob::Option option, const std return runJob(listJob); } +KAsync::Job ImapServerProxy::remove(const QString &mailbox, const KIMAP::ImapSet &set) +{ + return select(mailbox).then(store(set, QByteArrayList() << Flags::Deleted)).then(expunge(set)); +} + KAsync::Job ImapServerProxy::remove(const QString &mailbox, const QByteArray &imapSet) { const auto set = KIMAP::ImapSet::fromImapSequenceSet(imapSet); - return select(mailbox).then(store(set, QByteArrayList() << Flags::Deleted)).then(expunge()); + return remove(mailbox, set); } KAsync::Job ImapServerProxy::fetchFolders(std::function &)> callback) diff --git a/examples/imapresource/imapserverproxy.h b/examples/imapresource/imapserverproxy.h index cf315df..3afeee5 100644 --- a/examples/imapresource/imapserverproxy.h +++ b/examples/imapresource/imapserverproxy.h @@ -60,18 +60,20 @@ struct Folder { class ImapServerProxy { KIMAP::Session *mSession; QChar mSeparatorCharacter; + QStringList mCapabilities; public: ImapServerProxy(const QString &serverUrl, int port); //Standard IMAP calls KAsync::Job login(const QString &username, const QString &password); KAsync::Job select(const QString &mailbox); - KAsync::Job append(const QString &mailbox, const QByteArray &content, const QList &flags = QList(), const QDateTime &internalDate = QDateTime()); + KAsync::Job append(const QString &mailbox, const QByteArray &content, const QList &flags = QList(), const QDateTime &internalDate = QDateTime()); KAsync::Job store(const KIMAP::ImapSet &set, const QList &flags); KAsync::Job create(const QString &mailbox); KAsync::Job rename(const QString &mailbox, const QString &newMailbox); KAsync::Job remove(const QString &mailbox); KAsync::Job expunge(); + KAsync::Job expunge(const KIMAP::ImapSet &set); typedef std::function &, @@ -86,6 +88,7 @@ public: //Composed calls that do login etc. KAsync::Job> fetchHeaders(const QString &mailbox); + KAsync::Job remove(const QString &mailbox, const KIMAP::ImapSet &set); KAsync::Job remove(const QString &mailbox, const QByteArray &imapSet); KAsync::Job fetchFolders(std::function &)> callback); -- cgit v1.2.3