Branch 'kolab/integration/4.13.0' - 9 commits - akonadi/contact akonadi/itemcreatejob.cpp akonadi/itemcreatejob.h akonadi/itemdeletejob.cpp akonadi/itemsync.cpp akonadi/itemsync.h akonadi/protocolhelper.cpp akonadi/session_p.h akonadi/tagcreatejob.cpp akonadi/tageditwidget.cpp akonadi/tagmodifyjob.cpp akonadi/tests kholidays/holidays

Christian Mollekopf mollekopf at kolabsys.com
Thu May 8 12:03:16 CEST 2014


 akonadi/contact/contactgroupsearchjob.cpp |    2 
 akonadi/contact/contactsearchjob.cpp      |    2 
 akonadi/itemcreatejob.cpp                 |   85 +++++++++++
 akonadi/itemcreatejob.h                   |   30 ++++
 akonadi/itemdeletejob.cpp                 |    8 +
 akonadi/itemsync.cpp                      |  213 ++++--------------------------
 akonadi/itemsync.h                        |    5 
 akonadi/protocolhelper.cpp                |    2 
 akonadi/session_p.h                       |    2 
 akonadi/tagcreatejob.cpp                  |    6 
 akonadi/tageditwidget.cpp                 |    2 
 akonadi/tagmodifyjob.cpp                  |   10 -
 akonadi/tests/itemappendtest.cpp          |  101 +++++++++++++-
 akonadi/tests/itemappendtest.h            |    2 
 akonadi/tests/itemdeletetest.cpp          |   38 +++++
 akonadi/tests/itemsynctest.cpp            |   69 ++++++++-
 akonadi/tests/tagtest.cpp                 |   35 ++++
 kholidays/holidays/plan2/holiday_gr_el    |   89 ++++++------
 18 files changed, 450 insertions(+), 251 deletions(-)

New commits:
commit e9df106d2ccdcf0a5057713122c4ea3f2b622dec
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Wed May 7 01:10:41 2014 +0200

    Drop akonadi requirement to kolab version.

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6c5da8d..5b67f7a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -80,7 +80,7 @@ endif()
 
 if (NOT KDEPIM_ONLY_KLEO)
   #FindAkonadi.cmake is only there for compatibility reasons, but we don't want to use that.
-  set(Akonadi_MIN_VERSION "1.12.44")
+  set(Akonadi_MIN_VERSION "1.12.42")
   find_package(Akonadi ${Akonadi_MIN_VERSION} QUIET NO_MODULE)
   set_package_properties(Akonadi PROPERTIES DESCRIPTION "Akonadi server libraries" URL "http://pim.kde.org/akonadi" TYPE REQUIRED PURPOSE "Access to PIM storage and services")
 


commit 4b4dffa33201f5d7129052c4513e8a54736f0030
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Tue May 6 16:29:56 2014 +0200

    ItemSync: Use serverside merging.
    
    Instead of fetching all local items to check for changes, to then write modified items
    back to the server, we use the serverside merging instead.
    Additionally this patch allows to merge by gid instead of rid, simply by setting
    the gid additionally to the rid.
    By using the serverside merging, the code could be simplified and we only need
    to fetch the local items anymore to detect removed items when doing a full
    listing.
    
    This patch renders ItemSync::updateItem useless, but that should be ok as
    I couldn't find a single subclass of ItemSync, and I really don't
    expect there to be one out there.

diff --git a/akonadi/itemsync.cpp b/akonadi/itemsync.cpp
index 034226c..9f833e6 100644
--- a/akonadi/itemsync.cpp
+++ b/akonadi/itemsync.cpp
@@ -67,12 +67,10 @@ public:
         mFetchScope.fetchAllAttributes();
     }
 
-    void createLocalItem(const Item &item);
-    void modifyLocalItem(const Item &remoteItem, Akonadi::Item::Id localId);
+    void createOrMerge(const Item &item);
     void checkDone();
     void slotItemsReceived(const Item::List &items);
     void slotLocalListDone(KJob *job);
-    void slotLocalFetchDone(KJob *job);
     void slotLocalDeleteDone(KJob *);
     void slotLocalChangeDone(KJob *job);
     void execute();
@@ -82,14 +80,13 @@ public:
     void slotTransactionResult(KJob *job);
     void requestTransaction();
     Job *subjobParent() const;
-    void fetchLocalItems();
+    void fetchLocalItemsToDelete();
     QString jobDebuggingString() const /*Q_DECL_OVERRIDE*/;
     bool allProcessed() const;
 
     Q_DECLARE_PUBLIC(ItemSync)
     Collection mSyncCollection;
-    QSet<Akonadi::Item::Id> mUnprocessedLocalIds;
-    QHash<QString, Akonadi::Item::Id> mLocalIdByRid;
+    QSet<QString> mListedItems;
 
     ItemSync::TransactionMode mTransactionMode;
     TransactionSequence *mCurrentTransaction;
@@ -102,6 +99,7 @@ public:
     Akonadi::Item::List mRemovedRemoteItemQueue;
     Akonadi::Item::List mCurrentBatchRemoteItems;
     Akonadi::Item::List mCurrentBatchRemovedRemoteItems;
+    Akonadi::Item::List mItemsToDelete;
 
     // create counter
     int mPendingJobs;
@@ -120,7 +118,7 @@ public:
     int mBatchSize;
 };
 
-void ItemSyncPrivate::createLocalItem(const Item &item)
+void ItemSyncPrivate::createOrMerge(const Item &item)
 {
     Q_Q(ItemSync);
     // don't try to do anything in error state
@@ -129,57 +127,12 @@ void ItemSyncPrivate::createLocalItem(const Item &item)
     }
     mPendingJobs++;
     ItemCreateJob *create = new ItemCreateJob(item, mSyncCollection, subjobParent());
-    q->connect(create, SIGNAL(result(KJob*)), q, SLOT(slotLocalChangeDone(KJob*)));
-}
-
-void ItemSyncPrivate::modifyLocalItem(const Item &remoteItem, Akonadi::Item::Id localId)
-{
-    Q_Q(ItemSync);
-    // don't try to do anything in error state
-    if (q->error()) {
-        return;
-    }
-
-    //we fetch the local item to check if a modification is required and to make sure we have all parts
-    Akonadi::ItemFetchJob *fetchJob = new Akonadi::ItemFetchJob(Akonadi::Item(localId), subjobParent());
-    fetchJob->setFetchScope(mFetchScope);
-    fetchJob->fetchScope().setCacheOnly(true);
-    fetchJob->setDeliveryOption(ItemFetchJob::ItemGetter);
-    q->connect(fetchJob, SIGNAL(result(KJob*)), q, SLOT(slotLocalFetchDone(KJob*)));
-    fetchJob->setProperty("remoteItem", QVariant::fromValue(remoteItem));
-    mPendingJobs++;
-}
-
-void ItemSyncPrivate::slotLocalFetchDone(KJob *job)
-{
-    Q_Q(ItemSync);
-    mPendingJobs--;
-    if (job->error()) {
-        kWarning() << job->errorString();
-        checkDone();
-        return;
-    }
-    Akonadi::ItemFetchJob *fetchJob = static_cast<Akonadi::ItemFetchJob*>(job);
-    Akonadi::Item remoteItem = fetchJob->property("remoteItem").value<Akonadi::Item>();
-    if (fetchJob->items().isEmpty()) {
-        kWarning() << "Failed to fetch local item: " << remoteItem.remoteId() << remoteItem.gid();
-        checkDone();
-        return;
-    }
-    const Akonadi::Item localItem = fetchJob->items().first();
-    if (q->updateItem(localItem, remoteItem)) {
-        remoteItem.setId(localItem.id());
-        remoteItem.setRevision(localItem.revision());
-        remoteItem.setSize(localItem.size());
-        remoteItem.setRemoteId(localItem.remoteId());    // in case someone clears remoteId by accident
-        ItemModifyJob *mod = new ItemModifyJob(remoteItem, subjobParent());
-        mod->disableRevisionCheck();
-        q->connect(mod, SIGNAL(result(KJob*)), q, SLOT(slotLocalChangeDone(KJob*)));
-        mPendingJobs++;
+    if (!item.gid().isEmpty()) {
+        create->setMerge(ItemCreateJob::GID|ItemCreateJob::Silent);
     } else {
-        mProgress++;
+        create->setMerge(ItemCreateJob::RID|ItemCreateJob::Silent);
     }
-    checkDone();
+    q->connect(create, SIGNAL(result(KJob*)), q, SLOT(slotLocalChangeDone(KJob*)));
 }
 
 bool ItemSyncPrivate::allProcessed() const
@@ -323,103 +276,26 @@ void ItemSync::doStart()
 
 bool ItemSync::updateItem(const Item &storedItem, Item &newItem)
 {
-    Q_D(ItemSync);
-    // we are in error state, better not change anything at all anymore
-    if (error()) {
-        return false;
-    }
-
-    /*
-     * We know that this item has changed (as it is part of the
-     * incremental changed list), so we just put it into the
-     * storage.
-     */
-    if (d->mIncremental) {
-        return true;
-    }
-
-    if (newItem.d_func()->mClearPayload) {
-        return true;
-    }
-
-    // Check whether the remote revisions differ
-    if (storedItem.remoteRevision() != newItem.remoteRevision()) {
-        return true;
-    }
-
-    // Check whether the flags differ
-    if (storedItem.flags() != newItem.flags()) {
-        kDebug() << "Stored flags "  << storedItem.flags()
-                 << "new flags " << newItem.flags();
-        return true;
-    }
-
-    // Check whether the new item contains unknown parts
-    QSet<QByteArray> missingParts = newItem.loadedPayloadParts();
-    missingParts.subtract(storedItem.loadedPayloadParts());
-    if (!missingParts.isEmpty()) {
-        return true;
-    }
-
-    // ### FIXME SLOW!!!
-    // If the available part identifiers don't differ, check
-    // whether the content of the payload differs
-    if (newItem.hasPayload()
-        && storedItem.payloadData() != newItem.payloadData()) {
-        return true;
-     }
-
-    // check if remote attributes have been changed
-    foreach (Attribute *attr, newItem.attributes()) {
-        if (!storedItem.hasAttribute(attr->type())) {
-            return true;
-        }
-        if (attr->serialized() != storedItem.attribute(attr->type())->serialized()) {
-            return true;
-        }
-    }
-
+    Q_UNUSED(storedItem);
+    Q_UNUSED(newItem);
     return false;
 }
 
-void ItemSyncPrivate::fetchLocalItems()
+void ItemSyncPrivate::fetchLocalItemsToDelete()
 {
-    Q_Q( ItemSync );
-    ItemFetchJob* job;
+    Q_Q(ItemSync);
     if (mIncremental) {
-        //Try fetching the items so we have their id and know if they're available
-        const Akonadi::Item::List itemsToFetch = mCurrentBatchRemoteItems + mCurrentBatchRemovedRemoteItems;
-        if (itemsToFetch.isEmpty()) {
-            // The fetch job produces an error with an empty set
-            processBatch();
-            return;
-        }
-        // We need to fetch the items only to detect if they are new or modified
-        job = new ItemFetchJob(itemsToFetch, subjobParent());
-        job->fetchScope().setFetchRemoteIdentification(true);
-        job->fetchScope().setFetchModificationTime(false);
-        job->setCollection(mSyncCollection);
-        job->setDeliveryOption(ItemFetchJob::EmitItemsIndividually);
-        // We use this to check if items are available locally, so errors are inevitable
-        job->fetchScope().setIgnoreRetrievalErrors(true);
-        QObject::connect(job, SIGNAL(itemsReceived(Akonadi::Item::List)), q, SLOT(slotItemsReceived(Akonadi::Item::List)));
-    } else {
-        if (mFullListingDone) {
-            processBatch();
-            return;
-        }
-        //Otherwise we'll remove the created items again during the second run
-        mFullListingDone = true;
-        job = new ItemFetchJob(mSyncCollection, subjobParent());
-        job->fetchScope().setFetchRemoteIdentification(true);
-        job->fetchScope().setFetchModificationTime(false);
-        job->setDeliveryOption(ItemFetchJob::EmitItemsIndividually);
-        QObject::connect(job, SIGNAL(itemsReceived(Akonadi::Item::List)), q, SLOT(slotItemsReceived(Akonadi::Item::List)));
+        kFatal() << "This must not be called while in incremental mode";
+        return;
     }
-
+    ItemFetchJob *job = new ItemFetchJob(mSyncCollection, subjobParent());
+    job->fetchScope().setFetchRemoteIdentification(true);
+    job->fetchScope().setFetchModificationTime(false);
+    job->setDeliveryOption(ItemFetchJob::EmitItemsIndividually);
     // we only can fetch parts already in the cache, otherwise this will deadlock
     job->fetchScope().setCacheOnly(true);
 
+    QObject::connect(job, SIGNAL(itemsReceived(Akonadi::Item::List)), q, SLOT(slotItemsReceived(Akonadi::Item::List)));
     QObject::connect(job, SIGNAL(result(KJob*)), q, SLOT(slotLocalListDone(KJob*)));
     mPendingJobs++;
 }
@@ -431,13 +307,8 @@ void ItemSyncPrivate::slotItemsReceived(const Item::List &items)
         if (item.remoteId().isEmpty()) {
             continue;
         }
-        if (mLocalIdByRid.contains(item.remoteId())) {
-            kWarning() << "Found multiple items with the same rid : " << item.remoteId() << item.id();
-        } else {
-            mLocalIdByRid.insert(item.remoteId(), item.id());
-        }
-        if (!mIncremental) {
-            mUnprocessedLocalIds << item.id();
+        if (!mListedItems.contains(item.remoteId())) {
+            mItemsToDelete << Item(item.id());
         }
     }
 }
@@ -448,7 +319,8 @@ void ItemSyncPrivate::slotLocalListDone(KJob *job)
     if (job->error()) {
         kWarning() << job->errorString();
     }
-    processBatch();
+    deleteItems(mItemsToDelete);
+    checkDone();
 }
 
 QString ItemSyncPrivate::jobDebuggingString() const /*Q_DECL_OVERRIDE*/
@@ -482,7 +354,7 @@ void ItemSyncPrivate::execute()
             return;
         }
         mProcessingBatch = true;
-        fetchLocalItems();
+        processBatch();
         return;
     }
     checkDone();
@@ -502,29 +374,14 @@ void ItemSyncPrivate::processBatch()
     processItems();
 
     // removed
-    Akonadi::Item::List itemsToDelete;
     if (!mIncremental && allProcessed()) {
         //the full listing is done and we know which items to remove
-        foreach (Akonadi::Item::Id id, mUnprocessedLocalIds) {
-            itemsToDelete << Akonadi::Item(id);
-        }
-        mUnprocessedLocalIds.clear();
+        fetchLocalItemsToDelete();
     } else {
-        foreach (const Akonadi::Item &removedItem, mCurrentBatchRemovedRemoteItems) {
-            if (!mLocalIdByRid.contains(removedItem.remoteId())) {
-                kWarning() << "cannot remove item because it's not available locally. RID: " << removedItem.remoteId();
-                continue;
-            }
-            itemsToDelete << Akonadi::Item(mLocalIdByRid.value(removedItem.remoteId()));
-        }
+        deleteItems(mCurrentBatchRemovedRemoteItems);
         mCurrentBatchRemovedRemoteItems.clear();
     }
-    deleteItems(itemsToDelete);
 
-    if (mIncremental) {
-        //no longer required, we processed all items of the current batch
-        mLocalIdByRid.clear();
-    }
     checkDone();
 }
 
@@ -534,21 +391,13 @@ void ItemSyncPrivate::processItems()
     // added / updated
     foreach (const Item &remoteItem, mCurrentBatchRemoteItems) {
         if (remoteItem.remoteId().isEmpty()) {
-            kWarning() << "Item " << remoteItem.id() << " does not have a remote identifier";
+            kWarning() << "Item without rid passed to itemsync";
             continue;
         }
-
-        //TODO also check by id and gid
-        //Locally available
-        if (mLocalIdByRid.contains(remoteItem.remoteId())) {
-            const Akonadi::Item::Id localId = mLocalIdByRid.value(remoteItem.remoteId());
-            if (!mIncremental) {
-                mUnprocessedLocalIds.remove(localId);
-            }
-            modifyLocalItem(remoteItem, localId);
-        } else {
-            createLocalItem(remoteItem);
+        if (!mIncremental) {
+            mListedItems << remoteItem.remoteId();
         }
+        createOrMerge(remoteItem);
     }
     mCurrentBatchRemoteItems.clear();
 }
diff --git a/akonadi/itemsync.h b/akonadi/itemsync.h
index ab409f3..08729bd 100644
--- a/akonadi/itemsync.h
+++ b/akonadi/itemsync.h
@@ -236,8 +236,10 @@ protected:
      * @param newItem the item as it should be
      * You can update the @p newItem according to the @p storedItem before
      * it gets committed.
+     *
+     * @deprecated This method is disabled internally.
      */
-    virtual bool updateItem(const Item &storedItem, Item &newItem);
+    AKONADI_DEPRECATED virtual bool updateItem(const Item &storedItem, Item &newItem);
 
 private:
     //@cond PRIVATE
@@ -249,7 +251,6 @@ private:
     Q_PRIVATE_SLOT(d_func(), void slotLocalChangeDone(KJob *))
     Q_PRIVATE_SLOT(d_func(), void slotTransactionResult(KJob *))
     Q_PRIVATE_SLOT(d_func(), void slotItemsReceived(const Akonadi::Item::List &))
-    Q_PRIVATE_SLOT(d_func(), void slotLocalFetchDone(KJob *))
     //@endcond
 };
 
diff --git a/akonadi/tests/itemsynctest.cpp b/akonadi/tests/itemsynctest.cpp
index 2a2e22f..41a32c4 100644
--- a/akonadi/tests/itemsynctest.cpp
+++ b/akonadi/tests/itemsynctest.cpp
@@ -27,6 +27,7 @@
 #include <akonadi/itemfetchjob.h>
 #include <akonadi/itemfetchscope.h>
 #include <akonadi/itemsync.h>
+#include <akonadi/itemcreatejob.h>
 
 #include <krandom.h>
 
@@ -66,6 +67,14 @@ class ItemsyncTest : public QObject
       qRegisterMetaType<ItemSync::TransactionMode>();
     }
 
+    static Item modifyItem(Item item)
+    {
+      static int counter = 0;
+      item.setFlag(QByteArray("\\READ")+ QByteArray::number(counter));
+      counter++;
+      return item;
+    }
+
     void testFullSync()
     {
       const Collection col = Collection( collectionIdFromPath( "res1/foo" ) );
@@ -146,7 +155,8 @@ class ItemsyncTest : public QObject
 
       for ( int i = 0; i < origItems.count(); ++i ) {
         Item::List l;
-        l << origItems[i];
+        //Modify to trigger a changed signal
+        l << modifyItem(origItems[i]);
         syncer->setFullSyncItems( l );
         if (goToEventLoopAfterAddingItems) {
           QTest::qWait(0);
@@ -174,7 +184,7 @@ class ItemsyncTest : public QObject
       QTest::qWait(100);
       QTRY_COMPARE(deletedSpy.count(), 0);
       QTRY_COMPARE(addedSpy.count(), 0);
-      QTRY_COMPARE(changedSpy.count(), 0);
+      QTRY_COMPARE(changedSpy.count(), origItems.count());
     }
 
     void testIncrementalSync()
@@ -207,7 +217,7 @@ class ItemsyncTest : public QObject
       QTest::qWait(100);
       QTRY_COMPARE(deletedSpy.count(), 0);
       QCOMPARE(addedSpy.count(), 0);
-      QTRY_COMPARE(changedSpy.count(), origItems.count());
+      QTRY_COMPARE(changedSpy.count(), 0);
       deletedSpy.clear();
       addedSpy.clear();
       changedSpy.clear();
@@ -243,7 +253,7 @@ class ItemsyncTest : public QObject
       QTest::qWait(100);
       QTRY_COMPARE(deletedSpy.count(), 2);
       QCOMPARE(addedSpy.count(), 0);
-      QTRY_COMPARE(changedSpy.count(), resultItems.count());
+      QTRY_COMPARE(changedSpy.count(), 0);
     }
 
     void testIncrementalStreamingSync()
@@ -274,7 +284,8 @@ class ItemsyncTest : public QObject
 
       for ( int i = 0; i < origItems.count(); ++i ) {
         Item::List l;
-        l << origItems[i];
+        //Modify to trigger a changed signal
+        l << modifyItem(origItems[i]);
         syncer->setIncrementalSyncItems( l, Item::List() );
         if ( i < origItems.count() - 1 ) {
           QTest::qWait( 0 ); // enter the event loop so itemsync actually can do something
@@ -296,7 +307,7 @@ class ItemsyncTest : public QObject
       QTest::qWait(100);
       QCOMPARE(deletedSpy.count(), 0);
       QCOMPARE(addedSpy.count(), 0);
-      QTRY_COMPARE(changedSpy.count(), origItems.count());
+      QTRY_COMPARE(changedSpy.count(), origItems.size());
     }
 
     void testEmptyIncrementalSync()
@@ -359,7 +370,8 @@ class ItemsyncTest : public QObject
 
       for ( int i = 0; i < syncer->batchSize(); ++i ) {
         Item::List l;
-        l << origItems[i];
+        //Modify to trigger a changed signal
+        l << modifyItem(origItems[i]);
         syncer->setIncrementalSyncItems( l, Item::List() );
         if ( i < (syncer->batchSize() - 1) ) {
           QTest::qWait( 0 ); // enter the event loop so itemsync actually can do something
@@ -373,7 +385,8 @@ class ItemsyncTest : public QObject
 
       for ( int i = syncer->batchSize(); i < origItems.count(); ++i ) {
         Item::List l;
-        l << origItems[i];
+        //Modify to trigger a changed signal
+        l << modifyItem(origItems[i]);
         syncer->setIncrementalSyncItems( l, Item::List() );
         if ( i < origItems.count() - 1 ) {
           QTest::qWait( 0 ); // enter the event loop so itemsync actually can do something
@@ -395,6 +408,46 @@ class ItemsyncTest : public QObject
       QTRY_COMPARE(changedSpy.count(), resultItems.count());
     }
 
+    void testGidMerge()
+    {
+       Collection col(collectionIdFromPath("res3"));
+       {
+          Item item("application/octet-stream");
+          item.setRemoteId("rid1");
+          item.setGid("gid1");
+          item.setPayload<QByteArray>("payload1");
+          ItemCreateJob *job = new ItemCreateJob(item, col);
+          AKVERIFYEXEC(job);
+       }
+       {
+          Item item("application/octet-stream");
+          item.setRemoteId("rid2");
+          item.setGid("gid2");
+          item.setPayload<QByteArray>("payload1");
+          ItemCreateJob *job = new ItemCreateJob(item, col);
+          AKVERIFYEXEC(job);
+       }
+       Item modifiedItem("application/octet-stream");
+       modifiedItem.setRemoteId("rid3");
+       modifiedItem.setGid("gid2");
+       modifiedItem.setPayload<QByteArray>("payload2");
+
+       ItemSync* syncer = new ItemSync(col);
+       syncer->setTransactionMode(ItemSync::MultipleTransactions);
+       syncer->setIncrementalSyncItems(Item::List() << modifiedItem, Item::List());
+       AKVERIFYEXEC(syncer);
+
+       Item::List resultItems = fetchItems(col);
+       QCOMPARE(resultItems.count(), 2);
+
+       ItemFetchJob *fetchJob = new ItemFetchJob(modifiedItem);
+       fetchJob->fetchScope().fetchFullPayload();
+       AKVERIFYEXEC(fetchJob);
+       QCOMPARE(fetchJob->items().size(), 1);
+       QCOMPARE(fetchJob->items().first().payload<QByteArray>(), QByteArray("payload2"));
+       QCOMPARE(fetchJob->items().first().remoteId(), QString::fromLatin1("rid3"));
+    }
+
 };
 
 QTEST_AKONADIMAIN( ItemsyncTest, NoGUI )


commit 1c5dfc3388d9f8bbfcf08e14c22a10dead69fc22
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Wed May 7 00:17:22 2014 +0200

    TagCreateJob/TagModifyJob: tag type support + proper REMOTEID key + only finish job once complete.
    
    REVIEW: 117771

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 53b3501..6c5da8d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -80,7 +80,7 @@ endif()
 
 if (NOT KDEPIM_ONLY_KLEO)
   #FindAkonadi.cmake is only there for compatibility reasons, but we don't want to use that.
-  set(Akonadi_MIN_VERSION "1.12.43")
+  set(Akonadi_MIN_VERSION "1.12.44")
   find_package(Akonadi ${Akonadi_MIN_VERSION} QUIET NO_MODULE)
   set_package_properties(Akonadi PROPERTIES DESCRIPTION "Akonadi server libraries" URL "http://pim.kde.org/akonadi" TYPE REQUIRED PURPOSE "Access to PIM storage and services")
 
diff --git a/akonadi/protocolhelper.cpp b/akonadi/protocolhelper.cpp
index fe20c58..d9f30da 100644
--- a/akonadi/protocolhelper.cpp
+++ b/akonadi/protocolhelper.cpp
@@ -604,6 +604,8 @@ void ProtocolHelper::parseTagFetchResult( const QList<QByteArray> &lineTokens, T
       tag.setRemoteId(value);
     } else if (key == "PARENT") {
       tag.setParent(Tag(value.toLongLong()));
+    } else if ( key == "MIMETYPE" ) {
+      tag.setType(value);
     } else {
       Attribute *attr = AttributeFactory::createAttribute(key);
       if (!attr) {
diff --git a/akonadi/session_p.h b/akonadi/session_p.h
index 2f034ef..40e3fe6 100644
--- a/akonadi/session_p.h
+++ b/akonadi/session_p.h
@@ -116,7 +116,7 @@ public:
 
     static int minimumProtocolVersion()
     {
-        return 38;
+        return 39;
     }
 
     /**
diff --git a/akonadi/tagcreatejob.cpp b/akonadi/tagcreatejob.cpp
index a9e8408..5e6ce18 100644
--- a/akonadi/tagcreatejob.cpp
+++ b/akonadi/tagcreatejob.cpp
@@ -73,8 +73,12 @@ void TagCreateJob::doStart()
         list << "MERGE";
     }
 
+    if (!d->mTag.type().isEmpty()) {
+        list << "MIMETYPE";
+        list << ImapParser::quote(d->mTag.type());
+    }
     if (!d->mTag.remoteId().isEmpty()) {
-        list << "RID";
+        list << "REMOTEID";
         list << ImapParser::quote(d->mTag.remoteId());
     }
     if (d->mTag.parent().isValid()) {
diff --git a/akonadi/tagmodifyjob.cpp b/akonadi/tagmodifyjob.cpp
index 338c3f6..c4f8432 100644
--- a/akonadi/tagmodifyjob.cpp
+++ b/akonadi/tagmodifyjob.cpp
@@ -48,9 +48,13 @@ void TagModifyJob::doStart()
 
     QList<QByteArray> list;
     if (!d->mTag.remoteId().isEmpty()) {
-        list << "RID";
+        list << "REMOTEID";
         list << ImapParser::quote(d->mTag.remoteId());
     }
+    if (!d->mTag.type().isEmpty()) {
+        list << "MIMETYPE";
+        list << ImapParser::quote(d->mTag.type());
+    }
     if (d->mTag.parent().isValid() && !d->mTag.isImmutable()) {
         list << "PARENT";
         list << QString::number(d->mTag.parent().id()).toLatin1();
@@ -92,8 +96,6 @@ void TagModifyJob::doHandleResponse(const QByteArray &tag, const QByteArray &dat
 
     if (data.startsWith("OK")) {     //krazy:exclude=strings
         ChangeMediator::invalidateTag(d->mTag);
+        emitResult();
     }
-
-    emitResult();
-    return;
 }
diff --git a/akonadi/tests/tagtest.cpp b/akonadi/tests/tagtest.cpp
index 9b24b12..6595464 100644
--- a/akonadi/tests/tagtest.cpp
+++ b/akonadi/tests/tagtest.cpp
@@ -28,6 +28,7 @@
 #include <akonadi/tagattribute.h>
 #include <akonadi/tagfetchscope.h>
 #include <tagmodifyjob.h>
+#include <resourceselectjob_p.h>
 #include <akonadi/qtest_akonadi.h>
 #include <akonadi/item.h>
 #include <akonadi/itemcreatejob.h>
@@ -47,6 +48,7 @@ private Q_SLOTS:
     void initTestCase();
 
     void testCreateFetch();
+    void testRID();
     void testDelete();
     void testModify();
     void testCreateMerge();
@@ -69,6 +71,7 @@ void TagTest::testCreateFetch()
 {
     Tag tag;
     tag.setGid("gid");
+    tag.setType("mytype");
     TagCreateJob *createjob = new TagCreateJob(tag, this);
     AKVERIFYEXEC(createjob);
     QVERIFY(createjob->tag().isValid());
@@ -78,6 +81,7 @@ void TagTest::testCreateFetch()
         AKVERIFYEXEC(fetchJob);
         QCOMPARE(fetchJob->tags().size(), 1);
         QCOMPARE(fetchJob->tags().first().gid(), QByteArray("gid"));
+        QCOMPARE(fetchJob->tags().first().type(), QByteArray("mytype"));
         kDebug() << fetchJob->tags().first().id();
 
         TagDeleteJob *deleteJob = new TagDeleteJob(fetchJob->tags().first(), this);
@@ -91,6 +95,37 @@ void TagTest::testCreateFetch()
     }
 }
 
+void TagTest::testRID()
+{
+    {
+        ResourceSelectJob *select = new ResourceSelectJob("akonadi_knut_resource_0");
+        AKVERIFYEXEC(select);
+    }
+    Tag tag;
+    tag.setGid("gid");
+    tag.setType("mytype");
+    tag.setRemoteId("rid");
+    TagCreateJob *createjob = new TagCreateJob(tag, this);
+    AKVERIFYEXEC(createjob);
+    QVERIFY(createjob->tag().isValid());
+
+    {
+        TagFetchJob *fetchJob = new TagFetchJob(this);
+        AKVERIFYEXEC(fetchJob);
+        QCOMPARE(fetchJob->tags().size(), 1);
+        QCOMPARE(fetchJob->tags().first().gid(), QByteArray("gid"));
+        QCOMPARE(fetchJob->tags().first().type(), QByteArray("mytype"));
+        QCOMPARE(fetchJob->tags().first().remoteId(), QByteArray("rid"));
+
+        TagDeleteJob *deleteJob = new TagDeleteJob(fetchJob->tags().first(), this);
+        AKVERIFYEXEC(deleteJob);
+    }
+    {
+        ResourceSelectJob *select = new ResourceSelectJob("");
+        AKVERIFYEXEC(select);
+    }
+}
+
 void TagTest::testDelete()
 {
     Tag tag1;


commit fd6439ba8a6ac9c1daf862710b98500199459fff
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date:   Tue May 6 16:02:23 2014 +0200

    ItemCreateJob: Explicit merge API + support for incremental changes + tag support.
    
    * Merge only by certain identifiers.
    * Only merge certain parts of an item (such as flags)
    * allow to pass tags with a created/merged item.

diff --git a/akonadi/itemcreatejob.cpp b/akonadi/itemcreatejob.cpp
index 10b978c..621ad6a 100644
--- a/akonadi/itemcreatejob.cpp
+++ b/akonadi/itemcreatejob.cpp
@@ -23,6 +23,7 @@
 #include "collection.h"
 #include "imapparser_p.h"
 #include "item.h"
+#include "item_p.h"
 #include "itemserializer_p.h"
 #include "job_p.h"
 #include "protocolhelper_p.h"
@@ -39,7 +40,8 @@ class Akonadi::ItemCreateJobPrivate : public JobPrivate
 public:
     ItemCreateJobPrivate(ItemCreateJob *parent)
         : JobPrivate(parent)
-        , mMerge(false)
+        , mMergeOptions(ItemCreateJob::NoMerge)
+        , mItemReceived(false)
     {
     }
 
@@ -51,7 +53,8 @@ public:
     Item::Id mUid;
     QDateTime mDatetime;
     QByteArray mPendingData;
-    bool mMerge;
+    ItemCreateJob::MergeOptions mMergeOptions;
+    bool mItemReceived;
 };
 
 QByteArray ItemCreateJobPrivate::nextPartHeader()
@@ -106,7 +109,6 @@ void ItemCreateJob::doStart()
     QList<QByteArray> flags;
     flags.append("\\MimeType[" + d->mItem.mimeType().toLatin1() + ']');
     const QString gid = GidExtractor::getGid(d->mItem);
-    const bool merge = d->mMerge && !d->mItem.gid().isNull();
     if (!gid.isNull()) {
         flags.append(ImapParser::quote("\\Gid[" + gid.toUtf8() + ']'));
     }
@@ -116,17 +118,48 @@ void ItemCreateJob::doStart()
     if (!d->mItem.remoteRevision().isEmpty()) {
         flags.append(ImapParser::quote("\\RemoteRevision[" + d->mItem.remoteRevision().toUtf8() + ']'));
     }
-    flags += d->mItem.flags().toList();
+    const bool mergeByGid = (d->mMergeOptions & GID) && !d->mItem.gid().isEmpty();
+    const bool mergeByRid = (d->mMergeOptions & RID) && !d->mItem.remoteId().isEmpty();
+    const bool mergeSilent = (d->mMergeOptions & Silent);
+    const bool merge = mergeByGid || mergeByRid;
+    if (d->mItem.d_func()->mFlagsOverwritten || !merge) {
+        flags += d->mItem.flags().toList();
+    } else {
+        Q_FOREACH(const QByteArray &flag, d->mItem.d_func()->mAddedFlags.toList()) {
+            flags += "+" + flag;
+        }
+        Q_FOREACH(const QByteArray &flag, d->mItem.d_func()->mDeletedFlags.toList()) {
+            flags += "-" + flag;
+        }
+    }
+    if (d->mItem.d_func()->mTagsOverwritten || !merge) {
+        Q_FOREACH(const Akonadi::Tag &tag, d->mItem.d_func()->mAddedTags) {
+            flags += "TAG " + tag.remoteId();
+        }
+    } else {
+        Q_FOREACH(const Akonadi::Tag &tag, d->mItem.d_func()->mAddedTags) {
+            flags += "+TAG " + tag.remoteId();
+        }
+        Q_FOREACH(const Akonadi::Tag &tag, d->mItem.d_func()->mDeletedTags) {
+            flags += "-TAG " + tag.remoteId();
+        }
+    }
 
     QByteArray command = d->newTag();
     if (merge) {
-      command += " MERGE (GID";
-      if (!d->mItem.remoteId().isEmpty()) {
-        command += " REMOTEID";
-      }
-      command += ") ";
+        QList<QByteArray> mergeArgs;
+        if (mergeByGid) {
+            mergeArgs << "GID";
+        }
+        if (mergeByRid) {
+            mergeArgs << "REMOTEID";
+        }
+        if (mergeSilent) {
+            mergeArgs << "SILENT";
+        }
+        command += " MERGE (" + ImapParser::join(mergeArgs, " ") + ") ";
     } else {
-      command += " X-AKAPPEND ";
+        command += " X-AKAPPEND ";
     }
 
     command += QByteArray::number(d->mCollection.id())
@@ -164,6 +197,7 @@ void ItemCreateJob::doHandleResponse(const QByteArray &tag, const QByteArray &da
             // Error, maybe?
             return;
           }
+          d->mItemReceived = true;
           d->mItem = item;
         }
         return;
@@ -189,17 +223,21 @@ void ItemCreateJob::doHandleResponse(const QByteArray &tag, const QByteArray &da
     }
 }
 
-void ItemCreateJob::setMergeIfExists(bool merge)
+void ItemCreateJob::setMerge(ItemCreateJob::MergeOptions options)
 {
-  Q_D(ItemCreateJob);
+    Q_D(ItemCreateJob);
 
-  d->mMerge = merge;
+    d->mMergeOptions = options;
 }
 
 Item ItemCreateJob::item() const
 {
     Q_D(const ItemCreateJob);
 
+    if (d->mItemReceived) {
+        return d->mItem;
+    }
+
     if (d->mUid == 0) {
         return Item();
     }
diff --git a/akonadi/itemcreatejob.h b/akonadi/itemcreatejob.h
index b55ae3a..f4da1f4 100644
--- a/akonadi/itemcreatejob.h
+++ b/akonadi/itemcreatejob.h
@@ -95,23 +95,33 @@ public:
      */
     Item item() const;
 
+    enum MergeOption {
+      NoMerge = 0, ///< Don't merge
+      RID     = 1, ///< Merge by remote id
+      GID     = 2, ///< Merge by GID
+      Silent  = 4  ///< Only return the id of the merged/created item.
+    };
+    Q_DECLARE_FLAGS(MergeOptions, MergeOption)
+
     /**
-     * Merge this item into an existing one
+     * Merge this item into an existing one if available.
+     *
+     * If an item with same GID and/or remote ID as the created item exists in
+     * specified collection (depending on the provided options), the new item will
+     * be merged into the existing one and the merged item will be returned
+     * (unless the Silent option is used).
      *
-     * If an item with same GID and remote ID as the created item exists in
-     * specified collection, the new item will be merged into the existing one
-     * and the merged item will be returned.
+     * If no matching item is found a new item is created.
      *
-     * If the new item does not have remote ID specified, only GID-based merging
-     * will be performed. If the item does not have GID, this option will be
+     * If the item does not have a GID or RID, this option will be
      * ignored and a new item will be created.
      *
      * By default, merging is disabled.
      *
-     * @param merge Whether to enable or disable merging
+     * @param options Merge options.
      * @since 4.14
      */
-    void setMergeIfExists(bool merge);
+    void setMerge(MergeOptions options);
 
 protected:
     virtual void doStart();
@@ -121,6 +131,8 @@ private:
     Q_DECLARE_PRIVATE(ItemCreateJob)
 };
 
+Q_DECLARE_OPERATORS_FOR_FLAGS(ItemCreateJob::MergeOptions)
+
 }
 
 #endif
diff --git a/akonadi/tests/itemappendtest.cpp b/akonadi/tests/itemappendtest.cpp
index 354f272..873d1a0 100644
--- a/akonadi/tests/itemappendtest.cpp
+++ b/akonadi/tests/itemappendtest.cpp
@@ -271,27 +271,53 @@ void ItemAppendTest::testItemMerge_data()
   QTest::addColumn<Akonadi::Item>( "item1" );
   QTest::addColumn<Akonadi::Item>( "item2" );
   QTest::addColumn<Akonadi::Item>( "mergedItem" );
-
-  Item i1( "application/octet-stream" );
-  i1.setPayload( QByteArray( "ABCD" ) );
-  i1.setSize( 4 );
-  i1.setRemoteId( "XYZ" );
-  i1.setGid( "XYZ" );
-  i1.setFlag( "TestFlag1" );
-  i1.setRemoteRevision( "5" );
-
-  Item i2( "application/octet-stream" );
-  i2.setPayload( QByteArray( "DEFGH" ) );
-  i2.setSize( 5 );
-  i2.setRemoteId(( "XYZ" ) );
-  i2.setGid( "XYZ" );
-  i2.setFlag( "TestFlag2" );
-  i2.setRemoteRevision( "6" );
-
-  Item mergedItem( i2 );
-  mergedItem.setFlag( "TestFlag1" );
-
-  QTest::newRow( "ok merge" ) << i1 << i2 << mergedItem;
+  QTest::addColumn<bool>( "silent" );
+
+  {
+    Item i1( "application/octet-stream" );
+    i1.setPayload( QByteArray( "ABCD" ) );
+    i1.setSize( 4 );
+    i1.setRemoteId( "XYZ" );
+    i1.setGid( "XYZ" );
+    i1.setFlag( "TestFlag1" );
+    i1.setRemoteRevision( "5" );
+
+    Item i2( "application/octet-stream" );
+    i2.setPayload( QByteArray( "DEFGH" ) );
+    i2.setSize( 5 );
+    i2.setRemoteId(( "XYZ" ) );
+    i2.setGid( "XYZ" );
+    i2.setFlag( "TestFlag2" );
+    i2.setRemoteRevision( "6" );
+
+    Item mergedItem( i2 );
+    mergedItem.setFlag( "TestFlag1" );
+
+    QTest::newRow( "merge" ) << i1 << i2 << mergedItem << false;
+    QTest::newRow( "merge (silent)" ) << i1 << i2 << mergedItem << true;
+  }
+  {
+    Item i1( "application/octet-stream" );
+    i1.setPayload( QByteArray( "ABCD" ) );
+    i1.setSize( 4 );
+    i1.setRemoteId( "RID2" );
+    i1.setGid( "GID2" );
+    i1.setFlag( "TestFlag1" );
+    i1.setRemoteRevision( "5" );
+
+    Item i2( "application/octet-stream" );
+    i2.setRemoteId(( "RID2" ) );
+    i2.setGid( "GID2" );
+    i2.setFlags( Item::Flags() << "TestFlag2" );
+    i2.setRemoteRevision( "6" );
+
+    Item mergedItem( i1 );
+    mergedItem.setFlags( i2.flags() );
+    mergedItem.setRemoteRevision( i2.remoteRevision() );
+
+    QTest::newRow( "overwrite flags, and don't remove existing payload" ) << i1 << i2 << mergedItem << false;
+    QTest::newRow( "overwrite flags, and don't remove existing payload (silent)" ) << i1 << i2 << mergedItem << true;
+  }
 }
 
 void ItemAppendTest::testItemMerge()
@@ -299,22 +325,38 @@ void ItemAppendTest::testItemMerge()
   QFETCH( Akonadi::Item, item1 );
   QFETCH( Akonadi::Item, item2 );
   QFETCH( Akonadi::Item, mergedItem );
+  QFETCH( bool, silent );
 
   const Collection col( collectionIdFromPath( "res2/space folder" ) );
   QVERIFY( col.isValid() );
 
   ItemCreateJob *create = new ItemCreateJob( item1, col, this );
   AKVERIFYEXEC( create );
+  const Item createdItem = create->item();
 
   ItemCreateJob *merge = new ItemCreateJob( item2, col, this );
-  merge->setMergeIfExists( true );
+  ItemCreateJob::MergeOptions options = ItemCreateJob::GID | ItemCreateJob::RID;
+  if ( silent ) {
+    options |= ItemCreateJob::Silent;
+  }
+  merge->setMerge( options );
   AKVERIFYEXEC( merge );
 
-  QCOMPARE( mergedItem.gid(), merge->item().gid() );
-  QCOMPARE( mergedItem.remoteId(), merge->item().remoteId() );
-  QCOMPARE( mergedItem.remoteRevision(), merge->item().remoteRevision() );
-  QCOMPARE( mergedItem.payloadData(), merge->item().payloadData() );
-  QCOMPARE( mergedItem.size(), merge->item().size() );
-  QCOMPARE( mergedItem.flags(), merge->item().flags() );
+  QCOMPARE( merge->item().id(), createdItem.id() );
+  if ( !silent ) {
+    QCOMPARE( merge->item().gid(), mergedItem.gid() );
+    QCOMPARE( merge->item().remoteId(), mergedItem.remoteId() );
+    QCOMPARE( merge->item().remoteRevision(), mergedItem.remoteRevision() );
+    QCOMPARE( merge->item().payloadData(), mergedItem.payloadData() );
+    QCOMPARE( merge->item().size(), mergedItem.size() );
+    QCOMPARE( merge->item().flags(), mergedItem.flags() );
+  }
+
+  if ( merge->item().id() != createdItem.id() ) {
+    ItemDeleteJob *del = new ItemDeleteJob( merge->item(), this );
+    AKVERIFYEXEC( del );
+  }
+  ItemDeleteJob *del = new ItemDeleteJob( createdItem, this );
+  AKVERIFYEXEC( del );
 }
 


commit 0555bdeeecc4e8ea3b52a757abdad9e60b7db4dd
Author: Patrick Spendrin <ps_ml at gmx.de>
Date:   Mon May 5 11:11:28 2014 +0200

    fix errors gcc ignores

diff --git a/akonadi/contact/contactgroupsearchjob.cpp b/akonadi/contact/contactgroupsearchjob.cpp
index cf9cb97..4ea7d9f 100644
--- a/akonadi/contact/contactgroupsearchjob.cpp
+++ b/akonadi/contact/contactgroupsearchjob.cpp
@@ -68,7 +68,7 @@ static Akonadi::SearchTerm::Condition matchType( ContactGroupSearchJob::Match ma
     case ContactGroupSearchJob::ContainsMatch:
       return Akonadi::SearchTerm::CondContains;
   }
-  return Akonadi::SearchTerm::SearchTerm::CondEqual;
+  return Akonadi::SearchTerm::CondEqual;
 }
 
 void ContactGroupSearchJob::setQuery( Criterion criterion, const QString &value, Match match )
diff --git a/akonadi/contact/contactsearchjob.cpp b/akonadi/contact/contactsearchjob.cpp
index ee9b5cd..2ccbdfd 100644
--- a/akonadi/contact/contactsearchjob.cpp
+++ b/akonadi/contact/contactsearchjob.cpp
@@ -66,7 +66,7 @@ static Akonadi::SearchTerm::Condition matchType( ContactSearchJob::Match match )
     case ContactSearchJob::ContainsMatch:
       return Akonadi::SearchTerm::CondContains;
   }
-  return Akonadi::SearchTerm::SearchTerm::CondEqual;
+  return Akonadi::SearchTerm::CondEqual;
 }
 
 void ContactSearchJob::setQuery( Criterion criterion, const QString &value, Match match )
diff --git a/akonadi/tageditwidget.cpp b/akonadi/tageditwidget.cpp
index 04efd06..d0e0177 100644
--- a/akonadi/tageditwidget.cpp
+++ b/akonadi/tageditwidget.cpp
@@ -106,7 +106,7 @@ void TagEditWidget::Private::select(const QModelIndex &parent, int start, int en
 
 void TagEditWidget::Private::onRowsInserted(const QModelIndex &parent, int start, int end)
 {
-    select(parent, start, end, QItemSelectionModel::QItemSelectionModel::Select);
+    select(parent, start, end, QItemSelectionModel::Select);
 }
 
 void TagEditWidget::Private::slotCreateTag()


commit 50ffd66ac3401a54e4a0a08fb0102f773fd8b809
Author: Christophe Giboudeaux <cgiboudeaux at gmx.com>
Date:   Sun Apr 27 11:12:16 2014 +0200

    Update the Greek holidays file
    
    BUG: 312840
    FIXED-IN: 4.13.1

diff --git a/kholidays/holidays/plan2/holiday_gr_el b/kholidays/holidays/plan2/holiday_gr_el
index 6b0def1..36c4bb4 100644
--- a/kholidays/holidays/plan2/holiday_gr_el
+++ b/kholidays/holidays/plan2/holiday_gr_el
@@ -4,60 +4,64 @@
 :: Language: Greek
 ::
 :: Author:   capthookb <praktoreio2002 at yahoo.gr>
+::           Dimitrios Glentadakis <dglent at gmail.com>
+::           Marios Andreopoulos <opensource at andmarios.com>
+:: Updated:  2014-04-25
 ::
-:: Updated:
-::
-:: Source:
-::
+:: Source:   http://www.eortologio.gr/index_uk.php
+::           http://www.argies.gr/
 
 :: Metadata
 country     "GR"
 language    "el"
-:name        "optional - defaults to country name"
-:description "(please add description in source language) National holiday file for Greece"
+:            "National holiday file for Greece"
+:description "Εορτές και αργίες στην Ελλάδα"
 
-:: Public Holidays
-"Πρωτοχρονιά (Αργία)"                                                           weekend on january 1
-"Πρωτομαγιά (Αργία)"                                                            weekend on may 1
-"Επέτειος του ΟΧΙ (Αργία)"                                                      weekend on october 28
-"Επέτειος της επανάστασης του 1821 (Αργία)"                                     weekend on march 25
-"Μεγάλη Παρασκευή (Αργία)"                                                      weekend on pascha minus 2 days
+:: Public Holidays - Δημόσιες αργίες
+"Πρωτοχρονιά"                                                                   weekend on january 1
+"Άγια Θεοφάνεια"                                                                weekend on january 6
+"Πρωτομαγιά"                                                                    weekend on may 1
+"Επέτειος του ΟΧΙ"                                                              weekend on october 28
+"Επέτειος της επανάστασης του 1821"                                             weekend on march 25
+"Καθαρή Δευτέρα"                                                                weekend on pascha minus 48 days
+"Μεγάλη Παρασκευή"                                                              weekend on pascha minus 2 days
 "Μεγάλο Σάββατο"                                                                weekend on pascha minus 1 days
 "Το Άγιον Πάσχα"                                                                weekend on pascha
-"Δευτέρα του Πάσχα (Αργία)"                                                     weekend on pascha plus 1 days
-"Ιερεμίας"                                                                      weekend on may 1
-"Κοίμηση της Θεοτόκου (Αργία)"                                                  weekend on august 15
+"Δευτέρα του Πάσχα"                                                             weekend on pascha plus 1 days
+"Αγίου Πνεύματος"                                                               weekend on pascha plus 50 days
+"Κοίμηση της Θεοτόκου"                                                          weekend on august 15
 "Μαρία, Μάριος, Παναγιώτης, Παναγιώτα, Δέσποινα, Θεοτόκης"                      weekend on august 15
-"Σύναξις Θεοτόκου (Αργία)"                                                      weekend on december 26
-"Μανώλης, Εμμανουέλα, Δαβίδ"                                                    weekend on december 26
-"Χριστούγεννα (Αργία)"                                                          weekend on december 25
+"Χριστούγεννα"                                                                  weekend on december 25
+"Σύναξις Θεοτόκου"                                                              weekend on december 26
 
-:: Religious
+:: Religious - Θρησκευτικές
 
 :: Financial
 
 :: Cultural
+: Mothers' day,
+"Γιορτή της μητέρας"                                                            on second sunday in may
+: Fathers' day
+"Γιορτή του πατέρα"                                                             on third sunday in june
 
-:: School
+:: School - Σχολικές Αργίες
+"Εξέγερση του Πολυτεχνείου (Σχολική Αργία)"                                     on november 17
 
 :: Daylight Saving
-"Αλλαγή ώρας (1 ώρα μπροστά) "                                                  on last sunday in march
-"Αλλαγή ώρας (1 ώρα πίσω)"                                                      on last sunday in october
+: summer
+"Θερινή ώρα (1 ώρα μπροστά)"                                                    on last sunday in march
+:winter
+"Χειμερινή ώρα (1 ώρα πίσω)"                                                    on last sunday in october
 
 :: Seasons
 
-:: Name Days
-
-
-
 :: To be sorted, it's all Greek to me :-)
 
-:: Ονομαστικές εορτές
+:: Name Days - Ονομαστικές εορτές
 
 "Βασίλης, Βασιλική, Βασιλεία, Βίβιαν"                                           on january 1
 "Συλβέστρος"                                                                    on january 2
 "Θεώνη"                                                                         on january 5
-"Άγια Θεοφάνεια (Αργία)"                                                        on january 6
 "Φώτης, Φωτεινή, Θεοφάνης, Θεοφανία, Φανή, Ιορδάνης, Θεανώ, Ουρανία, Θεοπούλα"  on january 6
 "Ιωάννης, Ιωάννα, Πρόδρομος"                                                    on january 7
 "Αγάθων, Δομινίκη, Παρθένα, Κύρος"                                              on january 8
@@ -89,11 +93,12 @@ language    "el"
 "Βλάσης, Βλασία, Θοδωρής, Θοδώρα, Αυγή "                                        on february 11
 "Μελέτης, Πλωτίνος"                                                             on february 12
 "Πρίσκιλλα"                                                                     on february 13
-"Βαλεντίνος, Βαλεντίνη, Παγκόσμια Ημέρα Ερωτευμένων"                            on february 14
+"Χλόη"                                                                          on sunday after ([february 13])
+"Βαλεντίνος, Βαλεντίνη, ημέρα των ερωτευμένων"                                  on february 14
 "Ευσέβιος, Ευσεβία"                                                             on february 15
 "Πάμφιλος, Παμφίλη, Σέλευκος, Σελεύκη"                                          on february 16
 "Λέων, Αγαπητός"                                                                on february 18
-"Φιλοθέη, Χλόη"                                                                 on february 19
+"Φιλοθέη"                                                                       on february 19
 "Ανθούσα"                                                                       on february 22
 "Πολύκαρπος"                                                                    on february 23
 "Πορφύρης, Φωτεινή"                                                             on february 26
@@ -128,14 +133,16 @@ language    "el"
 "Λεωνίδας, Λάζαρος"                                                             on april 15
 "Βάϊος, Βάϊα, Δάφνη, Γαλήνη, Χιονία"                                            on april 16
 "Ναθαναήλ, Νέαρχος, Νιάρχος"                                                    on april 22
-"Αναστάσιος, Αναστασία, Λάμπρος, Λαμπρινή, Πασχάλης "                           on april 23
-"Γεώργιος, Γεωργία, Ελισάβετ, Αχιλλέας"                                         on april 24
-"Μάρκος, Νίκη, Ραφαήλ"                                                          on april 25
+"Γεώργιος, Γεωργία"                                                             on ( ( ([april 23]) >= ([pascha]) ) ?  ([april 23]) : ([pascha] + 1) )
+"Ελισάβετ, Αχιλλέας"                                                            on april 24
+"Μάρκος, Νίκη, Ραφαήλ"                                                          on ( ( ([april 23]) >= ([pascha]) ) ?  ([april 25]) : ([pascha] + 2) )
+"Νίκη"                                                                          on april 25
 "Ζωή, Πηγή"                                                                     on april 28
 "Ιάσωνας"                                                                       on april 29
 "Ιάκωβος, Θωμάς, Θωμαή, Ασημίνα"                                                on april 30
+"Ιερεμίας"                                                                      on may 1
 "Έσπερος, Εσπέρια"                                                              on may 2
-"Ροδόπη"                                                                        on may 3
+"Ροδόπη, Ξενία"                                                                 on may 3
 "Ειρήνη, Eιρηναίος, Ευφραίμ"                                                    on may 5
 "Μυροφόρα"                                                                      on may 7
 "Θεολόγος"                                                                      on may 8
@@ -162,7 +169,7 @@ language    "el"
 "Καλλιόπη"                                                                      on june 8
 "Ροδάνθη"                                                                       on june 9
 "Βαρθολομαίος, Βαρνάβας"                                                        on june 11
-"Ονούφριος, Ζήνων,Κορίνα, Αγίου Πνεύματος"                                      on june 12
+"Ονούφριος"                                                                     on june 12
 "Ελισαίος"                                                                      on june 14
 "Αυγουστίνος, Αυγούστα, Ιερώνυμος, Μόνικα, Ορτανσία"                            on june 15
 "Γιορτή του πατέρα"                                                             on june 16
@@ -286,10 +293,10 @@ language    "el"
 "Θεόκλητος, Ιακώβ"                                                              on december 1
 "Βαρβάρα, Δαμασκηνός"                                                           on december 4
 "Σάββας, Σαββούλα, Διογένης"                                                    on december 5
-"Νίκος, Νικολέττα"                                                              on december 6
+"Νίκος, Νικολέτα"                                                               on december 6
 "Αμβρόσιος"                                                                     on december 7
 "Άννα"                                                                          on december 9
-"Ααρών, Αδάμ, Δαυίδ, Δανάη, Εύα, Ισαάκ, Ιώβ, Ραχήλ, Ρουμπίνη"                   on december 11
+"Ααρών, Αδάμ, Δαυίδ, Δανάη, Εύα, Ισαάκ, Ιώβ, Ραχήλ, Ρουμπίνη"                   on sunday after ([december 11])
 "Σπύρος, Σπυριδούλα"                                                            on december 12
 "Ευστράτιος, Λουκάς, Λουκία, Άρης"                                              on december 13
 "Ελευθέριος, Ελευθερία, Ανθή, Σύλβια"                                           on december 15
@@ -301,21 +308,18 @@ language    "el"
 "Αναστασία"                                                                     on december 22
 "Ευγένιος, Ευγενία"                                                             on december 24
 "Χρήστος, Χριστίνα, Χρύσα"                                                      on december 25
+"Μανώλης, Εμμανουέλα"                                                           on december 26
 "Στέφανος, Στεφανία"                                                            on december 27
 "Δόμνα"                                                                         on december 28
 "Ιωσήφ"                                                                         on december 30
 
 
-:: Αργίες - Επέτειοι
-"Εξέγερση του Πολυτεχνείου (Σχολική Αργία)"                                     on november 17
-
 :: Κινητές εορτές
 "Τελώνου και Φαρισαίου - Αρχή Τριωδίου"                                         on pascha minus 70 days
 "Του Ασώτου"                                                                    on pascha minus 63 days
 "Τσικνοπέμπτη"                                                                  on pascha minus 59 days
 "Κυριακή των Απόκρεω"                                                           on pascha minus 56 days
 "Τυροφάγου"                                                                     on pascha minus 49 days
-"Καθαρή Δευτέρα (Αργία)"                                                        on pascha minus 48 days
 "Θεόδωρος, Θεοδώρα, Δώρα, Θώδης, Θώδος, Δώρης"                                  on pascha minus 43 days
 "Κυριακή της Ορθοδοξίας"                                                        on pascha minus 42 days
 "Σάββατο του Λαζάρου"                                                           on pascha minus 8 days
@@ -324,9 +328,10 @@ language    "el"
 "Μεγάλη Τρίτη"                                                                  on pascha minus 5 days
 "Μεγάλη Τετάρτη"                                                                on pascha minus 4 days
 "Μεγάλη Πέμπτη"                                                                 on pascha minus 3 days
+"Αναστάσιος, Αναστασία, Λάμπρος, Λαμπρινή, Πασχάλης "                           on pascha
+"Ραφαήλ, Ειρήνη"                                                                on pascha plus 2 days
 "Πηγή, Ζήσης, Ζησούλα, Ζήσιμος, Ζωή, Ζώης"                                      on pascha plus 5 days
 "Του Θωμά"                                                                      on pascha plus 7 days
 "Ανάληψη του Χριστού"                                                           on pascha plus 39 days
 "Πεντηκοστή"                                                                    on pascha plus 49 days
-"Αγ. Πνεύματος"                                                                 on pascha plus 50 days
 "Αγ. Πάντων"                                                                    on pascha plus 56 days


commit 45d9dc55a5a4363025a5302d54054b869ae0ff77
Author: Dan Vrátil <dvratil at redhat.com>
Date:   Fri May 2 16:42:50 2014 +0200

    Update itemappendtest to reflect changes in Akonadi
    
    Akonadi can now tell the difference between empty payload and
    no payload, so once Item::setPayload() is called, the item is
    considered to have a payload, even if it's empty.

diff --git a/akonadi/tests/itemappendtest.cpp b/akonadi/tests/itemappendtest.cpp
index f8d3e51..354f272 100644
--- a/akonadi/tests/itemappendtest.cpp
+++ b/akonadi/tests/itemappendtest.cpp
@@ -122,7 +122,9 @@ void ItemAppendTest::testContent()
 
   Item item;
   item.setMimeType( "application/octet-stream" );
-  item.setPayload( data );
+  if ( !data.isNull() ) {
+    item.setPayload( data );
+  }
 
   ItemCreateJob* job = new ItemCreateJob( item, testFolder1, this );
   AKVERIFYEXEC( job );
@@ -134,8 +136,7 @@ void ItemAppendTest::testContent()
   AKVERIFYEXEC( fjob );
   QCOMPARE( fjob->items().count(), 1 );
   Item item2 = fjob->items().first();
-  //akonadi does not distinguish empty and no payload
-  QCOMPARE( item2.hasPayload(), !data.isEmpty() );
+  QCOMPARE( item2.hasPayload(), !data.isNull() );
   if( item2.hasPayload() ) {
     QCOMPARE( item2.payload<QByteArray>(), data );
   }


commit ff60f4729476d181dfc8bd62e3ada66ef43c4457
Author: Dan Vrátil <dvratil at redhat.com>
Date:   Tue Apr 29 10:17:12 2014 +0200

    ItemDeleteJob: add missing implementation of tag-scope delete
    
    Looks like I added the ItemDeleteJob(Tag&) constructor to header but forgot
    to actually implement it.

diff --git a/akonadi/itemdeletejob.cpp b/akonadi/itemdeletejob.cpp
index 6d9f7ea..5eb1ee9 100644
--- a/akonadi/itemdeletejob.cpp
+++ b/akonadi/itemdeletejob.cpp
@@ -70,6 +70,14 @@ ItemDeleteJob::ItemDeleteJob(const Collection &collection, QObject *parent)
     d->mCollection = collection;
 }
 
+ItemDeleteJob::ItemDeleteJob(const Tag &tag, QObject *parent)
+    : Job(new ItemDeleteJobPrivate(this), parent)
+{
+    Q_D(ItemDeleteJob);
+
+    d->mTag = tag;
+}
+
 ItemDeleteJob::~ItemDeleteJob()
 {
 }
diff --git a/akonadi/tests/itemdeletetest.cpp b/akonadi/tests/itemdeletetest.cpp
index 7b0cefa..b5bcdf3 100644
--- a/akonadi/tests/itemdeletetest.cpp
+++ b/akonadi/tests/itemdeletetest.cpp
@@ -25,6 +25,8 @@
 #include <akonadi/itemdeletejob.h>
 #include <akonadi/itemfetchjob.h>
 #include <akonadi/transactionjobs.h>
+#include <akonadi/tagcreatejob.h>
+#include <akonadi/itemmodifyjob.h>
 #include "test_utils.h"
 
 #include <QtCore/QObject>
@@ -118,6 +120,42 @@ class ItemDeleteTest : public QObject
       QVERIFY( !fjob->exec() );
     }
 
+    void testTagDelete()
+    {
+      // Create tag
+      Tag tag;
+      tag.setName( QLatin1String( "Tag1" ) );
+      tag.setRemoteId( "Tag1" );
+      tag.setGid( "Tag1" );
+      TagCreateJob *tjob = new TagCreateJob( tag, this );
+      AKVERIFYEXEC( tjob );
+      tag = tjob->tag();
+
+      const Collection col( collectionIdFromPath( "res1/foo" ) );
+      QVERIFY( col.isValid() );
+
+      Item i;
+      i.setRemoteId( "D" );
+
+      ItemFetchJob *fjob = new ItemFetchJob( i, this );
+      fjob->setCollection( col );
+      AKVERIFYEXEC( fjob );
+      QCOMPARE( fjob->items().count(), 1 );
+
+      i = fjob->items().first();
+      i.setTag(tag);
+      ItemModifyJob *mjob = new ItemModifyJob( i, this );
+      AKVERIFYEXEC( mjob );
+
+      // Delete the tagged item
+      ItemDeleteJob *djob = new ItemDeleteJob( tag, this );
+      AKVERIFYEXEC( djob );
+
+      // Try to fetch the item again, there should be none
+      fjob = new ItemFetchJob( i, this );
+      QVERIFY( !fjob->exec() );
+    }
+
     void testCollectionDelete()
     {
       const Collection col( collectionIdFromPath( "res1/foo" ) );


commit 079c6e6b84fd6f6dd5fadbfad955f2a327c9b55e
Author: Dan Vrátil <dvratil at redhat.com>
Date:   Thu Apr 24 16:08:58 2014 +0200

    Implement support for MERGE command in ItemCreateJob
    
    By setting ItemCreateJob::setMergeIfExists to true, the server job will
    use MERGE instead of AK-APPEND command. Server will try to merge the
    new item with an existing one (if any such exists) by GID and RID match.
    
    If no such item exists, the server will fallback to AK-APPEND behavior and
    a new item is inserted. Otherwise the merged item is returned.

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5b67f7a..53b3501 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -80,7 +80,7 @@ endif()
 
 if (NOT KDEPIM_ONLY_KLEO)
   #FindAkonadi.cmake is only there for compatibility reasons, but we don't want to use that.
-  set(Akonadi_MIN_VERSION "1.12.42")
+  set(Akonadi_MIN_VERSION "1.12.43")
   find_package(Akonadi ${Akonadi_MIN_VERSION} QUIET NO_MODULE)
   set_package_properties(Akonadi PROPERTIES DESCRIPTION "Akonadi server libraries" URL "http://pim.kde.org/akonadi" TYPE REQUIRED PURPOSE "Access to PIM storage and services")
 
diff --git a/akonadi/itemcreatejob.cpp b/akonadi/itemcreatejob.cpp
index b0b81c6..10b978c 100644
--- a/akonadi/itemcreatejob.cpp
+++ b/akonadi/itemcreatejob.cpp
@@ -39,6 +39,7 @@ class Akonadi::ItemCreateJobPrivate : public JobPrivate
 public:
     ItemCreateJobPrivate(ItemCreateJob *parent)
         : JobPrivate(parent)
+        , mMerge(false)
     {
     }
 
@@ -50,6 +51,7 @@ public:
     Item::Id mUid;
     QDateTime mDatetime;
     QByteArray mPendingData;
+    bool mMerge;
 };
 
 QByteArray ItemCreateJobPrivate::nextPartHeader()
@@ -104,6 +106,7 @@ void ItemCreateJob::doStart()
     QList<QByteArray> flags;
     flags.append("\\MimeType[" + d->mItem.mimeType().toLatin1() + ']');
     const QString gid = GidExtractor::getGid(d->mItem);
+    const bool merge = d->mMerge && !d->mItem.gid().isNull();
     if (!gid.isNull()) {
         flags.append(ImapParser::quote("\\Gid[" + gid.toUtf8() + ']'));
     }
@@ -115,10 +118,21 @@ void ItemCreateJob::doStart()
     }
     flags += d->mItem.flags().toList();
 
-    QByteArray command = d->newTag() + " X-AKAPPEND " + QByteArray::number(d->mCollection.id())
-                         + ' ' + QByteArray::number(d->mItem.size())
-                         + " (" + ImapParser::join(flags, " ") + ")"
-                         + " ("; // list of parts
+    QByteArray command = d->newTag();
+    if (merge) {
+      command += " MERGE (GID";
+      if (!d->mItem.remoteId().isEmpty()) {
+        command += " REMOTEID";
+      }
+      command += ") ";
+    } else {
+      command += " X-AKAPPEND ";
+    }
+
+    command += QByteArray::number(d->mCollection.id())
+            + ' ' + QByteArray::number(d->mItem.size())
+            + " (" + ImapParser::join(flags, " ") + ")"
+            + " ("; // list of parts
     const QByteArray attrs = ProtocolHelper::attributesToByteArray(d->mItem, true);
     if (!attrs.isEmpty()) {
         command += attrs;
@@ -138,6 +152,22 @@ void ItemCreateJob::doHandleResponse(const QByteArray &tag, const QByteArray &da
         d->writeData(d->nextPartHeader());
         return;
     }
+    if (tag == "*") {
+        int begin = data.indexOf("FETCH");
+        if (begin >= 0) {
+          QList<QByteArray> fetchResponse;
+          ImapParser::parseParenthesizedList(data, fetchResponse, begin + 6);
+
+          Item item;
+          ProtocolHelper::parseItemFetchResult(fetchResponse, item);
+          if (!item.isValid()) {
+            // Error, maybe?
+            return;
+          }
+          d->mItem = item;
+        }
+        return;
+    }
     if (tag == d->tag()) {
         int uidNextPos = data.indexOf("UIDNEXT");
         if (uidNextPos != -1) {
@@ -159,6 +189,13 @@ void ItemCreateJob::doHandleResponse(const QByteArray &tag, const QByteArray &da
     }
 }
 
+void ItemCreateJob::setMergeIfExists(bool merge)
+{
+  Q_D(ItemCreateJob);
+
+  d->mMerge = merge;
+}
+
 Item ItemCreateJob::item() const
 {
     Q_D(const ItemCreateJob);
diff --git a/akonadi/itemcreatejob.h b/akonadi/itemcreatejob.h
index 82e1814..b55ae3a 100644
--- a/akonadi/itemcreatejob.h
+++ b/akonadi/itemcreatejob.h
@@ -95,6 +95,24 @@ public:
      */
     Item item() const;
 
+    /**
+     * Merge this item into an existing one
+     *
+     * If an item with same GID and remote ID as the created item exists in
+     * specified collection, the new item will be merged into the existing one
+     * and the merged item will be returned.
+     *
+     * If the new item does not have remote ID specified, only GID-based merging
+     * will be performed. If the item does not have GID, this option will be
+     * ignored and a new item will be created.
+     *
+     * By default, merging is disabled.
+     *
+     * @param merge Whether to enable or disable merging
+     * @since 4.14
+     */
+    void setMergeIfExists(bool merge);
+
 protected:
     virtual void doStart();
     virtual void doHandleResponse(const QByteArray &tag, const QByteArray &data);
diff --git a/akonadi/session_p.h b/akonadi/session_p.h
index 6f418a1..2f034ef 100644
--- a/akonadi/session_p.h
+++ b/akonadi/session_p.h
@@ -116,7 +116,7 @@ public:
 
     static int minimumProtocolVersion()
     {
-        return 37;
+        return 38;
     }
 
     /**
diff --git a/akonadi/tests/itemappendtest.cpp b/akonadi/tests/itemappendtest.cpp
index 3adcf0e..f8d3e51 100644
--- a/akonadi/tests/itemappendtest.cpp
+++ b/akonadi/tests/itemappendtest.cpp
@@ -265,3 +265,55 @@ void ItemAppendTest::testItemSize()
   QCOMPARE( fetch->items().first().size(), size );
 }
 
+void ItemAppendTest::testItemMerge_data()
+{
+  QTest::addColumn<Akonadi::Item>( "item1" );
+  QTest::addColumn<Akonadi::Item>( "item2" );
+  QTest::addColumn<Akonadi::Item>( "mergedItem" );
+
+  Item i1( "application/octet-stream" );
+  i1.setPayload( QByteArray( "ABCD" ) );
+  i1.setSize( 4 );
+  i1.setRemoteId( "XYZ" );
+  i1.setGid( "XYZ" );
+  i1.setFlag( "TestFlag1" );
+  i1.setRemoteRevision( "5" );
+
+  Item i2( "application/octet-stream" );
+  i2.setPayload( QByteArray( "DEFGH" ) );
+  i2.setSize( 5 );
+  i2.setRemoteId(( "XYZ" ) );
+  i2.setGid( "XYZ" );
+  i2.setFlag( "TestFlag2" );
+  i2.setRemoteRevision( "6" );
+
+  Item mergedItem( i2 );
+  mergedItem.setFlag( "TestFlag1" );
+
+  QTest::newRow( "ok merge" ) << i1 << i2 << mergedItem;
+}
+
+void ItemAppendTest::testItemMerge()
+{
+  QFETCH( Akonadi::Item, item1 );
+  QFETCH( Akonadi::Item, item2 );
+  QFETCH( Akonadi::Item, mergedItem );
+
+  const Collection col( collectionIdFromPath( "res2/space folder" ) );
+  QVERIFY( col.isValid() );
+
+  ItemCreateJob *create = new ItemCreateJob( item1, col, this );
+  AKVERIFYEXEC( create );
+
+  ItemCreateJob *merge = new ItemCreateJob( item2, col, this );
+  merge->setMergeIfExists( true );
+  AKVERIFYEXEC( merge );
+
+  QCOMPARE( mergedItem.gid(), merge->item().gid() );
+  QCOMPARE( mergedItem.remoteId(), merge->item().remoteId() );
+  QCOMPARE( mergedItem.remoteRevision(), merge->item().remoteRevision() );
+  QCOMPARE( mergedItem.payloadData(), merge->item().payloadData() );
+  QCOMPARE( mergedItem.size(), merge->item().size() );
+  QCOMPARE( mergedItem.flags(), merge->item().flags() );
+}
+
diff --git a/akonadi/tests/itemappendtest.h b/akonadi/tests/itemappendtest.h
index e536265..c2a204a 100644
--- a/akonadi/tests/itemappendtest.h
+++ b/akonadi/tests/itemappendtest.h
@@ -37,6 +37,8 @@ class ItemAppendTest : public QObject
     void testInvalidMultipartAppend();
     void testItemSize_data();
     void testItemSize();
+    void testItemMerge_data();
+    void testItemMerge();
 };
 
 #endif




More information about the commits mailing list