From f38554ad4c7ff918bf7792413e6599e69d30cf3a Mon Sep 17 00:00:00 2001 From: Christian Mollekopf Date: Sun, 22 May 2016 15:27:52 +0200 Subject: Support for folder hierarchies --- examples/imapresource/imapresource.cpp | 37 ++++++++++++++-------- examples/imapresource/imapresource.h | 8 +++-- examples/imapresource/imapserverproxy.cpp | 26 +++++++++++---- examples/imapresource/imapserverproxy.h | 13 ++++++-- examples/imapresource/tests/imapresourcetest.cpp | 27 +++++++++++++++- .../imapresource/tests/imapserverproxytest.cpp | 6 ++-- examples/imapresource/tests/resetmailbox.sh | 6 ++-- 7 files changed, 93 insertions(+), 30 deletions(-) (limited to 'examples') diff --git a/examples/imapresource/imapresource.cpp b/examples/imapresource/imapresource.cpp index baa88b9..49cbb20 100644 --- a/examples/imapresource/imapresource.cpp +++ b/examples/imapresource/imapresource.cpp @@ -49,6 +49,7 @@ #undef DEBUG_AREA #define DEBUG_AREA "resource.imap" +using namespace Imap; ImapResource::ImapResource(const QByteArray &instanceIdentifier, const QSharedPointer &pipeline) : Sink::GenericResource(instanceIdentifier, pipeline), @@ -71,20 +72,22 @@ QByteArray ImapResource::createFolder(const QString &folderPath, const QByteArra auto remoteId = folderPath.toUtf8(); auto bufferType = ENTITY_TYPE_FOLDER; Sink::ApplicationDomain::Folder folder; - folder.setProperty("name", folderPath.split('/').last()); + auto folderPathParts = folderPath.split('/'); + const auto name = folderPathParts.takeLast(); + folder.setProperty("name", name); folder.setProperty("icon", icon); - // if (!md.isRoot()) { - // folder.setProperty("parent", resolveRemoteId(ENTITY_TYPE_FOLDER, md.parent().path().toUtf8(), synchronizationTransaction)); - // } + if (!folderPathParts.isEmpty()) { + folder.setProperty("parent", resolveRemoteId(ENTITY_TYPE_FOLDER, folderPathParts.join('/').toUtf8(), synchronizationTransaction)); + } createOrModify(transaction, synchronizationTransaction, *mFolderAdaptorFactory, bufferType, remoteId, folder); return remoteId; } -void ImapResource::synchronizeFolders(const QStringList &folderList, Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction) +void ImapResource::synchronizeFolders(const QVector &folderList, Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction) { const QByteArray bufferType = ENTITY_TYPE_FOLDER; - Trace() << "Found folders " << folderList; + Trace() << "Found folders " << folderList.size(); scanForRemovals(transaction, synchronizationTransaction, bufferType, [&bufferType, &transaction](const std::function &callback) { @@ -99,12 +102,18 @@ void ImapResource::synchronizeFolders(const QStringList &folderList, Sink::Stora }); }, [&folderList](const QByteArray &remoteId) -> bool { - return folderList.contains(remoteId); + //folderList.contains(remoteId) + for (const auto folderPath : folderList) { + if (folderPath.pathParts.join('/') == remoteId) { + return true; + } + } + return false; } ); for (const auto folderPath : folderList) { - createFolder(folderPath, "folder", transaction, synchronizationTransaction); + createFolder(folderPath.pathParts.join('/'), "folder", transaction, synchronizationTransaction); } } @@ -176,9 +185,8 @@ KAsync::Job ImapResource::synchronizeWithSource(Sink::Storage &mainStore, Log() << " Synchronizing"; return KAsync::start([this, &mainStore, &synchronizationStore](KAsync::Future future) { ImapServerProxy imap(mServer, mPort); - QStringList folderList; - // QList> waitCondition; - auto folderFuture = imap.fetchFolders([this, &imap, &mainStore, &synchronizationStore, &folderList](const QStringList &folders) { + QVector folderList; + auto folderFuture = imap.fetchFolders([this, &imap, &mainStore, &synchronizationStore, &folderList](const QVector &folders) { auto transaction = mainStore.createTransaction(Sink::Storage::ReadOnly); auto syncTransaction = synchronizationStore.createTransaction(Sink::Storage::ReadWrite); synchronizeFolders(folders, transaction, syncTransaction); @@ -215,16 +223,17 @@ KAsync::Job ImapResource::synchronizeWithSource(Sink::Storage &mainStore, auto messagesFuture = imap.fetchMessages(folder, [this, &mainStore, &synchronizationStore, folder](const QVector &messages) { auto transaction = mainStore.createTransaction(Sink::Storage::ReadOnly); auto syncTransaction = synchronizationStore.createTransaction(Sink::Storage::ReadWrite); - Trace() << "Synchronizing mails" << folder; - synchronizeMails(transaction, syncTransaction, folder, messages); + Trace() << "Synchronizing mails" << folder.pathParts.join('/'); + synchronizeMails(transaction, syncTransaction, folder.pathParts.join('/'), messages); transaction.commit(); syncTransaction.commit(); }); messagesFuture.waitForFinished(); if (messagesFuture.errorCode()) { - future.setError(1, "Folder sync failed: " + folder); + future.setError(1, "Folder sync failed: " + folder.pathParts.join('/')); return; } + Trace() << "Folder synchronized: " << folder.pathParts.join('/'); } diff --git a/examples/imapresource/imapresource.h b/examples/imapresource/imapresource.h index 6fe15dd..23b7e1a 100644 --- a/examples/imapresource/imapresource.h +++ b/examples/imapresource/imapresource.h @@ -30,7 +30,11 @@ class ImapMailAdaptorFactory; class ImapFolderAdaptorFactory; + +namespace Imap { struct Message; +struct Folder; +} /** * An imap resource. @@ -46,8 +50,8 @@ private: KAsync::Job replay(Sink::Storage &synchronizationStore, const QByteArray &type, const QByteArray &key, const QByteArray &value) Q_DECL_OVERRIDE; QByteArray createFolder(const QString &folderPath, const QByteArray &icon, Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction); - void synchronizeFolders(const QStringList &folderList, Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction); - void synchronizeMails(Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction, const QString &path, const QVector &messages); + void synchronizeFolders(const QVector &folderList, Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction); + void synchronizeMails(Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction, const QString &path, const QVector &messages); QSharedPointer mMailAdaptorFactory; QSharedPointer mFolderAdaptorFactory; diff --git a/examples/imapresource/imapserverproxy.cpp b/examples/imapresource/imapserverproxy.cpp index 9630096..836a9bc 100644 --- a/examples/imapresource/imapserverproxy.cpp +++ b/examples/imapresource/imapserverproxy.cpp @@ -23,12 +23,15 @@ #include #include #include +#include #include #include #include "log.h" +using namespace Imap; + static KAsync::Job runJob(KJob *job) { return KAsync::start([job](KAsync::Future &future) { @@ -94,6 +97,16 @@ KAsync::Job ImapServerProxy::append(const QString &mailbox, const QByteArr return runJob(append); } +KAsync::Job ImapServerProxy::create(const QString &mailbox) +{ + if (mSession->state() == KIMAP::Session::State::Disconnected) { + return KAsync::error(1, "Not connected"); + } + auto create = new KIMAP::CreateJob(mSession); + create->setMailBox(mailbox); + return runJob(create); +} + KAsync::Job ImapServerProxy::fetch(const KIMAP::ImapSet &set, KIMAP::FetchJob::FetchScope scope, FetchCallback callback) { if (mSession->state() == KIMAP::Session::State::Disconnected) { @@ -154,14 +167,14 @@ KAsync::Job ImapServerProxy::list(KIMAP::ListJob::Option option, const std return runJob(listJob); } -KAsync::Future ImapServerProxy::fetchFolders(std::function callback) +KAsync::Future ImapServerProxy::fetchFolders(std::function &)> callback) { Trace() << "Fetching folders"; auto job = login("doe", "doe").then(list(KIMAP::ListJob::IncludeUnsubscribed, [callback](const QList &mailboxes, const QList > &flags){ - QStringList list; + QVector list; for (const auto &mailbox : mailboxes) { Trace() << "Found mailbox: " << mailbox.name; - list << mailbox.name; + list << Folder{mailbox.name.split(mailbox.separator)}; } callback(list); }), @@ -171,10 +184,11 @@ KAsync::Future ImapServerProxy::fetchFolders(std::function ImapServerProxy::fetchMessages(const QString &folder, std::function &)> callback) +KAsync::Future ImapServerProxy::fetchMessages(const Folder &folder, std::function &)> callback) { - auto job = login("doe", "doe").then(select(folder)).then>([this, callback, folder]() -> KAsync::Job { - return fetchHeaders(folder).then, QList>([this, callback](const QList &uidsToFetch){ + //TODO use the right separator + auto job = login("doe", "doe").then(select(folder.pathParts.join('.'))).then>([this, callback, folder]() -> KAsync::Job { + return fetchHeaders(folder.pathParts.join('.')).then, QList>([this, callback](const QList &uidsToFetch){ Trace() << "Uids to fetch: " << uidsToFetch; if (uidsToFetch.isEmpty()) { Trace() << "Nothing to fetch"; diff --git a/examples/imapresource/imapserverproxy.h b/examples/imapresource/imapserverproxy.h index 2beb28c..475a45d 100644 --- a/examples/imapresource/imapserverproxy.h +++ b/examples/imapresource/imapserverproxy.h @@ -26,6 +26,8 @@ #include #include +namespace Imap { + struct Message { qint64 uid; qint64 size; @@ -34,6 +36,10 @@ struct Message { KMime::Message::Ptr msg; }; +struct Folder { + QList pathParts; +}; + class ImapServerProxy { KIMAP::Session *mSession; public: @@ -43,6 +49,7 @@ public: 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 create(const QString &mailbox); typedef std::function &, @@ -57,6 +64,8 @@ public: //Composed calls that do login etc. KAsync::Job> fetchHeaders(const QString &mailbox); - KAsync::Future fetchFolders(std::function callback); - KAsync::Future fetchMessages(const QString &folder, std::function &)> callback); + KAsync::Future fetchFolders(std::function &)> callback); + KAsync::Future fetchMessages(const Folder &folder, std::function &)> callback); }; + +} diff --git a/examples/imapresource/tests/imapresourcetest.cpp b/examples/imapresource/tests/imapresourcetest.cpp index d8fc46e..8ce32c2 100644 --- a/examples/imapresource/tests/imapresourcetest.cpp +++ b/examples/imapresource/tests/imapresourcetest.cpp @@ -100,7 +100,32 @@ private slots: names << folder->getName(); } QVERIFY(names.contains("INBOX")); - QVERIFY(names.contains("INBOX.test")); + QVERIFY(names.contains("test")); + }); + VERIFYEXEC(job); + } + + void testListFolderHierarchy() + { + Sink::Query query; + query.resources << "org.kde.imap.instance1"; + query.request().request(); + + Imap::ImapServerProxy imap("localhost", 993); + VERIFYEXEC(imap.login("doe", "doe")); + VERIFYEXEC(imap.create("INBOX.test.sub")); + + // Ensure all local data is processed + VERIFYEXEC(Store::synchronize(query)); + ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished(); + + auto job = Store::fetchAll(query).then>([](const QList &folders) { + QCOMPARE(folders.size(), 3); + QHash map; + for (const auto &folder : folders) { + map.insert(folder->getName(), folder); + } + QCOMPARE(map.value("sub")->getParent(), map.value("test")->identifier()); }); VERIFYEXEC(job); } diff --git a/examples/imapresource/tests/imapserverproxytest.cpp b/examples/imapresource/tests/imapserverproxytest.cpp index d13a937..139597a 100644 --- a/examples/imapresource/tests/imapserverproxytest.cpp +++ b/examples/imapresource/tests/imapserverproxytest.cpp @@ -36,6 +36,8 @@ do {\ return;\ } while (0) +using namespace Imap; + /** */ class ImapServerProxyTest : public QObject @@ -77,7 +79,7 @@ private slots: void testFetchFolders() { ImapServerProxy imap("localhost", 993); - auto future = imap.fetchFolders([](const QStringList &){}); + auto future = imap.fetchFolders([](const QVector &){}); future.waitForFinished(); QVERIFY(!future.errorCode()); } @@ -85,7 +87,7 @@ private slots: void testFetchFoldersFailure() { ImapServerProxy imap("foobar", 993); - auto future = imap.fetchFolders([](const QStringList &){}); + auto future = imap.fetchFolders([](const QVector &){}); auto future2 = future; future2.waitForFinished(); QVERIFY(future2.errorCode()); diff --git a/examples/imapresource/tests/resetmailbox.sh b/examples/imapresource/tests/resetmailbox.sh index 966115a..fc78f60 100644 --- a/examples/imapresource/tests/resetmailbox.sh +++ b/examples/imapresource/tests/resetmailbox.sh @@ -1,9 +1,9 @@ #!/bin/bash - -sudo echo "dm user.doe.test" | cyradm --auth PLAIN -u cyrus -w admin localhost + +sudo echo "sam user.doe.* cyrus c" | cyradm --auth PLAIN -u cyrus -w admin localhost +sudo echo "dm user.doe.*" | cyradm --auth PLAIN -u cyrus -w admin localhost sudo echo "cm user.doe.test" | cyradm --auth PLAIN -u cyrus -w admin localhost sudo echo "sam user.doe cyrus c" | cyradm --auth PLAIN -u cyrus -w admin localhost -sudo echo "sam user.doe.test cyrus c" | cyradm --auth PLAIN -u cyrus -w admin localhost # sudo rm -R /var/spool/imap/d/user/doe/* sudo cp /work/source/Sink/tests/data/maildir1/cur/1365777830.R28.localhost.localdomain\:2\,S /var/spool/imap/d/user/doe/test/1. sudo chown cyrus:mail /var/spool/imap/d/user/doe/test/1. -- cgit v1.2.3