Branch 'kolab/integration/4.13.0' - 35 commits - resources/CMakeLists.txt resources/imap resources/kolab resources/kolabproxy resources/maildir

Christian Mollekopf mollekopf at kolabsys.com
Thu May 8 17:50:15 CEST 2014


 resources/CMakeLists.txt                          |    1 
 resources/imap/CMakeLists.txt                     |    1 
 resources/imap/additemtask.cpp                    |    6 
 resources/imap/changeitemsflagstask.cpp           |    8 
 resources/imap/changeitemtask.cpp                 |   15 
 resources/imap/imapresource.cpp                   |   94 +-
 resources/imap/imapresource.h                     |    9 
 resources/imap/imapresource.kcfg                  |    3 
 resources/imap/messagehelper.cpp                  |   69 +
 resources/imap/messagehelper.h                    |   36 
 resources/imap/resourcestate.cpp                  |   10 
 resources/imap/resourcestate.h                    |    6 
 resources/imap/resourcestateinterface.h           |    4 
 resources/imap/resourcetask.cpp                   |    4 
 resources/imap/resourcetask.h                     |    2 
 resources/imap/retrievecollectionmetadatatask.cpp |   46 -
 resources/imap/retrievecollectionmetadatatask.h   |    1 
 resources/imap/retrieveitemstask.cpp              |  892 +++++++++++-----------
 resources/imap/retrieveitemstask.h                |   74 -
 resources/imap/retrieveitemtask.cpp               |   30 
 resources/imap/settings.cpp                       |   24 
 resources/imap/setupserverview_desktop.ui         |    4 
 resources/imap/tests/dummyresourcestate.cpp       |    5 
 resources/imap/tests/dummyresourcestate.h         |    2 
 resources/imap/tests/testretrieveitemstask.cpp    |  203 +++--
 resources/kolab/CMakeLists.txt                    |   65 +
 resources/kolab/kolabhelpers.cpp                  |  386 +++++++++
 resources/kolab/kolabhelpers.h                    |   44 +
 resources/kolab/kolabmessagehelper.cpp            |   52 +
 resources/kolab/kolabmessagehelper.h              |   36 
 resources/kolab/kolabresource.cpp                 |  178 ++++
 resources/kolab/kolabresource.desktop             |   10 
 resources/kolab/kolabresource.h                   |   59 +
 resources/kolab/kolabresourcestate.cpp            |   74 +
 resources/kolab/kolabresourcestate.h              |   37 
 resources/kolab/kolabretrievecollectionstask.cpp  |  249 ++++++
 resources/kolab/kolabretrievecollectionstask.h    |   60 +
 resources/kolabproxy/collectiontreebuilder.cpp    |    2 
 resources/kolabproxy/kolabproxyresource.cpp       |   16 
 resources/kolabproxy/kolabproxyresource.desktop   |    4 
 resources/maildir/libmaildir/keycache.cpp         |    2 
 resources/maildir/libmaildir/maildir.cpp          |    2 
 42 files changed, 2198 insertions(+), 627 deletions(-)

New commits:
commit 1f397d034b9d5577d095448ea72044857a11a0a3
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Wed May 7 14:53:01 2014 +0200

    KolabProxy-Resource: Rename to distinguish from the new Kolab-Resource.

diff --git a/resources/kolabproxy/kolabproxyresource.desktop b/resources/kolabproxy/kolabproxyresource.desktop
index 4a8bbde..1e2a531 100644
--- a/resources/kolabproxy/kolabproxyresource.desktop
+++ b/resources/kolabproxy/kolabproxyresource.desktop
@@ -1,5 +1,5 @@
 [Desktop Entry]
-Name=Kolab Groupware Server
+Name=Kolab Groupware Server (legacy)
 Name[bg]=Сървър Kolab Groupware
 Name[bs]=Server kolaborativnog softvera
 Name[ca]=Servidor de treball en grup Kolab
@@ -45,7 +45,7 @@ Name[uk]=Сервер групової роботи Kolab
 Name[x-test]=xxKolab Groupware Serverxx
 Name[zh_CN]=Kolab 群件服务器
 Name[zh_TW]=Kolab 群組伺服器
-Comment=Provides access to Kolab groupware folders on an IMAP server (IMAP accounts need to be set up separately).
+Comment=Provides access to Kolab groupware folders on an IMAP server (IMAP accounts need to be set up separately). This resource is obsolete, use the Kolab Groupware Server resource instead.
 Comment[bs]=Omogućava pristup folderima kolaborativnog softvera na IMAP server (IMAP računi trebaju biti uspostavljeni odvojeno).
 Comment[ca]=Proporciona accés a carpetes de treball en grup Kolab en un servidor IMAP (els comptes IMAP s'han de configurar separadament).
 Comment[ca at valencia]=Proporciona accés a carpetes de treball en grup Kolab en un servidor IMAP (els comptes IMAP s'han de configurar separadament).


commit d10d66e1946dcfe13eac1ba7984786b163bcd48c
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Mon May 5 10:58:13 2014 +0200

    KolabProxy-Resource: Ignore kolab resource folders.

diff --git a/resources/kolabproxy/collectiontreebuilder.cpp b/resources/kolabproxy/collectiontreebuilder.cpp
index 06f95ca..88d0678 100644
--- a/resources/kolabproxy/collectiontreebuilder.cpp
+++ b/resources/kolabproxy/collectiontreebuilder.cpp
@@ -42,7 +42,7 @@ void CollectionTreeBuilder::doStart()
 void CollectionTreeBuilder::collectionsReceived( const Akonadi::Collection::List &collections )
 {
   foreach ( const Akonadi::Collection &collection, collections ) {
-    if ( collection.resource() == resource()->identifier() ) {
+    if ( collection.resource() == resource()->identifier() || collection.resource().startsWith("akonadi_kolab_resource") ) {
       continue;
     }
     resource()->updateHiddenAttribute( collection );
diff --git a/resources/kolabproxy/kolabproxyresource.cpp b/resources/kolabproxy/kolabproxyresource.cpp
index 30f9d3d..dfdd55f 100644
--- a/resources/kolabproxy/kolabproxyresource.cpp
+++ b/resources/kolabproxy/kolabproxyresource.cpp
@@ -600,7 +600,7 @@ void KolabProxyResource::imapItemAdded( const Akonadi::Item &item,
                                         const Akonadi::Collection &collection )
 {
   //We only want updates about collections that are not from this resource
-  if ( collection.resource() == identifier() ) {
+  if ( collection.resource() == identifier() || collection.resource().startsWith("akonadi_kolab_resource") ) {
     return;
   }
   if ( m_excludeAppend.contains( item.id() ) )   {
@@ -616,7 +616,7 @@ void KolabProxyResource::imapItemAdded( const Akonadi::Item &item,
 void KolabProxyResource::imapItemRemoved( const Akonadi::Item &item )
 {
   //We only want updates about collections that are not from this resource
-  if ( item.parentCollection().resource() == identifier() ) {
+  if ( item.parentCollection().resource() == identifier() || item.parentCollection().resource().startsWith("akonadi_kolab_resource") ) {
     return;
   }
   if ( const KolabHandler::Ptr handler = mHandlerManager->getHandler( item.parentCollection().id() ) ) {
@@ -635,7 +635,8 @@ void KolabProxyResource::imapItemMoved( const Akonadi::Item &item,
                                         const Akonadi::Collection &collectionDestination )
 {
   //We only want updates about collections that are not from this resource
-  if ( collectionSource.resource() == identifier() || collectionDestination.resource() == identifier() ) {
+  if ( collectionSource.resource() == identifier() || collectionDestination.resource() == identifier() ||
+       collectionSource.resource().startsWith("akonadi_kolab_resource") || collectionDestination.resource().startsWith("akonadi_kolab_resource") ) {
     return;
   }
   KJob *job = new Akonadi::ItemMoveJob( imapToKolab( item ), imapToKolab( collectionDestination ), this );
@@ -647,7 +648,7 @@ void KolabProxyResource::imapCollectionAdded( const Akonadi::Collection &collect
 {
   Q_UNUSED( parent );
   //We only want updates about collections that are not from this resource
-  if ( collection.resource() == identifier() ) {
+   if ( collection.resource() == identifier() || collection.resource().startsWith("akonadi_kolab_resource") ) {
     return;
   }
   if ( mHandlerManager->isMonitored( collection.id() ) ) {
@@ -669,7 +670,7 @@ void KolabProxyResource::imapCollectionAdded( const Akonadi::Collection &collect
 void KolabProxyResource::imapCollectionChanged( const Akonadi::Collection &collection )
 {
   //We only want updates about collections that are not from this resource
-  if ( collection.resource() == identifier() ) {
+  if ( collection.resource() == identifier() || collection.resource().startsWith("akonadi_kolab_resource") ) {
     return;
   }
 
@@ -706,7 +707,8 @@ void KolabProxyResource::imapCollectionMoved( const Akonadi::Collection &collect
                                               const Akonadi::Collection &destination )
 {
   //We only want updates about collections that are not from this resource
-  if ( source.resource() == identifier() || destination.resource() == identifier() ) {
+  if ( source.resource() == identifier() || destination.resource() == identifier() ||
+       source.resource().startsWith("akonadi_kolab_resource") || destination.resource().startsWith("akonadi_kolab_resource") ) {
     return;
   }
   if ( mHandlerManager->isMonitored( collection.id() ) ) {
@@ -737,7 +739,7 @@ void KolabProxyResource::removeFolder( const Akonadi::Collection &imapCollection
 void KolabProxyResource::imapCollectionRemoved( const Akonadi::Collection &imapCollection )
 {
   //We only want updates about collections that are not from this resource
-  if ( imapCollection.resource() == identifier() ) {
+  if ( imapCollection.resource() == identifier() || imapCollection.resource().startsWith("akonadi_kolab_resource") ) {
     return;
   }
   if (mHandlerManager->isMonitored( imapCollection.id())) {


commit 808544db5f87ef7377aa3bf7991bdb24adfb52bb
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Wed Apr 16 20:10:33 2014 +0200

    Kolab-Resource: New Kolab Resource that subclasses the imap resource (not a proxy anymore)
    
    The KolabRetrieveCollectionsTask is a refactored copy, that should eventually
    replace the imap resources version. It's kept separate for now until
    it's stable.
    
    Instead of separating the groupware/non-groupware trees we mix them.
    This way retrieving the collections remains a single IMAP command, and not one
    per folder, which would be prohibitively expensive with thousands of folders. Instead we
    always retrieve the metadata before syncing the folder.
    This also has the advantage that the kolab resource code stays closer to the imap code.

diff --git a/resources/CMakeLists.txt b/resources/CMakeLists.txt
index bc47ee7..d72af87 100644
--- a/resources/CMakeLists.txt
+++ b/resources/CMakeLists.txt
@@ -75,3 +75,4 @@ add_subdirectory( vcarddir )
 add_subdirectory( icaldir )
 add_subdirectory( vcard )
 add_subdirectory( folderarchivesettings )
+add_subdirectory( kolab )
\ No newline at end of file
diff --git a/resources/kolab/CMakeLists.txt b/resources/kolab/CMakeLists.txt
new file mode 100644
index 0000000..dc8aba4
--- /dev/null
+++ b/resources/kolab/CMakeLists.txt
@@ -0,0 +1,65 @@
+include_directories(
+    ${kdepim-runtime_SOURCE_DIR}
+    ${CMAKE_CURRENT_SOURCE_DIR}/../imap
+    ${CMAKE_CURRENT_BINARY_DIR}/../imap
+    ${QT_QTDBUS_INCLUDE_DIR}
+    ${Boost_INCLUDE_DIR}
+    ${Libkolab_INCLUDES}
+    ${Libkolabxml_INCLUDES}
+)
+
+set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" )
+
+########### next target ###############
+
+set(kolabresource_SRCS
+    ../imap/imapidlemanager.cpp
+    ../imap/imapresource.cpp
+    ../imap/resourcestate.cpp
+    ../imap/settings.cpp
+    ../imap/settingspasswordrequester.cpp
+    ../imap/setupserver.cpp
+    ../imap/serverinfodialog.cpp
+    ../imap/subscriptiondialog.cpp
+    kolabretrievecollectionstask.cpp
+    kolabresource.cpp
+    kolabresourcestate.cpp
+    kolabhelpers.cpp
+    kolabmessagehelper.cpp
+)
+
+kcfg_generate_dbus_interface(${CMAKE_CURRENT_SOURCE_DIR}/../imap/imapresource.kcfg org.kde.Akonadi.Imap.Settings)
+qt4_add_dbus_adaptor(kolabresource_SRCS
+    ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.Imap.Settings.xml settings.h Settings
+)
+qt4_generate_dbus_interface(${CMAKE_CURRENT_SOURCE_DIR}/../imap/imapresource.h org.kde.Akonadi.Imap.Resource.xml OPTIONS -a )
+qt4_add_dbus_adaptor(kolabresource_SRCS
+    ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.Imap.Resource.xml
+    imapresource.h ImapResource
+)
+
+kde4_add_kcfg_files(kolabresource_SRCS ../imap/settingsbase.kcfgc)
+
+kde4_add_executable(akonadi_kolab_resource ${kolabresource_SRCS})
+target_link_libraries(akonadi_kolab_resource
+    ${KDEPIMLIBS_AKONADI_LIBS}
+    ${QT_QTDBUS_LIBRARY}
+    ${QT_QTCORE_LIBRARY}
+    ${QT_QTGUI_LIBRARY}
+    ${QT_QTNETWORK_LIBRARY}
+    ${KDEPIMLIBS_KIMAP_LIBS}
+    ${KDEPIMLIBS_MAILTRANSPORT_LIBS}
+    ${KDE4_KIO_LIBS}
+    ${KDEPIMLIBS_KMIME_LIBS}
+    ${KDEPIMLIBS_AKONADI_KMIME_LIBS}
+    ${KDEPIMLIBS_KPIMIDENTITIES_LIBS}
+    imapresource
+    folderarchivesettings
+    ${Libkolab_LIBRARIES}
+    ${Libkolabxml_LIBRARIES}
+    ${KDEPIMLIBS_KABC_LIBS}
+    ${KDEPIMLIBS_KCALCORE_LIBS}
+)
+
+install(FILES kolabresource.desktop DESTINATION "${CMAKE_INSTALL_PREFIX}/share/akonadi/agents")
+install(TARGETS akonadi_kolab_resource ${INSTALL_TARGETS_DEFAULT_ARGS})
diff --git a/resources/kolab/kolabhelpers.cpp b/resources/kolab/kolabhelpers.cpp
new file mode 100644
index 0000000..4896e44
--- /dev/null
+++ b/resources/kolab/kolabhelpers.cpp
@@ -0,0 +1,386 @@
+/*
+    Copyright (c) 2014 Christian Mollekopf <mollekopf at kolabsys.com>
+
+    This library is free software; you can redistribute it and/or modify it
+    under the terms of the GNU Library General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or (at your
+    option) any later version.
+
+    This library is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+    License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to the
+    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA.
+*/
+
+#include "kolabhelpers.h"
+#include <KMime/KMimeMessage>
+#include <KCalCore/Incidence>
+#include <Akonadi/Collection>
+#include <Akonadi/ItemFetchJob>
+#include <Akonadi/ItemFetchScope>
+#include <akonadi/notes/noteutils.h>
+#include <kolab/kolabobject.h>
+#include <kolab/errorhandler.h>
+
+bool KolabHelpers::checkForErrors(const Akonadi::Item &item)
+{
+    if (!Kolab::ErrorHandler::instance().errorOccured()) {
+        Kolab::ErrorHandler::instance().clear();
+        return false;
+    }
+
+    QString errorMsg;
+    foreach (const Kolab::ErrorHandler::Err &error, Kolab::ErrorHandler::instance().getErrors()) {
+        errorMsg.append(error.message);
+        errorMsg.append(QLatin1String("\n"));
+    }
+
+    kWarning() << "Error on item with id: " << item.id() << " remote id: " << item.remoteId() << ":\n" << errorMsg;
+    Kolab::ErrorHandler::instance().clear();
+    return true;
+}
+
+Akonadi::Item KolabHelpers::translateFromImap(Kolab::FolderType folderType, const Akonadi::Item &imapItem, bool &ok)
+{
+    //Avoid trying to convert imap messages
+    if (folderType == Kolab::MailType) {
+        return imapItem;
+    }
+
+    //No payload, probably a flag change or alike, we just pass it through
+    if (!imapItem.hasPayload()) {
+        return imapItem;
+    }
+    if (!imapItem.hasPayload<KMime::Message::Ptr>()) {
+        kWarning() << "Payload is not a MessagePtr!";
+        Q_ASSERT(false);
+        ok = false;
+        return imapItem;
+    }
+
+    const KMime::Message::Ptr payload = imapItem.payload<KMime::Message::Ptr>();
+    const Kolab::KolabObjectReader reader(payload);
+    if (checkForErrors(imapItem)) {
+        //By not delivering items we cannot translate, they are simply deleted from local storage
+        ok = false;
+        return Akonadi::Item();
+    }
+    switch (reader.getType()) {
+        case Kolab::EventObject:
+        case Kolab::TodoObject:
+        case Kolab::JournalObject:
+        {
+            const KCalCore::Incidence::Ptr incidencePtr = reader.getIncidence();
+            if (!incidencePtr) {
+                kWarning() << "Failed to read incidence.";
+                ok = false;
+                return Akonadi::Item();
+            }
+            Akonadi::Item newItem(incidencePtr->mimeType());
+            newItem.setPayload(incidencePtr);
+            newItem.setRemoteId(imapItem.remoteId());
+            newItem.setGid(incidencePtr->instanceIdentifier());
+            return newItem;
+        }
+        break;
+        case Kolab::NoteObject:
+        {
+            const KMime::Message::Ptr note = reader.getNote();
+            if (!note) {
+                kWarning() << "Failed to read note.";
+                ok = false;
+                return Akonadi::Item();
+            }
+            Akonadi::Item newItem("text/x-vnd.akonadi.note");
+            newItem.setPayload(note);
+            newItem.setRemoteId(imapItem.remoteId());
+            const Akonadi::NoteUtils::NoteMessageWrapper wrapper(note);
+            newItem.setGid(wrapper.uid());
+            return newItem;
+        }
+        break;
+        case Kolab::ContactObject:
+        {
+            Akonadi::Item newItem(KABC::Addressee::mimeType());
+            newItem.setPayload(reader.getContact());
+            newItem.setRemoteId(imapItem.remoteId());
+            newItem.setGid(reader.getContact().uid());
+            return newItem;
+        }
+        break;
+        case Kolab::DistlistObject:
+        {
+            KABC::ContactGroup contactGroup = reader.getDistlist();
+
+            QList<KABC::ContactGroup::ContactReference> toAdd;
+            for (uint index = 0; index < contactGroup.contactReferenceCount(); ++index) {
+                const KABC::ContactGroup::ContactReference& reference = contactGroup.contactReference(index);
+                KABC::ContactGroup::ContactReference ref;
+                ref.setGid(reference.uid()); //libkolab set a gid with setUid()
+                toAdd << ref;
+            }
+            contactGroup.removeAllContactReferences();
+            foreach (const KABC::ContactGroup::ContactReference &ref, toAdd) {
+                contactGroup.append(ref);
+            }
+
+            Akonadi::Item newItem(KABC::ContactGroup::mimeType());
+            newItem.setPayload(contactGroup);
+            newItem.setRemoteId(imapItem.remoteId());
+            newItem.setGid(contactGroup.id());
+            return newItem;
+        }
+        break;
+        default:
+            kWarning() << "Object type not handled";
+            ok = false;
+            return Akonadi::Item();
+    }
+    ok = false;
+    return Akonadi::Item();
+}
+
+Akonadi::Item::List KolabHelpers::translateToImap(const Akonadi::Item::List &items, bool &ok)
+{
+    Akonadi::Item::List imapItems;
+    Q_FOREACH(const Akonadi::Item &item, items) {
+        bool translationOk = true;
+        imapItems << translateToImap(item, translationOk);
+        if (!translationOk) {
+            ok = false;
+        }
+    }
+    return imapItems;
+}
+
+static KABC::ContactGroup convertToGidOnly(const KABC::ContactGroup &contactGroup)
+{
+    QList<KABC::ContactGroup::ContactReference> toAdd;
+    for ( uint index = 0; index < contactGroup.contactReferenceCount(); ++index ) {
+        const KABC::ContactGroup::ContactReference& reference = contactGroup.contactReference( index );
+        QString gid;
+        if (!reference.gid().isEmpty()) {
+            gid = reference.gid();
+        } else {
+            // WARNING: this is an ugly hack for backwards compatiblity. Normally this codepath shouldn't be hit.
+            // Replace all references with real data-sets
+            // Hopefully all resources are available during saving, so we can look up
+            // in the addressbook to get name+email from the UID.
+
+            const Akonadi::Item item(reference.uid().toLongLong());
+            Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob(item);
+            job->fetchScope().fetchFullPayload();
+            if (!job->exec()) {
+                continue;
+            }
+
+            const Akonadi::Item::List items = job->items();
+            if (items.count() != 1) {
+                continue;
+            }
+            const KABC::Addressee addressee = job->items().first().payload<KABC::Addressee>();
+            gid = addressee.uid();
+        }
+        KABC::ContactGroup::ContactReference ref;
+        ref.setUid(gid); //libkolab expects a gid for uid()
+        toAdd << ref;
+    }
+    KABC::ContactGroup gidOnlyContactGroup = contactGroup;
+    gidOnlyContactGroup.removeAllContactReferences();
+    foreach ( const KABC::ContactGroup::ContactReference &ref, toAdd ) {
+      gidOnlyContactGroup.append( ref );
+    }
+    return gidOnlyContactGroup;
+}
+
+Akonadi::Item KolabHelpers::translateToImap(const Akonadi::Item &item, bool &ok)
+{
+    ok = true;
+    //imap messages don't need to be translated
+    if (item.mimeType() == KMime::Message::mimeType()) {
+        Q_ASSERT(item.hasPayload<KMime::Message::Ptr>());
+        return item;
+    }
+    const QLatin1String productId("Akonadi-Kolab-Resource");
+    //Everthing stays the same, except mime type and payload
+    Akonadi::Item imapItem = item;
+    imapItem.setMimeType( QLatin1String("message/rfc822") );
+    switch(getKolabTypeFromMimeType(item.mimeType())) {
+        case Kolab::EventObject:
+        case Kolab::TodoObject:
+        case Kolab::JournalObject:
+        {
+            Q_ASSERT(item.hasPayload<KCalCore::Incidence::Ptr>());
+            kDebug() << "converted event";
+            const KMime::Message::Ptr message = Kolab::KolabObjectWriter::writeIncidence(
+                item.payload<KCalCore::Incidence::Ptr>(),
+                Kolab::KolabV3, productId, QLatin1String("UTC") );
+            imapItem.setPayload( message );
+        }
+        break;
+        case Kolab::NoteObject:
+        {
+            Q_ASSERT(item.hasPayload<KMime::Message::Ptr>());
+            kDebug() << "converted note";
+            const KMime::Message::Ptr message = Kolab::KolabObjectWriter::writeNote(
+                item.payload<KMime::Message::Ptr>(), Kolab::KolabV3, productId);
+            imapItem.setPayload( message );
+        }
+        break;
+        case Kolab::ContactObject:
+        {
+            Q_ASSERT(item.hasPayload<KABC::Addressee>());
+            kDebug() << "converted contact";
+            const KMime::Message::Ptr message = Kolab::KolabObjectWriter::writeContact(
+                item.payload<KABC::Addressee>(), Kolab::KolabV3, productId);
+            imapItem.setPayload( message );
+        }
+        break;
+        case Kolab::DistlistObject:
+        {
+            Q_ASSERT(item.hasPayload<KABC::ContactGroup>());
+            const KABC::ContactGroup contactGroup = convertToGidOnly(item.payload<KABC::ContactGroup>());
+            kDebug() << "converted distlist";
+            const KMime::Message::Ptr message = Kolab::KolabObjectWriter::writeDistlist(
+                contactGroup, Kolab::KolabV3, productId);
+            imapItem.setPayload( message );
+        }
+        break;
+        default:
+            kWarning() << "object type not handled: " << item.id() << item.mimeType();
+            ok = false;
+            return Akonadi::Item();
+
+    }
+
+    if (checkForErrors(item)) {
+        kWarning() << "an error occured while trying to translate the item to the kolab format: " << item.id();
+        ok = false;
+        return Akonadi::Item();
+    }
+    return imapItem;
+}
+
+QByteArray KolabHelpers::kolabTypeForMimeType( const QStringList &contentMimeTypes )
+{
+    if (contentMimeTypes.contains(KABC::Addressee::mimeType())) {
+        return "contact";
+    } else if (contentMimeTypes.contains( KCalCore::Event::eventMimeType())) {
+        return "event";
+    } else if (contentMimeTypes.contains( KCalCore::Todo::todoMimeType())) {
+        return "task";
+    } else if (contentMimeTypes.contains( KCalCore::Journal::journalMimeType())) {
+        return "journal";
+    } else if (contentMimeTypes.contains(QLatin1String("application/x-vnd.akonadi.note")) ||
+                contentMimeTypes.contains(QLatin1String("text/x-vnd.akonadi.note"))) {
+        return "note";
+    }
+    return QByteArray();
+}
+
+Kolab::ObjectType KolabHelpers::getKolabTypeFromMimeType(const QString &type)
+{
+    if (type == KCalCore::Event::eventMimeType()) {
+        return Kolab::EventObject;
+    } else if (type == KCalCore::Todo::todoMimeType()) {
+        return Kolab::TodoObject;
+    } else if (type == KCalCore::Journal::journalMimeType()) {
+        return Kolab::JournalObject;
+    } else if (type == KABC::Addressee::mimeType()) {
+        return Kolab::ContactObject;
+    } else if (type == KABC::ContactGroup::mimeType()) {
+        return Kolab::DistlistObject;
+    } else if (type == QLatin1String("text/x-vnd.akonadi.note") ||
+               type == QLatin1String("application/x-vnd.akonadi.note")) {
+        return Kolab::NoteObject;
+    }
+    return Kolab::InvalidObject;
+}
+
+QStringList KolabHelpers::getContentMimeTypes(Kolab::FolderType type)
+{
+    QStringList contentTypes;
+    contentTypes << Akonadi::Collection::mimeType();
+    switch (type) {
+        case Kolab::EventType:
+        case Kolab::TaskType:
+        case Kolab::JournalType:
+            contentTypes <<  KCalCore::Incidence::mimeTypes();
+            break;
+        case Kolab::ContactType:
+            contentTypes << KABC::Addressee::mimeType() << KABC::ContactGroup::mimeType();
+            break;
+        case Kolab::NoteType:
+            contentTypes << QLatin1String("text/x-vnd.akonadi.note") << QLatin1String("application/x-vnd.akonadi.note");
+            break;
+        case Kolab::MailType:
+            contentTypes << KMime::Message::mimeType();
+            break;
+        default:
+            kDebug() << "unhandled folder type: " << type;
+    }
+    return contentTypes;
+}
+
+Kolab::FolderType KolabHelpers::folderTypeFromString(const QByteArray& folderTypeName)
+{
+  return Kolab::folderTypeFromString( std::string(folderTypeName.data(), folderTypeName.size()) );
+}
+
+QByteArray KolabHelpers::getFolderTypeAnnotation(const QMap< QByteArray, QByteArray > &annotations)
+{
+    if (annotations.contains("/shared" KOLAB_FOLDER_TYPE_ANNOTATION)) {
+        return annotations.value( "/shared" KOLAB_FOLDER_TYPE_ANNOTATION);
+    }
+    return annotations.value(KOLAB_FOLDER_TYPE_ANNOTATION);
+}
+
+void KolabHelpers::setFolderTypeAnnotation(QMap< QByteArray, QByteArray >& annotations, const QByteArray& value)
+{
+    annotations["/shared" KOLAB_FOLDER_TYPE_ANNOTATION] = value;
+}
+
+QString KolabHelpers::getIcon(Kolab::FolderType type)
+{
+    switch (type) {
+        case Kolab::EventType:
+        case Kolab::TaskType:
+        case Kolab::JournalType:
+            return QLatin1String("view-calendar");
+        case Kolab::ContactType:
+            return QLatin1String("view-pim-contacts");
+        case Kolab::NoteType:
+            return QLatin1String("view-pim-notes");
+        case Kolab::MailType:
+        case Kolab::ConfigurationType:
+        case Kolab::FreebusyType:
+        case Kolab::FileType:
+        default:
+            break;
+    }
+    return QString();
+}
+
+bool KolabHelpers::isHandledType(Kolab::FolderType type)
+{
+    switch (type) {
+        case Kolab::EventType:
+        case Kolab::TaskType:
+        case Kolab::JournalType:
+        case Kolab::ContactType:
+        case Kolab::NoteType:
+        case Kolab::MailType:
+            return true;
+        case Kolab::ConfigurationType:
+        case Kolab::FreebusyType:
+        case Kolab::FileType:
+        default:
+            break;
+    }
+    return false;
+}
\ No newline at end of file
diff --git a/resources/kolab/kolabhelpers.h b/resources/kolab/kolabhelpers.h
new file mode 100644
index 0000000..4ceacb2
--- /dev/null
+++ b/resources/kolab/kolabhelpers.h
@@ -0,0 +1,44 @@
+/*
+    Copyright (c) 2014 Christian Mollekopf <mollekopf at kolabsys.com>
+
+    This library is free software; you can redistribute it and/or modify it
+    under the terms of the GNU Library General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or (at your
+    option) any later version.
+
+    This library is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+    License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to the
+    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA.
+*/
+
+#ifndef KOLABHELPERS_H
+#define KOLABHELPERS_H
+
+#include <Akonadi/Item>
+#include <kolabdefinitions.h> //libkolab
+#include <formathelpers.h> //libkolab
+
+class KolabHelpers {
+public:
+    static bool checkForErrors(const Akonadi::Item &affectedItem);
+    static Akonadi::Item translateFromImap(Kolab::FolderType folderType, const Akonadi::Item &item, bool &ok);
+    static Akonadi::Item::List translateToImap(const Akonadi::Item::List &items, bool &ok);
+    static Akonadi::Item translateToImap(const Akonadi::Item &item, bool &ok);
+    static Kolab::FolderType folderTypeFromString( const QByteArray &folderTypeName );
+    static QByteArray getFolderTypeAnnotation( const QMap<QByteArray, QByteArray> &annotations);
+    static void setFolderTypeAnnotation( QMap<QByteArray, QByteArray> &annotations, const QByteArray &value);
+    static Kolab::ObjectType getKolabTypeFromMimeType(const QString &type);
+    static QByteArray kolabTypeForMimeType( const QStringList &contentMimeTypes );
+    static QStringList getContentMimeTypes(Kolab::FolderType type);
+    static QString getIcon(Kolab::FolderType type);
+    //Returns true if the folder type shouldn't be ignored
+    static bool isHandledType(Kolab::FolderType type);
+};
+
+#endif
\ No newline at end of file
diff --git a/resources/kolab/kolabmessagehelper.cpp b/resources/kolab/kolabmessagehelper.cpp
new file mode 100644
index 0000000..b97be4f
--- /dev/null
+++ b/resources/kolab/kolabmessagehelper.cpp
@@ -0,0 +1,52 @@
+/*
+    Copyright (c) 2014 Christian Mollekopf <mollekopf at kolabsys.com>
+
+    This library is free software; you can redistribute it and/or modify it
+    under the terms of the GNU Library General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or (at your
+    option) any later version.
+
+    This library is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+    License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to the
+    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA.
+*/
+
+#include "kolabmessagehelper.h"
+
+#include <collectionannotationsattribute.h>
+#include <kolabdefinitions.h> //libkolab
+
+#include "kolabhelpers.h"
+
+
+KolabMessageHelper::KolabMessageHelper(const Akonadi::Collection &col)
+    : mCollection(col)
+{
+
+}
+
+KolabMessageHelper::~KolabMessageHelper()
+{
+
+}
+
+Akonadi::Item KolabMessageHelper::createItemFromMessage(KMime::Message::Ptr message, const qint64 uid, const qint64 size, const QList< QByteArray >& flags, const KIMAP::FetchJob::FetchScope& scope, bool& ok) const
+{
+    const Akonadi::Item item = MessageHelper::createItemFromMessage(message, uid, size, flags, scope, ok);
+    if (!ok) {
+        kWarning() << "Failed to read imap message";
+        return item;
+    }
+    Kolab::FolderType folderType = Kolab::MailType;
+    if (mCollection.hasAttribute<Akonadi::CollectionAnnotationsAttribute>()) {
+        const QByteArray folderTypeString = KolabHelpers::getFolderTypeAnnotation(mCollection.attribute<Akonadi::CollectionAnnotationsAttribute>()->annotations());
+        folderType = KolabHelpers::folderTypeFromString(folderTypeString);
+    }
+    return KolabHelpers::translateFromImap(folderType, item, ok);
+}
diff --git a/resources/kolab/kolabmessagehelper.h b/resources/kolab/kolabmessagehelper.h
new file mode 100644
index 0000000..ec978e6
--- /dev/null
+++ b/resources/kolab/kolabmessagehelper.h
@@ -0,0 +1,36 @@
+/*
+    Copyright (c) 2014 Christian Mollekopf <mollekopf at kolabsys.com>
+
+    This library is free software; you can redistribute it and/or modify it
+    under the terms of the GNU Library General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or (at your
+    option) any later version.
+
+    This library is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+    License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to the
+    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA.
+*/
+
+#ifndef KOLABMESSAGEHELPER_H
+#define KOLABMESSAGEHELPER_H
+
+#include <messagehelper.h>
+#include <Akonadi/Collection>
+
+class KolabMessageHelper : public MessageHelper {
+public:
+    explicit KolabMessageHelper(const Akonadi::Collection &collection);
+    virtual ~KolabMessageHelper();
+    virtual Akonadi::Item createItemFromMessage(KMime::Message::Ptr message, const qint64 uid, const qint64 size, const QList<QByteArray> &flags, const KIMAP::FetchJob::FetchScope &scope, bool &ok) const;
+
+private:
+    Akonadi::Collection mCollection;
+};
+
+#endif
\ No newline at end of file
diff --git a/resources/kolab/kolabresource.cpp b/resources/kolab/kolabresource.cpp
new file mode 100644
index 0000000..c2b2979
--- /dev/null
+++ b/resources/kolab/kolabresource.cpp
@@ -0,0 +1,178 @@
+/*
+    Copyright (c) 2014 Christian Mollekopf <mollekopf at kolabsys.com>
+
+    This library is free software; you can redistribute it and/or modify it
+    under the terms of the GNU Library General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or (at your
+    option) any later version.
+
+    This library is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+    License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to the
+    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA.
+*/
+
+#include "kolabresource.h"
+
+#include <resourcestateinterface.h>
+#include <resourcestate.h>
+#include <timestampattribute.h>
+#include <retrieveitemstask.h>
+#include <collectionannotationsattribute.h>
+
+#include <KLocalizedString>
+#include <Akonadi/CollectionFetchJob>
+#include <Akonadi/CollectionFetchScope>
+
+#include "kolabretrievecollectionstask.h"
+#include "kolabresourcestate.h"
+#include "kolabhelpers.h"
+
+KolabResource::KolabResource(const QString& id)
+    :ImapResource(id)
+{
+
+}
+
+KolabResource::~KolabResource()
+{
+
+}
+
+QString KolabResource::defaultName()
+{
+    return i18n("Kolab Resource");
+}
+
+ResourceStateInterface::Ptr KolabResource::createResourceState(const TaskArguments &args)
+{
+    return ResourceStateInterface::Ptr(new KolabResourceState(this, args));
+}
+
+void KolabResource::retrieveCollections()
+{
+    emit status(AgentBase::Running, i18nc("@info:status", "Retrieving folders"));
+
+    setKeepLocalCollectionChanges(QSet<QByteArray>() << "CONTENTMIMETYPES" << "AccessRights");
+    startTask(new KolabRetrieveCollectionsTask(createResourceState(TaskArguments()), this));
+}
+
+void KolabResource::retrieveItems(const Akonadi::Collection &col)
+{
+    //The collection that we receive was fetched when the task was scheduled, it is therefore possible that it is outdated.
+    //We refetch the collection since we rely on up-to-date annotations.
+    //FIXME: because this is async and not part of the resourcetask, it can't be killed. ResourceBase should just provide an up-to date copy of the collection.
+    Akonadi::CollectionFetchJob *fetchJob = new Akonadi::CollectionFetchJob(col, Akonadi::CollectionFetchJob::Base, this);
+    fetchJob->fetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::All);
+    fetchJob->fetchScope().setIncludeStatistics(true);
+    fetchJob->fetchScope().setIncludeUnsubscribed(true);
+    connect(fetchJob, SIGNAL(result(KJob*)), this, SLOT(onItemRetrievalCollectionFetchDone(KJob*)));
+}
+
+void KolabResource::onItemRetrievalCollectionFetchDone(KJob *job)
+{
+    if (job->error()) {
+        kWarning() << "Failed to retrieve collection before RetrieveItemsTask: " << job->errorString();
+        cancelTask(i18n("Failed to retrieve items."));
+        return;
+    }
+
+    Akonadi::CollectionFetchJob *fetchJob = static_cast<Akonadi::CollectionFetchJob*>(job);
+    Q_ASSERT(fetchJob->collections().size() == 1);
+    const Akonadi::Collection col = fetchJob->collections().first();
+
+    //This is the only part that differs form the imap resource: We make sure the annotations are up-to date before synchronizing
+    //HACK avoid infinite recursions, the metadatatask should be scheduled at most once per retrieveItemsJob
+    static QSet<Akonadi::Collection::Id> updatedCollections;
+    if (!updatedCollections.contains(col.id()) &&
+        (!col.attribute<TimestampAttribute>() ||
+        col.attribute<TimestampAttribute>()->timestamp() < QDateTime::currentDateTime().addSecs(-60).toTime_t())) {
+        updatedCollections.insert(col.id());
+        scheduleCustomTask(this, "triggerCollectionExtraInfoJobs", QVariant::fromValue(col), Akonadi::ResourceBase::Prepend);
+        deferTask();
+        return;
+    }
+    updatedCollections.remove(col.id());
+
+    setItemStreamingEnabled(true);
+
+    RetrieveItemsTask *task = new RetrieveItemsTask( createResourceState(TaskArguments(col)), this);
+    connect(task, SIGNAL(status(int,QString)), SIGNAL(status(int,QString)));
+    connect(this, SIGNAL(retrieveNextItemSyncBatch(int)), task, SLOT(onReadyForNextBatch(int)));
+    startTask(task);
+}
+
+void KolabResource::itemAdded(const Akonadi::Item& item, const Akonadi::Collection& collection)
+{
+    bool ok = true;
+    const Akonadi::Item imapItem = KolabHelpers::translateToImap(item, ok);
+    if (!ok) {
+        kWarning() << "Failed to convert item";
+        cancelTask();
+        return;
+    }
+    ImapResource::itemAdded(imapItem, collection);
+}
+
+void KolabResource::itemChanged(const Akonadi::Item& item, const QSet< QByteArray >& parts)
+{
+    bool ok = true;
+    const Akonadi::Item imapItem = KolabHelpers::translateToImap(item, ok);
+    if (!ok) {
+        kWarning() << "Failed to convert item";
+        cancelTask();
+        return;
+    }
+    ImapResource::itemChanged(imapItem, parts);
+}
+
+void KolabResource::itemsMoved(const Akonadi::Item::List& items, const Akonadi::Collection& source, const Akonadi::Collection& destination)
+{
+    bool ok = true;
+    const Akonadi::Item::List imapItems = KolabHelpers::translateToImap(items, ok);
+    if (!ok) {
+        kWarning() << "Failed to convert item";
+        cancelTask();
+        return;
+    }
+    ImapResource::itemsMoved(imapItems, source, destination);
+}
+
+static Akonadi::Collection updateAnnotations(const Akonadi::Collection &collection)
+{
+    //Set the annotations on new folders
+    const QByteArray kolabType = KolabHelpers::kolabTypeForMimeType(collection.contentMimeTypes());
+    if (!kolabType.isEmpty()) {
+        Akonadi::Collection col = collection;
+        Akonadi::CollectionAnnotationsAttribute *attr = col.attribute<Akonadi::CollectionAnnotationsAttribute>(Akonadi::Collection::AddIfMissing);
+        QMap<QByteArray, QByteArray> annotations = attr->annotations();
+        KolabHelpers::setFolderTypeAnnotation(annotations, kolabType);
+        attr->setAnnotations(annotations);
+        return col;
+    }
+    return collection;
+}
+
+void KolabResource::collectionAdded(const Akonadi::Collection& collection, const Akonadi::Collection& parent)
+{
+    //Set the annotations on new folders
+    const Akonadi::Collection col = updateAnnotations(collection);
+    //TODO we need to save the collections as well if the annotations have changed
+    //or we simply don't have the annotations locally, which perhaps is also not required?
+    ImapResource::collectionAdded(col, parent);
+}
+
+void KolabResource::collectionChanged(const Akonadi::Collection& collection, const QSet< QByteArray >& parts)
+{
+    //Update annotations if necessary
+    const Akonadi::Collection col = updateAnnotations(collection);
+    //TODO we need to save the collections as well if the annotations have changed
+    ImapResource::collectionChanged(col, parts);
+}
+
+AKONADI_RESOURCE_MAIN( KolabResource )
diff --git a/resources/kolab/kolabresource.desktop b/resources/kolab/kolabresource.desktop
new file mode 100644
index 0000000..735f6f7
--- /dev/null
+++ b/resources/kolab/kolabresource.desktop
@@ -0,0 +1,10 @@
+[Desktop Entry]
+Name=Kolab Groupware Server
+Comment=Provides access to Kolab groupware folders and e-mail on a Kolab IMAP Server.
+Type=AkonadiResource
+Exec=akonadi_kolab_resource
+Icon=kolab
+
+X-Akonadi-MimeTypes=message/rfc822,text/directory,text/calendar,application/x-vnd.kde.contactgroup,application/x-vnd.akonadi.calendar.event,application/x-vnd.akonadi.calendar.todo,application/x-vnd.akonadi.calendar.journal,application/x-vnd.akonadi.calendar.freebusy,text/x-vnd.akonadi.note
+X-Akonadi-Capabilities=Resource
+X-Akonadi-Identifier=akonadi_kolab_resource
diff --git a/resources/kolab/kolabresource.h b/resources/kolab/kolabresource.h
new file mode 100644
index 0000000..38889ad
--- /dev/null
+++ b/resources/kolab/kolabresource.h
@@ -0,0 +1,59 @@
+/*
+    Copyright (c) 2014 Christian Mollekopf <mollekopf at kolabsys.com>
+
+    This library is free software; you can redistribute it and/or modify it
+    under the terms of the GNU Library General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or (at your
+    option) any later version.
+
+    This library is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+    License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to the
+    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA.
+*/
+
+#ifndef KOLABRESOURCE_H
+#define KOLABRESOURCE_H
+
+#include "imapresource.h"
+#include <resourcestate.h>
+
+class KolabResource : public ImapResource
+{
+    Q_OBJECT
+
+    using Akonadi::AgentBase::Observer::collectionChanged;
+
+public:
+    explicit KolabResource(const QString &id);
+    ~KolabResource();
+
+protected Q_SLOTS:
+    virtual void retrieveCollections();
+    virtual void retrieveItems(const Akonadi::Collection& col);
+
+protected:
+    virtual ResourceStateInterface::Ptr createResourceState(const TaskArguments &);
+
+    virtual void itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection );
+    virtual void itemChanged( const Akonadi::Item &item, const QSet<QByteArray> &parts );
+    virtual void itemsMoved( const Akonadi::Item::List &item, const Akonadi::Collection &source,
+                           const Akonadi::Collection &destination );
+    //itemsRemoved and itemsFlags changed do not require translation, because they don't use the payload
+
+    virtual void collectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent);
+    virtual void collectionChanged(const Akonadi::Collection &collection, const QSet<QByteArray> &parts);
+    //collectionRemoved & collectionMoved do not require adjustments since they don't change the annotations
+
+    virtual QString defaultName();
+
+private Q_SLOTS:
+    void onItemRetrievalCollectionFetchDone(KJob *job);
+};
+
+#endif
diff --git a/resources/kolab/kolabresourcestate.cpp b/resources/kolab/kolabresourcestate.cpp
new file mode 100644
index 0000000..1d5c624
--- /dev/null
+++ b/resources/kolab/kolabresourcestate.cpp
@@ -0,0 +1,74 @@
+/*
+    Copyright (c) 2014 Christian Mollekopf <mollekopf at kolabsys.com>
+
+    This library is free software; you can redistribute it and/or modify it
+    under the terms of the GNU Library General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or (at your
+    option) any later version.
+
+    This library is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+    License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to the
+    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA.
+*/
+
+#include "kolabresourcestate.h"
+#include "kolabhelpers.h"
+#include "kolabmessagehelper.h"
+#include <collectionannotationsattribute.h>
+#include <Akonadi/EntityDisplayAttribute>
+#include <Akonadi/CachePolicy>
+#include <Akonadi/KMime/MessageParts>
+
+
+KolabResourceState::KolabResourceState(ImapResource* resource, const TaskArguments& arguments)
+    : ResourceState(resource, arguments)
+{
+
+}
+
+void KolabResourceState::collectionAttributesRetrieved(const Akonadi::Collection& collection)
+{
+    if (!collection.isValid() && collection.remoteId().isEmpty()) {
+        ResourceState::collectionAttributesRetrieved(collection);
+        return;
+    }
+    Akonadi::Collection col = collection;
+    if (col.attribute<Akonadi::CollectionAnnotationsAttribute>()) {
+        const QMap<QByteArray, QByteArray> rawAnnotations = col.attribute<Akonadi::CollectionAnnotationsAttribute>()->annotations();
+        const QByteArray type = KolabHelpers::getFolderTypeAnnotation(rawAnnotations);
+        const Kolab::FolderType folderType = KolabHelpers::folderTypeFromString(type);
+        col.setContentMimeTypes(KolabHelpers::getContentMimeTypes(folderType));
+
+        const QString icon = KolabHelpers::getIcon(folderType);
+        if (!icon.isEmpty()) {
+            kDebug() << " setting icon " << icon;
+            Akonadi::EntityDisplayAttribute *attr = col.attribute<Akonadi::EntityDisplayAttribute>(Akonadi::Collection::AddIfMissing);
+            attr->setIconName(icon);
+        }
+        if (folderType != Kolab::MailType) {
+            //Groupware data always requires the full message, because it cannot translate without the body
+            Akonadi::CachePolicy cachePolicy = col.cachePolicy();
+            QStringList localParts = cachePolicy.localParts();
+            if (!localParts.contains(Akonadi::MessagePart::Body)) {
+                localParts << Akonadi::MessagePart::Body;
+                cachePolicy.setLocalParts(localParts);
+                cachePolicy.setCacheTimeout(-1);
+                cachePolicy.setInheritFromParent(false);
+                cachePolicy.setSyncOnDemand(true);
+                col.setCachePolicy(cachePolicy);
+            }
+        }
+    }
+    ResourceState::collectionAttributesRetrieved(col);
+}
+
+MessageHelper::Ptr KolabResourceState::messageHelper() const
+{
+    return MessageHelper::Ptr(new KolabMessageHelper(collection()));
+}
diff --git a/resources/kolab/kolabresourcestate.h b/resources/kolab/kolabresourcestate.h
new file mode 100644
index 0000000..2d37f29
--- /dev/null
+++ b/resources/kolab/kolabresourcestate.h
@@ -0,0 +1,37 @@
+/*
+    Copyright (c) 2014 Christian Mollekopf <mollekopf at kolabsys.com>
+
+    This library is free software; you can redistribute it and/or modify it
+    under the terms of the GNU Library General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or (at your
+    option) any later version.
+
+    This library is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+    License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to the
+    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA.
+*/
+
+#ifndef KOLABRESOURCESTATE_H
+#define KOLABRESOURCESTATE_H
+
+#include <resourcestate.h>
+
+class ImapResource;
+
+class KolabResourceState : public ::ResourceState
+{
+public:
+    explicit KolabResourceState(ImapResource* resource, const TaskArguments& arguments);
+
+private:
+    virtual void collectionAttributesRetrieved(const Akonadi::Collection& collection);
+    virtual MessageHelper::Ptr messageHelper() const;
+};
+
+#endif
diff --git a/resources/kolab/kolabretrievecollectionstask.cpp b/resources/kolab/kolabretrievecollectionstask.cpp
new file mode 100644
index 0000000..d007cd1
--- /dev/null
+++ b/resources/kolab/kolabretrievecollectionstask.cpp
@@ -0,0 +1,249 @@
+/*
+    Copyright (c) 2010 Klarälvdalens Datakonsult AB,
+                       a KDAB Group company <info at kdab.com>
+    Author: Kevin Ottens <kevin at kdab.com>
+    Copyright (c) 2014 Christian Mollekopf <mollekopf at kolabsys.com>
+
+    This library is free software; you can redistribute it and/or modify it
+    under the terms of the GNU Library General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or (at your
+    option) any later version.
+
+    This library is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+    License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to the
+    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA.
+*/
+
+#include "kolabretrievecollectionstask.h"
+#include "kolabhelpers.h"
+
+#include <noselectattribute.h>
+#include <noinferiorsattribute.h>
+#include <collectionannotationsattribute.h>
+
+#include <akonadi/cachepolicy.h>
+#include <akonadi/entitydisplayattribute.h>
+#include <akonadi/kmime/messageparts.h>
+
+#include <kmime/kmime_message.h>
+
+#include <KDE/KDebug>
+#include <KDE/KLocale>
+
+KolabRetrieveCollectionsTask::KolabRetrieveCollectionsTask(ResourceStateInterface::Ptr resource, QObject* parent)
+    : ResourceTask(CancelIfNoSession, resource, parent),
+    mJobs(0)
+{
+
+}
+
+KolabRetrieveCollectionsTask::~KolabRetrieveCollectionsTask()
+{
+
+}
+
+void KolabRetrieveCollectionsTask::doStart(KIMAP::Session *session)
+{
+    Akonadi::Collection root;
+    root.setName(resourceName());
+    root.setRemoteId(rootRemoteId());
+    root.setContentMimeTypes(QStringList(Akonadi::Collection::mimeType()));
+    root.setParentCollection(Akonadi::Collection::root());
+    root.addAttribute(new NoSelectAttribute(true));
+    root.attribute<Akonadi::EntityDisplayAttribute>(Akonadi::Collection::AddIfMissing)->setIconName(QLatin1String("kolab"));
+
+    Akonadi::CachePolicy policy;
+    policy.setInheritFromParent(false);
+    policy.setSyncOnDemand(true);
+
+    QStringList localParts;
+    localParts << QLatin1String(Akonadi::MessagePart::Envelope)
+                << QLatin1String(Akonadi::MessagePart::Header);
+    int cacheTimeout = 60;
+
+    if (isDisconnectedModeEnabled()) {
+        // For disconnected mode we also cache the body
+        // and we keep all data indifinitely
+        localParts << QLatin1String(Akonadi::MessagePart::Body);
+        cacheTimeout = -1;
+    }
+
+    policy.setLocalParts(localParts);
+    policy.setCacheTimeout(cacheTimeout);
+    policy.setIntervalCheckTime(intervalCheckTime());
+
+    root.setCachePolicy(policy);
+
+    mMailCollections.insert(QString(), root);
+
+    //jobs are serialized by the session
+    if (isSubscriptionEnabled()) {
+        KIMAP::ListJob *fullListJob = new KIMAP::ListJob(session);
+        fullListJob->setOption(KIMAP::ListJob::NoOption);
+        fullListJob->setQueriedNamespaces(serverNamespaces());
+        connect( fullListJob, SIGNAL(mailBoxesReceived(QList<KIMAP::MailBoxDescriptor>,QList<QList<QByteArray> >)),
+                this, SLOT(onFullMailBoxesReceived(QList<KIMAP::MailBoxDescriptor>,QList<QList<QByteArray> >)) );
+        connect( fullListJob, SIGNAL(result(KJob*)), SLOT(onFullMailBoxesReceiveDone(KJob*)));
+        mJobs++;
+        fullListJob->start();
+    }
+
+    KIMAP::ListJob *listJob = new KIMAP::ListJob(session);
+    listJob->setOption(KIMAP::ListJob::IncludeUnsubscribed);
+    listJob->setQueriedNamespaces(serverNamespaces());
+    connect(listJob, SIGNAL(mailBoxesReceived(QList<KIMAP::MailBoxDescriptor>,QList<QList<QByteArray> >)),
+            this, SLOT(onMailBoxesReceived(QList<KIMAP::MailBoxDescriptor>,QList<QList<QByteArray> >)));
+    connect(listJob, SIGNAL(result(KJob*)), SLOT(onMailBoxesReceiveDone(KJob*)));
+    mJobs++;
+    listJob->start();
+}
+
+void KolabRetrieveCollectionsTask::onMailBoxesReceived(const QList< KIMAP::MailBoxDescriptor > &descriptors,
+                                                   const QList< QList<QByteArray> > &flags)
+{
+    for (int i=0; i<descriptors.size(); ++i) {
+        const KIMAP::MailBoxDescriptor descriptor = descriptors[i];
+
+        //TODO Get all folders anyways, but locally unsubscribe from the ones we're not subscribed to online?
+        if (isSubscriptionEnabled() && !mSubscribedMailboxes.contains(descriptor.name)) {
+            // not subscribed, skipping
+            continue;
+        }
+
+        createCollection(descriptor.name, flags.at(i));
+    }
+    checkDone();
+}
+
+Akonadi::Collection KolabRetrieveCollectionsTask::getOrCreateParent(const QString &path)
+{
+    if (mMailCollections.contains(path)) {
+        return mMailCollections.value(path);
+    }
+    //create a dummy collection
+    const QString separator = separatorCharacter();
+    const QStringList pathParts = path.split(separator);
+    const QString pathPart = pathParts.last();
+    Akonadi::Collection c;
+    c.setName( pathPart );
+    c.setRemoteId( separator + pathPart );
+    const QStringList parentPath = pathParts.mid(0, pathParts.size() - 1);
+    const Akonadi::Collection parentCollection = getOrCreateParent(parentPath.join(separator));
+    c.setParentCollection(parentCollection);
+
+    c.addAttribute(new NoSelectAttribute(true));
+    c.setContentMimeTypes(QStringList() << Akonadi::Collection::mimeType());
+    c.setRights(Akonadi::Collection::ReadOnly);
+    mMailCollections.insert(path, c);
+    return c;
+}
+
+void KolabRetrieveCollectionsTask::createCollection(const QString &mailbox, const QList<QByteArray> &currentFlags)
+{
+    const QString separator = separatorCharacter();
+    Q_ASSERT(separator.size() == 1);
+    const QString boxName = mailbox.endsWith( separator )
+                          ? mailbox.left( mailbox.size()-1 )
+                          : mailbox;
+    const QStringList pathParts = boxName.split( separator );
+    const QString pathPart = pathParts.last();
+
+    Akonadi::Collection c;
+    //If we had a dummy collection we need to replace it
+    if (mMailCollections.contains(mailbox)) {
+        c = mMailCollections.value(mailbox);
+    }
+    c.setName( pathPart );
+    c.setRemoteId( separator + pathPart );
+    const QStringList parentPath = pathParts.mid(0, pathParts.size() - 1);
+    const Akonadi::Collection parentCollection = getOrCreateParent(parentPath.join(separator));
+    c.setParentCollection(parentCollection);
+    //TODO get from ResourceState, and add KMime::Message::mimeType() for the normal imap resource by default
+    //We add a dummy mimetype, otherwise the itemsync doesn't even work (action is disabled and resourcebase aborts the operation)
+    c.setContentMimeTypes(QStringList() << Akonadi::Collection::mimeType() << QLatin1String("application/x-kolab-objects"));
+    //assume LRS, until myrights is executed
+    if (serverCapabilities().contains("ACL")) {
+        c.setRights(Akonadi::Collection::ReadOnly);
+    } else {
+        c.setRights(Akonadi::Collection::AllRights);
+    }
+
+    // If the folder is the Inbox, make some special settings.
+    if (pathParts.size() == 1 && pathPart.compare(QLatin1String("inbox") , Qt::CaseInsensitive) == 0) {
+        Akonadi::EntityDisplayAttribute *attr = c.attribute<Akonadi::EntityDisplayAttribute>(Akonadi::Collection::AddIfMissing);
+        attr->setDisplayName(i18n("Inbox"));
+        attr->setIconName(QLatin1String("mail-folder-inbox"));
+        setIdleCollection(c);
+    }
+
+    // If the folder is the user top-level folder, mark it as well, although it is not officially noted in the RFC
+    if (pathParts.size() == 1 && pathPart == QLatin1String("user") && currentFlags.contains("\\noselect")) {
+        Akonadi::EntityDisplayAttribute *attr = c.attribute<Akonadi::EntityDisplayAttribute>(Akonadi::Collection::AddIfMissing);
+        attr->setDisplayName(i18n("Shared Folders"));
+        attr->setIconName(QLatin1String("x-mail-distribution-list"));
+    }
+
+    // If this folder is a noselect folder, make some special settings.
+    if (currentFlags.contains("\\noselect")) {
+        c.addAttribute(new NoSelectAttribute(true));
+        c.setContentMimeTypes(QStringList() << Akonadi::Collection::mimeType());
+        c.setRights( Akonadi::Collection::ReadOnly );
+    } else {
+        // remove the noselect attribute explicitly, in case we had set it before (eg. for non-subscribed non-leaf folders)
+        c.removeAttribute<NoSelectAttribute>();
+    }
+
+    // If this folder is a noinferiors folder, it is not allowed to create subfolders inside.
+    if (currentFlags.contains("\\noinferiors")) {
+        //kDebug() << "Noinferiors: " << currentPath;
+        c.addAttribute(new NoInferiorsAttribute(true));
+        c.setRights(c.rights() & ~Akonadi::Collection::CanCreateCollection);
+    }
+
+    kDebug() << "creating collection " << mailbox << " with parent " << parentPath;
+    mMailCollections.insert(mailbox, c);
+    //This is no longer required
+    mSubscribedMailboxes.remove(mailbox);
+}
+
+void KolabRetrieveCollectionsTask::onMailBoxesReceiveDone(KJob* job)
+{
+    mJobs--;
+    if (job->error()) {
+        cancelTask(job->errorString());
+    } else {
+        checkDone();
+    }
+}
+
+void KolabRetrieveCollectionsTask::checkDone()
+{
+    if (!mJobs) {
+        collectionsRetrieved(mMailCollections.values());
+    }
+}
+
+void KolabRetrieveCollectionsTask::onFullMailBoxesReceived(const QList< KIMAP::MailBoxDescriptor >& descriptors,
+                                                       const QList< QList< QByteArray > >& flags)
+{
+    Q_UNUSED(flags);
+    foreach (const KIMAP::MailBoxDescriptor &descriptor, descriptors) {
+        mSubscribedMailboxes.insert(descriptor.name);
+    }
+}
+
+void KolabRetrieveCollectionsTask::onFullMailBoxesReceiveDone(KJob* job)
+{
+    mJobs--;
+    if (job->error()) {
+        cancelTask(job->errorString());
+    } else {
+        checkDone();
+    }
+}
diff --git a/resources/kolab/kolabretrievecollectionstask.h b/resources/kolab/kolabretrievecollectionstask.h
new file mode 100644
index 0000000..5869149
--- /dev/null
+++ b/resources/kolab/kolabretrievecollectionstask.h
@@ -0,0 +1,60 @@
+/*
+    Copyright (c) 2010 Klarälvdalens Datakonsult AB,
+                       a KDAB Group company <info at kdab.com>
+    Author: Kevin Ottens <kevin at kdab.com>
+    Copyright (c) 2014 Christian Mollekopf <mollekopf at kolabsys.com>
+
+    This library is free software; you can redistribute it and/or modify it
+    under the terms of the GNU Library General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or (at your
+    option) any later version.
+
+    This library is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+    License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to the
+    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA.
+*/
+
+#ifndef KOLABRETRIEVECOLLECTIONSTASK_H
+#define KOLABRETRIEVECOLLECTIONSTASK_H
+
+#include <resourcetask.h>
+
+#include <akonadi/collection.h>
+
+#include <kimap/listjob.h>
+
+class KolabRetrieveCollectionsTask : public ResourceTask
+{
+    Q_OBJECT
+
+public:
+    explicit KolabRetrieveCollectionsTask(ResourceStateInterface::Ptr resource, QObject *parent = 0);
+    virtual ~KolabRetrieveCollectionsTask();
+
+private slots:
+    void onMailBoxesReceived(const QList<KIMAP::MailBoxDescriptor> &descriptors,
+                                const QList< QList<QByteArray> > &flags);
+    void onMailBoxesReceiveDone(KJob *job);
+    void onFullMailBoxesReceived(const QList<KIMAP::MailBoxDescriptor> &descriptors, const QList<QList<QByteArray> > &flags);
+    void onFullMailBoxesReceiveDone(KJob *job);
+
+protected:
+    virtual void doStart(KIMAP::Session *session);
+
+private:
+    void checkDone();
+    Akonadi::Collection getOrCreateParent(const QString &parentPath);
+    void createCollection(const QString &mailbox, const QList<QByteArray> &flags);
+
+    int mJobs;
+    QHash<QString, Akonadi::Collection> mMailCollections;;
+    QSet<QString> mSubscribedMailboxes;
+};
+
+#endif


commit 7792268ee1e36ef45139a01037ca5a47b5d1f92c
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Thu May 8 10:51:28 2014 +0200

    IMAP-Resource: Include unsubscribed where we rely on it.
    
    Otherwise we're crashing miserably.

diff --git a/resources/imap/imapresource.cpp b/resources/imap/imapresource.cpp
index f3fd352..6b1371f 100644
--- a/resources/imap/imapresource.cpp
+++ b/resources/imap/imapresource.cpp
@@ -442,6 +442,7 @@ void ImapResource::triggerCollectionExtraInfoJobs( const QVariant &collectionVar
   Akonadi::CollectionFetchJob *fetchJob = new Akonadi::CollectionFetchJob(collection, CollectionFetchJob::Base, this);
   fetchJob->fetchScope().setAncestorRetrieval( CollectionFetchScope::All );
   fetchJob->fetchScope().setIncludeStatistics( true );
+  fetchJob->fetchScope().setIncludeUnsubscribed( true );
   connect(fetchJob, SIGNAL(result(KJob*)), this, SLOT(onMetadataCollectionFetchDone(KJob*)));
 }
 
@@ -469,6 +470,7 @@ void ImapResource::retrieveItems( const Collection &col )
   Akonadi::CollectionFetchJob *fetchJob = new Akonadi::CollectionFetchJob(col, CollectionFetchJob::Base, this);
   fetchJob->fetchScope().setAncestorRetrieval( CollectionFetchScope::All );
   fetchJob->fetchScope().setIncludeStatistics( true );
+  fetchJob->fetchScope().setIncludeUnsubscribed( true );
   connect(fetchJob, SIGNAL(result(KJob*)), this, SLOT(onItemRetrievalCollectionFetchDone(KJob*)));
 }
 
@@ -696,6 +698,7 @@ void ImapResource::requestManualExpunge( qint64 collectionId )
     Akonadi::CollectionFetchScope scope;
     scope.setResource( identifier() );
     scope.setAncestorRetrieval( Akonadi::CollectionFetchScope::All );
+    scope.setIncludeUnsubscribed( true );
 
     Akonadi::CollectionFetchJob *fetch
       = new Akonadi::CollectionFetchJob( collection,


commit 35267d78cbb2df32410aefb9ee468fc4c2350c79
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Thu May 8 09:56:37 2014 +0200

    IMAP-Resource: Made default name customizable by subclasses.

diff --git a/resources/imap/imapresource.cpp b/resources/imap/imapresource.cpp
index 4195d58..f3fd352 100644
--- a/resources/imap/imapresource.cpp
+++ b/resources/imap/imapresource.cpp
@@ -110,9 +110,9 @@ ImapResource::ImapResource( const QString &id )
                                   -1 ).toInt();
 
     if ( instanceCounter > 0 ) {
-      setName( i18n( "IMAP Account %1", instanceCounter ) );
+      setName( QString("%1 %2").arg(defaultName()).arg(instanceCounter) );
     } else {
-      setName( i18n( "IMAP Account" ) );
+      setName( defaultName() );
     }
   }
 
@@ -195,6 +195,11 @@ ImapResource::~ImapResource()
   delete m_pool;
 }
 
+QString ImapResource::defaultName()
+{
+  return i18n( "IMAP Account" );
+}
+
 void ImapResource::aboutToQuit()
 {
   //TODO the resource would ideally have to signal when it's done with logging out etc, before the destructor gets called
diff --git a/resources/imap/imapresource.h b/resources/imap/imapresource.h
index fd4b4a3..0364a00 100644
--- a/resources/imap/imapresource.h
+++ b/resources/imap/imapresource.h
@@ -111,6 +111,7 @@ protected:
   virtual void aboutToQuit();
 
   virtual ResourceStateInterface::Ptr createResourceState(const TaskArguments &);
+  virtual QString defaultName();
 
 private Q_SLOTS:
   void doSearch( const QVariant &arg );


commit f409267b6948eee79d52cded38f18f388180a552
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Wed May 7 16:21:40 2014 +0200

    IMAP-Resource: Fixed "authentification" typo.

diff --git a/resources/imap/setupserverview_desktop.ui b/resources/imap/setupserverview_desktop.ui
index aec4da1..eb749ee 100644
--- a/resources/imap/setupserverview_desktop.ui
+++ b/resources/imap/setupserverview_desktop.ui
@@ -235,7 +235,7 @@
        <item>
         <widget class="QGroupBox" name="authentification">
          <property name="title">
-          <string>Authentification</string>
+          <string>Authentication</string>
          </property>
          <layout class="QGridLayout" name="gridLayout">
           <item row="5" column="1">
@@ -251,7 +251,7 @@
           <item row="1" column="0" colspan="2">
            <widget class="QRadioButton" name="noAuthentification">
             <property name="text">
-             <string>No authentification</string>
+             <string>No authentication</string>
             </property>
             <attribute name="buttonGroup">
              <string notr="true">customSieveGroup</string>


commit 5e8808a6e2eb7171b8aafeeb0f553a8e89ae3a8f
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Thu May 8 17:18:04 2014 +0200

    IMAP-Resource: Don't crash if IDLE collection is not subscribed locally.
    
    BUG: 329805

diff --git a/resources/imap/imapresource.cpp b/resources/imap/imapresource.cpp
index feef108..4195d58 100644
--- a/resources/imap/imapresource.cpp
+++ b/resources/imap/imapresource.cpp
@@ -634,6 +634,11 @@ void ImapResource::startIdle()
   if ( !m_pool->serverCapabilities().contains( QLatin1String("IDLE") ) )
     return;
 
+  //Without password we don't even have to try
+  if (Settings::self()->password().isEmpty()) {
+    return;
+  }
+
   const QStringList ridPath = Settings::self()->idleRidPath();
   if ( ridPath.size() < 2 )
     return;
@@ -661,20 +666,16 @@ void ImapResource::startIdle()
 
 void ImapResource::onIdleCollectionFetchDone( KJob *job )
 {
-  if ( job->error() == 0 ) {
-    Akonadi::CollectionFetchJob *fetch = static_cast<Akonadi::CollectionFetchJob*>( job );
-    Akonadi::Collection c = fetch->collections().first();
-
-    const QString password = Settings::self()->password();
-    if ( password.isEmpty() )
-      return;
-
-    m_idle = new ImapIdleManager( createResourceState(TaskArguments(c)), m_pool, this );
-
-  } else {
+  if (job->error()) {
     kWarning() << "CollectionFetch for idling failed."
                << "error=" << job->error()
                << ", errorString=" << job->errorString();
+    return;
+  }
+  Akonadi::CollectionFetchJob *fetch = static_cast<Akonadi::CollectionFetchJob*>(job);
+  //Can be empty if collection is not subscribed locally
+  if (!fetch->collections().isEmpty()) {
+    m_idle = new ImapIdleManager( createResourceState(TaskArguments(fetch->collections().first())), m_pool, this );
   }
 }
 


commit 423632618ed307817a088ae99607d19f3cce98ed
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Wed May 7 16:20:27 2014 +0200

    IMAP-Resource: Allow to override the encryption mode.
    
    Some ssl servers advertise an ssl version they don't actually support.
    This config-only option allows to override the used encryption mode, and
    supports all available options, so the auto-negotiation can be skipped.
    
    BUG: 328625

diff --git a/resources/imap/imapresource.kcfg b/resources/imap/imapresource.kcfg
index 33e953f..fbec299 100644
--- a/resources/imap/imapresource.kcfg
+++ b/resources/imap/imapresource.kcfg
@@ -20,6 +20,9 @@
       <label>Defines the encryption type to use</label>
       <default>SSL</default>
     </entry>
+    <entry name="OverrideEncryption" type="String">
+      <label>Override configured encryption mode</label>
+    </entry>
     <entry name="Authentication" type="Int">
       <label>Defines the authentication type to use</label>
       <default>1</default>
diff --git a/resources/imap/settings.cpp b/resources/imap/settings.cpp
index 5b0d0fe..1677686 100644
--- a/resources/imap/settings.cpp
+++ b/resources/imap/settings.cpp
@@ -271,11 +271,35 @@ void Settings::loadAccount( ImapAccount *account ) const
   if ( encryption == QLatin1String("SSL") ) {
     account->setEncryptionMode( KIMAP::LoginJob::AnySslVersion );
   } else if (  encryption == QLatin1String("STARTTLS") ) {
+    //KIMAP confused TLS and STARTTLS, TlsV1 really means "use STARTTLS"
     account->setEncryptionMode( KIMAP::LoginJob::TlsV1 );
   } else {
     account->setEncryptionMode( KIMAP::LoginJob::Unencrypted );
   }
 
+  //Some SSL Server fail to advertise an ssl version they support (AnySslVersion),
+  //we therefore allow overriding this in the config
+  //(so we don't have to make the UI unnecessarily complex for properly working servers).
+  const QString overrideEncryptionMode = overrideEncryption();
+  if (!overrideEncryptionMode.isEmpty()) {
+    kWarning() << "Overriding encryption mode with: " << overrideEncryptionMode;
+    if ( overrideEncryptionMode == QLatin1String("SSLV2") ) {
+      account->setEncryptionMode( KIMAP::LoginJob::SslV2 );
+    } else if (  overrideEncryptionMode == QLatin1String("SSLV3") ) {
+      account->setEncryptionMode( KIMAP::LoginJob::SslV3 );
+    } else if (  overrideEncryptionMode == QLatin1String("TLSV1") ) {
+      account->setEncryptionMode( KIMAP::LoginJob::SslV3_1 );
+    } else if ( overrideEncryptionMode == QLatin1String("SSL") ) {
+      account->setEncryptionMode( KIMAP::LoginJob::AnySslVersion );
+    } else if (  overrideEncryptionMode == QLatin1String("STARTTLS") ) {
+      account->setEncryptionMode( KIMAP::LoginJob::TlsV1 );
+    } else if (  overrideEncryptionMode == QLatin1String("UNENCRYPTED") ) {
+      account->setEncryptionMode( KIMAP::LoginJob::Unencrypted );
+    } else {
+      kWarning() << "Tried to force invalid encryption mode: " << overrideEncryptionMode;
+    }
+  }
+
   account->setAuthenticationMode(
       mapTransportAuthToKimap(
           (MailTransport::TransportBase::EnumAuthenticationType::type) authentication()


commit 94424520284417366159ea8e358a9e7eb0ee5d5f
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Wed May 7 14:04:48 2014 +0200

    IMAP-Resource: Properly count started and completed jobs.
    
    I.e. if no job was started because of no metadata capabilities the job would get stuck otherwise.
    
    BUG: 334448

diff --git a/resources/imap/retrievecollectionmetadatatask.cpp b/resources/imap/retrievecollectionmetadatatask.cpp
index da34087..355bfb8 100644
--- a/resources/imap/retrievecollectionmetadatatask.cpp
+++ b/resources/imap/retrievecollectionmetadatatask.cpp
@@ -53,7 +53,7 @@ void RetrieveCollectionMetadataTask::doStart( KIMAP::Session *session )
 {
   kDebug( 5327 ) << collection().remoteId();
 
-  // Prevent fetching items from noselect folders.
+  // Prevent fetching metadata from noselect folders.
   if ( collection().hasAttribute( "noselect" ) ) {
     NoSelectAttribute* noselect = static_cast<NoSelectAttribute*>( collection().attribute( "noselect" ) );
     if ( noselect->noSelect() ) {
@@ -82,8 +82,8 @@ void RetrieveCollectionMetadataTask::doStart( KIMAP::Session *session )
       meta->addEntry( "*", "value.shared" );
     }
     connect( meta, SIGNAL(result(KJob*)), SLOT(onGetMetaDataDone(KJob*)) );
-    meta->start();
     m_pendingMetaDataJobs++;
+    meta->start();
   }
 
   // Get the ACLs from the mailbox if it's supported
@@ -91,14 +91,14 @@ void RetrieveCollectionMetadataTask::doStart( KIMAP::Session *session )
     KIMAP::GetAclJob *acl = new KIMAP::GetAclJob( session );
     acl->setMailBox( mailBox );
     connect( acl, SIGNAL(result(KJob*)), SLOT(onGetAclDone(KJob*)) );
-    acl->start();
     m_pendingMetaDataJobs++;
+    acl->start();
 
     KIMAP::MyRightsJob *rights = new KIMAP::MyRightsJob( session );
     rights->setMailBox( mailBox );
     connect( rights, SIGNAL(result(KJob*)), SLOT(onRightsReceived(KJob*)) );
-    rights->start();
     m_pendingMetaDataJobs++;
+    rights->start();
   }
 
   // Get the QUOTA info from the mailbox if it's supported
@@ -106,8 +106,8 @@ void RetrieveCollectionMetadataTask::doStart( KIMAP::Session *session )
     KIMAP::GetQuotaRootJob *quota = new KIMAP::GetQuotaRootJob( session );
     quota->setMailBox( mailBox );
     connect( quota, SIGNAL(result(KJob*)), SLOT(onQuotasReceived(KJob*)) );
-    quota->start();
     m_pendingMetaDataJobs++;
+    quota->start();
   }
 
   // the server does not have any of the capabilities needed to get extra info, so this
@@ -119,7 +119,9 @@ void RetrieveCollectionMetadataTask::doStart( KIMAP::Session *session )
 
 void RetrieveCollectionMetadataTask::onGetMetaDataDone( KJob *job )
 {
+  m_pendingMetaDataJobs--;
   if ( job->error() ) {
+    kWarning() << "Get metadata failed: " << job->errorString();
     endTaskIfNeeded();
     return; // Well, no metadata for us then...
   }
@@ -146,7 +148,9 @@ void RetrieveCollectionMetadataTask::onGetMetaDataDone( KJob *job )
 
 void RetrieveCollectionMetadataTask::onGetAclDone( KJob *job )
 {
+  m_pendingMetaDataJobs--;
   if ( job->error() ) {
+    kWarning() << "GetACL failed: " << job->errorString();
     endTaskIfNeeded();
     return; // Well, no metadata for us then...
   }
@@ -166,7 +170,9 @@ void RetrieveCollectionMetadataTask::onGetAclDone( KJob *job )
 
 void RetrieveCollectionMetadataTask::onRightsReceived( KJob *job )
 {
+  m_pendingMetaDataJobs--;
   if ( job->error() ) {
+    kWarning() << "MyRights failed: " << job->errorString();
     endTaskIfNeeded();
     return; // Well, no metadata for us then...
   }
@@ -242,7 +248,9 @@ void RetrieveCollectionMetadataTask::onRightsReceived( KJob *job )
 
 void RetrieveCollectionMetadataTask::onQuotasReceived( KJob *job )
 {
+  m_pendingMetaDataJobs--;
   if ( job->error() ) {
+    kWarning() << "Quota retrieval failed: " << job->errorString();
     endTaskIfNeeded();
     return; // Well, no metadata for us then...
   }
@@ -298,8 +306,7 @@ void RetrieveCollectionMetadataTask::onQuotasReceived( KJob *job )
 
 void RetrieveCollectionMetadataTask::endTaskIfNeeded()
 {
-  if ( --m_pendingMetaDataJobs == 0 ) {
-    // the others have ended, we're done, the next one can go
+  if ( m_pendingMetaDataJobs <= 0 ) {
     const uint currentTimestamp = QDateTime::currentDateTime().toTime_t();
     TimestampAttribute *attr = m_collection.attribute<TimestampAttribute>( Akonadi::Collection::AddIfMissing );
     attr->setTimestamp( currentTimestamp );


commit 2f19590e97121c2ecec2a7a3bc53cf9d1e385e6c
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Tue May 6 16:49:30 2014 +0200

    IMAP-Resource: be a bit more verbose when changing messages.

diff --git a/resources/imap/changeitemsflagstask.cpp b/resources/imap/changeitemsflagstask.cpp
index 9908d1c..1316b65 100644
--- a/resources/imap/changeitemsflagstask.cpp
+++ b/resources/imap/changeitemsflagstask.cpp
@@ -37,6 +37,7 @@ ChangeItemsFlagsTask::~ChangeItemsFlagsTask()
 void ChangeItemsFlagsTask::doStart(KIMAP::Session* session)
 {
   const QString mailBox = mailBoxForCollection( items().first().parentCollection() );
+  kDebug(5327) << mailBox;
 
   if ( session->selectedMailBox() != mailBox ) {
     KIMAP::SelectJob *select = new KIMAP::SelectJob( session );
@@ -53,6 +54,7 @@ void ChangeItemsFlagsTask::doStart(KIMAP::Session* session)
     } else if ( !removedFlags().isEmpty() ) {
         triggerRemoveFlagsJob( session );
     } else {
+        kDebug(5327) << "nothing to do";
         changeProcessed();
     }
   }
@@ -61,14 +63,17 @@ void ChangeItemsFlagsTask::doStart(KIMAP::Session* session)
 void ChangeItemsFlagsTask::onSelectDone(KJob* job)
 {
   if ( job->error() ) {
+    kWarning() << "Select failed: " << job->errorString();
     cancelTask( job->errorString() );
   } else {
     KIMAP::SelectJob *select = static_cast<KIMAP::SelectJob*>( job );
+    kDebug(5327) << addedFlags();
     if ( !addedFlags().isEmpty() ) {
         triggerAppendFlagsJob( select->session() );
     } else if ( !removedFlags().isEmpty() ) {
         triggerRemoveFlagsJob( select->session() );
     } else {
+        kDebug(5327) << "nothing to do";
         changeProcessed();
     }
   }
@@ -109,8 +114,10 @@ void ChangeItemsFlagsTask::triggerRemoveFlagsJob(KIMAP::Session* session)
 void ChangeItemsFlagsTask::onAppendFlagsDone(KJob* job)
 {
   if ( job->error() ) {
+    kWarning() << "Flag append failed: " << job->errorString();
     cancelTask( job->errorString() );
   } else {
+    kDebug(5327) << removedFlags();
     if ( removedFlags().isEmpty() ) {
       changeProcessed();
     } else {
@@ -123,6 +130,7 @@ void ChangeItemsFlagsTask::onAppendFlagsDone(KJob* job)
 void ChangeItemsFlagsTask::onRemoveFlagsDone(KJob* job)
 {
   if ( job->error() ) {
+    kWarning() << "Flag remove failed: " << job->errorString();
     cancelTask( job->errorString() );
   } else {
     changeProcessed();
diff --git a/resources/imap/changeitemtask.cpp b/resources/imap/changeitemtask.cpp
index 8c42a4a..2c103d4 100644
--- a/resources/imap/changeitemtask.cpp
+++ b/resources/imap/changeitemtask.cpp
@@ -51,9 +51,11 @@ void ChangeItemTask::doStart( KIMAP::Session *session )
 
   const QString mailBox = mailBoxForCollection( item().parentCollection() );
   m_oldUid = item().remoteId().toLongLong();
+  kDebug(5327) << mailBox << m_oldUid << parts();
 
   if ( parts().contains( "PLD:RFC822" ) ) {
     if ( !item().hasPayload<KMime::Message::Ptr>() ) {
+      kWarning() << "Payload changed, no payload available.";
       changeProcessed();
       return;
     }
@@ -66,7 +68,9 @@ void ChangeItemTask::doStart( KIMAP::Session *session )
 
     job->setMailBox( mailBox );
     job->setContent( msg->encodedContent( true ) );
-    job->setFlags( fromAkonadiToSupportedImapFlags( item().flags().toList(), item().parentCollection() ) );
+    const QList<QByteArray> flags = fromAkonadiToSupportedImapFlags( item().flags().toList(), item().parentCollection() );
+    job->setFlags( flags );
+    kDebug(5327) << "Appending new message: " << flags;
 
     connect( job, SIGNAL(result(KJob*)),
              this, SLOT(onAppendMessageDone(KJob*)) );
@@ -89,6 +93,7 @@ void ChangeItemTask::doStart( KIMAP::Session *session )
     }
 
   } else {
+    kDebug(5327) << "Nothing to do";
     changeProcessed();
   }
 }
@@ -96,6 +101,7 @@ void ChangeItemTask::doStart( KIMAP::Session *session )
 void ChangeItemTask::onPreStoreSelectDone( KJob *job )
 {
   if ( job->error() ) {
+    kWarning() << "Select failed: " << job->errorString();
     cancelTask( job->errorString() );
   } else {
     triggerStoreJob();
@@ -105,6 +111,7 @@ void ChangeItemTask::onPreStoreSelectDone( KJob *job )
 void ChangeItemTask::triggerStoreJob()
 {
   QList<QByteArray> flags = fromAkonadiToSupportedImapFlags( item().flags().toList(), item().parentCollection() );
+  kDebug(5327) << flags;
 
   KIMAP::StoreJob *store = new KIMAP::StoreJob( m_session );
 
@@ -122,6 +129,7 @@ void ChangeItemTask::triggerStoreJob()
 void ChangeItemTask::onStoreFlagsDone( KJob *job )
 {
   if ( job->error() ) {
+    kWarning() << "Flag store failed: " << job->errorString();
     cancelTask( job->errorString() );
   } else {
     changeProcessed();
@@ -131,6 +139,7 @@ void ChangeItemTask::onStoreFlagsDone( KJob *job )
 void ChangeItemTask::onAppendMessageDone( KJob *job )
 {
   if ( job->error() ) {
+    kWarning() << "Append failed: " << job->errorString();
     cancelTask( job->errorString() );
     return;
   }
@@ -165,6 +174,7 @@ void ChangeItemTask::onAppendMessageDone( KJob *job )
 void ChangeItemTask::onPreDeleteSelectDone( KJob *job )
 {
   if ( job->error() ) {
+    kWarning() << "PreDelete select failed: " << job->errorString();
     if ( m_newUid > 0 ) {
       recordNewUid();
     } else {
@@ -196,6 +206,7 @@ void ChangeItemTask::triggerSearchJob()
 
     UidNextAttribute *uidNext = item().parentCollection().attribute<UidNextAttribute>();
     if ( !uidNext ) {
+      kWarning() << "Failed to determine new uid.";
       cancelTask( i18n( "Could not determine the UID for the newly created message on the server" ) );
       search->deleteLater();
       return;
@@ -214,6 +225,7 @@ void ChangeItemTask::triggerSearchJob()
 void ChangeItemTask::onSearchDone( KJob *job )
 {
   if ( job->error() ) {
+    kWarning() << "Search failed: " << job->errorString();
     cancelTask( job->errorString() );
     return;
   }
@@ -221,6 +233,7 @@ void ChangeItemTask::onSearchDone( KJob *job )
   KIMAP::SearchJob *search = static_cast<KIMAP::SearchJob*>( job );
 
   if ( search->results().count()!=1 ) {
+    kWarning() << "Failed to determine new uid.";
     cancelTask( i18n( "Could not determine the UID for the newly created message on the server" ) );
     return;
   }


commit 7802a783189adbe4e3e67cd9bb60867c82c36d69
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Mon May 5 23:10:48 2014 +0200

    IMAP-Resource: Check available UID's first & retrieve 50 time more flags.
    
    This way we avoid the UID fragmentation problem (we try to fetch a lot of
    messages that are not available on the server).
    It's also a nice first step towards only syncing a certain time-frame.
    
    Retrieving 50 times more flags than full messages should still result in
    low memory consumption while greatly reducing roundtrips.
    
    BUG: 334280

diff --git a/resources/imap/retrieveitemstask.cpp b/resources/imap/retrieveitemstask.cpp
index 65c0a66..47c48bd 100644
--- a/resources/imap/retrieveitemstask.cpp
+++ b/resources/imap/retrieveitemstask.cpp
@@ -44,6 +44,7 @@
 #include <kimap/fetchjob.h>
 #include <kimap/selectjob.h>
 #include <kimap/session.h>
+#include <kimap/searchjob.h>
 
 /**
  * A job that retrieves a set of messages in reverse-ordered batches.
@@ -57,6 +58,7 @@ public:
     virtual void start();
     void fetchNextBatch();
     void setUidBased(bool);
+    void setSearchTerm(const KIMAP::Term &);
 
 Q_SIGNALS:
     void itemsRetrieved(Akonadi::Item::List);
@@ -67,6 +69,7 @@ private Q_SLOTS:
                                            const QMap<qint64, KIMAP::MessageFlags> &flags,
                                            const QMap<qint64, KIMAP::MessagePtr> &messages);
     void onHeadersFetchDone(KJob *job);
+    void onUidSearchDone(KJob* job);
 
 private:
     //Batch fetching
@@ -79,6 +82,7 @@ private:
     const MessageHelper::Ptr m_messageHelper;
     bool m_fetchInProgress;
     bool m_continuationRequested;
+    KIMAP::Term m_searchTerm;
 };
 
 BatchFetcher::BatchFetcher(MessageHelper::Ptr messageHelper, const KIMAP::ImapSet &set, const KIMAP::FetchJob::FetchScope& scope, int batchSize, KIMAP::Session* session)
@@ -104,15 +108,45 @@ void BatchFetcher::setUidBased(bool uidBased)
     m_uidBased = uidBased;
 }
 
+void BatchFetcher::setSearchTerm(const KIMAP::Term &searchTerm)
+{
+    m_searchTerm = searchTerm;
+}
+
 void BatchFetcher::start()
 {
+    if (!m_searchTerm.isNull()) {
+        //Resolve the uid to sequence numbers
+        KIMAP::SearchJob *search = new KIMAP::SearchJob(m_session);
+        search->setUidBased(true);
+        search->setTerm(m_searchTerm);
+        connect(search, SIGNAL(result(KJob*)), this, SLOT(onUidSearchDone(KJob*)));
+        search->start();
+    } else {
+        fetchNextBatch();
+    }
+}
+
+void BatchFetcher::onUidSearchDone(KJob* job)
+{
+    if (job->error()) {
+        kWarning() << "Search job failed: " << job->errorString();
+        setError(KJob::UserDefinedError);
+        emitResult();
+        return;
+    }
+    KIMAP::SearchJob *search = static_cast<KIMAP::SearchJob*>(job);
+    m_uidBased = search->isUidBased();
+
+    KIMAP::ImapSet set;
+    set.add(search->results());
+    m_currentSet = set;
     fetchNextBatch();
 }
 
 void BatchFetcher::fetchNextBatch()
 {
     if (m_fetchInProgress) {
-        kWarning() << "fetchNextBatch called while fetch is in process";
         m_continuationRequested = true;
         return;
     }
@@ -125,38 +159,34 @@ void BatchFetcher::fetchNextBatch()
     }
 
     KIMAP::FetchJob *fetch = new KIMAP::FetchJob(m_session);
-    //In the most common case that we want optimized we use batch processing.
-    if (m_scope.changedSince == 0 && m_currentSet.intervals().size() == 1 && m_currentSet.intervals().first().hasDefinedEnd()) {
-        const KIMAP::ImapInterval interval = m_currentSet.intervals().first();
-        Q_ASSERT(interval.hasDefinedEnd());
-        //Reverse fetching would be great, because it gives you the most relevant (recent) messages first,
-        //but since we usually just check the latest uid, we wouldn't detect if an interval
-        //at the beginning is missing. Therefore this is disabled for now.
-
-        //get an interval of m_batchSize
-//         const qint64 end = interval.end();
-//         const qint64 begin = qMax(interval.begin(), end - m_batchSize + 1);
-//         const KIMAP::ImapSet intervalToFetch(begin, end);
-//         //fetch items in reverse order in chunks
-//         if (interval.begin() < (begin - 1)) {
-//             m_currentSet = KIMAP::ImapSet(interval.begin(), begin - 1);
-//         } else {
-//             m_currentSet = KIMAP::ImapSet();
-//         }
-        const qint64 begin = interval.begin();
-        const qint64 end = qMin(interval.end(), begin + m_batchSize - 1);
-        const KIMAP::ImapSet intervalToFetch(begin, end);
-        if (interval.end() > end) {
-            m_currentSet = KIMAP::ImapSet(end + 1, interval.end());
-        } else {
-            m_currentSet = KIMAP::ImapSet();
-        }
-        kDebug(5327) << "Fetching " << begin << " to " << end;
-        fetch->setSequenceSet(intervalToFetch);
-    } else {
-        kDebug(5327) << "Fetching all messages in one go.";
+    if (m_scope.changedSince != 0) {
+        kDebug(5327) << "Fetching all messages in one batch.";
         fetch->setSequenceSet(m_currentSet);
         m_currentSet = KIMAP::ImapSet();
+    } else {
+        KIMAP::ImapSet toFetch;
+        qint64 counter = 0;
+        KIMAP::ImapSet newSet;
+
+        //Take a chunk from the set
+        Q_FOREACH (const KIMAP::ImapInterval &interval, m_currentSet.intervals()) {
+            const qint64 wantedItems = m_batchSize - counter;
+            if (counter < m_batchSize) {
+                if (interval.size() <= wantedItems) {
+                    counter += interval.size();
+                    toFetch.add(interval);
+                } else {
+                    counter += wantedItems;
+                    toFetch.add(KIMAP::ImapInterval(interval.begin(), interval.begin() + wantedItems - 1));
+                    newSet.add(KIMAP::ImapInterval(interval.begin() + wantedItems, interval.end()));
+                }
+            } else {
+                newSet.add(interval);
+            }
+        }
+        kDebug(5327) << "Fetching " << toFetch.intervals().size() << " intervals";
+        fetch->setSequenceSet(toFetch);
+        m_currentSet = newSet;
     }
 
     fetch->setUidBased(m_uidBased);
@@ -585,6 +615,9 @@ void RetrieveItemsTask::retrieveItems(const KIMAP::ImapSet& set, const KIMAP::Fe
 
     m_batchFetcher = new BatchFetcher(resourceState()->messageHelper(), set, scope, batchSize(), m_session);
     m_batchFetcher->setUidBased(m_uidBasedFetch);
+    if (m_uidBasedFetch && set.intervals().size() == 1) {
+        m_batchFetcher->setSearchTerm(KIMAP::Term(KIMAP::Term::Uid, set));
+    }
     m_batchFetcher->setProperty("alreadyFetched", set.intervals().first().begin());
     connect(m_batchFetcher, SIGNAL(itemsRetrieved(Akonadi::Item::List)),
             this, SLOT(onItemsRetrieved(Akonadi::Item::List)));
@@ -645,7 +678,6 @@ void RetrieveItemsTask::onRetrievalDone(KJob *job)
     listFlagsForImapSet(KIMAP::ImapSet(1, alreadyFetchedBegin - 1));
 }
 
-
 void RetrieveItemsTask::listFlagsForImapSet(const KIMAP::ImapSet& set)
 {
   kDebug(5327) << "Listing flags " << set.intervals().first().begin() << set.intervals().first().end();
@@ -665,8 +697,11 @@ void RetrieveItemsTask::listFlagsForImapSet(const KIMAP::ImapSet& set)
       }
   }
 
-  m_batchFetcher = new BatchFetcher(resourceState()->messageHelper(), set, scope, batchSize(), m_session);
+  m_batchFetcher = new BatchFetcher(resourceState()->messageHelper(), set, scope, 50 * batchSize(), m_session);
   m_batchFetcher->setUidBased(m_uidBasedFetch);
+  if (m_uidBasedFetch && scope.changedSince == 0 && set.intervals().size() == 1) {
+      m_batchFetcher->setSearchTerm(KIMAP::Term(KIMAP::Term::Uid, set));
+  }
   connect(m_batchFetcher, SIGNAL(itemsRetrieved(Akonadi::Item::List)),
           this, SLOT(onItemsRetrieved(Akonadi::Item::List)));
   connect(m_batchFetcher, SIGNAL(result(KJob*)),
diff --git a/resources/imap/tests/testretrieveitemstask.cpp b/resources/imap/tests/testretrieveitemstask.cpp
index 7bf383b..9eb6624 100644
--- a/resources/imap/tests/testretrieveitemstask.cpp
+++ b/resources/imap/tests/testretrieveitemstask.cpp
@@ -60,7 +60,10 @@ private slots:
              << "S: * OK [ UIDVALIDITY 1149151135  ]"
              << "S: * OK [ UIDNEXT 9  ]"
              << "S: A000005 OK select done"
-             << "C: A000006 UID FETCH 1:9 (RFC822.SIZE INTERNALDATE "
+             << "C: A000006 UID SEARCH UID 1:9"
+             << "S: * SEARCH 1 2 3 4 5 6 7 8 9"
+             << "S: A000006 OK search done"
+             << "C: A000007 UID FETCH 1:9 (RFC822.SIZE INTERNALDATE "
                 "BODY.PEEK[HEADER] "
                 "FLAGS UID)"
              << "S: * 1 FETCH ( FLAGS (\\Seen) UID 7 INTERNALDATE \"29-Jun-2010 15:26:42 +0200\" "
@@ -70,7 +73,7 @@ private slots:
                 "Subject: Test Mail\r\n"
                 "\r\n"
                 " )"
-             << "S: A000006 OK fetch done";
+             << "S: A000007 OK fetch done";
 
     callNames.clear();
     callNames << "itemsRetrieved" << "applyCollectionChanges" << "itemsRetrievalDone" ;
@@ -101,7 +104,10 @@ private slots:
              << "S: * OK [ UIDVALIDITY 1149151135  ]"
              << "S: * OK [ UIDNEXT 9  ]"
              << "S: A000005 OK select done"
-             << "C: A000006 UID FETCH 1:9 (RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)"
+             << "C: A000006 UID SEARCH UID 1:9"
+             << "S: * SEARCH 1 2 3 4 5 6 7 8 9"
+             << "S: A000006 OK search done"
+             << "C: A000007 UID FETCH 1:9 (RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)"
              << "S: * 1 FETCH ( FLAGS (\\Seen) UID 7 INTERNALDATE \"29-Jun-2010 15:26:42 +0200\" "
                 "RFC822.SIZE 75 BODY[] {75}\r\n"
                 "From: Foo <foo at kde.org>\r\n"
@@ -110,7 +116,7 @@ private slots:
                 "\r\n"
                 "Test\r\n"
                 " )"
-             << "S: A000006 OK fetch done";
+             << "S: A000007 OK fetch done";
 
     callNames.clear();
     callNames << "itemsRetrieved" << "applyCollectionChanges" << "itemsRetrievalDone";
@@ -141,9 +147,12 @@ private slots:
              << "S: * OK [ UIDVALIDITY 1149151135  ]"
              << "S: * OK [ UIDNEXT 9  ]"
              << "S: A000005 OK select done"
-             << "C: A000006 UID FETCH 1:9 (FLAGS UID)"
+             << "C: A000006 UID SEARCH UID 1:9"
+             << "S: * SEARCH 1 2 3 4 5 6 7 8 9"
+             << "S: A000006 OK search done"
+             << "C: A000007 UID FETCH 1:9 (FLAGS UID)"
              << "S: * 1 FETCH ( FLAGS (\\Seen) UID 7 )"
-             << "S: A000006 OK fetch done";
+             << "S: A000007 OK fetch done";
 
     callNames.clear();
     callNames << "itemsRetrievedIncremental" << "applyCollectionChanges" << "itemsRetrievedIncremental" << "itemsRetrievalDone";
@@ -193,7 +202,10 @@ private slots:
              << "S: * OK [ UIDNEXT 9  ]"
              << "S: * OK [ HIGHESTMODSEQ 123456789 ]"
              << "S: A000005 OK select done"
-             << "C: A000006 UID FETCH 8:9 (RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)"
+             << "C: A000006 UID SEARCH UID 8:9"
+             << "S: * SEARCH 8 9"
+             << "S: A000006 OK search done"
+             << "C: A000007 UID FETCH 8:9 (RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)"
              << "S: * 4 FETCH ( FLAGS (\\Seen) UID 8 INTERNALDATE \"29-Jun-2010 15:26:42 +0200\" "
                 "RFC822.SIZE 75 BODY[] {75}\r\n"
                 "From: Foo <foo at kde.org>\r\n"
@@ -210,12 +222,15 @@ private slots:
                 "\r\n"
                 "Test\r\n"
                 " )"
-             << "S: A000006 OK fetch done"
-             << "C: A000007 UID FETCH 1:7 (FLAGS UID)"
+             << "S: A000007 OK fetch done"
+             << "C: A000008 UID SEARCH UID 1:7"
+             << "S: * SEARCH 1 2 3 4 5 6 7"
+             << "S: A000008 OK search done"
+             << "C: A000009 UID FETCH 1:7 (FLAGS UID)"
              << "S: * 1 FETCH"
              << "S: * 2 FETCH"
              << "S: * 3 FETCH"
-             << "S: A000007 OK fetch done";
+             << "S: A000009 OK fetch done";
 
     callNames.clear();
     callNames << "itemsRetrieved" << "applyCollectionChanges" << "itemsRetrievalDone";
@@ -304,9 +319,12 @@ private slots:
              << "S: * OK [ UIDNEXT 9 ]"
              << "S: * OK [ HIGHESTMODSEQ 123456789 ]"
              << "S: A000005 OK select done"
-             << "C: A000006 UID FETCH 1:9 (FLAGS UID)"
+             << "C: A000006 UID SEARCH UID 1:9"
+             << "S: * SEARCH 1 2 3 4 5 6 7 8 9"
+             << "S: A000006 OK search done"
+             << "C: A000007 UID FETCH 1:9 (FLAGS UID)"
              << "S: * 5 FETCH ( UID 8 FLAGS () )"
-             << "S: A000006 OK fetch done";
+             << "S: A000007 OK fetch done";
     callNames.clear();
     callNames << "itemsRetrievedIncremental" << "applyCollectionChanges" << "itemsRetrievedIncremental" << "itemsRetrievalDone";
 
@@ -335,7 +353,10 @@ private slots:
              << "S: * OK [ UIDVALIDITY 1149151135  ]"
              << "S: * OK [ UIDNEXT 9  ]"
              << "S: A000005 OK select done"
-             << "C: A000006 UID FETCH 1:9 (RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)"
+             << "C: A000006 UID SEARCH UID 1:9"
+             << "S: * SEARCH 1 2 3 4 5 6 7 8 9"
+             << "S: A000006 OK search done"
+             << "C: A000007 UID FETCH 1:9 (RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)"
              << "S: * 1 FETCH ( FLAGS (\\Seen) UID 2321 )"
              << "S: * 1 FETCH ( FLAGS (\\Seen) UID 2321 INTERNALDATE \"29-Jun-2010 15:26:42 +0200\" "
                 "RFC822.SIZE 75 BODY[] {75}\r\n"
@@ -345,7 +366,7 @@ private slots:
                 "\r\n"
                 "Test\r\n"
                 " )"
-             << "S: A000006 OK fetch done";
+             << "S: A000007 OK fetch done";
 
     callNames.clear();
     callNames << "itemsRetrieved" << "applyCollectionChanges" << "itemsRetrievalDone";
@@ -373,7 +394,11 @@ private slots:
              << "S: * OK [ UIDVALIDITY 1149151135  ]"
              << "S: * OK [ UIDNEXT 120  ]"
              << "S: A000005 OK select done"
-             << "C: A000006 UID FETCH 105:114 (RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)"
+             << "C: A000006 UID SEARCH UID 105:120"
+             //We asked for until 120 but only 119 is available (120 is uidnext)
+             << "S: * SEARCH 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119"
+             << "S: A000006 OK search done"
+             << "C: A000007 UID FETCH 105:114 (RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)"
              << "S: * 1 FETCH ( FLAGS (\\Seen) UID 105 INTERNALDATE \"29-Jun-2010 15:26:42 +0200\" "
                 "RFC822.SIZE 75 BODY[] {75}\r\n"
                 "From: Foo <foo at kde.org>\r\n"
@@ -383,8 +408,8 @@ private slots:
                 "Test\r\n"
                 " )"
               //9 more would follow but are excluded for clarity
-             << "S: A000006 OK fetch done"
-             << "C: A000007 UID FETCH 115:120 (RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)"
+             << "S: A000007 OK fetch done"
+             << "C: A000008 UID FETCH 115:119 (RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)"
              << "S: * 1 FETCH ( FLAGS (\\Seen) UID 115 INTERNALDATE \"29-Jun-2010 15:26:42 +0200\" "
                 "RFC822.SIZE 75 BODY[] {75}\r\n"
                 "From: Foo <foo at kde.org>\r\n"
@@ -394,18 +419,17 @@ private slots:
                 "Test\r\n"
                 " )"
               //4 more would follow but are excluded for clarity
-             << "S: A000007 OK fetch done"
-             << "C: A000008 UID FETCH 1:100 (FLAGS UID)"
-             << "S: * 1 FETCH ( FLAGS (\\Seen) UID 1 )"
-              //99 more would follow but are excluded for clarity
              << "S: A000008 OK fetch done"
-             << "C: A000009 UID FETCH 101:104 (FLAGS UID)"
-             << "S: * 1 FETCH ( FLAGS (\\Seen) UID 101 )"
+             << "C: A000009 UID SEARCH UID 1:104"
+             << "S: * SEARCH 1 2 99 100"
+             << "S: A000009 OK search done"
+             << "C: A000010 UID FETCH 1:2,99:100 (FLAGS UID)"
+             << "S: * 1 FETCH ( FLAGS (\\Seen) UID 1 )"
               //3 more would follow but are excluded for clarity
-             << "S: A000009 OK fetch done";
+             << "S: A000010 OK fetch done";
 
     callNames.clear();
-    callNames << "itemsRetrieved" << "itemsRetrieved" << "itemsRetrieved" << "itemsRetrieved" << "applyCollectionChanges" << "itemsRetrievalDone";
+    callNames << "itemsRetrieved" << "itemsRetrieved" << "itemsRetrieved" << "applyCollectionChanges" << "itemsRetrievalDone";
 
     QTest::newRow( "test batch processing" ) << collection << scenario << callNames;
 


commit 81f30e18c975f9cc44da7c904ed612df804200da
Author: Martin Steigerwald <martin at lichtvoll.de>
Date:   Sat May 3 16:53:15 2014 +0200

    Also do not sort directory entries in listCurrent and listNew in key cache:
    
    QDir sorts the list of directory entries unless specified otherwise.
    The last commit already disabled the sorting in maildir.cpp.
    
    This commit completes the work and makes the remaining sorting calls
    in the callgrind dumps at
    
    https://bugs.kde.org/show_bug.cgi?id=334218#c7
    
    go away completely as demonstrated in the callgrind dumps at:
    
    https://bugs.kde.org/show_bug.cgi?id=334218#c12
    
    Subjectively this has a huge impact on the performance of KMail with huge
    maildir. KMail hardly blocks anymore on folder changes and feels much more
    responsive now.
    
    Akonadi maildir resource went from hogging a Sandy Bridge core for minutes
    to not appearing using 100% of one core in even one averaged 10 second
    interval in atop. I hardly see it in atop at all anymore, except when
    accessing very large folders such as one with Linux Kernel mailing list
    and more than 245000 unread mails.
    
    Thanks to Sergio for pointing out the remaining sorting calls in the second
    callgrind dump and for help.
    
    Now the bottleneck appears to be MaildirResource::listRecursive but I do
    not know whether further optimization is necessary at this point.
    
    Both changes tested with kdepimlibs master as of today and KMail 4.12.4
    from Debian unstable packages with an enormous maildir including a
    folder for Linux Kernel mailing list with more than 245000 unread mails.
    
    CCBUG: 334218
    
    REVIEW: 117975
    
    DIGEST: Huge performance improvement for POP3 users with large maildirs.

diff --git a/resources/maildir/libmaildir/keycache.cpp b/resources/maildir/libmaildir/keycache.cpp
index f0af9c4..814ce6c 100644
--- a/resources/maildir/libmaildir/keycache.cpp
+++ b/resources/maildir/libmaildir/keycache.cpp
@@ -75,12 +75,14 @@ bool KeyCache::isNewKey( const QString& dir, const QString& key ) const
 QSet< QString > KeyCache::listNew( const QString& dir ) const
 {
     QDir d( dir + QString::fromLatin1( "/new" ) );
+    d.setSorting(QDir::NoSort);
     return d.entryList( QDir::Files ).toSet();
 }
 
 QSet< QString > KeyCache::listCurrent( const QString& dir ) const
 {
     QDir d( dir + QString::fromLatin1( "/cur"  ) );
+    d.setSorting(QDir::NoSort);
     return d.entryList( QDir::Files ).toSet();
 }
 


commit 1af2704a63c6641867eaf2b67976f5309a994cca
Author: Martin Steigerwald <martin at lichtvoll.de>
Date:   Sat May 3 16:52:01 2014 +0200

    Do not sort the directory entry list in listNew() and listCurrent():
    
    According to callgrind dumps at
    
    https://bugs.kde.org/show_bug.cgi?id=334218#c4
    
    QDir sorts the list of directory entries unless specified otherwise.
    
    After disabling the sorting the callgrind is quite different already:
    
    https://bugs.kde.org/show_bug.cgi?id=334218#c7
    
    This already helps shortening the time to synchronize folders visibly,
    but KMail gets still blocked for half a minute or more. There is another
    occurence of QDir entry sorting in keycache.cpp which the next commit
    will address.
    
    Thanks to Sergio and David for help and pointing out how to disable
    the sorting.
    
    CCBUG: 334218
    
    REVIEW: 117975
    
    DIGEST: Huge performance improvement for POP3 users with large maildir.

diff --git a/resources/maildir/libmaildir/maildir.cpp b/resources/maildir/libmaildir/maildir.cpp
index 9bd3802..36166ed 100644
--- a/resources/maildir/libmaildir/maildir.cpp
+++ b/resources/maildir/libmaildir/maildir.cpp
@@ -106,12 +106,14 @@ public:
     QStringList listNew() const
     {
         QDir d( path + QString::fromLatin1( "/new" ) );
+        d.setSorting(QDir::NoSort);
         return d.entryList( QDir::Files );
     }
 
     QStringList listCurrent() const
     {
         QDir d( path + QString::fromLatin1( "/cur" ) );
+        d.setSorting(QDir::NoSort);
         return d.entryList( QDir::Files );
     }
 


commit 0de7f0faef78b570b3930ec34f0320de7f2f055b
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Sun May 4 21:29:00 2014 +0200

    IMAP-Resource: Fixed stuck BatchFetcher
    
    The continuation request can be delivered while a fetch job is in progress
    if we deliver too many messages (which is likely with the uid based fetches
    where we can't predict the exact amount of fetched messages). In this
    case we need to remember the continuation request, and automatically proceed.
    
    This causes m_fetchedItemsInCurrentBatch to be off by the messages we delivered
    too many, but that's not a problem as it means we just deliver too many messages
    during every batch.
    
    BUG: 334269

diff --git a/resources/imap/retrieveitemstask.cpp b/resources/imap/retrieveitemstask.cpp
index 4e2fad9..65c0a66 100644
--- a/resources/imap/retrieveitemstask.cpp
+++ b/resources/imap/retrieveitemstask.cpp
@@ -78,6 +78,7 @@ private:
     int m_fetchedItemsInCurrentBatch;
     const MessageHelper::Ptr m_messageHelper;
     bool m_fetchInProgress;
+    bool m_continuationRequested;
 };
 
 BatchFetcher::BatchFetcher(MessageHelper::Ptr messageHelper, const KIMAP::ImapSet &set, const KIMAP::FetchJob::FetchScope& scope, int batchSize, KIMAP::Session* session)
@@ -89,7 +90,8 @@ BatchFetcher::BatchFetcher(MessageHelper::Ptr messageHelper, const KIMAP::ImapSe
     m_uidBased(false),
     m_fetchedItemsInCurrentBatch(0),
     m_messageHelper(messageHelper),
-    m_fetchInProgress(false)
+    m_fetchInProgress(false),
+    m_continuationRequested(false)
 {
 }
 
@@ -111,8 +113,10 @@ void BatchFetcher::fetchNextBatch()
 {
     if (m_fetchInProgress) {
         kWarning() << "fetchNextBatch called while fetch is in process";
+        m_continuationRequested = true;
         return;
     }
+    m_continuationRequested = false;
     Q_ASSERT(m_batchSize > 0);
     if (m_currentSet.isEmpty()) {
         kDebug(5327) << "fetch complete";
@@ -208,6 +212,12 @@ void BatchFetcher::onHeadersFetchDone( KJob *job )
         fetchNextBatch();
     } else {
         m_fetchedItemsInCurrentBatch = 0;
+        //Also fetch more if we already got a continuation request during the fetch.
+        //This can happen if we deliver too many items during a previous batch (after using )
+        //Note that m_fetchedItemsInCurrentBatch will be off by the items that we delivered already.
+        if (m_continuationRequested) {
+            fetchNextBatch();
+        }
     }
 }
 


commit a194425fc0a0ca65cc39e6202c6006177041e155
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Fri May 2 15:04:12 2014 +0200

    IMAP-Resource: Always fetch the collection before doing a RetrieveCollectionMetadataTask
    
    Otherwise the task will overwrite any meanwhile changed attributes with
    old values again.

diff --git a/resources/imap/imapresource.cpp b/resources/imap/imapresource.cpp
index 17462ec..feef108 100644
--- a/resources/imap/imapresource.cpp
+++ b/resources/imap/imapresource.cpp
@@ -431,7 +431,27 @@ void ImapResource::triggerCollectionExtraInfoJobs( const QVariant &collectionVar
   const Collection collection( collectionVariant.value<Collection>() );
   emit status( AgentBase::Running, i18nc( "@info:status", "Retrieving extra folder information for '%1'", collection.name() ) );
 
-  startTask(new RetrieveCollectionMetadataTask( createResourceState(TaskArguments(collection)), this ));
+  //The collection that we received is potentially outdated.
+  //Using it would overwrite attributes with old values.
+  //FIXME: because this is async and not part of the resourcetask, it can't be killed. ResourceBase should just provide an up-to date copy of the collection.
+  Akonadi::CollectionFetchJob *fetchJob = new Akonadi::CollectionFetchJob(collection, CollectionFetchJob::Base, this);
+  fetchJob->fetchScope().setAncestorRetrieval( CollectionFetchScope::All );
+  fetchJob->fetchScope().setIncludeStatistics( true );
+  connect(fetchJob, SIGNAL(result(KJob*)), this, SLOT(onMetadataCollectionFetchDone(KJob*)));
+}
+
+void ImapResource::onMetadataCollectionFetchDone(KJob *job)
+{
+  if (job->error()) {
+    kWarning() << "Failed to retrieve collection before RetrieveCollectionMetadataTask " << job->errorString();
+    cancelTask(i18n("Failed to collection metadata."));
+    return;
+  }
+
+  Akonadi::CollectionFetchJob *fetchJob = static_cast<Akonadi::CollectionFetchJob*>(job);
+  Q_ASSERT(fetchJob->collections().size() == 1);
+
+  startTask(new RetrieveCollectionMetadataTask( createResourceState(TaskArguments(fetchJob->collections().first())), this ));
 }
 
 void ImapResource::retrieveItems( const Collection &col )
diff --git a/resources/imap/imapresource.h b/resources/imap/imapresource.h
index ecf831d..fd4b4a3 100644
--- a/resources/imap/imapresource.h
+++ b/resources/imap/imapresource.h
@@ -125,6 +125,7 @@ private Q_SLOTS:
 
   void onIdleCollectionFetchDone( KJob *job );
   void onItemRetrievalCollectionFetchDone( KJob *job );
+  void onMetadataCollectionFetchDone( KJob *job );
 
   void onExpungeCollectionFetchDone( KJob *job );
   void triggerCollectionExpunge( const QVariant &collectionVariant );


commit e456e7721587e331c56680200170b4fa8492146b
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Fri May 2 14:05:53 2014 +0200

    IMAP-Resource: Don't use batchprocessing with changedsince.
    
    The returned amount of items is typically small, so one job vs. N jobs is
    much more efficient.

diff --git a/resources/imap/retrieveitemstask.cpp b/resources/imap/retrieveitemstask.cpp
index 5532af9..4e2fad9 100644
--- a/resources/imap/retrieveitemstask.cpp
+++ b/resources/imap/retrieveitemstask.cpp
@@ -122,7 +122,7 @@ void BatchFetcher::fetchNextBatch()
 
     KIMAP::FetchJob *fetch = new KIMAP::FetchJob(m_session);
     //In the most common case that we want optimized we use batch processing.
-    if (m_currentSet.intervals().size() == 1 && m_currentSet.intervals().first().hasDefinedEnd()) {
+    if (m_scope.changedSince == 0 && m_currentSet.intervals().size() == 1 && m_currentSet.intervals().first().hasDefinedEnd()) {
         const KIMAP::ImapInterval interval = m_currentSet.intervals().first();
         Q_ASSERT(interval.hasDefinedEnd());
         //Reverse fetching would be great, because it gives you the most relevant (recent) messages first,


commit 7e5b3810e04f57b83fe266d16b4e257e175042e3
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Fri May 2 17:08:29 2014 +0200

    IMAP-Resource: Improved debug output.
    
    With this debug output it should be possible to find problems in the ItemSync
    without recompiling.

diff --git a/resources/imap/retrieveitemstask.cpp b/resources/imap/retrieveitemstask.cpp
index d6f2bd6..5532af9 100644
--- a/resources/imap/retrieveitemstask.cpp
+++ b/resources/imap/retrieveitemstask.cpp
@@ -115,7 +115,7 @@ void BatchFetcher::fetchNextBatch()
     }
     Q_ASSERT(m_batchSize > 0);
     if (m_currentSet.isEmpty()) {
-        kDebug() << "fetch complete";
+        kDebug(5327) << "fetch complete";
         emitResult();
         return;
     }
@@ -147,10 +147,10 @@ void BatchFetcher::fetchNextBatch()
         } else {
             m_currentSet = KIMAP::ImapSet();
         }
-        kDebug() << "Fetching " << begin << " to " << end;
+        kDebug(5327) << "Fetching " << begin << " to " << end;
         fetch->setSequenceSet(intervalToFetch);
     } else {
-        kDebug() << "Fetching all messages in one go.";
+        kDebug(5327) << "Fetching all messages in one go.";
         fetch->setSequenceSet(m_currentSet);
         m_currentSet = KIMAP::ImapSet();
     }
@@ -484,6 +484,9 @@ void RetrieveItemsTask::onFinalSelectDone(KJob *job)
     const qint64 realMessageCount = col.statistics().count();
 
     kDebug(5327) << "Starting message retrieval. Elapsed(ms): " << m_time.elapsed();
+    kDebug(5327) << "MessageCount: " << messageCount << "Local message count: " << realMessageCount;
+    kDebug(5327) << "UidNext: " << nextUid << "Local UidNext: "<< oldNextUid;
+    kDebug(5327) << "HighestModSeq: " << highestModSeq << "Local HighestModSeq: "<< oldHighestModSeq;
 
     /*
     * A synchronization has 3 mandatory steps:
@@ -675,6 +678,7 @@ void RetrieveItemsTask::onFlagsFetchDone(KJob *job)
 void RetrieveItemsTask::taskComplete()
 {
     if (m_modifiedCollection.isValid()) {
+        kDebug(5327) << "Applying collection changes";
         applyCollectionChanges(m_modifiedCollection);
     }
     if (m_incremental) {


commit 108033321ba6522368d8c496037a39eaa66972bc
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Fri May 2 12:36:51 2014 +0200

    IMAP-Resource: Don't use a higher batchSize when fetching flags.
    
    Likely the result why fetchNextBatch is called too often.

diff --git a/resources/imap/retrieveitemstask.cpp b/resources/imap/retrieveitemstask.cpp
index a4e0063..d6f2bd6 100644
--- a/resources/imap/retrieveitemstask.cpp
+++ b/resources/imap/retrieveitemstask.cpp
@@ -652,7 +652,7 @@ void RetrieveItemsTask::listFlagsForImapSet(const KIMAP::ImapSet& set)
       }
   }
 
-  m_batchFetcher = new BatchFetcher(resourceState()->messageHelper(), set, scope, 10 * batchSize(), m_session);
+  m_batchFetcher = new BatchFetcher(resourceState()->messageHelper(), set, scope, batchSize(), m_session);
   m_batchFetcher->setUidBased(m_uidBasedFetch);
   connect(m_batchFetcher, SIGNAL(itemsRetrieved(Akonadi::Item::List)),
           this, SLOT(onItemsRetrieved(Akonadi::Item::List)));


commit f6696a8ef6d9bffac934cbc9a649ce36c2fefc09
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Fri May 2 12:34:57 2014 +0200

    IMAP-Resource: set correct expected message counts.
    
    Non-incremental updates retrieve *all* messages.

diff --git a/resources/imap/retrieveitemstask.cpp b/resources/imap/retrieveitemstask.cpp
index 39fc2db..a4e0063 100644
--- a/resources/imap/retrieveitemstask.cpp
+++ b/resources/imap/retrieveitemstask.cpp
@@ -528,13 +528,13 @@ void RetrieveItemsTask::onFinalSelectDone(KJob *job)
         //Fetch new messages, and then check for changed flags and removed messages
         //We can make an incremental update and use modseq.
         kDebug( 5327 ) << "Incrementally fetching new messages: UidNext: " << nextUid << " Old UidNext: " << oldNextUid << " message count " << messageCount << realMessageCount;
-        setTotalItems(messageCount);
+        setTotalItems(qMax(1ll, messageCount - realMessageCount));
         m_flagsChanged = !(highestModSeq == oldHighestModSeq);
         retrieveItems(KIMAP::ImapSet(qMax(1, oldNextUid), nextUid), scope, true, true);
     } else if (nextUid > oldNextUid) {
         //New messages are available. Fetch new messages, and then check for changed flags and removed messages
         kDebug( 5327 ) << "Fetching new messages: UidNext: " << nextUid << " Old UidNext: " << oldNextUid;
-        setTotalItems(qMax(1ll, messageCount - realMessageCount));
+        setTotalItems(messageCount);
         retrieveItems(KIMAP::ImapSet(qMax(1, oldNextUid), nextUid), scope, false, true);
     } else if (messageCount == realMessageCount && oldNextUid == nextUid) {
         //Optimization:


commit ec9640321addd05477c68d3f9260094e25848d7b
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Fri May 2 18:15:55 2014 +0200

    IMAP-Resource: modify collection in resource and check result.
    
    Conflicts:
    	resources/imap/imapresource.h

diff --git a/resources/imap/imapresource.cpp b/resources/imap/imapresource.cpp
index 9a38dbd..17462ec 100644
--- a/resources/imap/imapresource.cpp
+++ b/resources/imap/imapresource.cpp
@@ -32,6 +32,7 @@
 #include <klocale.h>
 #include <kstandarddirs.h>
 #include <KWindowSystem>
+#include <Akonadi/CollectionModifyJob>
 
 #include <akonadi/agentmanager.h>
 #include <akonadi/attributefactory.h>
@@ -764,3 +765,16 @@ void ImapResource::clearStatusMessage()
   emit status( Akonadi::AgentBase::Idle, QString() );
 }
 
+void ImapResource::modifyCollection(const Collection &col)
+{
+    Akonadi::CollectionModifyJob *modJob = new Akonadi::CollectionModifyJob(col, this);
+    connect(modJob, SIGNAL(result(KJob*)), this, SLOT(onCollectionModifyDone(KJob*)));
+}
+
+void ImapResource::onCollectionModifyDone(KJob* job)
+{
+    if (job->error()) {
+        kWarning() << "Failed to modify collection: " << job->errorString();
+    }
+}
+
diff --git a/resources/imap/imapresource.h b/resources/imap/imapresource.h
index 4aa0678..ecf831d 100644
--- a/resources/imap/imapresource.h
+++ b/resources/imap/imapresource.h
@@ -138,6 +138,8 @@ private Q_SLOTS:
   void clearStatusMessage();
 
   void onConfigurationDone( int result );
+  void onCollectionModifyDone( KJob *job );
+
 protected:
   //Starts and queues a task
   void startTask( ResourceTask *task );
@@ -148,6 +150,7 @@ private:
   friend class ResourceState;
 
   bool needsNetwork() const;
+  void modifyCollection(const Akonadi::Collection &);
 
   friend class ImapIdleManager;
 
diff --git a/resources/imap/resourcestate.cpp b/resources/imap/resourcestate.cpp
index ecb7732..3f09a21 100644
--- a/resources/imap/resourcestate.cpp
+++ b/resources/imap/resourcestate.cpp
@@ -28,7 +28,6 @@
 #include "noselectattribute.h"
 #include "timestampattribute.h"
 
-#include <akonadi/collectionmodifyjob.h>
 #include <akonadi/agentsearchinterface.h>
 #include <kmessagebox.h>
 
@@ -157,7 +156,7 @@ void ResourceState::setIdleCollection( const Akonadi::Collection &collection )
 
 void ResourceState::applyCollectionChanges( const Akonadi::Collection &collection )
 {
-  new Akonadi::CollectionModifyJob( collection );
+  m_resource->modifyCollection(collection);
 }
 
 void ResourceState::collectionAttributesRetrieved( const Akonadi::Collection &collection )
@@ -288,7 +287,7 @@ void ResourceState::cancelTask( const QString &errorString )
       Akonadi::Collection c = collection;
       c.removeAttribute<TimestampAttribute>();
 
-      new Akonadi::CollectionModifyJob( c );
+      m_resource->modifyCollection( c );
       newMailBoxes.removeAll( mailBoxForCollection( c ) );
     }
   }


commit ac98c9db15f9a8d2b47e16b66f5692fb7e100d3f
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Fri May 2 11:23:52 2014 +0200

    IMAP-Resource: Made sure the next batch is not fetched before the old one completes.
    
    Otherwise we're in danger of emitting result before all items were
    retrieved, resulting in local items getting removed if a non-incremental
    ItemSync was in progress.

diff --git a/resources/imap/retrieveitemstask.cpp b/resources/imap/retrieveitemstask.cpp
index d330a85..39fc2db 100644
--- a/resources/imap/retrieveitemstask.cpp
+++ b/resources/imap/retrieveitemstask.cpp
@@ -77,6 +77,7 @@ private:
     bool m_uidBased;
     int m_fetchedItemsInCurrentBatch;
     const MessageHelper::Ptr m_messageHelper;
+    bool m_fetchInProgress;
 };
 
 BatchFetcher::BatchFetcher(MessageHelper::Ptr messageHelper, const KIMAP::ImapSet &set, const KIMAP::FetchJob::FetchScope& scope, int batchSize, KIMAP::Session* session)
@@ -87,7 +88,8 @@ BatchFetcher::BatchFetcher(MessageHelper::Ptr messageHelper, const KIMAP::ImapSe
     m_batchSize(batchSize),
     m_uidBased(false),
     m_fetchedItemsInCurrentBatch(0),
-    m_messageHelper(messageHelper)
+    m_messageHelper(messageHelper),
+    m_fetchInProgress(false)
 {
 }
 
@@ -107,6 +109,10 @@ void BatchFetcher::start()
 
 void BatchFetcher::fetchNextBatch()
 {
+    if (m_fetchInProgress) {
+        kWarning() << "fetchNextBatch called while fetch is in process";
+        return;
+    }
     Q_ASSERT(m_batchSize > 0);
     if (m_currentSet.isEmpty()) {
         kDebug() << "fetch complete";
@@ -141,9 +147,10 @@ void BatchFetcher::fetchNextBatch()
         } else {
             m_currentSet = KIMAP::ImapSet();
         }
+        kDebug() << "Fetching " << begin << " to " << end;
         fetch->setSequenceSet(intervalToFetch);
     } else {
-        kDebug() << "Fetching all messages in one go";
+        kDebug() << "Fetching all messages in one go.";
         fetch->setSequenceSet(m_currentSet);
         m_currentSet = KIMAP::ImapSet();
     }
@@ -154,6 +161,7 @@ void BatchFetcher::fetchNextBatch()
             this, SLOT(onHeadersReceived(QString,QMap<qint64,qint64>,QMap<qint64,qint64>,QMap<qint64,KIMAP::MessageFlags>,QMap<qint64,KIMAP::MessagePtr>)) );
     connect(fetch, SIGNAL(result(KJob*)),
             this, SLOT(onHeadersFetchDone(KJob*)));
+    m_fetchInProgress = true;
     fetch->start();
 }
 
@@ -183,6 +191,7 @@ void BatchFetcher::onHeadersReceived(const QString &mailBox, const QMap<qint64,
 
 void BatchFetcher::onHeadersFetchDone( KJob *job )
 {
+    m_fetchInProgress = false;
     if (job->error()) {
         kWarning() << "Fetch job failed " << job->errorString();
         setError(KJob::UserDefinedError);


commit 94a56440c9f5181f944f1a6bc35de04fd6d5cf6d
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Thu May 1 15:32:28 2014 +0200

    IMAP-Resource: Always update the timestamp after trying to update collectionmetadata.
    
    We want to know when the metadata was last updated (or tried to), and
    not when it last changed.

diff --git a/resources/imap/retrievecollectionmetadatatask.cpp b/resources/imap/retrievecollectionmetadatatask.cpp
index 10bd9c2..da34087 100644
--- a/resources/imap/retrievecollectionmetadatatask.cpp
+++ b/resources/imap/retrievecollectionmetadatatask.cpp
@@ -41,7 +41,7 @@
 
 RetrieveCollectionMetadataTask::RetrieveCollectionMetadataTask( ResourceStateInterface::Ptr resource, QObject *parent )
   : ResourceTask( CancelIfNoSession, resource, parent ),
-    m_pendingMetaDataJobs( 0 ), m_collectionChanged( false )
+    m_pendingMetaDataJobs( 0 )
 {
 }
 
@@ -58,7 +58,7 @@ void RetrieveCollectionMetadataTask::doStart( KIMAP::Session *session )
     NoSelectAttribute* noselect = static_cast<NoSelectAttribute*>( collection().attribute( "noselect" ) );
     if ( noselect->noSelect() ) {
       kDebug( 5327 ) << "No Select folder";
-      collectionAttributesRetrieved(Akonadi::Collection());
+      endTaskIfNeeded();
       return;
     }
   }
@@ -113,7 +113,7 @@ void RetrieveCollectionMetadataTask::doStart( KIMAP::Session *session )
   // the server does not have any of the capabilities needed to get extra info, so this
   // step is done here
   if ( m_pendingMetaDataJobs == 0 ) {
-    collectionAttributesRetrieved(Akonadi::Collection());
+    endTaskIfNeeded();
   }
 }
 
@@ -139,7 +139,6 @@ void RetrieveCollectionMetadataTask::onGetMetaDataDone( KJob *job )
   const QMap<QByteArray, QByteArray> oldAnnotations = annotationsAttribute->annotations();
   if ( oldAnnotations != rawAnnotations ) {
     annotationsAttribute->setAnnotations( rawAnnotations );
-    m_collectionChanged = true;
   }
 
   endTaskIfNeeded();
@@ -160,7 +159,6 @@ void RetrieveCollectionMetadataTask::onGetAclDone( KJob *job )
   const QMap<QByteArray, KIMAP::Acl::Rights> oldRights = aclAttribute->rights();
   if ( oldRights != acl->allRights() ) {
     aclAttribute->setRights( acl->allRights() );
-    m_collectionChanged = true;
   }
 
   endTaskIfNeeded();
@@ -237,7 +235,6 @@ void RetrieveCollectionMetadataTask::onRightsReceived( KJob *job )
 
   if ( newRights != m_collection.rights() ) {
     m_collection.setRights( newRights );
-    m_collectionChanged = true;
   }
 
   endTaskIfNeeded();
@@ -282,7 +279,6 @@ void RetrieveCollectionMetadataTask::onQuotasReceived( KJob *job )
     || oldLimits != newLimits
     || oldUsages != newUsages ) {
     imapQuotaAttribute->setQuotas( newRoots, newLimits, newUsages );
-    m_collectionChanged = true;
   }
 
   // Store the collection Quota
@@ -295,7 +291,6 @@ void RetrieveCollectionMetadataTask::onQuotasReceived( KJob *job )
     || oldMax != newMax ) {
     quotaAttribute->setCurrentValue( newCurrent );
     quotaAttribute->setMaximumValue( newMax );
-    m_collectionChanged = true;
   }
 
   endTaskIfNeeded();
@@ -305,16 +300,10 @@ void RetrieveCollectionMetadataTask::endTaskIfNeeded()
 {
   if ( --m_pendingMetaDataJobs == 0 ) {
     // the others have ended, we're done, the next one can go
-    if ( m_collectionChanged ) {
-      const uint currentTimestamp = QDateTime::currentDateTime().toTime_t();
-
-      TimestampAttribute *attr = m_collection.attribute<TimestampAttribute>( Akonadi::Collection::AddIfMissing );
-      attr->setTimestamp( currentTimestamp );
-
-      collectionAttributesRetrieved( m_collection );
-      return;
-    }
+    const uint currentTimestamp = QDateTime::currentDateTime().toTime_t();
+    TimestampAttribute *attr = m_collection.attribute<TimestampAttribute>( Akonadi::Collection::AddIfMissing );
+    attr->setTimestamp( currentTimestamp );
 
-    collectionAttributesRetrieved(Akonadi::Collection());
+    collectionAttributesRetrieved( m_collection );
   }
 }
diff --git a/resources/imap/retrievecollectionmetadatatask.h b/resources/imap/retrievecollectionmetadatatask.h
index 2f10f1e..53eebfe 100644
--- a/resources/imap/retrievecollectionmetadatatask.h
+++ b/resources/imap/retrievecollectionmetadatatask.h
@@ -46,7 +46,6 @@ private:
 
   int m_pendingMetaDataJobs;
 
-  bool m_collectionChanged;
   Akonadi::Collection m_collection;
 };
 


commit cef9e9150460167487a3c2d93402801d8191f1cb
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Thu May 1 14:34:39 2014 +0200

    IMAP-Resource: collectionChanged needs to be available to subclasses as well.

diff --git a/resources/imap/imapresource.h b/resources/imap/imapresource.h
index d4f59fc..4aa0678 100644
--- a/resources/imap/imapresource.h
+++ b/resources/imap/imapresource.h
@@ -52,7 +52,7 @@ class ImapResource : public Akonadi::ResourceBase,
 {
   Q_OBJECT
   Q_CLASSINFO( "D-Bus Interface", "org.kde.Akonadi.Imap.Resource" )
-
+protected:
   using Akonadi::AgentBase::Observer::collectionChanged;
 
 public:


commit fbac04a75bd58bb45e04fc49eed1ff04653b35b1
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Thu May 1 13:49:38 2014 +0200

    IMAP-Resource: Fetch the collection before starting the RetrieveItemsTask.
    
    We rely on up-to date annotations, and the copy provided by ResourceBase could
    be outdated (it was fetched when the task was scheduled). We therefore always fetch
    the collection before starting the task.

diff --git a/resources/imap/imapresource.cpp b/resources/imap/imapresource.cpp
index ed4d2ec..9a38dbd 100644
--- a/resources/imap/imapresource.cpp
+++ b/resources/imap/imapresource.cpp
@@ -437,9 +437,29 @@ void ImapResource::retrieveItems( const Collection &col )
 {
   scheduleCustomTask( this, "triggerCollectionExtraInfoJobs", QVariant::fromValue( col ), ResourceBase::Append );
 
+  //The collection that we receive was fetched when the task was scheduled, it is therefore possible that it is outdated.
+  //We refetch the collection since we rely on up-to-date annotations.
+  //FIXME: because this is async and not part of the resourcetask, it can't be killed. ResourceBase should just provide an up-to date copy of the collection.
+  Akonadi::CollectionFetchJob *fetchJob = new Akonadi::CollectionFetchJob(col, CollectionFetchJob::Base, this);
+  fetchJob->fetchScope().setAncestorRetrieval( CollectionFetchScope::All );
+  fetchJob->fetchScope().setIncludeStatistics( true );
+  connect(fetchJob, SIGNAL(result(KJob*)), this, SLOT(onItemRetrievalCollectionFetchDone(KJob*)));
+}
+
+void ImapResource::onItemRetrievalCollectionFetchDone(KJob *job)
+{
+  if (job->error()) {
+    kWarning() << "Failed to retrieve collection before RetrieveItemsTask: " << job->errorString();
+    cancelTask(i18n("Failed to retrieve items."));
+    return;
+  }
+
+  Akonadi::CollectionFetchJob *fetchJob = static_cast<Akonadi::CollectionFetchJob*>(job);
+  Q_ASSERT(fetchJob->collections().size() == 1);
+
   setItemStreamingEnabled( true );
 
-  RetrieveItemsTask *task = new RetrieveItemsTask( createResourceState(TaskArguments(col)), this );
+  RetrieveItemsTask *task = new RetrieveItemsTask( createResourceState(TaskArguments(fetchJob->collections().first())), this);
   connect(task, SIGNAL(status(int,QString)), SIGNAL(status(int,QString)));
   connect(this, SIGNAL(retrieveNextItemSyncBatch(int)), task, SLOT(onReadyForNextBatch(int)));
   startTask(task);
diff --git a/resources/imap/imapresource.h b/resources/imap/imapresource.h
index 7ec18cd..d4f59fc 100644
--- a/resources/imap/imapresource.h
+++ b/resources/imap/imapresource.h
@@ -124,7 +124,7 @@ private Q_SLOTS:
 
 
   void onIdleCollectionFetchDone( KJob *job );
-
+  void onItemRetrievalCollectionFetchDone( KJob *job );
 
   void onExpungeCollectionFetchDone( KJob *job );
   void triggerCollectionExpunge( const QVariant &collectionVariant );


commit 564a5c94d200549895b1262e56e10d6aa0506126
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Thu May 1 13:29:25 2014 +0200

    IMAP-Resource: Allow subclasses to override the MessageHelper.
    
    By doing the translations directly in the BatchFetcher we can properly deal with
    translation failures.

diff --git a/resources/imap/messagehelper.cpp b/resources/imap/messagehelper.cpp
index 371b803..8697caf 100644
--- a/resources/imap/messagehelper.cpp
+++ b/resources/imap/messagehelper.cpp
@@ -22,7 +22,12 @@
 
 #include "resourcetask.h"
 
-Akonadi::Item MessageHelper::createItemFromMessage(KMime::Message::Ptr message, const qint64 uid, const qint64 size, const QList<QByteArray> &flags, const KIMAP::FetchJob::FetchScope &scope)
+MessageHelper::~MessageHelper()
+{
+
+}
+
+Akonadi::Item MessageHelper::createItemFromMessage(KMime::Message::Ptr message, const qint64 uid, const qint64 size, const QList<QByteArray> &flags, const KIMAP::FetchJob::FetchScope &scope, bool &ok) const
 {
     Akonadi::Item i;
     if (scope.mode == KIMAP::FetchJob::FetchScope::Flags) {
@@ -59,5 +64,6 @@ Akonadi::Item MessageHelper::createItemFromMessage(KMime::Message::Ptr message,
             i.setFlag(flag);
         }
     }
+    ok = true;
     return i;
 }
diff --git a/resources/imap/messagehelper.h b/resources/imap/messagehelper.h
index 4378991..96ce6b2 100644
--- a/resources/imap/messagehelper.h
+++ b/resources/imap/messagehelper.h
@@ -23,10 +23,14 @@
 #include <Akonadi/Item>
 #include <KMime/Message>
 #include <KIMAP/FetchJob>
+#include <boost/shared_ptr.hpp>
 
 class MessageHelper {
 public:
-    static Akonadi::Item createItemFromMessage(KMime::Message::Ptr message, const qint64 uid, const qint64 size, const QList<QByteArray> &flags, const KIMAP::FetchJob::FetchScope &scope);
+    typedef boost::shared_ptr<MessageHelper> Ptr;
+
+    virtual ~MessageHelper();
+    virtual Akonadi::Item createItemFromMessage(KMime::Message::Ptr message, const qint64 uid, const qint64 size, const QList<QByteArray> &flags, const KIMAP::FetchJob::FetchScope &scope, bool &ok) const;
 };
 
 #endif
\ No newline at end of file
diff --git a/resources/imap/resourcestate.cpp b/resources/imap/resourcestate.cpp
index 12b7907..ecb7732 100644
--- a/resources/imap/resourcestate.cpp
+++ b/resources/imap/resourcestate.cpp
@@ -366,3 +366,8 @@ int ResourceState::batchSize() const
 {
   return m_resource->itemSyncBatchSize();
 }
+
+MessageHelper::Ptr ResourceState::messageHelper() const
+{
+  return MessageHelper::Ptr(new MessageHelper());
+}
diff --git a/resources/imap/resourcestate.h b/resources/imap/resourcestate.h
index 8958a78..02c8ff8 100644
--- a/resources/imap/resourcestate.h
+++ b/resources/imap/resourcestate.h
@@ -23,6 +23,7 @@
 #define RESOURCESTATE_H
 
 #include "resourcestateinterface.h"
+#include "messagehelper.h"
 
 class ImapResource;
 
@@ -126,6 +127,7 @@ public:
 
   virtual int batchSize() const;
 
+  virtual MessageHelper::Ptr messageHelper() const;
 
 private:
   ImapResource *m_resource;
diff --git a/resources/imap/resourcestateinterface.h b/resources/imap/resourcestateinterface.h
index 145ba10..704038e 100644
--- a/resources/imap/resourcestateinterface.h
+++ b/resources/imap/resourcestateinterface.h
@@ -30,6 +30,7 @@
 #include <kimap/listjob.h>
 
 #include <boost/shared_ptr.hpp>
+#include "messagehelper.h"
 
 class ResourceStateInterface
 {
@@ -106,6 +107,9 @@ public:
   virtual void showInformationDialog( const QString &message, const QString &title, const QString &dontShowAgainName ) = 0;
 
   virtual int batchSize() const = 0;
+
+  virtual MessageHelper::Ptr messageHelper() const = 0;
+
 };
 
 #endif
diff --git a/resources/imap/resourcetask.cpp b/resources/imap/resourcetask.cpp
index eca857b..b4c4d36 100644
--- a/resources/imap/resourcetask.cpp
+++ b/resources/imap/resourcetask.cpp
@@ -486,3 +486,7 @@ int ResourceTask::batchSize() const
     return m_resource->batchSize();
 }
 
+ResourceStateInterface::Ptr ResourceTask::resourceState()
+{
+    return m_resource;
+}
diff --git a/resources/imap/resourcetask.h b/resources/imap/resourcetask.h
index 2c2589a..d012f17 100644
--- a/resources/imap/resourcetask.h
+++ b/resources/imap/resourcetask.h
@@ -138,6 +138,8 @@ protected:
 
   int batchSize() const;
 
+  ResourceStateInterface::Ptr resourceState();
+
 private:
 
   static QList<QByteArray> fromAkonadiFlags( const QList<QByteArray> &flags );
diff --git a/resources/imap/retrieveitemstask.cpp b/resources/imap/retrieveitemstask.cpp
index 17f5b39..d330a85 100644
--- a/resources/imap/retrieveitemstask.cpp
+++ b/resources/imap/retrieveitemstask.cpp
@@ -52,7 +52,7 @@
 class BatchFetcher : public KJob {
     Q_OBJECT
 public:
-    BatchFetcher(const KIMAP::ImapSet &set, const KIMAP::FetchJob::FetchScope &scope, int batchSize, KIMAP::Session *session);
+    BatchFetcher(MessageHelper::Ptr messageHelper, const KIMAP::ImapSet &set, const KIMAP::FetchJob::FetchScope &scope, int batchSize, KIMAP::Session *session);
     virtual ~BatchFetcher();
     virtual void start();
     void fetchNextBatch();
@@ -76,16 +76,18 @@ private:
     int m_batchSize;
     bool m_uidBased;
     int m_fetchedItemsInCurrentBatch;
+    const MessageHelper::Ptr m_messageHelper;
 };
 
-BatchFetcher::BatchFetcher(const KIMAP::ImapSet &set, const KIMAP::FetchJob::FetchScope& scope, int batchSize, KIMAP::Session* session)
+BatchFetcher::BatchFetcher(MessageHelper::Ptr messageHelper, const KIMAP::ImapSet &set, const KIMAP::FetchJob::FetchScope& scope, int batchSize, KIMAP::Session* session)
     : KJob(session),
     m_currentSet(set),
     m_scope(scope),
     m_session(session),
     m_batchSize(batchSize),
     m_uidBased(false),
-    m_fetchedItemsInCurrentBatch(0)
+    m_fetchedItemsInCurrentBatch(0),
+    m_messageHelper(messageHelper)
 {
 }
 
@@ -166,11 +168,17 @@ void BatchFetcher::onHeadersReceived(const QString &mailBox, const QMap<qint64,
     Akonadi::Item::List addedItems;
     foreach (qint64 number, uids.keys()) { //krazy:exclude=foreach
         //kDebug( 5327 ) << "Flags: " << i.flags();
-        m_fetchedItemsInCurrentBatch++;
-        addedItems << MessageHelper::createItemFromMessage(messages[number], uids[number], sizes[number], flags[number], fetch->scope());
+        bool ok;
+        const Akonadi::Item item = m_messageHelper->createItemFromMessage(messages[number], uids[number], sizes[number], flags[number], fetch->scope(), ok);
+        if (ok) {
+            m_fetchedItemsInCurrentBatch++;
+            addedItems << item;
+        }
     }
 //     kDebug() << addedItems.size();
-    emit itemsRetrieved(addedItems);
+    if (!addedItems.isEmpty()) {
+        emit itemsRetrieved(addedItems);
+    }
 }
 
 void BatchFetcher::onHeadersFetchDone( KJob *job )
@@ -185,6 +193,8 @@ void BatchFetcher::onHeadersFetchDone( KJob *job )
         emitResult();
         return;
     }
+    //Fetch more if we didn't deliver enough yet.
+    //This can happen because no message is in the fetched uid range, or if the translation failed
     if (m_fetchedItemsInCurrentBatch < m_batchSize) {
         fetchNextBatch();
     } else {
@@ -551,7 +561,7 @@ void RetrieveItemsTask::retrieveItems(const KIMAP::ImapSet& set, const KIMAP::Fe
     m_incremental = incremental;
     m_uidBasedFetch = uidBased;
 
-    m_batchFetcher = new BatchFetcher(set, scope, batchSize(), m_session);
+    m_batchFetcher = new BatchFetcher(resourceState()->messageHelper(), set, scope, batchSize(), m_session);
     m_batchFetcher->setUidBased(m_uidBasedFetch);
     m_batchFetcher->setProperty("alreadyFetched", set.intervals().first().begin());
     connect(m_batchFetcher, SIGNAL(itemsRetrieved(Akonadi::Item::List)),
@@ -633,7 +643,7 @@ void RetrieveItemsTask::listFlagsForImapSet(const KIMAP::ImapSet& set)
       }
   }
 
-  m_batchFetcher = new BatchFetcher(set, scope, 10 * batchSize(), m_session);
+  m_batchFetcher = new BatchFetcher(resourceState()->messageHelper(), set, scope, 10 * batchSize(), m_session);
   m_batchFetcher->setUidBased(m_uidBasedFetch);
   connect(m_batchFetcher, SIGNAL(itemsRetrieved(Akonadi::Item::List)),
           this, SLOT(onItemsRetrieved(Akonadi::Item::List)));
diff --git a/resources/imap/retrieveitemtask.cpp b/resources/imap/retrieveitemtask.cpp
index 58c82d5..0062c63 100644
--- a/resources/imap/retrieveitemtask.cpp
+++ b/resources/imap/retrieveitemtask.cpp
@@ -107,7 +107,13 @@ void RetrieveItemTask::onMessagesReceived( const QString &mailBox, const QMap<qi
   Q_ASSERT( item().isValid() );
 
   const qint64 number = uids.keys().first();
-  const Akonadi::Item remoteItem = MessageHelper::createItemFromMessage(messages[number], uids[number], 0, QList<QByteArray>(), fetch->scope());
+  bool ok;
+  const Akonadi::Item remoteItem = resourceState()->messageHelper()->createItemFromMessage(messages[number], uids[number], 0, QList<QByteArray>(), fetch->scope(), ok);
+  if (!ok) {
+    kWarning() << "Failed to retrieve message " << uids[number];
+    cancelTask( i18n( "No message retrieved, failed to read the message." ) );
+    return;
+  }
   i.setMimeType(remoteItem.mimeType());
   i.setPayload(remoteItem.payload<KMime::Message::Ptr>());
   foreach (const QByteArray &flag, remoteItem.flags()) {
diff --git a/resources/imap/tests/dummyresourcestate.cpp b/resources/imap/tests/dummyresourcestate.cpp
index e6abe25..b6d6e73 100644
--- a/resources/imap/tests/dummyresourcestate.cpp
+++ b/resources/imap/tests/dummyresourcestate.cpp
@@ -345,3 +345,8 @@ int DummyResourceState::batchSize() const
 {
   return 10;
 }
+
+MessageHelper::Ptr DummyResourceState::messageHelper() const
+{
+  return MessageHelper::Ptr(new MessageHelper());
+}
diff --git a/resources/imap/tests/dummyresourcestate.h b/resources/imap/tests/dummyresourcestate.h
index ff47b32..9e77a74 100644
--- a/resources/imap/tests/dummyresourcestate.h
+++ b/resources/imap/tests/dummyresourcestate.h
@@ -123,6 +123,8 @@ public:
 
   virtual int batchSize() const;
 
+  virtual MessageHelper::Ptr messageHelper() const;
+
   QList< QPair<QByteArray, QVariant> > calls() const;
 
 private:


commit e53cb42fc47628d0b4fe4ba99e9535bf5090d5ef
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Thu May 1 12:11:45 2014 +0200

    IMAP-Resource: Detect inconsistency in local cache and refetch.

diff --git a/resources/imap/retrieveitemstask.cpp b/resources/imap/retrieveitemstask.cpp
index ebed9c1..17f5b39 100644
--- a/resources/imap/retrieveitemstask.cpp
+++ b/resources/imap/retrieveitemstask.cpp
@@ -526,6 +526,14 @@ void RetrieveItemsTask::onFinalSelectDone(KJob *job)
         m_flagsChanged = !(highestModSeq == oldHighestModSeq);
         setTotalItems(messageCount);
         listFlagsForImapSet(KIMAP::ImapSet(1, nextUid));
+    } else if (messageCount > realMessageCount) {
+        //Error recovery:
+        //We didn't detect any new messages based on the uid, but according to the message count there are new ones.
+        //Our local cache is invalid and has to be refetched.
+        kWarning() << "Detected inconsistency in local cache, we're missing some messages. Server: " << messageCount << " Local: "<< realMessageCount;
+        kWarning() << "Refetching complete mailbox.";
+        setTotalItems(messageCount);
+        retrieveItems(KIMAP::ImapSet(1, nextUid), scope, false, true);
     } else {
         //Shortcut:
         //No new messages are available. Directly check for changed flags and removed messages.


commit 2a2113de3065902a3e5b6bafa51b4961fc6a772e
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Wed Apr 30 13:05:59 2014 +0200

    IMAP-Resource: Fix AddItemTask
    
    During the refactoring I accidentally changed how we pass the collection
    to the task, which caused all AddItemTasks to fail. This is fixed now,
    and we're also printing all errors as we should.

diff --git a/resources/imap/additemtask.cpp b/resources/imap/additemtask.cpp
index 5a81605..8be675d 100644
--- a/resources/imap/additemtask.cpp
+++ b/resources/imap/additemtask.cpp
@@ -54,6 +54,9 @@ void AddItemTask::doStart( KIMAP::Session *session )
   }
 
   const QString mailBox = mailBoxForCollection( collection() );
+  if ( mailBox.isEmpty() ) {
+    kWarning() << "Trying to append message to invalid mailbox, this will fail. Id: " << parentCollection().id();
+  }
 
   kDebug( 5327 ) << "Got notification about item added for local id " << item().id() << " and remote id " << item().remoteId();
 
@@ -76,6 +79,7 @@ void AddItemTask::onAppendMessageDone( KJob *job )
   KIMAP::AppendJob *append = qobject_cast<KIMAP::AppendJob*>( job );
 
   if ( append->error() ) {
+    kWarning() << append->errorString();
     cancelTask( append->errorString() );
     return;
   }
@@ -109,6 +113,7 @@ void AddItemTask::onAppendMessageDone( KJob *job )
 void AddItemTask::onPreSearchSelectDone( KJob *job )
 {
   if ( job->error() ) {
+    kWarning() << job->errorString();
     cancelTask( job->errorString() );
   } else {
     KIMAP::SelectJob *select = static_cast<KIMAP::SelectJob*>( job );
@@ -151,6 +156,7 @@ void AddItemTask::triggerSearchJob( KIMAP::Session *session )
 void AddItemTask::onSearchDone( KJob *job )
 {
   if ( job->error() ) {
+    kWarning() << job->errorString();
     cancelTask( job->errorString() );
     return;
   }
diff --git a/resources/imap/resourcestate.h b/resources/imap/resourcestate.h
index 04bc0c6..8958a78 100644
--- a/resources/imap/resourcestate.h
+++ b/resources/imap/resourcestate.h
@@ -29,7 +29,7 @@ class ImapResource;
 struct TaskArguments {
     TaskArguments(){}
     TaskArguments(const Akonadi::Item &_item): items(Akonadi::Item::List() << _item) {}
-    TaskArguments(const Akonadi::Item &_item, const Akonadi::Collection &_parentCollection): items(Akonadi::Item::List() << _item), parentCollection(_parentCollection) {}
+    TaskArguments(const Akonadi::Item &_item, const Akonadi::Collection &_collection): collection(_collection), items(Akonadi::Item::List() << _item) {}
     TaskArguments(const Akonadi::Item &_item, const QSet<QByteArray> &_parts): items(Akonadi::Item::List() << _item), parts(_parts) {}
     TaskArguments(const Akonadi::Item::List &_items): items(_items) {}
     TaskArguments(const Akonadi::Item::List &_items, const QSet<QByteArray> &_addedFlags, const QSet<QByteArray> &_removedFlags): items(_items), addedFlags(_addedFlags), removedFlags(_removedFlags) {}
@@ -40,7 +40,7 @@ struct TaskArguments {
     TaskArguments(const Akonadi::Collection &_collection, const QSet<QByteArray> &_parts): collection(_collection), parts(_parts){}
     Akonadi::Collection collection;
     Akonadi::Item::List items;
-    Akonadi::Collection parentCollection;
+    Akonadi::Collection parentCollection; //only used as parent of a collection
     Akonadi::Collection sourceCollection;
     Akonadi::Collection targetCollection;
     QSet<QByteArray> parts;


commit 2ad7c165244e80164d6406f8873c195698c59bd2
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Wed Apr 30 12:35:48 2014 +0200

    IMAP-Resource: Adapted tests to batch processing.

diff --git a/resources/imap/tests/testretrieveitemstask.cpp b/resources/imap/tests/testretrieveitemstask.cpp
index 24de458..7bf383b 100644
--- a/resources/imap/tests/testretrieveitemstask.cpp
+++ b/resources/imap/tests/testretrieveitemstask.cpp
@@ -58,12 +58,12 @@ private slots:
              << "S: * 1 EXISTS"
              << "S: * 0 RECENT"
              << "S: * OK [ UIDVALIDITY 1149151135  ]"
-             << "S: * OK [ UIDNEXT 2471  ]"
+             << "S: * OK [ UIDNEXT 9  ]"
              << "S: A000005 OK select done"
-             << "C: A000006 UID FETCH 1:2471 (RFC822.SIZE INTERNALDATE "
+             << "C: A000006 UID FETCH 1:9 (RFC822.SIZE INTERNALDATE "
                 "BODY.PEEK[HEADER] "
                 "FLAGS UID)"
-             << "S: * 1 FETCH ( FLAGS (\\Seen) UID 2321 INTERNALDATE \"29-Jun-2010 15:26:42 +0200\" "
+             << "S: * 1 FETCH ( FLAGS (\\Seen) UID 7 INTERNALDATE \"29-Jun-2010 15:26:42 +0200\" "
                 "RFC822.SIZE 75 BODY[HEADER] {69}\r\n"
                 "From: Foo <foo at kde.org>\r\n"
                 "To: Bar <bar at kde.org>\r\n"
@@ -99,10 +99,10 @@ private slots:
              << "S: * 1 EXISTS"
              << "S: * 0 RECENT"
              << "S: * OK [ UIDVALIDITY 1149151135  ]"
-             << "S: * OK [ UIDNEXT 2471  ]"
+             << "S: * OK [ UIDNEXT 9  ]"
              << "S: A000005 OK select done"
-             << "C: A000006 UID FETCH 1:2471 (RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)"
-             << "S: * 1 FETCH ( FLAGS (\\Seen) UID 2321 INTERNALDATE \"29-Jun-2010 15:26:42 +0200\" "
+             << "C: A000006 UID FETCH 1:9 (RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)"
+             << "S: * 1 FETCH ( FLAGS (\\Seen) UID 7 INTERNALDATE \"29-Jun-2010 15:26:42 +0200\" "
                 "RFC822.SIZE 75 BODY[] {75}\r\n"
                 "From: Foo <foo at kde.org>\r\n"
                 "To: Bar <bar at kde.org>\r\n"
@@ -123,7 +123,7 @@ private slots:
 
     collection = createCollectionChain( QLatin1String("/INBOX/Foo") );
     collection.attribute<UidValidityAttribute>(Akonadi::Entity::AddIfMissing)->setUidValidity(1149151135);
-    collection.attribute<UidNextAttribute>( Akonadi::Collection::AddIfMissing )->setUidNext(2471);
+    collection.attribute<UidNextAttribute>( Akonadi::Collection::AddIfMissing )->setUidNext(9);
     collection.setCachePolicy( policy );
     collection.setStatistics( stats );
 
@@ -139,10 +139,10 @@ private slots:
              << "S: * 1 EXISTS"
              << "S: * 0 RECENT"
              << "S: * OK [ UIDVALIDITY 1149151135  ]"
-             << "S: * OK [ UIDNEXT 2471  ]"
+             << "S: * OK [ UIDNEXT 9  ]"
              << "S: A000005 OK select done"
-             << "C: A000006 UID FETCH 1:2471 (FLAGS UID)"
-             << "S: * 1 FETCH ( FLAGS (\\Seen) UID 2321 )"
+             << "C: A000006 UID FETCH 1:9 (FLAGS UID)"
+             << "S: * 1 FETCH ( FLAGS (\\Seen) UID 7 )"
              << "S: A000006 OK fetch done";
 
     callNames.clear();
@@ -167,7 +167,7 @@ private slots:
              << "S: * 0 EXISTS"
              << "S: * 0 RECENT"
              << "S: * OK [ UIDVALIDITY 1149151135  ]"
-             << "S: * OK [ UIDNEXT 2471  ]"
+             << "S: * OK [ UIDNEXT 9  ]"
              << "S: A000005 OK select done";
 
     callNames.clear();
@@ -175,7 +175,7 @@ private slots:
 
     QTest::newRow( "third listing, full sync, empty folder" ) << collection << scenario << callNames;
 
-    collection.attribute<UidNextAttribute>( Akonadi::Collection::AddIfMissing )->setUidNext( 2470 );
+    collection.attribute<UidNextAttribute>( Akonadi::Collection::AddIfMissing )->setUidNext( 8 );
     stats.setCount( 5 );
     collection.setStatistics( stats );
     scenario.clear();
@@ -190,11 +190,11 @@ private slots:
              << "S: * 5 EXISTS"
              << "S: * 0 RECENT"
              << "S: * OK [ UIDVALIDITY 1149151135  ]"
-             << "S: * OK [ UIDNEXT 2471  ]"
+             << "S: * OK [ UIDNEXT 9  ]"
              << "S: * OK [ HIGHESTMODSEQ 123456789 ]"
              << "S: A000005 OK select done"
-             << "C: A000006 UID FETCH 2470:2471 (RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)"
-             << "S: * 4 FETCH ( FLAGS (\\Seen) UID 2470 INTERNALDATE \"29-Jun-2010 15:26:42 +0200\" "
+             << "C: A000006 UID FETCH 8:9 (RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)"
+             << "S: * 4 FETCH ( FLAGS (\\Seen) UID 8 INTERNALDATE \"29-Jun-2010 15:26:42 +0200\" "
                 "RFC822.SIZE 75 BODY[] {75}\r\n"
                 "From: Foo <foo at kde.org>\r\n"
                 "To: Bar <bar at kde.org>\r\n"
@@ -202,7 +202,7 @@ private slots:
                 "\r\n"
                 "Test\r\n"
                 " )"
-             << "S: * 5 FETCH ( FLAGS (\\Seen) UID 2471 INTERNALDATE \"29-Jun-2010 15:26:42 +0200\" "
+             << "S: * 5 FETCH ( FLAGS (\\Seen) UID 9 INTERNALDATE \"29-Jun-2010 15:26:42 +0200\" "
                 "RFC822.SIZE 75 BODY[] {75}\r\n"
                 "From: Foo <foo at kde.org>\r\n"
                 "To: Bar <bar at kde.org>\r\n"
@@ -211,7 +211,7 @@ private slots:
                 "Test\r\n"
                 " )"
              << "S: A000006 OK fetch done"
-             << "C: A000007 UID FETCH 1:2469 (FLAGS UID)"
+             << "C: A000007 UID FETCH 1:7 (FLAGS UID)"
              << "S: * 1 FETCH"
              << "S: * 2 FETCH"
              << "S: * 3 FETCH"
@@ -226,7 +226,7 @@ private slots:
     collection = createCollectionChain(QLatin1String("/INBOX/Foo") );
     collection.attribute<UidValidityAttribute>(Akonadi::Entity::AddIfMissing)->setUidValidity(1149151135);
     collection.setCachePolicy( policy );
-    collection.attribute<UidNextAttribute>( Akonadi::Collection::AddIfMissing )->setUidNext( 2471 );
+    collection.attribute<UidNextAttribute>( Akonadi::Collection::AddIfMissing )->setUidNext( 9 );
     collection.attribute<HighestModSeqAttribute>( Akonadi::Entity::AddIfMissing )->setHighestModSeq( 123456789 );
     stats.setCount( 5 );
     collection.setStatistics( stats );
@@ -242,7 +242,7 @@ private slots:
              << "S: * 5 EXISTS"
              << "S: * 0 RECENT"
              << "S: * OK [ UIDVALIDITY 1149151135 ]"
-             << "S: * OK [ UIDNEXT 2471 ]"
+             << "S: * OK [ UIDNEXT 9 ]"
              << "S: * OK [ HIGHESTMODSEQ 123456789 ]"
              << "S: A000005 OK select done";
     callNames.clear();
@@ -254,7 +254,7 @@ private slots:
     collection = createCollectionChain(QLatin1String("/INBOX/Foo") );
     collection.attribute<UidValidityAttribute>(Akonadi::Entity::AddIfMissing)->setUidValidity(1149151135);
     collection.setCachePolicy( policy );
-    collection.attribute<UidNextAttribute>( Akonadi::Collection::AddIfMissing )->setUidNext( 2471 );
+    collection.attribute<UidNextAttribute>( Akonadi::Collection::AddIfMissing )->setUidNext( 9 );
     collection.attribute<HighestModSeqAttribute>( Akonadi::Entity::AddIfMissing )->setHighestModSeq( 123456788 );
     stats.setCount( 5 );
     collection.setStatistics( stats );
@@ -270,11 +270,11 @@ private slots:
              << "S: * 5 EXISTS"
              << "S: * 0 RECENT"
              << "S: * OK [ UIDVALIDITY 1149151135 ]"
-             << "S: * OK [ UIDNEXT 2471 ]"
+             << "S: * OK [ UIDNEXT 9 ]"
              << "S: * OK [ HIGHESTMODSEQ 123456789 ]"
              << "S: A000005 OK select done"
-             << "C: A000006 UID FETCH 1:2471 (FLAGS UID) (CHANGEDSINCE 123456788)"
-             << "S: * 5 FETCH ( UID 2470 FLAGS () )"
+             << "C: A000006 UID FETCH 1:9 (FLAGS UID) (CHANGEDSINCE 123456788)"
+             << "S: * 5 FETCH ( UID 8 FLAGS () )"
              << "S: A000006 OK fetch done";
     callNames.clear();
     callNames << "itemsRetrievedIncremental" << "applyCollectionChanges" << "itemsRetrievedIncremental" << "itemsRetrievalDone";
@@ -285,7 +285,7 @@ private slots:
     collection = createCollectionChain(QLatin1String("/INBOX/Foo") );
     collection.setCachePolicy( policy );
     collection.attribute<UidValidityAttribute>(Akonadi::Entity::AddIfMissing)->setUidValidity(1149151135);
-    collection.attribute<UidNextAttribute>( Akonadi::Collection::AddIfMissing )->setUidNext( 2471 );
+    collection.attribute<UidNextAttribute>( Akonadi::Collection::AddIfMissing )->setUidNext( 9 );
     collection.attribute<HighestModSeqAttribute>( Akonadi::Entity::AddIfMissing )->setHighestModSeq( 123456788 );
     stats.setCount( 5 );
     collection.setStatistics( stats );
@@ -301,11 +301,11 @@ private slots:
              << "S: * 5 EXISTS"
              << "S: * 0 RECENT"
              << "S: * OK [ UIDVALIDITY 1149151135 ]"
-             << "S: * OK [ UIDNEXT 2471 ]"
+             << "S: * OK [ UIDNEXT 9 ]"
              << "S: * OK [ HIGHESTMODSEQ 123456789 ]"
              << "S: A000005 OK select done"
-             << "C: A000006 UID FETCH 1:2471 (FLAGS UID)"
-             << "S: * 5 FETCH ( UID 2470 FLAGS () )"
+             << "C: A000006 UID FETCH 1:9 (FLAGS UID)"
+             << "S: * 5 FETCH ( UID 8 FLAGS () )"
              << "S: A000006 OK fetch done";
     callNames.clear();
     callNames << "itemsRetrievedIncremental" << "applyCollectionChanges" << "itemsRetrievedIncremental" << "itemsRetrievalDone";
@@ -315,6 +315,7 @@ private slots:
 
 
     collection = createCollectionChain( QLatin1String("/INBOX/Foo") );
+    collection.attribute<UidNextAttribute>( Akonadi::Collection::AddIfMissing )->setUidNext( 9 );
     collection.attribute<UidValidityAttribute>(Akonadi::Entity::AddIfMissing)->setUidValidity(3);
     collection.setCachePolicy( policy );
     stats.setCount( 1 );
@@ -332,9 +333,9 @@ private slots:
              << "S: * 1 EXISTS"
              << "S: * 0 RECENT"
              << "S: * OK [ UIDVALIDITY 1149151135  ]"
-             << "S: * OK [ UIDNEXT 2471  ]"
+             << "S: * OK [ UIDNEXT 9  ]"
              << "S: A000005 OK select done"
-             << "C: A000006 UID FETCH 1:2471 (RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)"
+             << "C: A000006 UID FETCH 1:9 (RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)"
              << "S: * 1 FETCH ( FLAGS (\\Seen) UID 2321 )"
              << "S: * 1 FETCH ( FLAGS (\\Seen) UID 2321 INTERNALDATE \"29-Jun-2010 15:26:42 +0200\" "
                 "RFC822.SIZE 75 BODY[] {75}\r\n"
@@ -351,6 +352,63 @@ private slots:
 
     QTest::newRow( "uidvalidity changed" ) << collection << scenario << callNames;
 
+    collection = createCollectionChain( QLatin1String("/INBOX/Foo") );
+    collection.attribute<UidNextAttribute>(Akonadi::Collection::AddIfMissing)->setUidNext(105);
+    collection.attribute<UidValidityAttribute>(Akonadi::Entity::AddIfMissing)->setUidValidity(1149151135);
+    collection.setCachePolicy( policy );
+    stats.setCount(15);
+    collection.setStatistics( stats );
+
+    scenario.clear();
+    scenario << defaultPoolConnectionScenario()
+             << "C: A000003 SELECT \"INBOX/Foo\""
+             << "S: A000003 OK select done"
+             << "C: A000004 EXPUNGE"
+             << "S: A000004 OK expunge done"
+             << "C: A000005 SELECT \"INBOX/Foo\""
+             << "S: * FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)"
+             << "S: * OK [ PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen) ]"
+             << "S: * 120 EXISTS"
+             << "S: * 0 RECENT"
+             << "S: * OK [ UIDVALIDITY 1149151135  ]"
+             << "S: * OK [ UIDNEXT 120  ]"
+             << "S: A000005 OK select done"
+             << "C: A000006 UID FETCH 105:114 (RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)"
+             << "S: * 1 FETCH ( FLAGS (\\Seen) UID 105 INTERNALDATE \"29-Jun-2010 15:26:42 +0200\" "
+                "RFC822.SIZE 75 BODY[] {75}\r\n"
+                "From: Foo <foo at kde.org>\r\n"
+                "To: Bar <bar at kde.org>\r\n"
+                "Subject: Test Mail\r\n"
+                "\r\n"
+                "Test\r\n"
+                " )"
+              //9 more would follow but are excluded for clarity
+             << "S: A000006 OK fetch done"
+             << "C: A000007 UID FETCH 115:120 (RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)"
+             << "S: * 1 FETCH ( FLAGS (\\Seen) UID 115 INTERNALDATE \"29-Jun-2010 15:26:42 +0200\" "
+                "RFC822.SIZE 75 BODY[] {75}\r\n"
+                "From: Foo <foo at kde.org>\r\n"
+                "To: Bar <bar at kde.org>\r\n"
+                "Subject: Test Mail\r\n"
+                "\r\n"
+                "Test\r\n"
+                " )"
+              //4 more would follow but are excluded for clarity
+             << "S: A000007 OK fetch done"
+             << "C: A000008 UID FETCH 1:100 (FLAGS UID)"
+             << "S: * 1 FETCH ( FLAGS (\\Seen) UID 1 )"
+              //99 more would follow but are excluded for clarity
+             << "S: A000008 OK fetch done"
+             << "C: A000009 UID FETCH 101:104 (FLAGS UID)"
+             << "S: * 1 FETCH ( FLAGS (\\Seen) UID 101 )"
+              //3 more would follow but are excluded for clarity
+             << "S: A000009 OK fetch done";
+
+    callNames.clear();
+    callNames << "itemsRetrieved" << "itemsRetrieved" << "itemsRetrieved" << "itemsRetrieved" << "applyCollectionChanges" << "itemsRetrievalDone";
+
+    QTest::newRow( "test batch processing" ) << collection << scenario << callNames;
+
   }
 
   void shouldIntrospectCollection()


commit 84207d9835a62942d41dcf44190702a0b4d5f97b
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Wed Apr 30 12:07:07 2014 +0200

    IMAP-Resource: Only try to expunge if we have sufficient rights.

diff --git a/resources/imap/retrieveitemstask.cpp b/resources/imap/retrieveitemstask.cpp
index b2be03f..ebed9c1 100644
--- a/resources/imap/retrieveitemstask.cpp
+++ b/resources/imap/retrieveitemstask.cpp
@@ -283,7 +283,8 @@ void RetrieveItemsTask::startRetrievalTasks()
     m_time.start();
 
     // Now is the right time to expunge the messages marked \\Deleted from this mailbox.
-    if (isAutomaticExpungeEnabled()) {
+    // We assume that we can only expunge if we can delete items (correct would be to check for "e" ACL right).
+    if (isAutomaticExpungeEnabled() && (collection().rights() & Akonadi::Collection::CanDeleteItem)) {
         if (m_session->selectedMailBox() != mailBox) {
             triggerPreExpungeSelect(mailBox);
         } else {


commit d3b8cf3213b5516fefa9191b4f759ea48e3f0687
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Wed Apr 30 11:49:41 2014 +0200

    IMAP-Resource: Consistent coding style for RetrieveItemsTask.
    
    The whole file was rewritten anyways.

diff --git a/resources/imap/retrieveitemstask.cpp b/resources/imap/retrieveitemstask.cpp
index c3043d9..b2be03f 100644
--- a/resources/imap/retrieveitemstask.cpp
+++ b/resources/imap/retrieveitemstask.cpp
@@ -31,7 +31,6 @@
 
 #include <akonadi/cachepolicy.h>
 #include <akonadi/collectionstatistics.h>
-#include <akonadi/kmime/messageflags.h>
 #include <akonadi/kmime/messageparts.h>
 #include <akonadi/agentbase.h>
 #include <akonadi/itemfetchjob.h>
@@ -194,14 +193,14 @@ void BatchFetcher::onHeadersFetchDone( KJob *job )
 }
 
 
-RetrieveItemsTask::RetrieveItemsTask( ResourceStateInterface::Ptr resource, QObject *parent )
-  : ResourceTask( CancelIfNoSession, resource, parent ),
-  m_session( 0 ),
-  m_fetchedMissingBodies( -1 ),
-  m_fetchMissingBodies( false ),
-  m_batchFetcher( 0 ),
-  m_uidBasedFetch( true ),
-  m_flagsChanged( false )
+RetrieveItemsTask::RetrieveItemsTask(ResourceStateInterface::Ptr resource, QObject *parent)
+    : ResourceTask(CancelIfNoSession, resource, parent),
+    m_session(0),
+    m_fetchedMissingBodies(-1),
+    m_fetchMissingBodies(false),
+    m_batchFetcher(0),
+    m_uidBasedFetch(true),
+    m_flagsChanged(false)
 {
 
 }
@@ -212,380 +211,378 @@ RetrieveItemsTask::~RetrieveItemsTask()
 
 void RetrieveItemsTask::setFetchMissingItemBodies(bool enabled)
 {
-  m_fetchMissingBodies = enabled;
+    m_fetchMissingBodies = enabled;
 }
 
-void RetrieveItemsTask::doStart( KIMAP::Session *session )
+void RetrieveItemsTask::doStart(KIMAP::Session *session)
 {
-  emitPercent(0);
-  // Prevent fetching items from noselect folders.
-  if ( collection().hasAttribute( "noselect" ) ) {
-    NoSelectAttribute* noselect = static_cast<NoSelectAttribute*>( collection().attribute( "noselect" ) );
-    if ( noselect->noSelect() ) {
-      kDebug( 5327 ) << "No Select folder";
-      itemsRetrievalDone();
-      return;
+    emitPercent(0);
+    // Prevent fetching items from noselect folders.
+    if (collection().hasAttribute("noselect")) {
+        NoSelectAttribute* noselect = static_cast<NoSelectAttribute*>(collection().attribute("noselect"));
+        if (noselect->noSelect()) {
+            kDebug(5327) << "No Select folder";
+            itemsRetrievalDone();
+            return;
+        }
     }
-  }
 
-  m_session = session;
+    m_session = session;
 
-  const Akonadi::Collection col = collection();
-  if ( m_fetchMissingBodies && col.cachePolicy()
-       .localParts().contains( QLatin1String(Akonadi::MessagePart::Body) ) ) { //disconnected mode, make sure we really have the body cached
+    const Akonadi::Collection col = collection();
+    if (m_fetchMissingBodies && col.cachePolicy()
+        .localParts().contains( QLatin1String(Akonadi::MessagePart::Body))) { //disconnected mode, make sure we really have the body cached
 
-    Akonadi::Session *session = new Akonadi::Session( resourceName().toLatin1() + "_body_checker", this );
-    Akonadi::ItemFetchJob *fetchJob = new Akonadi::ItemFetchJob( col, session );
-    fetchJob->fetchScope().setCheckForCachedPayloadPartsOnly();
-    fetchJob->fetchScope().fetchPayloadPart( Akonadi::MessagePart::Body );
-    fetchJob->fetchScope().setFetchModificationTime( false );
-    connect( fetchJob, SIGNAL(result(KJob*)), this, SLOT(fetchItemsWithoutBodiesDone(KJob*)) );
-    connect( fetchJob, SIGNAL(result(KJob*)), session, SLOT(deleteLater()) );
-  } else {
-    startRetrievalTasks();
-  }
+        Akonadi::Session *session = new Akonadi::Session(resourceName().toLatin1() + "_body_checker", this);
+        Akonadi::ItemFetchJob *fetchJob = new Akonadi::ItemFetchJob(col, session);
+        fetchJob->fetchScope().setCheckForCachedPayloadPartsOnly();
+        fetchJob->fetchScope().fetchPayloadPart(Akonadi::MessagePart::Body);
+        fetchJob->fetchScope().setFetchModificationTime(false);
+        connect(fetchJob, SIGNAL(result(KJob*)), this, SLOT(fetchItemsWithoutBodiesDone(KJob*)));
+        connect(fetchJob, SIGNAL(result(KJob*)), session, SLOT(deleteLater()));
+    } else {
+        startRetrievalTasks();
+    }
 }
 
-void RetrieveItemsTask::fetchItemsWithoutBodiesDone( KJob *job )
+void RetrieveItemsTask::fetchItemsWithoutBodiesDone(KJob *job)
 {
-  Akonadi::ItemFetchJob *fetch = static_cast<Akonadi::ItemFetchJob*>( job );
-  QList<qint64> uids;
-  if ( job->error() ) {
-    kWarning() << job->errorString();
-    cancelTask( job->errorString() );
-    return;
-  } else {
-    int i = 0;
-    Q_FOREACH( const Akonadi::Item &item, fetch->items() )  {
-      if ( !item.cachedPayloadParts().contains( Akonadi::MessagePart::Body ) ) {
-          kWarning() << "Item " << item.id() << " is missing the payload! Cached payloads: " << item.cachedPayloadParts();
-          uids.append( item.remoteId().toInt() );
-          i++;
-      }
-    }
-    if ( i > 0 ) {
-      kWarning() << "Number of items missing the body: " << i;
+    QList<qint64> uids;
+    if (job->error()) {
+        kWarning() << job->errorString();
+        cancelTask(job->errorString());
+        return;
+    } else {
+        int i = 0;
+        Akonadi::ItemFetchJob *fetch = static_cast<Akonadi::ItemFetchJob*>(job);
+        Q_FOREACH(const Akonadi::Item &item, fetch->items())  {
+            if (!item.cachedPayloadParts().contains(Akonadi::MessagePart::Body)) {
+                kWarning() << "Item " << item.id() << " is missing the payload! Cached payloads: " << item.cachedPayloadParts();
+                uids.append(item.remoteId().toInt());
+                i++;
+            }
+        }
+        if (i > 0) {
+            kWarning() << "Number of items missing the body: " << i;
+        }
     }
-  }
-
-  onFetchItemsWithoutBodiesDone(uids);
+    onFetchItemsWithoutBodiesDone(uids);
 }
 
-void RetrieveItemsTask::onFetchItemsWithoutBodiesDone( const QList<qint64> &items )
+void RetrieveItemsTask::onFetchItemsWithoutBodiesDone(const QList<qint64> &items)
 {
-  m_messageUidsMissingBody = items;
-  startRetrievalTasks();
+    m_messageUidsMissingBody = items;
+    startRetrievalTasks();
 }
 
 
 void RetrieveItemsTask::startRetrievalTasks()
 {
-  const QString mailBox = mailBoxForCollection( collection() );
-  kDebug(5327) << "Starting retrieval for " << mailBox;
-  m_time.start();
-
-  // Now is the right time to expunge the messages marked \\Deleted from this mailbox.
-  if ( isAutomaticExpungeEnabled() ) {
-    if ( m_session->selectedMailBox() != mailBox ) {
-      triggerPreExpungeSelect( mailBox );
+    const QString mailBox = mailBoxForCollection(collection());
+    kDebug(5327) << "Starting retrieval for " << mailBox;
+    m_time.start();
+
+    // Now is the right time to expunge the messages marked \\Deleted from this mailbox.
+    if (isAutomaticExpungeEnabled()) {
+        if (m_session->selectedMailBox() != mailBox) {
+            triggerPreExpungeSelect(mailBox);
+        } else {
+            triggerExpunge(mailBox);
+        }
     } else {
-      triggerExpunge( mailBox );
+        // Always select to get the stats updated
+        triggerFinalSelect(mailBox);
     }
-  } else {
-    // Always select to get the stats updated
-    triggerFinalSelect( mailBox );
-  }
 }
 
-void RetrieveItemsTask::triggerPreExpungeSelect( const QString &mailBox )
+void RetrieveItemsTask::triggerPreExpungeSelect(const QString &mailBox)
 {
-  KIMAP::SelectJob *select = new KIMAP::SelectJob( m_session );
-  select->setMailBox( mailBox );
-  select->setCondstoreEnabled( serverSupportsCondstore() );
-  connect( select, SIGNAL(result(KJob*)),
-           this, SLOT(onPreExpungeSelectDone(KJob*)) );
-  select->start();
+    KIMAP::SelectJob *select = new KIMAP::SelectJob(m_session);
+    select->setMailBox(mailBox);
+    select->setCondstoreEnabled(serverSupportsCondstore());
+    connect(select, SIGNAL(result(KJob*)),
+            this, SLOT(onPreExpungeSelectDone(KJob*)));
+    select->start();
 }
 
-void RetrieveItemsTask::onPreExpungeSelectDone( KJob *job )
+void RetrieveItemsTask::onPreExpungeSelectDone(KJob *job)
 {
-  if ( job->error() ) {
-    kWarning() << job->errorString();
-    cancelTask( job->errorString() );
-  } else {
-    KIMAP::SelectJob *select = static_cast<KIMAP::SelectJob*>( job );
-    triggerExpunge( select->mailBox() );
-  }
+    if (job->error()) {
+        kWarning() << job->errorString();
+        cancelTask(job->errorString());
+    } else {
+        KIMAP::SelectJob *select = static_cast<KIMAP::SelectJob*>(job);
+        triggerExpunge(select->mailBox());
+    }
 }
 
-void RetrieveItemsTask::triggerExpunge( const QString &mailBox )
+void RetrieveItemsTask::triggerExpunge(const QString &mailBox)
 {
-  kDebug( 5327 ) << mailBox;
-
-  KIMAP::ExpungeJob *expunge = new KIMAP::ExpungeJob( m_session );
-  connect( expunge, SIGNAL(result(KJob*)),
-           this, SLOT(onExpungeDone(KJob*)) );
-  expunge->start();
+    KIMAP::ExpungeJob *expunge = new KIMAP::ExpungeJob(m_session);
+    connect(expunge, SIGNAL(result(KJob*)),
+            this, SLOT(onExpungeDone(KJob*)));
+    expunge->start();
 }
 
-void RetrieveItemsTask::onExpungeDone( KJob *job )
+void RetrieveItemsTask::onExpungeDone(KJob *job)
 {
-  // We can ignore the error, we just had a wrong expunge so some old messages will just reappear.
-  // TODO we should probably hide messages that are marked as deleted (skipping will not work with our mode of synchronization)
-  if (job->error()) {
-    kWarning() << "Expunge failed: " << job->errorString();
-  }
-  // Except for network errors.
-  if ( job->error() && m_session->state() == KIMAP::Session::Disconnected ) {
-    cancelTask( job->errorString() );
-    return;
-  }
+    // We can ignore the error, we just had a wrong expunge so some old messages will just reappear.
+    // TODO we should probably hide messages that are marked as deleted (skipping will not work because we rely on the message count)
+    if (job->error()) {
+        kWarning() << "Expunge failed: " << job->errorString();
+    }
+    // Except for network errors.
+    if (job->error() && m_session->state() == KIMAP::Session::Disconnected) {
+        cancelTask( job->errorString() );
+        return;
+    }
 
-  // We have to re-select the mailbox to update all the stats after the expunge
-  // (the EXPUNGE command doesn't return enough for our needs)
-  triggerFinalSelect( m_session->selectedMailBox() );
+    // We have to re-select the mailbox to update all the stats after the expunge
+    // (the EXPUNGE command doesn't return enough for our needs)
+    triggerFinalSelect(m_session->selectedMailBox());
 }
 
-void RetrieveItemsTask::triggerFinalSelect( const QString &mailBox )
+void RetrieveItemsTask::triggerFinalSelect(const QString &mailBox)
 {
-  KIMAP::SelectJob *select = new KIMAP::SelectJob( m_session );
-  select->setMailBox( mailBox );
-  select->setCondstoreEnabled( serverSupportsCondstore() );
-  connect( select, SIGNAL(result(KJob*)),
-           this, SLOT(onFinalSelectDone(KJob*)) );
-  select->start();
+    KIMAP::SelectJob *select = new KIMAP::SelectJob(m_session);
+    select->setMailBox(mailBox);
+    select->setCondstoreEnabled(serverSupportsCondstore());
+    connect( select, SIGNAL(result(KJob*)),
+            this, SLOT(onFinalSelectDone(KJob*)) );
+    select->start();
 }
 
-void RetrieveItemsTask::onFinalSelectDone( KJob *job )
+void RetrieveItemsTask::onFinalSelectDone(KJob *job)
 {
-  if ( job->error() ) {
-    kWarning() << job->errorString();
-    cancelTask( job->errorString() );
-    return;
-  }
-
-  KIMAP::SelectJob *select = qobject_cast<KIMAP::SelectJob*>( job );
+    if (job->error()) {
+        kWarning() << job->errorString();
+        cancelTask(job->errorString());
+        return;
+    }
 
-  const QString mailBox = select->mailBox();
-  const int messageCount = select->messageCount();
-  const qint64 uidValidity = select->uidValidity();
-  const qint64 nextUid = select->nextUid();
-  quint64 highestModSeq = select->highestModSequence();
-  const QList<QByteArray> flags = select->permanentFlags();
+    KIMAP::SelectJob *select = qobject_cast<KIMAP::SelectJob*>(job);
 
-  //The select job retrieves highestmodseq whenever it's available, but in case of no CONDSTORE support we ignore it
-  if ( !serverSupportsCondstore() ) {
-    highestModSeq = 0;
-  }
+    const QString mailBox = select->mailBox();
+    const int messageCount = select->messageCount();
+    const qint64 uidValidity = select->uidValidity();
+    const qint64 nextUid = select->nextUid();
+    quint64 highestModSeq = select->highestModSequence();
+    const QList<QByteArray> flags = select->permanentFlags();
 
-  Akonadi::Collection col = collection();
-  bool modifyNeeded = false;
-
-  // Get the current uid validity value and store it
-  int oldUidValidity = 0;
-  if ( !col.hasAttribute( "uidvalidity" ) ) {
-    UidValidityAttribute* currentUidValidity  = new UidValidityAttribute( uidValidity );
-    col.addAttribute( currentUidValidity );
-    modifyNeeded = true;
-  } else {
-    UidValidityAttribute* currentUidValidity =
-      static_cast<UidValidityAttribute*>( col.attribute( "uidvalidity" ) );
-    oldUidValidity = currentUidValidity->uidValidity();
-    if ( oldUidValidity != uidValidity ) {
-      currentUidValidity->setUidValidity( uidValidity );
-      modifyNeeded = true;
+    //The select job retrieves highestmodseq whenever it's available, but in case of no CONDSTORE support we ignore it
+    if (!serverSupportsCondstore()) {
+        highestModSeq = 0;
     }
-  }
 
-  // Get the current uid next value and store it
-  int oldNextUid = 0;
-  if ( !col.hasAttribute( "uidnext" ) ) {
-    UidNextAttribute* currentNextUid  = new UidNextAttribute( nextUid );
-    col.addAttribute( currentNextUid );
-    modifyNeeded = true;
-  } else {
-    UidNextAttribute* currentNextUid =
-      static_cast<UidNextAttribute*>( col.attribute( "uidnext" ) );
-    oldNextUid = currentNextUid->uidNext();
-    if ( oldNextUid != nextUid ) {
-      currentNextUid->setUidNext( nextUid );
-      modifyNeeded = true;
-    }
-  }
+    Akonadi::Collection col = collection();
+    bool modifyNeeded = false;
 
-  // Store the mailbox flags
-  if ( !col.hasAttribute( "collectionflags" ) ) {
-    Akonadi::CollectionFlagsAttribute *flagsAttribute  = new Akonadi::CollectionFlagsAttribute( flags );
-    col.addAttribute( flagsAttribute );
-    modifyNeeded = true;
-  } else {
-    Akonadi::CollectionFlagsAttribute *flagsAttribute =
-      static_cast<Akonadi::CollectionFlagsAttribute*>( col.attribute( "collectionflags" ) );
-    const QList<QByteArray> oldFlags = flagsAttribute->flags();
-    if ( oldFlags != flags ) {
-      flagsAttribute->setFlags( flags );
-      modifyNeeded = true;
+    // Get the current uid validity value and store it
+    int oldUidValidity = 0;
+    if (!col.hasAttribute("uidvalidity")) {
+        UidValidityAttribute* currentUidValidity  = new UidValidityAttribute(uidValidity);
+        col.addAttribute(currentUidValidity);
+        modifyNeeded = true;
+    } else {
+        UidValidityAttribute* currentUidValidity =
+        static_cast<UidValidityAttribute*>(col.attribute("uidvalidity" ));
+        oldUidValidity = currentUidValidity->uidValidity();
+        if (oldUidValidity != uidValidity) {
+            currentUidValidity->setUidValidity(uidValidity);
+            modifyNeeded = true;
+        }
     }
-  }
 
-  quint64 oldHighestModSeq = 0;
-  if ( serverSupportsCondstore() && highestModSeq > 0 ) {
-    if ( !col.hasAttribute( "highestmodseq" ) ) {
-      HighestModSeqAttribute *attr = new HighestModSeqAttribute( highestModSeq );
-      col.addAttribute( attr );
-      modifyNeeded = true;
-    } else {
-      HighestModSeqAttribute *attr = col.attribute<HighestModSeqAttribute>();
-      if ( attr->highestModSequence() < highestModSeq ) {
-        oldHighestModSeq = attr->highestModSequence();
-        attr->setHighestModSeq( highestModSeq );
+    // Get the current uid next value and store it
+    int oldNextUid = 0;
+    if (!col.hasAttribute("uidnext")) {
+        UidNextAttribute* currentNextUid  = new UidNextAttribute(nextUid);
+        col.addAttribute(currentNextUid);
         modifyNeeded = true;
-      } else if ( attr->highestModSequence() == highestModSeq ) {
-        oldHighestModSeq = attr->highestModSequence();
-      } else if ( attr->highestModSequence() > highestModSeq ) {
-        // This situation should not happen. If it does, update the highestModSeq
-        // attribute, but rather do a full sync
-        attr->setHighestModSeq( highestModSeq );
+    } else {
+        UidNextAttribute* currentNextUid =
+        static_cast<UidNextAttribute*>(col.attribute("uidnext"));
+        oldNextUid = currentNextUid->uidNext();
+        if (oldNextUid != nextUid) {
+            currentNextUid->setUidNext(nextUid);
+            modifyNeeded = true;
+        }
+    }
+
+    // Store the mailbox flags
+    if (!col.hasAttribute("collectionflags")) {
+        Akonadi::CollectionFlagsAttribute *flagsAttribute  = new Akonadi::CollectionFlagsAttribute(flags);
+        col.addAttribute(flagsAttribute);
         modifyNeeded = true;
-      }
+    } else {
+        Akonadi::CollectionFlagsAttribute *flagsAttribute =
+        static_cast<Akonadi::CollectionFlagsAttribute*>(col.attribute("collectionflags"));
+        const QList<QByteArray> oldFlags = flagsAttribute->flags();
+        if (oldFlags != flags) {
+            flagsAttribute->setFlags(flags);
+            modifyNeeded = true;
+        }
     }
-  }
-  m_highestModseq = oldHighestModSeq;
 
-  if ( modifyNeeded ) {
-    m_modifiedCollection = col;
-  }
+    quint64 oldHighestModSeq = 0;
+    if (serverSupportsCondstore() && highestModSeq > 0) {
+        if (!col.hasAttribute("highestmodseq")) {
+            HighestModSeqAttribute *attr = new HighestModSeqAttribute(highestModSeq);
+            col.addAttribute(attr);
+            modifyNeeded = true;
+        } else {
+            HighestModSeqAttribute *attr = col.attribute<HighestModSeqAttribute>();
+            if (attr->highestModSequence() < highestModSeq) {
+                oldHighestModSeq = attr->highestModSequence();
+                attr->setHighestModSeq(highestModSeq);
+                modifyNeeded = true;
+            } else if (attr->highestModSequence() == highestModSeq) {
+                oldHighestModSeq = attr->highestModSequence();
+            } else if (attr->highestModSequence() > highestModSeq) {
+                // This situation should not happen. If it does, update the highestModSeq
+                // attribute, but rather do a full sync
+                attr->setHighestModSeq(highestModSeq);
+                modifyNeeded = true;
+            }
+        }
+    }
+    m_highestModseq = oldHighestModSeq;
 
-  KIMAP::FetchJob::FetchScope scope;
-  scope.parts.clear();
-  scope.mode = KIMAP::FetchJob::FetchScope::FullHeaders;
+    if ( modifyNeeded ) {
+        m_modifiedCollection = col;
+    }
 
-  if ( col.cachePolicy()
-       .localParts().contains( QLatin1String(Akonadi::MessagePart::Body) ) ) {
-    scope.mode = KIMAP::FetchJob::FetchScope::Full;
-  }
+    KIMAP::FetchJob::FetchScope scope;
+    scope.parts.clear();
+    scope.mode = KIMAP::FetchJob::FetchScope::FullHeaders;
 
-  const qint64 realMessageCount = col.statistics().count();
-
-  kDebug(5327) << "Starting message retrieval. Elapsed(ms): " << m_time.elapsed();
-
-  /*
-   * A synchronization has 3 mandatory steps:
-   * * If uidvalidity changed the local cache must be invalidated
-   * * New messages can be fetched usin uidNext and the last known fetched uid
-   * * flag changes and removals can be detected by listing all messages that weren't part of the previous step
-   *
-   * Everything else is optimizations.
-   */
-
-  if (messageCount == 0) {
-    //Shortcut:
-    //If no messages are present on the server, clear local cash and finish
-    if (realMessageCount > 0) {
-      kDebug( 5327 ) << "No messages present so we are done, deleting local messages.";
-      itemsRetrieved(Akonadi::Item::List());
-    } else {
-      kDebug( 5327 ) << "No messages present so we are done";
+    if ( col.cachePolicy()
+        .localParts().contains( QLatin1String(Akonadi::MessagePart::Body) ) ) {
+        scope.mode = KIMAP::FetchJob::FetchScope::Full;
     }
-    taskComplete();
-  } else if (oldUidValidity != uidValidity) {
-    //If uidvalidity has changed our local cache is worthless and has to be refetched completely
-    if (oldUidValidity != 0) {
-      kDebug( 5327 ) << "UIDVALIDITY check failed (" << oldUidValidity << "|"
-                    << uidValidity << ") refetching " << mailBox;
+
+    const qint64 realMessageCount = col.statistics().count();
+
+    kDebug(5327) << "Starting message retrieval. Elapsed(ms): " << m_time.elapsed();
+
+    /*
+    * A synchronization has 3 mandatory steps:
+    * * If uidvalidity changed the local cache must be invalidated
+    * * New messages can be fetched usin uidNext and the last known fetched uid
+    * * flag changes and removals can be detected by listing all messages that weren't part of the previous step
+    *
+    * Everything else is optimizations.
+    */
+
+    if (messageCount == 0) {
+        //Shortcut:
+        //If no messages are present on the server, clear local cash and finish
+        if (realMessageCount > 0) {
+            kDebug( 5327 ) << "No messages present so we are done, deleting local messages.";
+            itemsRetrieved(Akonadi::Item::List());
+        } else {
+            kDebug( 5327 ) << "No messages present so we are done";
+        }
+        taskComplete();
+    } else if (oldUidValidity != uidValidity) {
+        //If uidvalidity has changed our local cache is worthless and has to be refetched completely
+        if (oldUidValidity != 0) {
+            kDebug( 5327 ) << "UIDVALIDITY check failed (" << oldUidValidity << "|"
+                            << uidValidity << ") refetching " << mailBox;
+        } else {
+            kDebug( 5327 ) << "Fetching complete mailbox " << mailBox;
+        }
+
+        setTotalItems(messageCount);
+        retrieveItems(KIMAP::ImapSet(1, nextUid), scope, false, true);
+    } else if (!m_messageUidsMissingBody.isEmpty()) {
+        //fetch missing uids
+        m_fetchedMissingBodies = 0;
+        setTotalItems(m_messageUidsMissingBody.size());
+        KIMAP::ImapSet imapSet;
+        imapSet.add(m_messageUidsMissingBody);
+        retrieveItems(imapSet, scope, true, true);
+    } else if (nextUid > oldNextUid && ((realMessageCount + nextUid - oldNextUid) == messageCount)) {
+        //Optimization:
+        //New messages are available, but we know no messages have been removed.
+        //Fetch new messages, and then check for changed flags and removed messages
+        //We can make an incremental update and use modseq.
+        kDebug( 5327 ) << "Incrementally fetching new messages: UidNext: " << nextUid << " Old UidNext: " << oldNextUid << " message count " << messageCount << realMessageCount;
+        setTotalItems(messageCount);
+        m_flagsChanged = !(highestModSeq == oldHighestModSeq);
+        retrieveItems(KIMAP::ImapSet(qMax(1, oldNextUid), nextUid), scope, true, true);
+    } else if (nextUid > oldNextUid) {
+        //New messages are available. Fetch new messages, and then check for changed flags and removed messages
+        kDebug( 5327 ) << "Fetching new messages: UidNext: " << nextUid << " Old UidNext: " << oldNextUid;
+        setTotalItems(qMax(1ll, messageCount - realMessageCount));
+        retrieveItems(KIMAP::ImapSet(qMax(1, oldNextUid), nextUid), scope, false, true);
+    } else if (messageCount == realMessageCount && oldNextUid == nextUid) {
+        //Optimization:
+        //We know no messages were added or removed (if the message count and uidnext is still the same)
+        //We only check the flags incrementally and can make use of modseq
+        m_uidBasedFetch = true;
+        m_incremental = true;
+        m_flagsChanged = !(highestModSeq == oldHighestModSeq);
+        setTotalItems(messageCount);
+        listFlagsForImapSet(KIMAP::ImapSet(1, nextUid));
     } else {
-      kDebug( 5327 ) << "Fetching complete mailbox " << mailBox;
+        //Shortcut:
+        //No new messages are available. Directly check for changed flags and removed messages.
+        m_uidBasedFetch = true;
+        m_incremental = false;
+        setTotalItems(messageCount);
+        listFlagsForImapSet(KIMAP::ImapSet(1, nextUid));
     }
-
-    setTotalItems(messageCount);
-    retrieveItems(KIMAP::ImapSet(1, nextUid), scope, false, true);
-  } else if (!m_messageUidsMissingBody.isEmpty()) {
-    //fetch missing uids
-    m_fetchedMissingBodies = 0;
-    setTotalItems(m_messageUidsMissingBody.size());
-    KIMAP::ImapSet imapSet;
-    imapSet.add(m_messageUidsMissingBody);
-    retrieveItems(imapSet, scope, true, true);
-  } else if (nextUid > oldNextUid && ((realMessageCount + nextUid - oldNextUid) == messageCount)) {
-    //Optimization:
-    //New messages are available, but we know no messages have been removed.
-    //Fetch new messages, and then check for changed flags and removed messages
-    //We can make an incremental update and use modseq.
-    kDebug( 5327 ) << "Incrementally fetching new messages: UidNext: " << nextUid << " Old UidNext: " << oldNextUid << " message count " << messageCount << realMessageCount;
-    setTotalItems(messageCount);
-    m_flagsChanged = !(highestModSeq == oldHighestModSeq);
-    retrieveItems(KIMAP::ImapSet(qMax(1, oldNextUid), nextUid), scope, true, true);
-  } else if (nextUid > oldNextUid) {
-    //New messages are available. Fetch new messages, and then check for changed flags and removed messages
-    kDebug( 5327 ) << "Fetching new messages: UidNext: " << nextUid << " Old UidNext: " << oldNextUid;
-    setTotalItems(qMax(1ll, messageCount - realMessageCount));
-    retrieveItems(KIMAP::ImapSet(qMax(1, oldNextUid), nextUid), scope, false, true);
-  } else if (messageCount == realMessageCount && oldNextUid == nextUid) {
-    //Optimization:
-    //We know no messages were added or removed (if the message count and uidnext is still the same)
-    //We only check the flags incrementally and can make use of modseq
-    m_uidBasedFetch = true;
-    m_incremental = true;
-    m_flagsChanged = !(highestModSeq == oldHighestModSeq);
-    setTotalItems(messageCount);
-    listFlagsForImapSet(KIMAP::ImapSet(1, nextUid));
-  } else {
-    //Shortcut:
-    //No new messages are available. Directly check for changed flags and removed messages.
-    m_uidBasedFetch = true;
-    m_incremental = false;
-    setTotalItems(messageCount);
-    listFlagsForImapSet(KIMAP::ImapSet(1, nextUid));
-  }
 }
 
 void RetrieveItemsTask::retrieveItems(const KIMAP::ImapSet& set, const KIMAP::FetchJob::FetchScope &scope, bool incremental, bool uidBased)
 {
-  Q_ASSERT(set.intervals().size() == 1);
-
-  m_incremental = incremental;
-  m_uidBasedFetch = uidBased;
-
-  m_batchFetcher = new BatchFetcher(set, scope, batchSize(), m_session);
-  m_batchFetcher->setUidBased(m_uidBasedFetch);
-  m_batchFetcher->setProperty("alreadyFetched", set.intervals().first().begin());
-  connect(m_batchFetcher, SIGNAL(itemsRetrieved(Akonadi::Item::List)),
-          this, SLOT(onItemsRetrieved(Akonadi::Item::List)));
-  connect(m_batchFetcher, SIGNAL(result(KJob*)),
-          this, SLOT(onRetrievalDone(KJob*)));
-  m_batchFetcher->start();
+    Q_ASSERT(set.intervals().size() == 1);
+
+    m_incremental = incremental;
+    m_uidBasedFetch = uidBased;
+
+    m_batchFetcher = new BatchFetcher(set, scope, batchSize(), m_session);
+    m_batchFetcher->setUidBased(m_uidBasedFetch);
+    m_batchFetcher->setProperty("alreadyFetched", set.intervals().first().begin());
+    connect(m_batchFetcher, SIGNAL(itemsRetrieved(Akonadi::Item::List)),
+            this, SLOT(onItemsRetrieved(Akonadi::Item::List)));
+    connect(m_batchFetcher, SIGNAL(result(KJob*)),
+            this, SLOT(onRetrievalDone(KJob*)));
+    m_batchFetcher->start();
 }
 
 void RetrieveItemsTask::onReadyForNextBatch(int size)
 {
-  if (m_batchFetcher) {
-    m_batchFetcher->fetchNextBatch();
-  }
+    Q_UNUSED(size);
+    if (m_batchFetcher) {
+        m_batchFetcher->fetchNextBatch();
+    }
 }
 
 void RetrieveItemsTask::onItemsRetrieved(const Akonadi::Item::List &addedItems)
 {
-  if ( m_incremental ) {
-    itemsRetrievedIncremental( addedItems, Akonadi::Item::List() );
-  } else {
-    itemsRetrieved( addedItems );
-  }
+    if (m_incremental) {
+        itemsRetrievedIncremental(addedItems, Akonadi::Item::List());
+    } else {
+        itemsRetrieved(addedItems);
+    }
 
-  //m_fetchedMissingBodies is -1 if we fetch for other reason, but missing bodies
-  if ( m_fetchedMissingBodies != -1 ) {
-    const QString mailBox = mailBoxForCollection( collection() );
-    m_fetchedMissingBodies += addedItems.count();
-    emit status(Akonadi::AgentBase::Running,
-                i18nc( "@info:status", "Fetching missing mail bodies in %3: %1/%2", m_fetchedMissingBodies, m_messageUidsMissingBody.count(), mailBox));
-  }
+    //m_fetchedMissingBodies is -1 if we fetch for other reason, but missing bodies
+    if (m_fetchedMissingBodies != -1) {
+        const QString mailBox = mailBoxForCollection(collection());
+        m_fetchedMissingBodies += addedItems.count();
+        emit status(Akonadi::AgentBase::Running,
+                    i18nc( "@info:status", "Fetching missing mail bodies in %3: %1/%2", m_fetchedMissingBodies, m_messageUidsMissingBody.count(), mailBox));
+    }
 }
 
-void RetrieveItemsTask::onRetrievalDone( KJob *job )
+void RetrieveItemsTask::onRetrievalDone(KJob *job)
 {
     m_batchFetcher = 0;
-    if ( job->error() ) {
+    if (job->error()) {
         kWarning() << job->errorString();
-        cancelTask( job->errorString() );
+        cancelTask(job->errorString());
         m_fetchedMissingBodies = -1;
         return;
     }
@@ -597,7 +594,7 @@ void RetrieveItemsTask::onRetrievalDone( KJob *job )
     // already have them all from the previous full fetch. This is not
     // just an optimization, as incremental retrieval assumes nothing
     // will be listed twice.
-    if ( m_fetchedMissingBodies != -1 || alreadyFetchedBegin <= 1 ) {
+    if (m_fetchedMissingBodies != -1 || alreadyFetchedBegin <= 1) {
         taskComplete();
         return;
     }
@@ -608,7 +605,7 @@ void RetrieveItemsTask::onRetrievalDone( KJob *job )
 }
 
 
-void RetrieveItemsTask::listFlagsForImapSet( const KIMAP::ImapSet& set )
+void RetrieveItemsTask::listFlagsForImapSet(const KIMAP::ImapSet& set)
 {
   kDebug(5327) << "Listing flags " << set.intervals().first().begin() << set.intervals().first().end();
   kDebug(5327) << "Starting flag retrieval. Elapsed(ms): " << m_time.elapsed();
@@ -636,15 +633,15 @@ void RetrieveItemsTask::listFlagsForImapSet( const KIMAP::ImapSet& set )
   m_batchFetcher->start();
 }
 
-void RetrieveItemsTask::onFlagsFetchDone( KJob *job )
+void RetrieveItemsTask::onFlagsFetchDone(KJob *job)
 {
-  m_batchFetcher = 0;
-  if ( job->error() ) {
-    kWarning() << job->errorString();
-    cancelTask( job->errorString() );
-  } else {
-    taskComplete();
-  }
+    m_batchFetcher = 0;
+    if ( job->error() ) {
+        kWarning() << job->errorString();
+        cancelTask(job->errorString());
+    } else {
+        taskComplete();
+    }
 }
 
 void RetrieveItemsTask::taskComplete()
@@ -652,7 +649,7 @@ void RetrieveItemsTask::taskComplete()
     if (m_modifiedCollection.isValid()) {
         applyCollectionChanges(m_modifiedCollection);
     }
-    if ( m_incremental ) {
+    if (m_incremental) {
         // Calling itemsRetrievalDone() before previous call to itemsRetrievedIncremental()
         // behaves like if we called itemsRetrieved(Items::List()), so make sure
         // Akonadi knows we did incremental fetch that came up with no changes
diff --git a/resources/imap/retrieveitemstask.h b/resources/imap/retrieveitemstask.h
index 49024bb..5cf4693 100644
--- a/resources/imap/retrieveitemstask.h
+++ b/resources/imap/retrieveitemstask.h
@@ -2,6 +2,7 @@
     Copyright (c) 2010 Klarälvdalens Datakonsult AB,
                        a KDAB Group company <info at kdab.com>
     Author: Kevin Ottens <kevin at kdab.com>
+    Copyright (c) 2014 Christian Mollekopf <mollekopf at kolabsys.com>
 
     This library is free software; you can redistribute it and/or modify it
     under the terms of the GNU Library General Public License as published by
@@ -28,7 +29,7 @@
 
 class BatchFetcher;
 namespace Akonadi {
-  class Session;
+    class Session;
 }
 
 class RetrieveItemsTask : public ResourceTask
@@ -36,50 +37,46 @@ class RetrieveItemsTask : public ResourceTask
   Q_OBJECT
 
 public:
-  explicit RetrieveItemsTask( ResourceStateInterface::Ptr resource, QObject *parent = 0 );
-  virtual ~RetrieveItemsTask();
-  void setFetchMissingItemBodies(bool enabled);
+    explicit RetrieveItemsTask(ResourceStateInterface::Ptr resource, QObject *parent = 0);
+    virtual ~RetrieveItemsTask();
+    void setFetchMissingItemBodies(bool enabled);
 
 public slots:
-  void onFetchItemsWithoutBodiesDone( const QList<qint64> &items );
-  void onReadyForNextBatch(int size);
+    void onFetchItemsWithoutBodiesDone(const QList<qint64> &items);
+    void onReadyForNextBatch(int size);
 
 private slots:
-  void fetchItemsWithoutBodiesDone( KJob *job );
-  void onPreExpungeSelectDone( KJob *job );
-  void onExpungeDone( KJob *job );
-
-  void onFinalSelectDone( KJob *job );
-
-  void onItemsRetrieved(const Akonadi::Item::List &addedItems);
-  void onRetrievalDone(KJob *job);
-  void onFlagsFetchDone( KJob *job );
+    void fetchItemsWithoutBodiesDone(KJob *job);
+    void onPreExpungeSelectDone(KJob *job);
+    void onExpungeDone(KJob *job);
+    void onFinalSelectDone(KJob *job);
+    void onItemsRetrieved(const Akonadi::Item::List &addedItems);
+    void onRetrievalDone(KJob *job);
+    void onFlagsFetchDone( KJob *job );
 
 protected:
-  virtual void doStart( KIMAP::Session *session );
+    virtual void doStart(KIMAP::Session *session);
 
 private:
-  void startRetrievalTasks();
-  void triggerPreExpungeSelect( const QString &mailBox );
-  void triggerExpunge( const QString &mailBox );
-  void triggerFinalSelect( const QString &mailBox );
-  void retrieveItems(const KIMAP::ImapSet& set, const KIMAP::FetchJob::FetchScope &scope, bool incremental = false, bool uidBased = false);
-
-  void listFlagsForImapSet( const KIMAP::ImapSet& set );
-  void taskComplete();
-
-  KIMAP::Session *m_session;
-  QList<qint64> m_messageUidsMissingBody;
-  int m_fetchedMissingBodies;
-  bool m_fetchMissingBodies;
-  bool m_incremental;
-  qint64 m_highestModseq;
-  BatchFetcher *m_batchFetcher;
-  bool m_collectionModifyNeeded;
-  Akonadi::Collection m_modifiedCollection;
-  bool m_uidBasedFetch;
-  bool m_flagsChanged;
-  QTime m_time;
+    void startRetrievalTasks();
+    void triggerPreExpungeSelect(const QString &mailBox);
+    void triggerExpunge(const QString &mailBox);
+    void triggerFinalSelect(const QString &mailBox);
+    void retrieveItems(const KIMAP::ImapSet& set, const KIMAP::FetchJob::FetchScope &scope, bool incremental = false, bool uidBased = false);
+    void listFlagsForImapSet(const KIMAP::ImapSet& set);
+    void taskComplete();
+
+    KIMAP::Session *m_session;
+    QList<qint64> m_messageUidsMissingBody;
+    int m_fetchedMissingBodies;
+    bool m_fetchMissingBodies;
+    bool m_incremental;
+    qint64 m_highestModseq;
+    BatchFetcher *m_batchFetcher;
+    Akonadi::Collection m_modifiedCollection;
+    bool m_uidBasedFetch;
+    bool m_flagsChanged;
+    QTime m_time;
 };
 
 #endif


commit da4583b382b5d7b54cce3be32afe02b9ed699338
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Wed Apr 30 09:18:19 2014 +0200

    IMAP-Resource: Increased the batch size.
    
    This speeded the synchronization of mailbox up by ~30% on my system.

diff --git a/resources/imap/imapresource.cpp b/resources/imap/imapresource.cpp
index ddddbb0..ed4d2ec 100644
--- a/resources/imap/imapresource.cpp
+++ b/resources/imap/imapresource.cpp
@@ -153,6 +153,7 @@ ImapResource::ImapResource( const QString &id )
   scope.setAncestorRetrieval( ItemFetchScope::None );
   setItemSynchronizationFetchScope( scope );
   setDisableAutomaticItemDeliveryDone( true );
+  setItemSyncBatchSize( 100 );
 
   connect( this, SIGNAL(reloadConfiguration()), SLOT(reconnect()) );
 


commit dbac398758e421f5a99ae8dec50158d6de946ce6
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Tue Apr 29 21:36:47 2014 +0200

    IMAP-Resource: Use batch-processing also for the flag fetch. Another optimization to avoid a full refetch.

diff --git a/resources/imap/messagehelper.cpp b/resources/imap/messagehelper.cpp
index 54871df..371b803 100644
--- a/resources/imap/messagehelper.cpp
+++ b/resources/imap/messagehelper.cpp
@@ -25,33 +25,39 @@
 Akonadi::Item MessageHelper::createItemFromMessage(KMime::Message::Ptr message, const qint64 uid, const qint64 size, const QList<QByteArray> &flags, const KIMAP::FetchJob::FetchScope &scope)
 {
     Akonadi::Item i;
-    // Sometimes messages might not have a body at all
-    if (message->body().isEmpty() && (scope.mode == KIMAP::FetchJob::FetchScope::Full || scope.mode == KIMAP::FetchJob::FetchScope::Content)) {
-        // In that case put a space in as body so that it gets cached
-        // otherwise we'll wrongly believe the body part is missing from the cache
-        message->setBody( " " );
-    }
-    i.setRemoteId(QString::number(uid));
-    i.setMimeType(KMime::Message::mimeType());
-    i.setPayload(KMime::Message::Ptr(message));
-    i.setSize(size);
-
-    // update status flags
-    if (KMime::isSigned(message.get())) {
-        i.setFlag(Akonadi::MessageFlags::Signed);
-    }
-    if (KMime::isEncrypted(message.get())) {
-        i.setFlag(Akonadi::MessageFlags::Encrypted);
-    }
-    if (KMime::isInvitation(message.get())) {
-        i.setFlag(Akonadi::MessageFlags::HasInvitation);
-    }
-    if (KMime::hasAttachment(message.get())) {
-        i.setFlag(Akonadi::MessageFlags::HasAttachment);
-    }
-
-    foreach (const QByteArray &flag, ResourceTask::toAkonadiFlags(flags)) {
-        i.setFlag(flag);
+    if (scope.mode == KIMAP::FetchJob::FetchScope::Flags) {
+        i.setRemoteId(QString::number(uid));
+        i.setMimeType(KMime::Message::mimeType());
+        i.setFlags(Akonadi::Item::Flags::fromList(ResourceTask::toAkonadiFlags(flags)));
+    } else {
+        // Sometimes messages might not have a body at all
+        if (message->body().isEmpty() && (scope.mode == KIMAP::FetchJob::FetchScope::Full || scope.mode == KIMAP::FetchJob::FetchScope::Content)) {
+            // In that case put a space in as body so that it gets cached
+            // otherwise we'll wrongly believe the body part is missing from the cache
+            message->setBody( " " );
+        }
+        i.setRemoteId(QString::number(uid));
+        i.setMimeType(KMime::Message::mimeType());
+        i.setPayload(KMime::Message::Ptr(message));
+        i.setSize(size);
+
+        // update status flags
+        if (KMime::isSigned(message.get())) {
+            i.setFlag(Akonadi::MessageFlags::Signed);
+        }
+        if (KMime::isEncrypted(message.get())) {
+            i.setFlag(Akonadi::MessageFlags::Encrypted);
+        }
+        if (KMime::isInvitation(message.get())) {
+            i.setFlag(Akonadi::MessageFlags::HasInvitation);
+        }
+        if (KMime::hasAttachment(message.get())) {
+            i.setFlag(Akonadi::MessageFlags::HasAttachment);
+        }
+
+        foreach (const QByteArray &flag, ResourceTask::toAkonadiFlags(flags)) {
+            i.setFlag(flag);
+        }
     }
     return i;
 }
diff --git a/resources/imap/retrieveitemstask.cpp b/resources/imap/retrieveitemstask.cpp
index f0c66a1..c3043d9 100644
--- a/resources/imap/retrieveitemstask.cpp
+++ b/resources/imap/retrieveitemstask.cpp
@@ -200,7 +200,8 @@ RetrieveItemsTask::RetrieveItemsTask( ResourceStateInterface::Ptr resource, QObj
   m_fetchedMissingBodies( -1 ),
   m_fetchMissingBodies( false ),
   m_batchFetcher( 0 ),
-  m_uidBasedFetch( true )
+  m_uidBasedFetch( true ),
+  m_flagsChanged( false )
 {
 
 }
@@ -280,6 +281,8 @@ void RetrieveItemsTask::onFetchItemsWithoutBodiesDone( const QList<qint64> &item
 void RetrieveItemsTask::startRetrievalTasks()
 {
   const QString mailBox = mailBoxForCollection( collection() );
+  kDebug(5327) << "Starting retrieval for " << mailBox;
+  m_time.start();
 
   // Now is the right time to expunge the messages marked \\Deleted from this mailbox.
   if ( isAutomaticExpungeEnabled() ) {
@@ -464,6 +467,17 @@ void RetrieveItemsTask::onFinalSelectDone( KJob *job )
 
   const qint64 realMessageCount = col.statistics().count();
 
+  kDebug(5327) << "Starting message retrieval. Elapsed(ms): " << m_time.elapsed();
+
+  /*
+   * A synchronization has 3 mandatory steps:
+   * * If uidvalidity changed the local cache must be invalidated
+   * * New messages can be fetched usin uidNext and the last known fetched uid
+   * * flag changes and removals can be detected by listing all messages that weren't part of the previous step
+   *
+   * Everything else is optimizations.
+   */
+
   if (messageCount == 0) {
     //Shortcut:
     //If no messages are present on the server, clear local cash and finish
@@ -492,6 +506,15 @@ void RetrieveItemsTask::onFinalSelectDone( KJob *job )
     KIMAP::ImapSet imapSet;
     imapSet.add(m_messageUidsMissingBody);
     retrieveItems(imapSet, scope, true, true);
+  } else if (nextUid > oldNextUid && ((realMessageCount + nextUid - oldNextUid) == messageCount)) {
+    //Optimization:
+    //New messages are available, but we know no messages have been removed.
+    //Fetch new messages, and then check for changed flags and removed messages
+    //We can make an incremental update and use modseq.
+    kDebug( 5327 ) << "Incrementally fetching new messages: UidNext: " << nextUid << " Old UidNext: " << oldNextUid << " message count " << messageCount << realMessageCount;
+    setTotalItems(messageCount);
+    m_flagsChanged = !(highestModSeq == oldHighestModSeq);
+    retrieveItems(KIMAP::ImapSet(qMax(1, oldNextUid), nextUid), scope, true, true);
   } else if (nextUid > oldNextUid) {
     //New messages are available. Fetch new messages, and then check for changed flags and removed messages
     kDebug( 5327 ) << "Fetching new messages: UidNext: " << nextUid << " Old UidNext: " << oldNextUid;
@@ -501,16 +524,11 @@ void RetrieveItemsTask::onFinalSelectDone( KJob *job )
     //Optimization:
     //We know no messages were added or removed (if the message count and uidnext is still the same)
     //We only check the flags incrementally and can make use of modseq
-    if (!serverSupportsCondstore() || highestModSeq > oldHighestModSeq) {
-      m_uidBasedFetch = true;
-      m_incremental = true;
-      setTotalItems(messageCount);
-      listFlagsForImapSet(KIMAP::ImapSet(1, nextUid));
-    } else {
-      kDebug(5327)  << "No changes";
-      m_incremental = true;
-      taskComplete();
-    }
+    m_uidBasedFetch = true;
+    m_incremental = true;
+    m_flagsChanged = !(highestModSeq == oldHighestModSeq);
+    setTotalItems(messageCount);
+    listFlagsForImapSet(KIMAP::ImapSet(1, nextUid));
   } else {
     //Shortcut:
     //No new messages are available. Directly check for changed flags and removed messages.
@@ -593,6 +611,7 @@ void RetrieveItemsTask::onRetrievalDone( KJob *job )
 void RetrieveItemsTask::listFlagsForImapSet( const KIMAP::ImapSet& set )
 {
   kDebug(5327) << "Listing flags " << set.intervals().first().begin() << set.intervals().first().end();
+  kDebug(5327) << "Starting flag retrieval. Elapsed(ms): " << m_time.elapsed();
 
   KIMAP::FetchJob::FetchScope scope;
   scope.parts.clear();
@@ -601,51 +620,25 @@ void RetrieveItemsTask::listFlagsForImapSet( const KIMAP::ImapSet& set )
   // otherwise we would overwrite our local data with an incomplete dataset
   if(m_incremental && serverSupportsCondstore()) {
       scope.changedSince = m_highestModseq;
+      if (!m_flagsChanged) {
+          kDebug(5327)  << "No flag changes.";
+          taskComplete();
+          return;
+      }
   }
 
-  KIMAP::FetchJob* fetch = new KIMAP::FetchJob( m_session );
-  fetch->setSequenceSet( set );
-  fetch->setScope( scope );
-  fetch->setUidBased(m_uidBasedFetch);
-  connect( fetch, SIGNAL(headersReceived(QString,QMap<qint64,qint64>,QMap<qint64,qint64>,QMap<qint64,KIMAP::MessageFlags>,QMap<qint64,KIMAP::MessagePtr>)),
-           this, SLOT(onFlagsReceived(QString,QMap<qint64,qint64>,QMap<qint64,qint64>,QMap<qint64,KIMAP::MessageFlags>,QMap<qint64,KIMAP::MessagePtr>)) );
-  connect( fetch, SIGNAL(result(KJob*)),
-           this, SLOT(onFlagsFetchDone(KJob*)) );
-  fetch->start();
-}
-
-void RetrieveItemsTask::onFlagsReceived( const QString &mailBox, const QMap<qint64, qint64> &uids,
-                                         const QMap<qint64, qint64> &sizes,
-                                         const QMap<qint64, KIMAP::MessageFlags> &flags,
-                                         const QMap<qint64, KIMAP::MessagePtr> &messages )
-{
-  Q_UNUSED( mailBox );
-  Q_UNUSED( sizes );
-  Q_UNUSED( messages );
-
-  Akonadi::Item::List changedItems;
-
-  foreach ( qint64 number, uids.keys() ) { //krazy:exclude=foreach
-    Akonadi::Item i;
-    i.setRemoteId( QString::number( uids[number] ) );
-    i.setMimeType( KMime::Message::mimeType() );
-    i.setFlags( Akonadi::Item::Flags::fromList( toAkonadiFlags( flags[number] ) ) );
-
-    //kDebug( 5327 ) << "Flags: " << i.flags();
-    changedItems << i;
-  }
-
-  if ( !changedItems.isEmpty() ) {
-    if ( m_incremental ) {
-        itemsRetrievedIncremental( changedItems, Akonadi::Item::List() );
-    } else {
-        itemsRetrieved( changedItems );
-    }
-  }
+  m_batchFetcher = new BatchFetcher(set, scope, 10 * batchSize(), m_session);
+  m_batchFetcher->setUidBased(m_uidBasedFetch);
+  connect(m_batchFetcher, SIGNAL(itemsRetrieved(Akonadi::Item::List)),
+          this, SLOT(onItemsRetrieved(Akonadi::Item::List)));
+  connect(m_batchFetcher, SIGNAL(result(KJob*)),
+          this, SLOT(onFlagsFetchDone(KJob*)));
+  m_batchFetcher->start();
 }
 
 void RetrieveItemsTask::onFlagsFetchDone( KJob *job )
 {
+  m_batchFetcher = 0;
   if ( job->error() ) {
     kWarning() << job->errorString();
     cancelTask( job->errorString() );
@@ -665,6 +658,7 @@ void RetrieveItemsTask::taskComplete()
         // Akonadi knows we did incremental fetch that came up with no changes
         itemsRetrievedIncremental(Akonadi::Item::List(), Akonadi::Item::List());
     }
+    kDebug(5327) << "Retrieval complete. Elapsed(ms): " << m_time.elapsed();
     itemsRetrievalDone();
 }
 
diff --git a/resources/imap/retrieveitemstask.h b/resources/imap/retrieveitemstask.h
index 226894c..49024bb 100644
--- a/resources/imap/retrieveitemstask.h
+++ b/resources/imap/retrieveitemstask.h
@@ -53,11 +53,6 @@ private slots:
 
   void onItemsRetrieved(const Akonadi::Item::List &addedItems);
   void onRetrievalDone(KJob *job);
-
-  void onFlagsReceived( const QString &mailBox, const QMap<qint64, qint64> &uids,
-                        const QMap<qint64, qint64> &sizes,
-                        const QMap<qint64, KIMAP::MessageFlags> &flags,
-                        const QMap<qint64, KIMAP::MessagePtr> &messages );
   void onFlagsFetchDone( KJob *job );
 
 protected:
@@ -83,6 +78,8 @@ private:
   bool m_collectionModifyNeeded;
   Akonadi::Collection m_modifiedCollection;
   bool m_uidBasedFetch;
+  bool m_flagsChanged;
+  QTime m_time;
 };
 
 #endif


commit d81a85e902e56300d8ad3147f79d7a3b9177a3c8
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Tue Apr 29 20:23:43 2014 +0200

    IMAP-Resource: Deduplicate creating an akonadi item from a message.

diff --git a/resources/imap/CMakeLists.txt b/resources/imap/CMakeLists.txt
index 14118d9..053d715 100644
--- a/resources/imap/CMakeLists.txt
+++ b/resources/imap/CMakeLists.txt
@@ -23,6 +23,7 @@ set( imapresource_LIB_SRCS
   highestmodseqattribute.cpp
   imapaccount.cpp
   imapflags.cpp
+  messagehelper.cpp
   movecollectiontask.cpp
   moveitemstask.cpp
   noselectattribute.cpp
diff --git a/resources/imap/messagehelper.cpp b/resources/imap/messagehelper.cpp
new file mode 100644
index 0000000..54871df
--- /dev/null
+++ b/resources/imap/messagehelper.cpp
@@ -0,0 +1,57 @@
+/*
+    Copyright (c) 2014 Christian Mollekopf <mollekopf at kolabsys.com>
+
+    This library is free software; you can redistribute it and/or modify it
+    under the terms of the GNU Library General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or (at your
+    option) any later version.
+
+    This library is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+    License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to the
+    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA.
+*/
+#include "messagehelper.h"
+
+#include <Akonadi/KMime/MessageFlags>
+
+#include "resourcetask.h"
+
+Akonadi::Item MessageHelper::createItemFromMessage(KMime::Message::Ptr message, const qint64 uid, const qint64 size, const QList<QByteArray> &flags, const KIMAP::FetchJob::FetchScope &scope)
+{
+    Akonadi::Item i;
+    // Sometimes messages might not have a body at all
+    if (message->body().isEmpty() && (scope.mode == KIMAP::FetchJob::FetchScope::Full || scope.mode == KIMAP::FetchJob::FetchScope::Content)) {
+        // In that case put a space in as body so that it gets cached
+        // otherwise we'll wrongly believe the body part is missing from the cache
+        message->setBody( " " );
+    }
+    i.setRemoteId(QString::number(uid));
+    i.setMimeType(KMime::Message::mimeType());
+    i.setPayload(KMime::Message::Ptr(message));
+    i.setSize(size);
+
+    // update status flags
+    if (KMime::isSigned(message.get())) {
+        i.setFlag(Akonadi::MessageFlags::Signed);
+    }
+    if (KMime::isEncrypted(message.get())) {
+        i.setFlag(Akonadi::MessageFlags::Encrypted);
+    }
+    if (KMime::isInvitation(message.get())) {
+        i.setFlag(Akonadi::MessageFlags::HasInvitation);
+    }
+    if (KMime::hasAttachment(message.get())) {
+        i.setFlag(Akonadi::MessageFlags::HasAttachment);
+    }
+
+    foreach (const QByteArray &flag, ResourceTask::toAkonadiFlags(flags)) {
+        i.setFlag(flag);
+    }
+    return i;
+}
diff --git a/resources/imap/messagehelper.h b/resources/imap/messagehelper.h
new file mode 100644
index 0000000..4378991
--- /dev/null
+++ b/resources/imap/messagehelper.h
@@ -0,0 +1,32 @@
+/*
+    Copyright (c) 2014 Christian Mollekopf <mollekopf at kolabsys.com>
+
+    This library is free software; you can redistribute it and/or modify it
+    under the terms of the GNU Library General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or (at your
+    option) any later version.
+
+    This library is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+    License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to the
+    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA.
+*/
+
+#ifndef MESSAGEHELPER_H
+#define MESSAGEHELPER_H
+
+#include <Akonadi/Item>
+#include <KMime/Message>
+#include <KIMAP/FetchJob>
+
+class MessageHelper {
+public:
+    static Akonadi::Item createItemFromMessage(KMime::Message::Ptr message, const qint64 uid, const qint64 size, const QList<QByteArray> &flags, const KIMAP::FetchJob::FetchScope &scope);
+};
+
+#endif
\ No newline at end of file
diff --git a/resources/imap/retrieveitemstask.cpp b/resources/imap/retrieveitemstask.cpp
index 3581622..f0c66a1 100644
--- a/resources/imap/retrieveitemstask.cpp
+++ b/resources/imap/retrieveitemstask.cpp
@@ -27,6 +27,7 @@
 #include "uidvalidityattribute.h"
 #include "uidnextattribute.h"
 #include "highestmodseqattribute.h"
+#include "messagehelper.h"
 
 #include <akonadi/cachepolicy.h>
 #include <akonadi/collectionstatistics.h>
@@ -162,45 +163,12 @@ void BatchFetcher::onHeadersReceived(const QString &mailBox, const QMap<qint64,
 {
     KIMAP::FetchJob *fetch = static_cast<KIMAP::FetchJob*>( sender() );
     Q_ASSERT( fetch );
-    KIMAP::FetchJob::FetchScope scope = fetch->scope();
 
     Akonadi::Item::List addedItems;
     foreach (qint64 number, uids.keys()) { //krazy:exclude=foreach
-        const qint64 uid = uids[number];
-        Akonadi::Item i;
-        KMime::Message::Ptr message( messages[number] );
-        // Sometimes messages might not have a body at all
-        if ( message->body().isEmpty() && scope.mode == KIMAP::FetchJob::FetchScope::Full ) {
-            // In that case put a space in as body so that it gets cached
-            // otherwise we'll wrongly believe the body part is missing from the cache
-            message->setBody( " " );
-        }
-        i.setRemoteId(QString::number(uid));
-        i.setMimeType(KMime::Message::mimeType());
-        i.setPayload(KMime::Message::Ptr(message));
-        i.setSize(sizes[number]);
-
-        // update status flags
-        if (KMime::isSigned(messages[number].get())) {
-            i.setFlag(Akonadi::MessageFlags::Signed);
-        }
-        if (KMime::isEncrypted(messages[number].get())) {
-            i.setFlag(Akonadi::MessageFlags::Encrypted);
-        }
-        if (KMime::isInvitation(messages[number].get())) {
-            i.setFlag(Akonadi::MessageFlags::HasInvitation);
-        }
-        if (KMime::hasAttachment(messages[number].get())) {
-            i.setFlag(Akonadi::MessageFlags::HasAttachment);
-        }
-
-        const QList<QByteArray> akonadiFlags = ResourceTask::toAkonadiFlags(flags[number]);
-        foreach (const QByteArray &flag, akonadiFlags) {
-            i.setFlag(flag);
-        }
         //kDebug( 5327 ) << "Flags: " << i.flags();
         m_fetchedItemsInCurrentBatch++;
-        addedItems << i;
+        addedItems << MessageHelper::createItemFromMessage(messages[number], uids[number], sizes[number], flags[number], fetch->scope());
     }
 //     kDebug() << addedItems.size();
     emit itemsRetrieved(addedItems);
diff --git a/resources/imap/retrieveitemtask.cpp b/resources/imap/retrieveitemtask.cpp
index 0281fbb..58c82d5 100644
--- a/resources/imap/retrieveitemtask.cpp
+++ b/resources/imap/retrieveitemtask.cpp
@@ -20,6 +20,7 @@
 */
 
 #include "retrieveitemtask.h"
+#include "messagehelper.h"
 
 #include <KDE/KDebug>
 #include <KDE/KLocale>
@@ -94,33 +95,24 @@ void RetrieveItemTask::onMessagesReceived( const QString &mailBox, const QMap<qi
                                            const QMap<qint64, KIMAP::MessagePtr> &messages )
 {
   Q_UNUSED( mailBox );
-  Q_UNUSED( uids );
 
   KIMAP::FetchJob *fetch = qobject_cast<KIMAP::FetchJob*>( sender() );
   Q_ASSERT( fetch!=0 );
   Q_ASSERT( uids.size()==1 );
   Q_ASSERT( messages.size()==1 );
-  Q_UNUSED( fetch );
 
   Akonadi::Item i = item();
 
   kDebug( 5327 ) << "MESSAGE from Imap server" << item().remoteId();
   Q_ASSERT( item().isValid() );
 
-  KIMAP::MessagePtr message = messages[messages.keys().first()];
-
-  i.setMimeType( KMime::Message::mimeType() );
-  i.setPayload( KMime::Message::Ptr( message ) );
-
-  // update status flags
-  if ( KMime::isSigned( message.get() ) )
-    i.setFlag( Akonadi::MessageFlags::Signed );
-  if ( KMime::isEncrypted( message.get() ) )
-    i.setFlag( Akonadi::MessageFlags::Encrypted );
-  if ( KMime::isInvitation( message.get() ) )
-    i.setFlag( Akonadi::MessageFlags::HasInvitation );
-  if ( KMime::hasAttachment( message.get() ) )
-    i.setFlag( Akonadi::MessageFlags::HasAttachment );
+  const qint64 number = uids.keys().first();
+  const Akonadi::Item remoteItem = MessageHelper::createItemFromMessage(messages[number], uids[number], 0, QList<QByteArray>(), fetch->scope());
+  i.setMimeType(remoteItem.mimeType());
+  i.setPayload(remoteItem.payload<KMime::Message::Ptr>());
+  foreach (const QByteArray &flag, remoteItem.flags()) {
+    i.setFlag(flag);
+  }
 
   kDebug( 5327 ) << "Has Payload: " << i.hasPayload();
 


commit 16fb40940c645f9334a8bacd202a524e2cc8b602
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Tue Apr 29 18:19:53 2014 +0200

    IMAP-Resource: Switched to uid based item retrieval
    
    Just using uid's instead of sequence numbers is a lot less error prone,
    and will make it easier in the future to i.e. only retrieve a certain
    range of messages.

diff --git a/resources/imap/retrieveitemstask.cpp b/resources/imap/retrieveitemstask.cpp
index baadf14..3581622 100644
--- a/resources/imap/retrieveitemstask.cpp
+++ b/resources/imap/retrieveitemstask.cpp
@@ -57,7 +57,6 @@ public:
     virtual void start();
     void fetchNextBatch();
     void setUidBased(bool);
-    qint64 lowestUidFetched() const;
 
 Q_SIGNALS:
     void itemsRetrieved(Akonadi::Item::List);
@@ -76,7 +75,7 @@ private:
     KIMAP::Session *m_session;
     int m_batchSize;
     bool m_uidBased;
-    qint64 m_lowestUid;
+    int m_fetchedItemsInCurrentBatch;
 };
 
 BatchFetcher::BatchFetcher(const KIMAP::ImapSet &set, const KIMAP::FetchJob::FetchScope& scope, int batchSize, KIMAP::Session* session)
@@ -86,7 +85,7 @@ BatchFetcher::BatchFetcher(const KIMAP::ImapSet &set, const KIMAP::FetchJob::Fet
     m_session(session),
     m_batchSize(batchSize),
     m_uidBased(false),
-    m_lowestUid(-1)
+    m_fetchedItemsInCurrentBatch(0)
 {
 }
 
@@ -112,14 +111,15 @@ void BatchFetcher::fetchNextBatch()
         emitResult();
         return;
     }
-    const KIMAP::ImapInterval interval = m_currentSet.intervals().first();
-    Q_ASSERT(interval.hasDefinedEnd());
 
     KIMAP::FetchJob *fetch = new KIMAP::FetchJob(m_session);
-    if (!m_uidBased) {
+    //In the most common case that we want optimized we use batch processing.
+    if (m_currentSet.intervals().size() == 1 && m_currentSet.intervals().first().hasDefinedEnd()) {
+        const KIMAP::ImapInterval interval = m_currentSet.intervals().first();
+        Q_ASSERT(interval.hasDefinedEnd());
         //Reverse fetching would be great, because it gives you the most relevant (recent) messages first,
-        //but since we usually just check the number of messages, we have the problem that we don't know
-        //whether the interval at the beginning or the end is missing. Therefore it's disabled for now
+        //but since we usually just check the latest uid, we wouldn't detect if an interval
+        //at the beginning is missing. Therefore this is disabled for now.
 
         //get an interval of m_batchSize
 //         const qint64 end = interval.end();
@@ -141,13 +141,12 @@ void BatchFetcher::fetchNextBatch()
         }
         fetch->setSequenceSet(intervalToFetch);
     } else {
+        kDebug() << "Fetching all messages in one go";
         fetch->setSequenceSet(m_currentSet);
         m_currentSet = KIMAP::ImapSet();
     }
 
-    if (m_uidBased) {
-        fetch->setUidBased(true);
-    }
+    fetch->setUidBased(m_uidBased);
     fetch->setScope(m_scope);
     connect(fetch, SIGNAL(headersReceived(QString,QMap<qint64,qint64>,QMap<qint64,qint64>,QMap<qint64,KIMAP::MessageFlags>,QMap<qint64,KIMAP::MessagePtr>)),
             this, SLOT(onHeadersReceived(QString,QMap<qint64,qint64>,QMap<qint64,qint64>,QMap<qint64,KIMAP::MessageFlags>,QMap<qint64,KIMAP::MessagePtr>)) );
@@ -199,10 +198,8 @@ void BatchFetcher::onHeadersReceived(const QString &mailBox, const QMap<qint64,
         foreach (const QByteArray &flag, akonadiFlags) {
             i.setFlag(flag);
         }
-        if (m_lowestUid > uid || m_lowestUid == -1) {
-            m_lowestUid = uid;
-        }
         //kDebug( 5327 ) << "Flags: " << i.flags();
+        m_fetchedItemsInCurrentBatch++;
         addedItems << i;
     }
 //     kDebug() << addedItems.size();
@@ -221,11 +218,11 @@ void BatchFetcher::onHeadersFetchDone( KJob *job )
         emitResult();
         return;
     }
-}
-
-qint64 BatchFetcher::lowestUidFetched() const
-{
-    return m_lowestUid;
+    if (m_fetchedItemsInCurrentBatch < m_batchSize) {
+        fetchNextBatch();
+    } else {
+        m_fetchedItemsInCurrentBatch = 0;
+    }
 }
 
 
@@ -235,7 +232,7 @@ RetrieveItemsTask::RetrieveItemsTask( ResourceStateInterface::Ptr resource, QObj
   m_fetchedMissingBodies( -1 ),
   m_fetchMissingBodies( false ),
   m_batchFetcher( 0 ),
-  m_lowestExpectedUid( 0 )
+  m_uidBasedFetch( true )
 {
 
 }
@@ -405,7 +402,7 @@ void RetrieveItemsTask::onFinalSelectDone( KJob *job )
   quint64 highestModSeq = select->highestModSequence();
   const QList<QByteArray> flags = select->permanentFlags();
 
-  //The select job retrieves highestmodset whenever it's available, but in case of no CONDSTORE support we ignore it
+  //The select job retrieves highestmodseq whenever it's available, but in case of no CONDSTORE support we ignore it
   if ( !serverSupportsCondstore() ) {
     highestModSeq = 0;
   }
@@ -497,83 +494,62 @@ void RetrieveItemsTask::onFinalSelectDone( KJob *job )
     scope.mode = KIMAP::FetchJob::FetchScope::Full;
   }
 
-  qint64 realMessageCount = col.statistics().count();
-  if (realMessageCount < 0) {
-      realMessageCount = 0;
-  }
-  m_fetchedMissingBodies = -1;
+  const qint64 realMessageCount = col.statistics().count();
 
-  // First check the uidvalidity, if this has changed, it means the folder
-  // has been deleted and recreated. So we wipe out the messages and
-  // retrieve all.
-  if ( oldUidValidity != uidValidity && messageCount > 0 ) {
+  if (messageCount == 0) {
+    //Shortcut:
+    //If no messages are present on the server, clear local cash and finish
+    if (realMessageCount > 0) {
+      kDebug( 5327 ) << "No messages present so we are done, deleting local messages.";
+      itemsRetrieved(Akonadi::Item::List());
+    } else {
+      kDebug( 5327 ) << "No messages present so we are done";
+    }
+    taskComplete();
+  } else if (oldUidValidity != uidValidity) {
+    //If uidvalidity has changed our local cache is worthless and has to be refetched completely
     if (oldUidValidity != 0) {
-        kDebug( 5327 ) << "UIDVALIDITY check failed (" << oldUidValidity << "|"
+      kDebug( 5327 ) << "UIDVALIDITY check failed (" << oldUidValidity << "|"
                     << uidValidity << ") refetching " << mailBox;
     } else {
-        kDebug( 5327 ) << "Fetching complete mailbox " << mailBox;
+      kDebug( 5327 ) << "Fetching complete mailbox " << mailBox;
     }
 
     setTotalItems(messageCount);
-    retrieveItems(KIMAP::ImapSet(1, messageCount), scope, false);
-  } else if ( messageCount > realMessageCount && messageCount > 0 ) {
-    // The amount on the server is bigger than that we have in the cache
-    // that probably means that there is new mail. Fetch missing.
-    kDebug( 5327 ) << "Fetch missing: " << messageCount << " But: " << realMessageCount;
-
-    // Since we're only fetching new messages, we know the lowest expected uid
-    // If we get something lower, our realMessageCount doesn't correspond to that of the server,
-    // meaning we're missing some messages locally.
-    m_lowestExpectedUid = oldNextUid;
-    setTotalItems(messageCount - realMessageCount);
-    retrieveItems(KIMAP::ImapSet( realMessageCount + 1, messageCount ), scope, realMessageCount != 0);
-  } else if ( messageCount == realMessageCount && oldNextUid != nextUid
-           && oldNextUid != 0 && messageCount > 0 ) {
-    // amount is right but uidnext is different.... something happened
-    // behind our back...
-
-    qint64 startIndex = 1;
-    // one scenario we can recover from is that an equal amount of mails has been deleted and added while we were not looking
-    // the amount has to be less or equal to (nextUid - oldNextUid) due to strictly ascending UIDs
-    // so, we just have to reload the last (nextUid - oldNextUid) mails if the uidnext values seem sane
-    if ( oldNextUid < nextUid && oldNextUid != 0 ) {
-      startIndex = qMax( 1ll, messageCount - ( nextUid - oldNextUid ) );
-    }
-
-    Q_ASSERT( startIndex >= 1 );
-    Q_ASSERT( startIndex <= messageCount );
-
-    kDebug( 5327 ) << "UIDNEXT check failed, refetching mailbox: " << startIndex << messageCount;
-    setTotalItems(messageCount - startIndex);
-    retrieveItems(KIMAP::ImapSet( startIndex, messageCount ), scope, false);
-  } else if (!m_messageUidsMissingBody.isEmpty() ) {
+    retrieveItems(KIMAP::ImapSet(1, nextUid), scope, false, true);
+  } else if (!m_messageUidsMissingBody.isEmpty()) {
     //fetch missing uids
     m_fetchedMissingBodies = 0;
     setTotalItems(m_messageUidsMissingBody.size());
     KIMAP::ImapSet imapSet;
-    imapSet.add( m_messageUidsMissingBody );
+    imapSet.add(m_messageUidsMissingBody);
     retrieveItems(imapSet, scope, true, true);
-  } else if ( messageCount > 0 ) {
-    if ( messageCount < realMessageCount ) {
-        // Some messages were removed, list all flags to find out which messages
-        // are missing
-        kDebug( 5327 ) << ( realMessageCount - messageCount ) << "messages were removed from maildir";
-        m_incremental = false;
+  } else if (nextUid > oldNextUid) {
+    //New messages are available. Fetch new messages, and then check for changed flags and removed messages
+    kDebug( 5327 ) << "Fetching new messages: UidNext: " << nextUid << " Old UidNext: " << oldNextUid;
+    setTotalItems(qMax(1ll, messageCount - realMessageCount));
+    retrieveItems(KIMAP::ImapSet(qMax(1, oldNextUid), nextUid), scope, false, true);
+  } else if (messageCount == realMessageCount && oldNextUid == nextUid) {
+    //Optimization:
+    //We know no messages were added or removed (if the message count and uidnext is still the same)
+    //We only check the flags incrementally and can make use of modseq
+    if (!serverSupportsCondstore() || highestModSeq > oldHighestModSeq) {
+      m_uidBasedFetch = true;
+      m_incremental = true;
+      setTotalItems(messageCount);
+      listFlagsForImapSet(KIMAP::ImapSet(1, nextUid));
     } else {
-        kDebug( 5327 ) << "All fine, asking for changed flags looking for changes";
-        m_incremental = true;
+      kDebug(5327)  << "No changes";
+      m_incremental = true;
+      taskComplete();
     }
-    setTotalItems(messageCount);
-    listFlagsForImapSet( KIMAP::ImapSet( 1, messageCount ));
   } else {
+    //Shortcut:
+    //No new messages are available. Directly check for changed flags and removed messages.
+    m_uidBasedFetch = true;
     m_incremental = false;
-    if (realMessageCount > 0) {
-        kDebug( 5327 ) << "No messages present so we are done, deleting local messages.";
-        itemsRetrieved(Akonadi::Item::List());
-    } else {
-        kDebug( 5327 ) << "No messages present so we are done";
-    }
-    taskComplete();
+    setTotalItems(messageCount);
+    listFlagsForImapSet(KIMAP::ImapSet(1, nextUid));
   }
 }
 
@@ -582,9 +558,10 @@ void RetrieveItemsTask::retrieveItems(const KIMAP::ImapSet& set, const KIMAP::Fe
   Q_ASSERT(set.intervals().size() == 1);
 
   m_incremental = incremental;
+  m_uidBasedFetch = uidBased;
 
   m_batchFetcher = new BatchFetcher(set, scope, batchSize(), m_session);
-  m_batchFetcher->setUidBased(uidBased);
+  m_batchFetcher->setUidBased(m_uidBasedFetch);
   m_batchFetcher->setProperty("alreadyFetched", set.intervals().first().begin());
   connect(m_batchFetcher, SIGNAL(itemsRetrieved(Akonadi::Item::List)),
           this, SLOT(onItemsRetrieved(Akonadi::Item::List)));
@@ -627,20 +604,6 @@ void RetrieveItemsTask::onRetrievalDone( KJob *job )
         return;
     }
 
-    BatchFetcher *batchFetcher = static_cast<BatchFetcher*>(job);
-    if (batchFetcher->lowestUidFetched() < m_lowestExpectedUid) {
-        //We just fetched an item we already have,
-        //we probably miss some messages on the lower end.
-        Akonadi::Collection col = collection();
-        //This will trigger a complete resync
-        col.removeAttribute("uidvalidity");
-        applyCollectionChanges(col);
-        kWarning() << "Inconsistent cache, retrying sync." << batchFetcher->lowestUidFetched() << m_lowestExpectedUid;
-        //We cannot use deferTask because that doesn't refetch the collection and we rely on up-to date annotations
-        restartItemRetrieval(col.id());
-        return;
-    }
-
     //This is the lowest sequence number that we just fetched.
     const KIMAP::ImapSet::Id alreadyFetchedBegin = job->property("alreadyFetched").value<KIMAP::ImapSet::Id>();
 
@@ -661,6 +624,8 @@ void RetrieveItemsTask::onRetrievalDone( KJob *job )
 
 void RetrieveItemsTask::listFlagsForImapSet( const KIMAP::ImapSet& set )
 {
+  kDebug(5327) << "Listing flags " << set.intervals().first().begin() << set.intervals().first().end();
+
   KIMAP::FetchJob::FetchScope scope;
   scope.parts.clear();
   scope.mode = KIMAP::FetchJob::FetchScope::Flags;
@@ -673,6 +638,7 @@ void RetrieveItemsTask::listFlagsForImapSet( const KIMAP::ImapSet& set )
   KIMAP::FetchJob* fetch = new KIMAP::FetchJob( m_session );
   fetch->setSequenceSet( set );
   fetch->setScope( scope );
+  fetch->setUidBased(m_uidBasedFetch);
   connect( fetch, SIGNAL(headersReceived(QString,QMap<qint64,qint64>,QMap<qint64,qint64>,QMap<qint64,KIMAP::MessageFlags>,QMap<qint64,KIMAP::MessagePtr>)),
            this, SLOT(onFlagsReceived(QString,QMap<qint64,qint64>,QMap<qint64,qint64>,QMap<qint64,KIMAP::MessageFlags>,QMap<qint64,KIMAP::MessagePtr>)) );
   connect( fetch, SIGNAL(result(KJob*)),
@@ -702,7 +668,6 @@ void RetrieveItemsTask::onFlagsReceived( const QString &mailBox, const QMap<qint
   }
 
   if ( !changedItems.isEmpty() ) {
-    KIMAP::FetchJob *fetch = static_cast<KIMAP::FetchJob*>( sender() );
     if ( m_incremental ) {
         itemsRetrievedIncremental( changedItems, Akonadi::Item::List() );
     } else {
diff --git a/resources/imap/retrieveitemstask.h b/resources/imap/retrieveitemstask.h
index 41787bf..226894c 100644
--- a/resources/imap/retrieveitemstask.h
+++ b/resources/imap/retrieveitemstask.h
@@ -81,8 +81,8 @@ private:
   qint64 m_highestModseq;
   BatchFetcher *m_batchFetcher;
   bool m_collectionModifyNeeded;
-  qint64 m_lowestExpectedUid;
   Akonadi::Collection m_modifiedCollection;
+  bool m_uidBasedFetch;
 };
 
 #endif
diff --git a/resources/imap/tests/testretrieveitemstask.cpp b/resources/imap/tests/testretrieveitemstask.cpp
index 4b8e93b..24de458 100644
--- a/resources/imap/tests/testretrieveitemstask.cpp
+++ b/resources/imap/tests/testretrieveitemstask.cpp
@@ -60,7 +60,7 @@ private slots:
              << "S: * OK [ UIDVALIDITY 1149151135  ]"
              << "S: * OK [ UIDNEXT 2471  ]"
              << "S: A000005 OK select done"
-             << "C: A000006 FETCH 1 (RFC822.SIZE INTERNALDATE "
+             << "C: A000006 UID FETCH 1:2471 (RFC822.SIZE INTERNALDATE "
                 "BODY.PEEK[HEADER] "
                 "FLAGS UID)"
              << "S: * 1 FETCH ( FLAGS (\\Seen) UID 2321 INTERNALDATE \"29-Jun-2010 15:26:42 +0200\" "
@@ -101,7 +101,7 @@ private slots:
              << "S: * OK [ UIDVALIDITY 1149151135  ]"
              << "S: * OK [ UIDNEXT 2471  ]"
              << "S: A000005 OK select done"
-             << "C: A000006 FETCH 1 (RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)"
+             << "C: A000006 UID FETCH 1:2471 (RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)"
              << "S: * 1 FETCH ( FLAGS (\\Seen) UID 2321 INTERNALDATE \"29-Jun-2010 15:26:42 +0200\" "
                 "RFC822.SIZE 75 BODY[] {75}\r\n"
                 "From: Foo <foo at kde.org>\r\n"
@@ -115,16 +115,15 @@ private slots:
     callNames.clear();
     callNames << "itemsRetrieved" << "applyCollectionChanges" << "itemsRetrievalDone";
 
-
     QTest::newRow( "first listing, disconnected IMAP" ) << collection << scenario << callNames;
 
 
-
     Akonadi::CollectionStatistics stats;
     stats.setCount( 1 );
 
     collection = createCollectionChain( QLatin1String("/INBOX/Foo") );
     collection.attribute<UidValidityAttribute>(Akonadi::Entity::AddIfMissing)->setUidValidity(1149151135);
+    collection.attribute<UidNextAttribute>( Akonadi::Collection::AddIfMissing )->setUidNext(2471);
     collection.setCachePolicy( policy );
     collection.setStatistics( stats );
 
@@ -142,15 +141,14 @@ private slots:
              << "S: * OK [ UIDVALIDITY 1149151135  ]"
              << "S: * OK [ UIDNEXT 2471  ]"
              << "S: A000005 OK select done"
-             << "C: A000006 FETCH 1 (FLAGS UID)"
+             << "C: A000006 UID FETCH 1:2471 (FLAGS UID)"
              << "S: * 1 FETCH ( FLAGS (\\Seen) UID 2321 )"
              << "S: A000006 OK fetch done";
 
     callNames.clear();
     callNames << "itemsRetrievedIncremental" << "applyCollectionChanges" << "itemsRetrievedIncremental" << "itemsRetrievalDone";
 
-
-    QTest::newRow( "second listing, full sync" ) << collection << scenario << callNames;
+    QTest::newRow( "second listing, checking for flag changes" ) << collection << scenario << callNames;
 
 
     collection = createCollectionChain( QLatin1String("/INBOX/Foo") );
@@ -195,7 +193,7 @@ private slots:
              << "S: * OK [ UIDNEXT 2471  ]"
              << "S: * OK [ HIGHESTMODSEQ 123456789 ]"
              << "S: A000005 OK select done"
-             << "C: A000006 FETCH 4:5 (RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)"
+             << "C: A000006 UID FETCH 2470:2471 (RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)"
              << "S: * 4 FETCH ( FLAGS (\\Seen) UID 2470 INTERNALDATE \"29-Jun-2010 15:26:42 +0200\" "
                 "RFC822.SIZE 75 BODY[] {75}\r\n"
                 "From: Foo <foo at kde.org>\r\n"
@@ -213,7 +211,7 @@ private slots:
                 "Test\r\n"
                 " )"
              << "S: A000006 OK fetch done"
-             << "C: A000007 FETCH 1:3 (FLAGS UID)"
+             << "C: A000007 UID FETCH 1:2469 (FLAGS UID)"
              << "S: * 1 FETCH"
              << "S: * 2 FETCH"
              << "S: * 3 FETCH"
@@ -222,13 +220,14 @@ private slots:
     callNames.clear();
     callNames << "itemsRetrieved" << "applyCollectionChanges" << "itemsRetrievalDone";
 
-    QTest::newRow( "uidnext mismatch with recovery attempt" ) << collection << scenario << callNames;
+    QTest::newRow( "uidnext changed, fetch new messages and list flags" ) << collection << scenario << callNames;
+
 
     collection = createCollectionChain(QLatin1String("/INBOX/Foo") );
     collection.attribute<UidValidityAttribute>(Akonadi::Entity::AddIfMissing)->setUidValidity(1149151135);
     collection.setCachePolicy( policy );
     collection.attribute<UidNextAttribute>( Akonadi::Collection::AddIfMissing )->setUidNext( 2471 );
-    collection.attribute<HighestModSeqAttribute>( Akonadi::Entity::AddIfMissing )->setHighestModSeq( 123456788 );
+    collection.attribute<HighestModSeqAttribute>( Akonadi::Entity::AddIfMissing )->setHighestModSeq( 123456789 );
     stats.setCount( 5 );
     collection.setStatistics( stats );
     scenario.clear();
@@ -245,29 +244,27 @@ private slots:
              << "S: * OK [ UIDVALIDITY 1149151135 ]"
              << "S: * OK [ UIDNEXT 2471 ]"
              << "S: * OK [ HIGHESTMODSEQ 123456789 ]"
-             << "S: A000005 OK select done"
-             << "C: A000006 FETCH 1:5 (FLAGS UID) (CHANGEDSINCE 123456788)"
-             << "S: * 5 FETCH ( UID 2470 FLAGS () )"
-             << "S: A000006 OK fetch done";
+             << "S: A000005 OK select done";
     callNames.clear();
-    callNames << "itemsRetrievedIncremental" << "applyCollectionChanges" << "itemsRetrievedIncremental" << "itemsRetrievalDone";
+    callNames << "applyCollectionChanges" << "itemsRetrievedIncremental" << "itemsRetrievalDone";
 
+    //No flags have changed
     QTest::newRow( "highestmodseq test" ) << collection << scenario << callNames;
 
     collection = createCollectionChain(QLatin1String("/INBOX/Foo") );
-    collection.setCachePolicy( policy );
     collection.attribute<UidValidityAttribute>(Akonadi::Entity::AddIfMissing)->setUidValidity(1149151135);
+    collection.setCachePolicy( policy );
     collection.attribute<UidNextAttribute>( Akonadi::Collection::AddIfMissing )->setUidNext( 2471 );
     collection.attribute<HighestModSeqAttribute>( Akonadi::Entity::AddIfMissing )->setHighestModSeq( 123456788 );
     stats.setCount( 5 );
     collection.setStatistics( stats );
     scenario.clear();
-    scenario << defaultPoolConnectionScenario( QList<QByteArray>() << "XYMHIGHESTMODSEQ" )
-             << "C: A000003 SELECT \"INBOX/Foo\""
+    scenario << defaultPoolConnectionScenario( QList<QByteArray>() << "CONDSTORE" )
+             << "C: A000003 SELECT \"INBOX/Foo\" (CONDSTORE)"
              << "S: A000003 OK select done"
              << "C: A000004 EXPUNGE"
              << "S: A000004 OK expunge DONE"
-             << "C: A000005 SELECT \"INBOX/Foo\""
+             << "C: A000005 SELECT \"INBOX/Foo\" (CONDSTORE)"
              << "S: * FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)"
              << "S: * OK [ PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen) ]"
              << "S: * 5 EXISTS"
@@ -276,61 +273,45 @@ private slots:
              << "S: * OK [ UIDNEXT 2471 ]"
              << "S: * OK [ HIGHESTMODSEQ 123456789 ]"
              << "S: A000005 OK select done"
-             << "C: A000006 FETCH 1:5 (FLAGS UID)"
+             << "C: A000006 UID FETCH 1:2471 (FLAGS UID) (CHANGEDSINCE 123456788)"
              << "S: * 5 FETCH ( UID 2470 FLAGS () )"
              << "S: A000006 OK fetch done";
     callNames.clear();
     callNames << "itemsRetrievedIncremental" << "applyCollectionChanges" << "itemsRetrievedIncremental" << "itemsRetrievalDone";
 
-    QTest::newRow( "yahoo highestmodseq test" ) << collection << scenario << callNames;
+    //fetch only changed flags
+    QTest::newRow( "changedsince test" ) << collection << scenario << callNames;
 
     collection = createCollectionChain(QLatin1String("/INBOX/Foo") );
     collection.setCachePolicy( policy );
     collection.attribute<UidValidityAttribute>(Akonadi::Entity::AddIfMissing)->setUidValidity(1149151135);
     collection.attribute<UidNextAttribute>( Akonadi::Collection::AddIfMissing )->setUidNext( 2471 );
     collection.attribute<HighestModSeqAttribute>( Akonadi::Entity::AddIfMissing )->setHighestModSeq( 123456788 );
-    stats.setCount( 3 );
+    stats.setCount( 5 );
     collection.setStatistics( stats );
     scenario.clear();
-    scenario << defaultPoolConnectionScenario()
+    scenario << defaultPoolConnectionScenario( QList<QByteArray>() << "XYMHIGHESTMODSEQ" )
              << "C: A000003 SELECT \"INBOX/Foo\""
              << "S: A000003 OK select done"
              << "C: A000004 EXPUNGE"
-             << "S: A000004 OK expunge done"
+             << "S: A000004 OK expunge DONE"
              << "C: A000005 SELECT \"INBOX/Foo\""
              << "S: * FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)"
              << "S: * OK [ PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen) ]"
              << "S: * 5 EXISTS"
              << "S: * 0 RECENT"
-             << "S: * OK [ UIDVALIDITY 1149151135  ]"
-             << "S: * OK [ UIDNEXT 2471  ]"
+             << "S: * OK [ UIDVALIDITY 1149151135 ]"
+             << "S: * OK [ UIDNEXT 2471 ]"
              << "S: * OK [ HIGHESTMODSEQ 123456789 ]"
              << "S: A000005 OK select done"
-             << "C: A000006 FETCH 4:5 (RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)"
-             << "S: * 4 FETCH ( FLAGS (\\Seen) UID 2470 INTERNALDATE \"29-Jun-2010 15:26:42 +0200\" "
-                "RFC822.SIZE 75 BODY[] {75}\r\n"
-                "From: Foo <foo at kde.org>\r\n"
-                "To: Bar <bar at kde.org>\r\n"
-                "Subject: Test Mail\r\n"
-                "\r\n"
-                "Test\r\n"
-                " )"
-             << "S: * 5 FETCH ( FLAGS (\\Seen) UID 2471 INTERNALDATE \"29-Jun-2010 15:26:42 +0200\" "
-                "RFC822.SIZE 75 BODY[] {75}\r\n"
-                "From: Foo <foo at kde.org>\r\n"
-                "To: Bar <bar at kde.org>\r\n"
-                "Subject: Test Mail\r\n"
-                "\r\n"
-                "Test\r\n"
-                " )"
+             << "C: A000006 UID FETCH 1:2471 (FLAGS UID)"
+             << "S: * 5 FETCH ( UID 2470 FLAGS () )"
              << "S: A000006 OK fetch done";
-
     callNames.clear();
-    callNames << "itemsRetrievedIncremental" << "applyCollectionChanges" << "restartItemRetrieval";
+    callNames << "itemsRetrievedIncremental" << "applyCollectionChanges" << "itemsRetrievedIncremental" << "itemsRetrievalDone";
 
-    // We miss one item in the local collection, and therefore fetch the wrong items based
-    // the item count of the collection.
-    QTest::newRow( "Inconsistent local cache" ) << collection << scenario << callNames;
+    //Don't rely on yahoos highestmodseq implementation
+    QTest::newRow( "yahoo highestmodseq test" ) << collection << scenario << callNames;
 
 
     collection = createCollectionChain( QLatin1String("/INBOX/Foo") );
@@ -353,7 +334,7 @@ private slots:
              << "S: * OK [ UIDVALIDITY 1149151135  ]"
              << "S: * OK [ UIDNEXT 2471  ]"
              << "S: A000005 OK select done"
-             << "C: A000006 FETCH 1 (RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)"
+             << "C: A000006 UID FETCH 1:2471 (RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)"
              << "S: * 1 FETCH ( FLAGS (\\Seen) UID 2321 )"
              << "S: * 1 FETCH ( FLAGS (\\Seen) UID 2321 INTERNALDATE \"29-Jun-2010 15:26:42 +0200\" "
                 "RFC822.SIZE 75 BODY[] {75}\r\n"
@@ -368,7 +349,7 @@ private slots:
     callNames.clear();
     callNames << "itemsRetrieved" << "applyCollectionChanges" << "itemsRetrievalDone";
 
-    QTest::newRow( "uidvalidity" ) << collection << scenario << callNames;
+    QTest::newRow( "uidvalidity changed" ) << collection << scenario << callNames;
 
   }
 


commit 69655612e9c2c3aba112520ae2d2ae51b3a68406
Author: Kevin Ottens <ervin at kde.org>
Date:   Mon Apr 28 14:40:05 2014 +0200

    Insert a body to empty messages
    
    When we download an empty message, add to it a body containing a single
    space in it. This way we don't wrongly believe that the body is missing
    from the cache... it's just that there was no body to begin with.

diff --git a/resources/imap/retrieveitemstask.cpp b/resources/imap/retrieveitemstask.cpp
index 22ab2af..baadf14 100644
--- a/resources/imap/retrieveitemstask.cpp
+++ b/resources/imap/retrieveitemstask.cpp
@@ -161,13 +161,24 @@ void BatchFetcher::onHeadersReceived(const QString &mailBox, const QMap<qint64,
                                            const QMap<qint64, KIMAP::MessageFlags> &flags,
                                            const QMap<qint64, KIMAP::MessagePtr> &messages)
 {
+    KIMAP::FetchJob *fetch = static_cast<KIMAP::FetchJob*>( sender() );
+    Q_ASSERT( fetch );
+    KIMAP::FetchJob::FetchScope scope = fetch->scope();
+
     Akonadi::Item::List addedItems;
     foreach (qint64 number, uids.keys()) { //krazy:exclude=foreach
         const qint64 uid = uids[number];
         Akonadi::Item i;
+        KMime::Message::Ptr message( messages[number] );
+        // Sometimes messages might not have a body at all
+        if ( message->body().isEmpty() && scope.mode == KIMAP::FetchJob::FetchScope::Full ) {
+            // In that case put a space in as body so that it gets cached
+            // otherwise we'll wrongly believe the body part is missing from the cache
+            message->setBody( " " );
+        }
         i.setRemoteId(QString::number(uid));
         i.setMimeType(KMime::Message::mimeType());
-        i.setPayload(KMime::Message::Ptr(messages[number]));
+        i.setPayload(KMime::Message::Ptr(message));
         i.setSize(sizes[number]);
 
         // update status flags





More information about the commits mailing list