Branch 'kolab/integration/4.13.0' - 17 commits - akonadi/CMakeLists.txt akonadi/collectionfetchscope.cpp akonadi/collectionsync.cpp akonadi/entitycache_p.h akonadi/item.cpp akonadi/itemfetchscope.cpp akonadi/itemfetchscope.h akonadi/itemfetchscope_p.h akonadi/item.h akonadi/item_p.h akonadi/itemsearchjob.cpp akonadi/monitor.h akonadi/monitor_p.cpp akonadi/monitor_p.h akonadi/protocolhelper.cpp akonadi/protocolhelper_p.h akonadi/qtest_akonadi.h akonadi/relation.cpp akonadi/relationcreatejob.cpp akonadi/relationcreatejob.h akonadi/relationdeletejob.cpp akonadi/relationdeletejob.h akonadi/relationfetchjob.cpp akonadi/relationfetchjob.h akonadi/relation.h akonadi/resourcebase.cpp akonadi/resourcebase.h akonadi/resourcescheduler.cpp akonadi/resourcescheduler_p.h akonadi/searchcreatejob.cpp akonadi/session.cpp akonadi/tagmodifyjob.cpp akonadi/tagsync.cpp akonadi/tagsync.h akonadi/tests kimap/fetchjob.cpp kimap/tests
Christian Mollekopf
mollekopf at kolabsys.com
Fri Aug 29 13:14:52 CEST 2014
akonadi/CMakeLists.txt | 9 +
akonadi/collectionfetchscope.cpp | 1
akonadi/collectionsync.cpp | 2
akonadi/entitycache_p.h | 5
akonadi/item.cpp | 6
akonadi/item.h | 8
akonadi/item_p.h | 1
akonadi/itemfetchscope.cpp | 10 +
akonadi/itemfetchscope.h | 18 ++
akonadi/itemfetchscope_p.h | 3
akonadi/itemsearchjob.cpp | 2
akonadi/monitor.h | 37 ++++
akonadi/monitor_p.cpp | 93 ++++++++++
akonadi/monitor_p.h | 2
akonadi/protocolhelper.cpp | 38 ++++
akonadi/protocolhelper_p.h | 1
akonadi/qtest_akonadi.h | 3
akonadi/relation.cpp | 136 +++++++++++++++
akonadi/relation.h | 133 +++++++++++++++
akonadi/relationcreatejob.cpp | 86 +++++++++
akonadi/relationcreatejob.h | 62 +++++++
akonadi/relationdeletejob.cpp | 84 +++++++++
akonadi/relationdeletejob.h | 62 +++++++
akonadi/relationfetchjob.cpp | 164 ++++++++++++++++++
akonadi/relationfetchjob.h | 79 ++++++++
akonadi/resourcebase.cpp | 55 ++++++
akonadi/resourcebase.h | 11 +
akonadi/resourcescheduler.cpp | 16 +
akonadi/resourcescheduler_p.h | 4
akonadi/searchcreatejob.cpp | 2
akonadi/session.cpp | 2
akonadi/tagmodifyjob.cpp | 2
akonadi/tagsync.cpp | 243 +++++++++++++++++++++++++++
akonadi/tagsync.h | 61 ++++++
akonadi/tests/CMakeLists.txt | 13 +
akonadi/tests/actionstatemanagertest.cpp | 13 +
akonadi/tests/protocolhelpertest.cpp | 6
akonadi/tests/relationtest.cpp | 165 ++++++++++++++++++
akonadi/tests/tagsynctest.cpp | 273 +++++++++++++++++++++++++++++++
akonadi/tests/tagtest.cpp | 114 ++++++++++++
akonadi/tests/virtualresource.cpp | 111 ++++++++++++
akonadi/tests/virtualresource.h | 53 ++++++
kimap/fetchjob.cpp | 2
kimap/tests/kimaptest/fakeserver.cpp | 3
44 files changed, 2173 insertions(+), 21 deletions(-)
New commits:
commit 0211264bb762cc765d853c93fd728926ccccff38
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date: Wed Aug 27 10:39:38 2014 +0200
KIMAP-FakeServer: Allow skipping client parts.
diff --git a/kimap/tests/kimaptest/fakeserver.cpp b/kimap/tests/kimaptest/fakeserver.cpp
index 5b374ed..2619b7d 100644
--- a/kimap/tests/kimaptest/fakeserver.cpp
+++ b/kimap/tests/kimaptest/fakeserver.cpp
@@ -231,6 +231,9 @@ void FakeServer::readClientPart( int scenarioNumber )
scenario.first().startsWith( "C: " ) ) {
QByteArray received = "C: "+clientParser->readUntilCommandEnd().trimmed();
QByteArray expected = scenario.takeFirst();
+ if (expected.contains("C: SKIP")) {
+ continue;
+ }
compareReceived(received, expected);
if (received.contains("STARTTLS")) {
m_starttls = true;
commit 32f261e71f80629d8cd404334985fec9f9694aee
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date: Tue Aug 26 19:30:08 2014 +0200
tagsync for resourcescheduler
diff --git a/akonadi/resourcebase.cpp b/akonadi/resourcebase.cpp
index d11f216..5568560 100644
--- a/akonadi/resourcebase.cpp
+++ b/akonadi/resourcebase.cpp
@@ -26,6 +26,7 @@
#include "collectionsync_p.h"
#include "dbusconnectionpool.h"
#include "itemsync.h"
+#include "tagsync.h"
#include "kdepimlibs-version.h"
#include "resourcescheduler_p.h"
#include "tracerinterface.h"
@@ -76,6 +77,7 @@ public:
, mItemSyncFetchScope(0)
, mItemTransactionMode(ItemSync::SingleTransaction)
, mCollectionSyncer(0)
+ , mTagSyncer(0)
, mHierarchicalRid(false)
, mUnemittedProgress(0)
, mAutomaticProgressReporting(true)
@@ -141,6 +143,7 @@ public:
void slotSynchronizeCollectionAttributes(const Collection &col);
void slotCollectionListForAttributesDone(KJob *job);
void slotCollectionAttributesSyncDone(KJob *job);
+ void slotSynchronizeTags();
void slotItemSyncDone(KJob *job);
@@ -160,6 +163,7 @@ public:
void slotRecursiveMoveReplay(RecursiveMover *mover);
void slotRecursiveMoveReplayResult(KJob *job);
+ void slotTagSyncDone(KJob *job);
void slotSessionReconnected()
{
Q_Q(ResourceBase);
@@ -437,6 +441,7 @@ public:
ItemFetchScope *mItemSyncFetchScope;
ItemSync::TransactionMode mItemTransactionMode;
CollectionSync *mCollectionSyncer;
+ TagSync *mTagSyncer;
bool mHierarchicalRid;
QTimer mProgressEmissionCompressor;
int mUnemittedProgress;
@@ -473,6 +478,8 @@ ResourceBase::ResourceBase(const QString &id)
SLOT(slotSynchronizeCollection(Akonadi::Collection)));
connect(d->scheduler, SIGNAL(executeCollectionAttributesSync(Akonadi::Collection)),
SLOT(slotSynchronizeCollectionAttributes(Akonadi::Collection)));
+ connect(d->scheduler, SIGNAL(executeTagSync()),
+ SLOT(slotSynchronizeTags()));
connect(d->scheduler, SIGNAL(executeItemFetch(Akonadi::Item,QSet<QByteArray>)),
SLOT(slotPrepareItemRetrieval(Akonadi::Item)));
connect(d->scheduler, SIGNAL(executeResourceCollectionDeletion()),
@@ -924,6 +931,12 @@ void ResourceBasePrivate::slotSynchronizeCollectionAttributes(const Collection &
QMetaObject::invokeMethod(q, "retrieveCollectionAttributes", Q_ARG(Akonadi::Collection, col));
}
+void ResourceBasePrivate::slotSynchronizeTags()
+{
+ Q_Q(ResourceBase);
+ QMetaObject::invokeMethod(q, "retrieveTags");
+}
+
void ResourceBasePrivate::slotPrepareItemRetrieval(const Akonadi::Item &item)
{
Q_Q(ResourceBase);
@@ -1032,6 +1045,11 @@ void ResourceBase::synchronizeCollectionTree()
d_func()->scheduler->scheduleCollectionTreeSync();
}
+void ResourceBase::synchronizeTags()
+{
+ d_func()->scheduler->scheduleTagSync();
+}
+
void ResourceBase::cancelTask()
{
Q_D(ResourceBase);
@@ -1242,6 +1260,12 @@ void ResourceBase::retrieveCollectionAttributes(const Collection &collection)
collectionAttributesRetrieved(collection);
}
+void ResourceBase::retrieveTags()
+{
+ Q_D(ResourceBase);
+ d->scheduler->taskDone();
+}
+
void Akonadi::ResourceBase::abortActivity()
{
}
@@ -1291,5 +1315,36 @@ QString ResourceBase::dumpMemoryInfoToString() const
return d->dumpMemoryInfoToString();
}
+void ResourceBase::tagsRetrieved(const Tag::List &tags, const QHash<QString, Item::List> &tagMembers)
+{
+ Q_D(ResourceBase);
+ Q_ASSERT_X(d->scheduler->currentTask().type == ResourceScheduler::SyncTags ||
+ d->scheduler->currentTask().type == ResourceScheduler::SyncAll ||
+ d->scheduler->currentTask().type == ResourceScheduler::Custom,
+ "ResourceBase::tagsRetrieved()",
+ "Calling tagsRetrieved() although no tag retrieval is in progress");
+ if (!d->mTagSyncer) {
+ d->mTagSyncer = new TagSync(this);
+ connect(d->mTagSyncer, SIGNAL(percent(KJob*,ulong)), SLOT(slotPercent(KJob*,ulong)));
+ connect(d->mTagSyncer, SIGNAL(result(KJob*)), SLOT(slotTagSyncDone(KJob*)));
+ }
+ d->mTagSyncer->setFullTagList(tags);
+ d->mTagSyncer->setTagMembers(tagMembers);
+}
+
+void ResourceBasePrivate::slotTagSyncDone(KJob *job)
+{
+ Q_Q(ResourceBase);
+ mTagSyncer = 0;
+ if (job->error()) {
+ if (job->error() != Job::UserCanceled) {
+ kWarning() << "TagSync failed: " << job->errorString();
+ emit q->error(job->errorString());
+ }
+ }
+ scheduler->taskDone();
+}
+
+
#include "resourcebase.moc"
#include "moc_resourcebase.cpp"
diff --git a/akonadi/resourcebase.h b/akonadi/resourcebase.h
index 19c3186..a66e85a 100644
--- a/akonadi/resourcebase.h
+++ b/akonadi/resourcebase.h
@@ -270,6 +270,8 @@ protected Q_SLOTS:
*/
virtual void retrieveCollections() = 0;
+ virtual void retrieveTags();
+
/**
* Retrieve the attributes of a single collection from the backend. The
* collection to retrieve attributes for is provided as @p collection.
@@ -427,6 +429,8 @@ protected:
*/
void collectionsRetrieved(const Collection::List &collections);
+ void tagsRetrieved(const Tag::List &tags, const QHash<QString, Item::List> &tagMembers);
+
/**
* Call this to supply incrementally retrieved collections from the remote server.
*
@@ -631,6 +635,11 @@ protected:
void synchronizeCollectionTree();
/**
+ * Refetches Tags.
+ */
+ void synchronizeTags();
+
+ /**
* Stops the execution of the current task and continues with the next one.
*/
void cancelTask();
@@ -781,6 +790,8 @@ private:
Q_PRIVATE_SLOT(d_func(), void slotSessionReconnected())
Q_PRIVATE_SLOT(d_func(), void slotRecursiveMoveReplay(RecursiveMover *))
Q_PRIVATE_SLOT(d_func(), void slotRecursiveMoveReplayResult(KJob *))
+ Q_PRIVATE_SLOT(d_func(), void slotTagSyncDone(KJob *))
+ Q_PRIVATE_SLOT(d_func(), void slotSynchronizeTags())
};
}
diff --git a/akonadi/resourcescheduler.cpp b/akonadi/resourcescheduler.cpp
index dad51a6..52e43a8 100644
--- a/akonadi/resourcescheduler.cpp
+++ b/akonadi/resourcescheduler.cpp
@@ -68,6 +68,18 @@ void ResourceScheduler::scheduleCollectionTreeSync()
scheduleNext();
}
+void ResourceScheduler::scheduleTagSync()
+{
+ Task t;
+ t.type = SyncTags;
+ TaskList& queue = queueForTaskType( t.type );
+ if ( queue.contains( t ) || mCurrentTask == t )
+ return;
+ queue << t;
+ signalTaskToTracker( t, "SyncTags" );
+ scheduleNext();
+}
+
void ResourceScheduler::scheduleSync(const Collection & col)
{
Task t;
@@ -318,6 +330,9 @@ void ResourceScheduler::executeNext()
case SyncCollectionAttributes:
emit executeCollectionAttributesSync( mCurrentTask.collection );
break;
+ case SyncTags:
+ emit executeTagSync();
+ break;
case FetchItem:
emit executeItemFetch( mCurrentTask.item, mCurrentTask.itemParts );
break;
@@ -532,6 +547,7 @@ static const char s_taskTypes[][27] = {
"SyncCollectionTree",
"SyncCollection",
"SyncCollectionAttributes",
+ "SyncTags",
"FetchItem",
"ChangeReplay",
"RecursiveMoveReplay",
diff --git a/akonadi/resourcescheduler_p.h b/akonadi/resourcescheduler_p.h
index 3dfaa5f..676a7f4 100644
--- a/akonadi/resourcescheduler_p.h
+++ b/akonadi/resourcescheduler_p.h
@@ -54,6 +54,7 @@ public:
SyncCollectionTree,
SyncCollection,
SyncCollectionAttributes,
+ SyncTags,
FetchItem,
ChangeReplay,
RecursiveMoveReplay,
@@ -122,6 +123,8 @@ public:
*/
void scheduleAttributesSync(const Collection &collection);
+ void scheduleTagSync();
+
/**
Schedules fetching of a single PIM item.
@param item The item to fetch.
@@ -227,6 +230,7 @@ Q_SIGNALS:
void executeCollectionAttributesSync(const Akonadi::Collection &col);
void executeCollectionSync(const Akonadi::Collection &col);
void executeCollectionTreeSync();
+ void executeTagSync();
void executeItemFetch(const Akonadi::Item &item, const QSet<QByteArray> &parts);
void executeResourceCollectionDeletion();
void executeCacheInvalidation(const Akonadi::Collection &collection);
commit ad79a93155045eb201a3604c8d25160b7f252eb4
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date: Tue Aug 26 03:02:03 2014 +0200
VirtualResource: An interface for unittests to create resource collections
diff --git a/akonadi/tests/CMakeLists.txt b/akonadi/tests/CMakeLists.txt
index 61133bf..d0a4ae3 100644
--- a/akonadi/tests/CMakeLists.txt
+++ b/akonadi/tests/CMakeLists.txt
@@ -74,6 +74,17 @@ add_library(akonaditestfake STATIC
inspectablechangerecorder.cpp
)
+add_library(akonaditest ${LIBRARY_TYPE}
+ virtualresource.cpp
+ ../resourcescheduler.cpp
+)
+target_link_libraries(akonaditest akonadi-kde ${KDE4_KDEUI_LIBS})
+install(TARGETS akonaditest ${INSTALL_TARGETS_DEFAULT_ARGS})
+install(FILES
+ virtualresource.h
+ DESTINATION ${INCLUDE_INSTALL_DIR}/akonadi COMPONENT Devel
+)
+
# demo applications
add_akonadi_demo(itemdumper.cpp)
add_akonadi_demo(subscriber.cpp)
diff --git a/akonadi/tests/virtualresource.cpp b/akonadi/tests/virtualresource.cpp
new file mode 100644
index 0000000..a675d81
--- /dev/null
+++ b/akonadi/tests/virtualresource.cpp
@@ -0,0 +1,111 @@
+/*
+ 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 "virtualresource.h"
+
+#include <akonadi/collectioncreatejob.h>
+#include <akonadi/collectiondeletejob.h>
+#include <akonadi/itemcreatejob.h>
+#include <akonadi/resourceselectjob_p.h>
+#include <akonadi/servermanager.h>
+#include <akonadi/session_p.h>
+#include <qdbusinterface.h>
+#include <dbusconnectionpool.h>
+#include <QStringList>
+
+#define EXEC(job) \
+do { \
+ if (!job->exec()) { \
+ kFatal() << "Job failed: " << job->errorString(); \
+ } \
+} while ( 0 )
+
+using namespace Akonadi;
+
+VirtualResource::VirtualResource(const QString &name, QObject *parent)
+ : QObject(parent),
+ mResourceName(name)
+{
+ // QDBusInterface *interface = new QDBusInterface(ServerManager::serviceName(ServerManager::Control),
+ // QString::fromLatin1("/"),
+ // QString::fromLatin1("org.freedesktop.Akonadi.AgentManager"),
+ // DBusConnectionPool::threadConnection(), this);
+ // if (interface->isValid()) {
+ // const QDBusMessage reply = interface->call(QString::fromUtf8("createAgentInstance"), name, QStringList());
+ // if (reply.type() == QDBusMessage::ErrorMessage) {
+ // // This means that the resource doesn't provide a synchronizeCollectionAttributes method, so we just finish the job
+ // return;
+ // }
+ // } else {
+ // Q_ASSERT(false);
+ // }
+ // mSession = new Akonadi::Session(name.toLatin1(), this);
+
+ // Since this is in the same process as the test, all jobs in the test get executed in the resource session by default
+ SessionPrivate::createDefaultSession(name.toLatin1());
+ mSession = Session::defaultSession();
+ ResourceSelectJob *select = new ResourceSelectJob(name, mSession);
+ EXEC(select);
+}
+
+VirtualResource::~VirtualResource()
+{
+ if (mRootCollection.isValid()) {
+ CollectionDeleteJob *d = new CollectionDeleteJob(mRootCollection, mSession);
+ EXEC(d);
+ }
+}
+
+Akonadi::Collection VirtualResource::createCollection(const Akonadi::Collection &collection)
+{
+ // kDebug() << collection.name() << collection.parentCollection().remoteId();
+ // kDebug() << "contentMimeTypes: " << collection.contentMimeTypes();
+
+ Q_ASSERT(!collection.name().isEmpty());
+ Collection col = collection;
+ if (!col.parentCollection().isValid()) {
+ col.setParentCollection(mRootCollection);
+ }
+ CollectionCreateJob *create = new CollectionCreateJob(col, mSession);
+ EXEC(create);
+ return create->collection();
+}
+Akonadi::Collection VirtualResource::createRootCollection(const Akonadi::Collection &collection)
+{
+ kDebug() << collection.name();
+ mRootCollection = createCollection(collection);
+ return mRootCollection;
+}
+
+Akonadi::Item VirtualResource::createItem(const Akonadi::Item &item, const Collection &parent)
+{
+ ItemCreateJob *create = new ItemCreateJob(item, parent, mSession);
+ EXEC(create);
+ return create->item();
+}
+
+void VirtualResource::reset()
+{
+ Q_ASSERT(mRootCollection.isValid());
+ Akonadi::Collection col = mRootCollection;
+ CollectionDeleteJob *d = new CollectionDeleteJob(mRootCollection, mSession);
+ EXEC(d);
+ col.setId(-1);
+ createRootCollection(col);
+}
+
diff --git a/akonadi/tests/virtualresource.h b/akonadi/tests/virtualresource.h
new file mode 100644
index 0000000..6b03df2
--- /dev/null
+++ b/akonadi/tests/virtualresource.h
@@ -0,0 +1,53 @@
+/*
+ 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 VIRTUALRESOURCE_H
+#define VIRTUALRESOURCE_H
+
+#include <akonadi/collection.h>
+#include <akonadi/item.h>
+#include <akonadi/session.h>
+
+namespace Akonadi {
+
+/**
+ * For testing only.
+ *
+ */
+class AKONADI_EXPORT VirtualResource : public QObject
+{
+ Q_OBJECT
+public:
+ VirtualResource(const QString &name, QObject *parent = 0);
+ ~VirtualResource();
+
+ Akonadi::Collection createCollection(const Akonadi::Collection &collection);
+ Akonadi::Collection createRootCollection(const Akonadi::Collection &collection);
+ Akonadi::Item createItem(const Akonadi::Item &item, const Akonadi::Collection &parent);
+
+ void reset();
+private:
+ Akonadi::Collection mRootCollection;
+ QString mResourceName;
+ Akonadi::Session *mSession;
+};
+
+}
+
+#endif
commit cd27dcb68d86dfbed0e575dff56f969f92e1b9ae
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date: Tue Aug 26 03:00:51 2014 +0200
KIMAP-FetchJob: Emit headersReceived on full message retrieval as advertised in the docs.
diff --git a/kimap/fetchjob.cpp b/kimap/fetchjob.cpp
index f3a6125..bf2af67 100644
--- a/kimap/fetchjob.cpp
+++ b/kimap/fetchjob.cpp
@@ -61,7 +61,7 @@ namespace KIMAP
pendingUids, pendingAttributes,
pendingParts );
}
- if ( !pendingSizes.isEmpty() || !pendingFlags.isEmpty() ) {
+ if ( !pendingSizes.isEmpty() || !pendingFlags.isEmpty() || !pendingMessages.isEmpty() ) {
emit q->headersReceived( selectedMailBox,
pendingUids, pendingSizes,
pendingFlags, pendingMessages );
commit 4ddf61c67641f725be942be06ff2b13da5d2a992
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date: Mon Aug 25 16:15:22 2014 +0200
Detect if we failed to start with QTEST_AKONADIMAIN.
This used to silently block because it couldn't connect to the server
because the environment variables weren't set-up correctly.
diff --git a/akonadi/qtest_akonadi.h b/akonadi/qtest_akonadi.h
index 4ddd1a5..8cb12ea 100644
--- a/akonadi/qtest_akonadi.h
+++ b/akonadi/qtest_akonadi.h
@@ -65,6 +65,9 @@ void checkTestIsIsolated() {
Q_ASSERT_X(!qgetenv("TESTRUNNER_DB_ENVIRONMENT").isEmpty(),
"AkonadiTest::checkTestIsIsolated",
"This test must be run using ctest, in order to use the testrunner environment. Aborting, to avoid messing up your real akonadi");
+ Q_ASSERT_X(qgetenv("XDG_DATA_HOME").contains("testrunner"),
+ "AkonadiTest::checkTestIsIsolated",
+ "Did you forget to run the test using QTEST_AKONADIMAIN?");
}
/**
commit 9028b5ef9395f1fb3d83e0ee4eae996c8f96241d
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date: Mon Aug 25 15:19:40 2014 +0200
Print warnings for problems.
diff --git a/akonadi/session.cpp b/akonadi/session.cpp
index ec6bc28..313c6b5 100644
--- a/akonadi/session.cpp
+++ b/akonadi/session.cpp
@@ -118,7 +118,7 @@ void SessionPrivate::reconnect()
const QString connectionConfigFile = connectionFile();
const QFileInfo fileInfo(connectionConfigFile);
if (!fileInfo.exists()) {
- kDebug() << "Akonadi Client Session: connection config file '"
+ kWarning() << "Akonadi Client Session: connection config file '"
"akonadi/akonadiconnectionrc' can not be found in"
<< XdgBaseDirs::homePath("config") << "nor in any of"
<< XdgBaseDirs::systemPathList("config");
commit 86c5a9eca962b9c708010852d40835c76399e95d
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date: Mon Aug 25 01:33:15 2014 +0200
TagModifyJob: Allow unsetting the remoteId
diff --git a/akonadi/tagmodifyjob.cpp b/akonadi/tagmodifyjob.cpp
index c4f8432..ddd3cbc 100644
--- a/akonadi/tagmodifyjob.cpp
+++ b/akonadi/tagmodifyjob.cpp
@@ -47,7 +47,7 @@ void TagModifyJob::doStart()
Q_D(TagModifyJob);
QList<QByteArray> list;
- if (!d->mTag.remoteId().isEmpty()) {
+ if (!d->mTag.remoteId().isNull()) {
list << "REMOTEID";
list << ImapParser::quote(d->mTag.remoteId());
}
diff --git a/akonadi/tests/tagtest.cpp b/akonadi/tests/tagtest.cpp
index 7570b8c..1ddf340 100644
--- a/akonadi/tests/tagtest.cpp
+++ b/akonadi/tests/tagtest.cpp
@@ -51,9 +51,11 @@ private Q_SLOTS:
void testRID();
void testDelete();
void testModify();
+ void testModifyFromResource();
void testCreateMerge();
void testAttributes();
void testTagItem();
+ void testCreateItem();
void testRIDIsolation();
void testFetchTagIdWithItem();
void testFetchFullTagWithItem();
@@ -291,6 +293,36 @@ void TagTest::testModify()
AKVERIFYEXEC(deleteJob);
}
+void TagTest::testModifyFromResource()
+{
+ ResourceSelectJob *select = new ResourceSelectJob(QLatin1String("akonadi_knut_resource_0"));
+ AKVERIFYEXEC(select);
+
+ Tag tag;
+ {
+ tag.setGid("gid");
+ tag.setRemoteId("rid");
+ TagCreateJob *createjob = new TagCreateJob(tag, this);
+ AKVERIFYEXEC(createjob);
+ QVERIFY(createjob->tag().isValid());
+ tag = createjob->tag();
+ }
+
+ {
+ tag.setRemoteId(QByteArray(""));
+ TagModifyJob *modJob = new TagModifyJob(tag, this);
+ AKVERIFYEXEC(modJob);
+
+ TagFetchJob *fetchJob = new TagFetchJob(this);
+ AKVERIFYEXEC(fetchJob);
+ QCOMPARE(fetchJob->tags().size(), 1);
+ QVERIFY(fetchJob->tags().first().remoteId().isEmpty());
+ }
+
+ TagDeleteJob *deleteJob = new TagDeleteJob(tag, this);
+ AKVERIFYEXEC(deleteJob);
+}
+
void TagTest::testCreateMerge()
{
Tag tag;
@@ -412,8 +444,11 @@ void TagTest::testTagItem()
AKVERIFYEXEC(deleteJob);
}
-void TagTest::testFetchTagIdWithItem()
+void TagTest::testCreateItem()
{
+ // Akonadi::Monitor monitor;
+ // monitor.itemFetchScope().setFetchTags(true);
+ // monitor.setAllMonitored(true);
const Collection res3 = Collection( collectionIdFromPath( "res3" ) );
Tag tag;
{
@@ -422,18 +457,51 @@ void TagTest::testFetchTagIdWithItem()
tag = createjob->tag();
}
+ // QSignalSpy tagsSpy(&monitor, SIGNAL(itemsTagsChanged(Akonadi::Item::List,QSet<Akonadi::Tag>,QSet<Akonadi::Tag>)));
+ // QVERIFY(tagsSpy.isValid());
+
Item item1;
{
- item1.setMimeType( "application/octet-stream" );
+ item1.setMimeType("application/octet-stream");
+ item1.setTag(tag);
ItemCreateJob *append = new ItemCreateJob(item1, res3, this);
AKVERIFYEXEC(append);
item1 = append->item();
+ }
- // FIXME This should also be possible with create, but isn't
- item1.setTag(tag);
- ItemModifyJob *modJob = new ItemModifyJob(item1, this);
- AKVERIFYEXEC(modJob);
+ // QTRY_VERIFY(tagsSpy.count() >= 1);
+ // QTest::qWait(10);
+ // kDebug() << tagsSpy.count();
+ // QTRY_COMPARE(tagsSpy.last().first().value<Akonadi::Item::List>().first().id(), item1.id());
+ // QTRY_COMPARE(tagsSpy.last().at(1).value< QSet<Tag> >().size(), 1); //1 added tag
+
+ ItemFetchJob *fetchJob = new ItemFetchJob(item1, this);
+ fetchJob->fetchScope().setFetchTags(true);
+ AKVERIFYEXEC(fetchJob);
+ QCOMPARE(fetchJob->items().first().tags().size(), 1);
+
+ TagDeleteJob *deleteJob = new TagDeleteJob(tag, this);
+ AKVERIFYEXEC(deleteJob);
+}
+
+void TagTest::testFetchTagIdWithItem()
+{
+ const Collection res3 = Collection( collectionIdFromPath( "res3" ) );
+ Tag tag;
+ {
+ TagCreateJob *createjob = new TagCreateJob(Tag("gid1"), this);
+ AKVERIFYEXEC(createjob);
+ tag = createjob->tag();
+ }
+
+ Item item1;
+ {
+ item1.setMimeType( "application/octet-stream" );
+ item1.setTag(tag);
+ ItemCreateJob *append = new ItemCreateJob(item1, res3, this);
+ AKVERIFYEXEC(append);
+ item1 = append->item();
}
ItemFetchJob *fetchJob = new ItemFetchJob(item1, this);
commit 20acbaf079f231b9a8a8f249948fefc14a781af2
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date: Mon Aug 25 01:32:46 2014 +0200
TagSync
diff --git a/akonadi/CMakeLists.txt b/akonadi/CMakeLists.txt
index 7f2417f..622a41c 100644
--- a/akonadi/CMakeLists.txt
+++ b/akonadi/CMakeLists.txt
@@ -207,6 +207,7 @@ set( akonadikde_LIB_SRC
tageditwidget.cpp
tagmanagementdialog.cpp
tagselectiondialog.cpp
+ tagsync.cpp
tagwidget.cpp
unlinkjob.cpp
# Temporary until ported to Qt-plugin framework
@@ -430,6 +431,7 @@ install( FILES
tagwidget.h
tagmanagementdialog.h
tagselectiondialog.h
+ tagsync.h
trashjob.h
trashrestorejob.h
trashsettings.h
diff --git a/akonadi/tagsync.cpp b/akonadi/tagsync.cpp
new file mode 100644
index 0000000..f534dba
--- /dev/null
+++ b/akonadi/tagsync.cpp
@@ -0,0 +1,243 @@
+/*
+ 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.
+*/
+namespace Akonadi {
+ class Item;
+}
+
+unsigned int qHash(const Akonadi::Item &item);
+
+#include "tagsync.h"
+
+#include <akonadi/itemfetchjob.h>
+#include <akonadi/itemfetchscope.h>
+#include <akonadi/itemmodifyjob.h>
+#include <akonadi/tagfetchjob.h>
+#include <akonadi/tagcreatejob.h>
+#include <akonadi/tagmodifyjob.h>
+
+using namespace Akonadi;
+
+//We want to compare items by remoteId and not by id
+uint qHash(const Item &item)
+{
+ if (item.isValid()) {
+ return qHash(item.id());
+ }
+ Q_ASSERT(!item.remoteId().isEmpty());
+ return qHash(item.remoteId());
+}
+
+bool operator==(const Item &left, const Item &right)
+{
+ if (left.isValid() && right.isValid() && (left.id() == right.id())) {
+ return true;
+ }
+ if (!left.remoteId().isEmpty() && !right.remoteId().isEmpty() && (left.remoteId() == right.remoteId())) {
+ return true;
+ }
+ return false;
+}
+
+TagSync::TagSync(QObject *parent)
+ : Job(parent),
+ mDeliveryDone(false),
+ mTagMembersDeliveryDone(false),
+ mLocalTagsFetched(false)
+{
+
+}
+
+TagSync::~TagSync()
+{
+
+}
+
+void TagSync::setFullTagList(const Akonadi::Tag::List &tags)
+{
+ mRemoteTags = tags;
+ mDeliveryDone = true;
+ diffTags();
+}
+
+void TagSync::setTagMembers(const QHash<QString, Akonadi::Item::List> &ridMemberMap)
+{
+ mRidMemberMap = ridMemberMap;
+ mTagMembersDeliveryDone = true;
+ diffTags();
+}
+
+void TagSync::doStart()
+{
+ // kDebug();
+ //This should include all tags, including the ones that don't have a remote id
+ Akonadi::TagFetchJob *fetch = new Akonadi::TagFetchJob(this);
+ connect(fetch, SIGNAL(result(KJob*)), this, SLOT(onLocalTagFetchDone(KJob*)));
+}
+
+void TagSync::onLocalTagFetchDone(KJob *job)
+{
+ // kDebug();
+ TagFetchJob *fetch = static_cast<TagFetchJob*>(job);
+ mLocalTags = fetch->tags();
+ mLocalTagsFetched = true;
+ diffTags();
+}
+
+void TagSync::diffTags()
+{
+ if (!mDeliveryDone || !mTagMembersDeliveryDone || !mLocalTagsFetched) {
+ kDebug() << "waiting for delivery: " << mDeliveryDone << mLocalTagsFetched;
+ return;
+ }
+ // kDebug() << "diffing";
+ QHash<QByteArray, Akonadi::Tag> tagByGid;
+ QHash<QByteArray, Akonadi::Tag> tagByRid;
+ QHash<Akonadi::Tag::Id, Akonadi::Tag> tagById;
+ Q_FOREACH (const Akonadi::Tag &localTag, mLocalTags) {
+ tagByRid.insert(localTag.remoteId(), localTag);
+ tagByGid.insert(localTag.gid(), localTag);
+ if (!localTag.remoteId().isEmpty()) {
+ tagById.insert(localTag.id(), localTag);
+ }
+ }
+ Q_FOREACH (const Akonadi::Tag &remoteTag, mRemoteTags) {
+ if (tagByRid.contains(remoteTag.remoteId())) {
+ //Tag still exists, check members
+ Tag tag = tagByRid.value(remoteTag.remoteId());
+ ItemFetchJob *itemFetch = new ItemFetchJob(tag, this);
+ itemFetch->setProperty("tag", QVariant::fromValue(tag));
+ itemFetch->setProperty("merge", false);
+ connect(itemFetch, SIGNAL(result(KJob*)), this, SLOT(onTagItemsFetchDone(KJob*)));
+ connect(itemFetch, SIGNAL(result(KJob*)), this, SLOT(onJobDone(KJob*)));
+ tagById.remove(tagByRid.value(remoteTag.remoteId()).id());
+ } else if (tagByGid.contains(remoteTag.gid())) {
+ //Tag exists but has no rid
+ //Merge members and set rid
+ Tag tag = tagByGid.value(remoteTag.gid());
+ tag.setRemoteId(remoteTag.remoteId());
+ ItemFetchJob *itemFetch = new ItemFetchJob(tag, this);
+ itemFetch->setProperty("tag", QVariant::fromValue(tag));
+ itemFetch->setProperty("merge", true);
+ connect(itemFetch, SIGNAL(result(KJob*)), this, SLOT(onTagItemsFetchDone(KJob*)));
+ connect(itemFetch, SIGNAL(result(KJob*)), this, SLOT(onJobDone(KJob*)));
+ tagById.remove(tagByGid.value(remoteTag.gid()).id());
+ } else {
+ //New tag, create
+ TagCreateJob *createJob = new TagCreateJob(remoteTag, this);
+ createJob->setMergeIfExisting(true);
+ connect(createJob, SIGNAL(result(KJob*)), this, SLOT(onCreateTagDone(KJob*)));
+ connect(createJob, SIGNAL(result(KJob*)), this, SLOT(onJobDone(KJob*)));
+ //TODO add tags
+ }
+ }
+ Q_FOREACH (const Akonadi::Tag::Id &removedTag, tagById.keys()) {
+ //Removed remotely, unset rid
+ Tag tag = tagById.value(removedTag);
+ tag.setRemoteId(QByteArray(""));
+ TagModifyJob *modJob = new TagModifyJob(tag, this);
+ connect(modJob, SIGNAL(result(KJob*)), this, SLOT(onJobDone(KJob*)));
+ }
+ checkDone();
+}
+
+static QSet<QString> ridSet(const Akonadi::Item::List &list)
+{
+ QSet<QString> set;
+ Q_FOREACH (const Akonadi::Item &item, list) {
+ set << item.remoteId();
+ }
+ return set;
+}
+
+void TagSync::onCreateTagDone(KJob *job)
+{
+ if (job->error()) {
+ kWarning() << "ItemFetch failed: " << job->errorString();
+ // cancelTask(job->errorString());
+ return;
+ }
+
+ Akonadi::Tag tag = static_cast<Akonadi::TagCreateJob*>(job)->tag();
+ const Item::List remoteMembers = mRidMemberMap.value(QString::fromLatin1(tag.remoteId()));
+ Q_FOREACH (Item item, remoteMembers) {
+ item.setTag(tag);
+ ItemModifyJob *modJob = new ItemModifyJob(item, this);
+ connect(modJob, SIGNAL(result(KJob*)), this, SLOT(onJobDone(KJob*)));
+ kDebug() << "setting tag " << item.remoteId();
+ }
+}
+
+void TagSync::onTagItemsFetchDone(KJob *job)
+{
+ if (job->error()) {
+ kWarning() << "ItemFetch failed: " << job->errorString();
+ // cancelTask(job->errorString());
+ return;
+ }
+
+ const Akonadi::Item::List items = static_cast<Akonadi::ItemFetchJob*>(job)->items();
+ const Akonadi::Tag tag = job->property("tag").value<Akonadi::Tag>();
+ const bool merge = job->property("merge").toBool();
+ const QSet<Item> localMembers = items.toSet();
+ const QSet<Item> remoteMembers = mRidMemberMap.value(QString::fromLatin1(tag.remoteId())).toSet();
+ const QSet<Item> toAdd = remoteMembers - localMembers;
+ const QSet<Item> toRemove = localMembers - remoteMembers;
+ if (!merge) {
+ Q_FOREACH (Item item, toRemove) {
+ item.clearTag(tag);
+ ItemModifyJob *modJob = new ItemModifyJob(item, this);
+ connect(modJob, SIGNAL(result(KJob*)), this, SLOT(onJobDone(KJob*)));
+ kDebug() << "removing tag " << item.remoteId();
+ }
+ }
+ Q_FOREACH (Item item, toAdd) {
+ item.setTag(tag);
+ ItemModifyJob *modJob = new ItemModifyJob(item, this);
+ connect(modJob, SIGNAL(result(KJob*)), this, SLOT(onJobDone(KJob*)));
+ kDebug() << "setting tag " << item.remoteId();
+ }
+}
+
+void TagSync::onJobDone(KJob *job)
+{
+ checkDone();
+}
+
+void TagSync::slotResult(KJob *job)
+{
+ if (job->error()) {
+ kWarning() << "Error during CollectionSync: " << job->errorString() << job->metaObject()->className();
+ // pretent there were no errors
+ Akonadi::Job::removeSubjob(job);
+ } else {
+ Akonadi::Job::slotResult(job);
+ }
+}
+
+void TagSync::checkDone()
+{
+ if (hasSubjobs()) {
+ kDebug() << "Still going";
+ return;
+ }
+ kDebug() << "done";
+ emitResult();
+}
+
+#include "tagsync.moc"
diff --git a/akonadi/tagsync.h b/akonadi/tagsync.h
new file mode 100644
index 0000000..44146d2
--- /dev/null
+++ b/akonadi/tagsync.h
@@ -0,0 +1,61 @@
+/*
+ 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 TAGSYNC_H
+#define TAGSYNC_H
+
+#include "akonadi_export.h"
+#include <Akonadi/Job>
+#include <Akonadi/Tag>
+#include <Akonadi/Item>
+
+class AKONADI_EXPORT TagSync : public Akonadi::Job
+{
+ Q_OBJECT
+public:
+ TagSync(QObject *parent = 0);
+ virtual ~TagSync();
+
+ void setFullTagList(const Akonadi::Tag::List &tags);
+ void setTagMembers(const QHash<QString, Akonadi::Item::List> &ridMemberMap);
+
+protected:
+ void doStart();
+
+private Q_SLOTS:
+ void onLocalTagFetchDone(KJob *job);
+ // void onItemsFetchDone(KJob *job);
+ void onCreateTagDone(KJob *job);
+ void onTagItemsFetchDone(KJob *job);
+ void onJobDone(KJob *job);
+ void slotResult(KJob *job);
+
+private:
+ void diffTags();
+ void checkDone();
+
+private:
+ Akonadi::Tag::List mRemoteTags;
+ Akonadi::Tag::List mLocalTags;
+ bool mDeliveryDone;
+ bool mTagMembersDeliveryDone;
+ bool mLocalTagsFetched;
+ QHash<QString, Akonadi::Item::List> mRidMemberMap;
+};
+
+#endif
diff --git a/akonadi/tests/CMakeLists.txt b/akonadi/tests/CMakeLists.txt
index 7d66e4a..61133bf 100644
--- a/akonadi/tests/CMakeLists.txt
+++ b/akonadi/tests/CMakeLists.txt
@@ -152,5 +152,6 @@ add_akonadi_isolated_test(lazypopulationtest.cpp)
add_akonadi_isolated_test(favoriteproxytest.cpp)
add_akonadi_isolated_test_advanced(itemsearchjobtest.cpp testsearchplugin/testsearchplugin.cpp "")
add_akonadi_isolated_test(tagtest.cpp)
+add_akonadi_isolated_test(tagsynctest.cpp)
add_akonadi_isolated_test(relationtest.cpp)
add_akonadi_isolated_test(etmpopulationtest.cpp)
diff --git a/akonadi/tests/tagsynctest.cpp b/akonadi/tests/tagsynctest.cpp
new file mode 100644
index 0000000..5f8558c
--- /dev/null
+++ b/akonadi/tests/tagsynctest.cpp
@@ -0,0 +1,273 @@
+/*
+ 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 "test_utils.h"
+
+#include <akonadi/agentmanager.h>
+#include <akonadi/agentinstance.h>
+#include <akonadi/control.h>
+#include <akonadi/tagfetchjob.h>
+#include <akonadi/tagdeletejob.h>
+#include <akonadi/tagcreatejob.h>
+#include <akonadi/tag.h>
+#include <akonadi/tagsync.h>
+#include <akonadi/resourceselectjob_p.h>
+#include <akonadi/itemcreatejob.h>
+#include <akonadi/itemfetchjob.h>
+#include <akonadi/itemfetchscope.h>
+
+#include <QtCore/QObject>
+#include <QSignalSpy>
+
+#include <qtest_akonadi.h>
+
+using namespace Akonadi;
+
+bool operator==(const Tag &left, const Tag &right)
+{
+ if (left.gid() == right.gid()) {
+ return true;
+ }
+ qDebug() << left.gid();
+ qDebug() << right.gid();
+ return false;
+}
+
+class TagSyncTest : public QObject
+{
+ Q_OBJECT
+private Q_SLOTS:
+ void initTestCase()
+ {
+ AkonadiTest::checkTestIsIsolated();
+ Control::start();
+ AkonadiTest::setAllResourcesOffline();
+ cleanTags();
+ }
+
+ Tag::List getTags()
+ {
+ TagFetchJob *fetchJob = new TagFetchJob();
+ bool ret = fetchJob->exec();
+ Q_ASSERT(ret);
+ return fetchJob->tags();
+ }
+
+ Tag::List getTagsWithRid()
+ {
+ Tag::List tags;
+ Q_FOREACH(const Tag &t, getTags()) {
+ if (!t.remoteId().isEmpty()) {
+ tags << t;
+ kDebug() << t.remoteId();
+ }
+ }
+ return tags;
+ }
+
+ void cleanTags()
+ {
+ Q_FOREACH(const Tag &t, getTags()) {
+ TagDeleteJob *job = new TagDeleteJob(t);
+ bool ret = job->exec();
+ Q_ASSERT(ret);
+ }
+ }
+
+ void newTag()
+ {
+ ResourceSelectJob *select = new ResourceSelectJob(QLatin1String("akonadi_knut_resource_0"));
+ AKVERIFYEXEC(select);
+
+ Tag::List remoteTags;
+
+ Tag tag1("tag1");
+ tag1.setRemoteId("rid1");
+ remoteTags << tag1;
+
+ TagSync* syncer = new TagSync(this);
+ syncer->setFullTagList(remoteTags);
+ syncer->setTagMembers(QHash<QString, Item::List>());
+ AKVERIFYEXEC(syncer);
+
+ Tag::List resultTags = getTags();
+ QCOMPARE(resultTags.count(), remoteTags.count());
+ QCOMPARE(resultTags, remoteTags);
+ cleanTags();
+ }
+
+ void newTagWithItems()
+ {
+ const Collection res3 = Collection( collectionIdFromPath( "res3" ) );
+ ResourceSelectJob *select = new ResourceSelectJob(QLatin1String("akonadi_knut_resource_0"));
+ AKVERIFYEXEC(select);
+
+ Tag::List remoteTags;
+
+ Tag tag1("tag1");
+ tag1.setRemoteId("rid1");
+ remoteTags << tag1;
+
+ Item item1;
+ {
+ item1.setMimeType("application/octet-stream");
+ item1.setRemoteId("item1");
+ ItemCreateJob *append = new ItemCreateJob(item1, res3, this);
+ AKVERIFYEXEC(append);
+ item1 = append->item();
+ }
+
+ QHash<QString, Item::List> tagMembers;
+ tagMembers.insert(tag1.remoteId(), Item::List() << item1);
+
+ TagSync* syncer = new TagSync(this);
+ syncer->setFullTagList(remoteTags);
+ syncer->setTagMembers(tagMembers);
+ AKVERIFYEXEC(syncer);
+
+ Tag::List resultTags = getTags();
+ QCOMPARE(resultTags.count(), remoteTags.count());
+ QCOMPARE(resultTags, remoteTags);
+
+ //We need the id of the fetch
+ tag1 = resultTags.first();
+
+ ItemFetchJob *fetchJob = new ItemFetchJob(tag1);
+ AKVERIFYEXEC(fetchJob);
+ QCOMPARE(tagMembers.value(tag1.remoteId()).count(), fetchJob->items().count());
+ QCOMPARE(tagMembers.value(tag1.remoteId()), fetchJob->items());
+
+ cleanTags();
+ }
+
+ void existingTag()
+ {
+ ResourceSelectJob *select = new ResourceSelectJob(QLatin1String("akonadi_knut_resource_0"));
+ AKVERIFYEXEC(select);
+
+ Tag tag1("tag1");
+ tag1.setRemoteId("rid1");
+
+ TagCreateJob *createJob = new TagCreateJob(tag1, this);
+ AKVERIFYEXEC(createJob);
+
+ Tag::List remoteTags;
+ remoteTags << tag1;
+
+ TagSync* syncer = new TagSync(this);
+ syncer->setFullTagList(remoteTags);
+ syncer->setTagMembers(QHash<QString, Item::List>());
+ AKVERIFYEXEC(syncer);
+
+ Tag::List resultTags = getTags();
+ QCOMPARE(resultTags.count(), remoteTags.count());
+ QCOMPARE(resultTags, remoteTags);
+ cleanTags();
+ }
+
+ void existingTagWithItems()
+ {
+ const Collection res3 = Collection( collectionIdFromPath( "res3" ) );
+
+ ResourceSelectJob *select = new ResourceSelectJob(QLatin1String("akonadi_knut_resource_0"));
+ AKVERIFYEXEC(select);
+
+ Tag tag1("tag1");
+ tag1.setRemoteId("rid1");
+
+ TagCreateJob *createJob = new TagCreateJob(tag1, this);
+ AKVERIFYEXEC(createJob);
+
+ Tag::List remoteTags;
+ remoteTags << tag1;
+
+ Item item1;
+ {
+ item1.setMimeType("application/octet-stream");
+ item1.setRemoteId("item1");
+ ItemCreateJob *append = new ItemCreateJob(item1, res3, this);
+ AKVERIFYEXEC(append);
+ item1 = append->item();
+ }
+
+ Item item2;
+ {
+ item2.setMimeType("application/octet-stream");
+ item2.setRemoteId("item2");
+ item2.setTag(tag1);
+ ItemCreateJob *append = new ItemCreateJob(item2, res3, this);
+ AKVERIFYEXEC(append);
+ item2 = append->item();
+ }
+
+ QHash<QString, Item::List> tagMembers;
+ tagMembers.insert(tag1.remoteId(), Item::List() << item1);
+
+ TagSync* syncer = new TagSync(this);
+ syncer->setFullTagList(remoteTags);
+ syncer->setTagMembers(tagMembers);
+ AKVERIFYEXEC(syncer);
+
+ Tag::List resultTags = getTags();
+ QCOMPARE(resultTags.count(), remoteTags.count());
+ QCOMPARE(resultTags, remoteTags);
+ {
+ ItemFetchJob *fetchJob = new ItemFetchJob(item1, this);
+ fetchJob->fetchScope().setFetchTags(true);
+ AKVERIFYEXEC(fetchJob);
+ QCOMPARE(fetchJob->items().first().tags().count(), 1);
+ }
+ {
+ ItemFetchJob *fetchJob = new ItemFetchJob(item2, this);
+ fetchJob->fetchScope().setFetchTags(true);
+ AKVERIFYEXEC(fetchJob);
+ QCOMPARE(fetchJob->items().first().tags().count(), 0);
+ }
+
+ cleanTags();
+ }
+
+ void removeTag()
+ {
+ ResourceSelectJob *select = new ResourceSelectJob(QLatin1String("akonadi_knut_resource_0"));
+ AKVERIFYEXEC(select);
+
+ Tag tag1("tag1");
+ tag1.setRemoteId("rid1");
+
+ TagCreateJob *createJob = new TagCreateJob(tag1, this);
+ AKVERIFYEXEC(createJob);
+
+ Tag::List remoteTags;
+
+ TagSync* syncer = new TagSync(this);
+ syncer->setFullTagList(remoteTags);
+ syncer->setTagMembers(QHash<QString, Item::List>());
+ AKVERIFYEXEC(syncer);
+
+ Tag::List resultTags = getTagsWithRid();
+ QCOMPARE(resultTags.count(), remoteTags.count());
+ QCOMPARE(resultTags, remoteTags);
+ cleanTags();
+ }
+};
+
+QTEST_AKONADIMAIN(TagSyncTest, NoGUI)
+
+#include "tagsynctest.moc"
commit a36fe428ec49831ede5c19b63bb4a07075a23823
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date: Fri Aug 22 13:32:52 2014 +0200
CollectionSync: Only modify collections that have actually changed.
We used to unconditionally modify all if at least one changed.
diff --git a/akonadi/collectionsync.cpp b/akonadi/collectionsync.cpp
index 4185a50..ff16d7f 100644
--- a/akonadi/collectionsync.cpp
+++ b/akonadi/collectionsync.cpp
@@ -454,7 +454,7 @@ public:
}
}
- {
+ if (checkLocalCollection(localNode, remoteNode)) {
// ### HACK to work around the implicit move attempts of CollectionModifyJob
// which we do explicitly below
Collection c(upd);
commit 1da801b81c156ff24a9b29e6ebf3992c9997d9ba
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date: Fri Aug 22 13:31:25 2014 +0200
TagTest: added test to fetch items by tag.
diff --git a/akonadi/tests/tagtest.cpp b/akonadi/tests/tagtest.cpp
index 915237c..7570b8c 100644
--- a/akonadi/tests/tagtest.cpp
+++ b/akonadi/tests/tagtest.cpp
@@ -60,6 +60,7 @@ private Q_SLOTS:
void testModifyItemWithTagByGID();
void testModifyItemWithTagByRID();
void testMonitor();
+ void testFetchItemsByTag();
};
void TagTest::initTestCase()
@@ -609,6 +610,39 @@ void TagTest::testMonitor()
}
}
+void TagTest::testFetchItemsByTag()
+{
+ const Collection res3 = Collection( collectionIdFromPath( "res3" ) );
+ Tag tag;
+ {
+ TagCreateJob *createjob = new TagCreateJob(Tag("gid1"), this);
+ AKVERIFYEXEC(createjob);
+ tag = createjob->tag();
+ }
+
+ Item item1;
+ {
+ item1.setMimeType( "application/octet-stream" );
+ ItemCreateJob *append = new ItemCreateJob(item1, res3, this);
+ AKVERIFYEXEC(append);
+ item1 = append->item();
+ //FIXME This should also be possible with create, but isn't
+ item1.setTag(tag);
+ }
+
+ ItemModifyJob *modJob = new ItemModifyJob(item1, this);
+ AKVERIFYEXEC(modJob);
+
+ ItemFetchJob *fetchJob = new ItemFetchJob(tag, this);
+ AKVERIFYEXEC(fetchJob);
+ QCOMPARE(fetchJob->items().size(), 1);
+ Item i = fetchJob->items().first();
+ QCOMPARE(i, item1);
+
+ TagDeleteJob *deleteJob = new TagDeleteJob(tag, this);
+ AKVERIFYEXEC(deleteJob);
+}
+
#include "tagtest.moc"
QTEST_AKONADIMAIN(TagTest, NoGUI)
commit 6846f6dcbc5681a91174267ef97029eb5885a2f1
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date: Wed Aug 20 17:08:15 2014 +0200
Disable remote search by default.
Looks like a previous patch somehow got reverted...
diff --git a/akonadi/itemsearchjob.cpp b/akonadi/itemsearchjob.cpp
index aef8472..431b609 100644
--- a/akonadi/itemsearchjob.cpp
+++ b/akonadi/itemsearchjob.cpp
@@ -37,7 +37,7 @@ public:
: JobPrivate(parent)
, mQuery(query)
, mRecursive(false)
- , mRemote(true)
+ , mRemote(false)
, mEmitTimer(0)
{
}
diff --git a/akonadi/searchcreatejob.cpp b/akonadi/searchcreatejob.cpp
index 2bedbd5..4f0b955 100644
--- a/akonadi/searchcreatejob.cpp
+++ b/akonadi/searchcreatejob.cpp
@@ -38,7 +38,7 @@ public:
, mName(name)
, mQuery(query)
, mRecursive(false)
- , mRemote(true)
+ , mRemote(false)
{
}
commit 2b20f479e02657f3dffe4d44b3a6ed1fc6ddb5e6
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date: Tue Aug 19 22:31:12 2014 +0200
EntityCache: If we don't do error handling, at least print the error.
diff --git a/akonadi/entitycache_p.h b/akonadi/entitycache_p.h
index f296686..48fb5c1 100644
--- a/akonadi/entitycache_p.h
+++ b/akonadi/entitycache_p.h
@@ -184,7 +184,10 @@ class EntityCache : public EntityCacheBase
void processResult( KJob* job )
{
- // Error handling?
+ if (job->error()) {
+ //This can happen if we have stale notifications for items that have already been removed
+ kWarning() << "An error occured: " << job->errorString();
+ }
typename T::Id id = job->property( "EntityCacheNode" ).template value<typename T::Id>();
EntityCacheNode<T> *node = cacheNodeForId( id );
if ( !node ) {
commit 2c3d657dce223cc819c3146fc8e8fdaf4919c293
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date: Tue Aug 19 22:30:19 2014 +0200
ProtocolHelper: It should be possible to fetch tags even with the root collection set.
diff --git a/akonadi/protocolhelper.cpp b/akonadi/protocolhelper.cpp
index c0027c7..3cd4197 100644
--- a/akonadi/protocolhelper.cpp
+++ b/akonadi/protocolhelper.cpp
@@ -386,7 +386,7 @@ QByteArray ProtocolHelper::commandContextToByteArray(const Akonadi::Collection &
}
if (collection == Collection::root()) {
- if (requestedItems.isEmpty()) { // collection content listing
+ if (requestedItems.isEmpty() && !tag.isValid()) { // collection content listing
throw Exception("Cannot perform item operations on root collection.");
}
} else {
commit c10aba63f8500b513ad9a16a923f7d133d7257fa
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date: Tue Aug 19 22:29:44 2014 +0200
CollectionFetchScope: set fetchid only when we request attributes.
diff --git a/akonadi/collectionfetchscope.cpp b/akonadi/collectionfetchscope.cpp
index 560b31d..8265d3c 100644
--- a/akonadi/collectionfetchscope.cpp
+++ b/akonadi/collectionfetchscope.cpp
@@ -172,6 +172,7 @@ QSet<QByteArray> CollectionFetchScope::attributes() const
void CollectionFetchScope::fetchAttribute(const QByteArray &type, bool fetch)
{
+ d->fetchIdOnly = false;
if (fetch) {
d->attributes.insert(type);
} else {
commit 666a3389c5e1e4122c7e48417f77267de78fb10c
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date: Tue Aug 19 00:03:12 2014 +0200
Fixed protocolhelpertest.
diff --git a/akonadi/tests/protocolhelpertest.cpp b/akonadi/tests/protocolhelpertest.cpp
index f4a4d93..2a2294f 100644
--- a/akonadi/tests/protocolhelpertest.cpp
+++ b/akonadi/tests/protocolhelpertest.cpp
@@ -175,13 +175,15 @@ class ProtocolHelperTest : public QObject
QTest::newRow( "empty" ) << Collection() << QByteArray();
QTest::newRow( "root" ) << Collection::root() << QByteArray( "(0 \"\")" );
Collection c;
+ c.setId(1);
c.setParentCollection( Collection::root() );
c.setRemoteId( "r1" );
- QTest::newRow( "one level" ) << c << QByteArray( "(-23 \"r1\") (0 \"\")" );
+ QTest::newRow( "one level" ) << c << QByteArray( "(1 \"r1\") (0 \"\")" );
Collection c2;
+ c2.setId(2);
c2.setParentCollection( c );
c2.setRemoteId( "r2" );
- QTest::newRow( "two level ok" ) << c2 << QByteArray( "(-24 \"r2\") (-23 \"r1\") (0 \"\")" );
+ QTest::newRow( "two level ok" ) << c2 << QByteArray( "(2 \"r2\") (1 \"r1\") (0 \"\")" );
}
void testHRidToByteArray()
commit 3589b73236e7e93b7dbef07fcc2ff60043866e5d
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date: Tue Aug 19 00:02:55 2014 +0200
Adapted actionstatemanagertest to new action
diff --git a/akonadi/tests/actionstatemanagertest.cpp b/akonadi/tests/actionstatemanagertest.cpp
index 74acdf3..6d83b73 100644
--- a/akonadi/tests/actionstatemanagertest.cpp
+++ b/akonadi/tests/actionstatemanagertest.cpp
@@ -178,6 +178,7 @@ class ActionStateManagerTest : public QObject
map.insert( StandardActionManager::RestoreItemsFromTrash, false );
map.insert( StandardActionManager::MoveToTrashRestoreCollection, false );
map.insert( StandardActionManager::MoveToTrashRestoreItem, false );
+ map.insert( StandardActionManager::SynchronizeCollectionTree, false );
QTest::newRow( "nothing selected" ) << collectionList << map;
}
@@ -219,6 +220,7 @@ class ActionStateManagerTest : public QObject
map.insert( StandardActionManager::RestoreItemsFromTrash, false );
map.insert( StandardActionManager::MoveToTrashRestoreCollection, false );
map.insert( StandardActionManager::MoveToTrashRestoreItem, false );
+ map.insert( StandardActionManager::SynchronizeCollectionTree, false );
QTest::newRow( "root collection selected" ) << collectionList << map;
}
@@ -260,6 +262,7 @@ class ActionStateManagerTest : public QObject
map.insert( StandardActionManager::RestoreItemsFromTrash, false );
map.insert( StandardActionManager::MoveToTrashRestoreCollection, false );
map.insert( StandardActionManager::MoveToTrashRestoreItem, false );
+ map.insert( StandardActionManager::SynchronizeCollectionTree, true );
QTest::newRow( "read-only resource collection selected" ) << collectionList << map;
}
@@ -301,6 +304,7 @@ class ActionStateManagerTest : public QObject
map.insert( StandardActionManager::RestoreItemsFromTrash, false );
map.insert( StandardActionManager::MoveToTrashRestoreCollection, false );
map.insert( StandardActionManager::MoveToTrashRestoreItem, false );
+ map.insert( StandardActionManager::SynchronizeCollectionTree, true );
QTest::newRow( "writable resource collection selected" ) << collectionList << map;
}
@@ -342,6 +346,7 @@ class ActionStateManagerTest : public QObject
map.insert( StandardActionManager::RestoreItemsFromTrash, false );
map.insert( StandardActionManager::MoveToTrashRestoreCollection, false );
map.insert( StandardActionManager::MoveToTrashRestoreItem, false );
+ map.insert( StandardActionManager::SynchronizeCollectionTree, true );
QTest::newRow( "non-configurable resource collection selected" ) << collectionList << map;
}
@@ -383,6 +388,7 @@ class ActionStateManagerTest : public QObject
map.insert( StandardActionManager::RestoreItemsFromTrash, false );
map.insert( StandardActionManager::MoveToTrashRestoreCollection, false );
map.insert( StandardActionManager::MoveToTrashRestoreItem, false );
+ map.insert( StandardActionManager::SynchronizeCollectionTree, false );
QTest::newRow( "read-only folder collection selected" ) << collectionList << map;
}
@@ -424,6 +430,7 @@ class ActionStateManagerTest : public QObject
map.insert( StandardActionManager::RestoreItemsFromTrash, false );
map.insert( StandardActionManager::MoveToTrashRestoreCollection, true );
map.insert( StandardActionManager::MoveToTrashRestoreItem, false );
+ map.insert( StandardActionManager::SynchronizeCollectionTree, false );
QTest::newRow( "writable folder collection selected" ) << collectionList << map;
}
@@ -465,6 +472,7 @@ class ActionStateManagerTest : public QObject
map.insert( StandardActionManager::RestoreItemsFromTrash, false );
map.insert( StandardActionManager::MoveToTrashRestoreCollection, true );
map.insert( StandardActionManager::MoveToTrashRestoreItem, false );
+ map.insert( StandardActionManager::SynchronizeCollectionTree, false );
QTest::newRow( "favorite writable folder collection selected" ) << collectionList << map;
}
@@ -506,6 +514,7 @@ class ActionStateManagerTest : public QObject
map.insert( StandardActionManager::RestoreItemsFromTrash, false );
map.insert( StandardActionManager::MoveToTrashRestoreCollection, true );
map.insert( StandardActionManager::MoveToTrashRestoreItem, false );
+ map.insert( StandardActionManager::SynchronizeCollectionTree, false );
QTest::newRow( "structural folder collection selected" ) << collectionList << map;
}
@@ -548,6 +557,8 @@ class ActionStateManagerTest : public QObject
map.insert( StandardActionManager::RestoreItemsFromTrash, false );
map.insert( StandardActionManager::MoveToTrashRestoreCollection, false );
map.insert( StandardActionManager::MoveToTrashRestoreItem, false );
+ map.insert( StandardActionManager::MoveToTrashRestoreItem, false );
+ map.insert( StandardActionManager::SynchronizeCollectionTree, false );
QTest::newRow( "root collection and writable resource collection selected" ) << collectionList << map;
}
@@ -566,7 +577,7 @@ class ActionStateManagerTest : public QObject
QHashIterator<StandardActionManager::Type, bool> it( stateMap );
while ( it.hasNext() ) {
it.next();
- //qDebug() << it.key();
+ qDebug() << it.key();
QVERIFY( mStateMap.contains( it.key() ) );
QCOMPARE( it.value(), mStateMap.value( it.key() ) );
}
commit 5bdcb266a5ea99715157d443aadb822f2ae9e483
Author: Christian Mollekopf <chrigi_1 at fastmail.fm>
Date: Thu Aug 28 11:52:48 2014 +0200
Relations support
diff --git a/akonadi/CMakeLists.txt b/akonadi/CMakeLists.txt
index 5b748c3..7f2417f 100644
--- a/akonadi/CMakeLists.txt
+++ b/akonadi/CMakeLists.txt
@@ -191,6 +191,10 @@ set( akonadikde_LIB_SRC
transportresourcebase.cpp
typepluginloader.cpp
attributeentity.cpp
+ relation.cpp
+ relationcreatejob.cpp
+ relationdeletejob.cpp
+ relationfetchjob.cpp
tag.cpp
tagmodel.cpp
tagmodel_p.cpp
@@ -412,6 +416,9 @@ install( FILES
specialcollectionsdiscoveryjob.h
standardactionmanager.h
statisticsproxymodel.h
+ relation.h
+ relationcreatejob.h
+ relationdeletejob.h
tagattribute.h
tag.h
tagmodel.h
diff --git a/akonadi/item.cpp b/akonadi/item.cpp
index a1d024e..38681ba 100644
--- a/akonadi/item.cpp
+++ b/akonadi/item.cpp
@@ -267,6 +267,12 @@ Tag::List Item::tags() const
return d->mTags;
}
+Relation::List Item::relations() const
+{
+ Q_D(const Item);
+ return d->mRelations;
+}
+
QSet<QByteArray> Item::loadedPayloadParts() const
{
return ItemSerializer::parts(*this);
diff --git a/akonadi/item.h b/akonadi/item.h
index 73c8225..c7a87a3 100644
--- a/akonadi/item.h
+++ b/akonadi/item.h
@@ -26,6 +26,7 @@
#include <akonadi/entity.h>
#include <akonadi/exception.h>
#include <akonadi/tag.h>
+#include <akonadi/relation.h>
#include <akonadi/collection.h>
#include "itempayloadinternals_p.h"
@@ -229,6 +230,13 @@ public:
void clearTags();
/**
+ * Returns all relations of this item.
+ * @since 4.15
+ * @see RelationCreateJob, RelationDeleteJob to modify relations
+ */
+ Relation::List relations() const;
+
+ /**
* Sets the payload based on the canonical representation normally
* used for data of this mime type.
*
diff --git a/akonadi/item_p.h b/akonadi/item_p.h
index d05ca44..8a2ea4c 100644
--- a/akonadi/item_p.h
+++ b/akonadi/item_p.h
@@ -414,6 +414,7 @@ public:
int mRevision;
Item::Flags mFlags;
Tag::List mTags;
+ Relation::List mRelations;
Entity::Id mCollectionId;
Collection::List mVirtualReferences;
qint64 mSize;
diff --git a/akonadi/itemfetchscope.cpp b/akonadi/itemfetchscope.cpp
index 3baaf1a..c142c5f 100644
--- a/akonadi/itemfetchscope.cpp
+++ b/akonadi/itemfetchscope.cpp
@@ -218,3 +218,13 @@ bool ItemFetchScope::fetchVirtualReferences() const
{
return d->mFetchVRefs;
}
+
+void ItemFetchScope::setFetchRelations(bool fetchRelations)
+{
+ d->mFetchRelations = fetchRelations;
+}
+
+bool ItemFetchScope::fetchRelations() const
+{
+ return d->mFetchRelations;
+}
diff --git a/akonadi/itemfetchscope.h b/akonadi/itemfetchscope.h
index bb01461..32ef450 100644
--- a/akonadi/itemfetchscope.h
+++ b/akonadi/itemfetchscope.h
@@ -405,6 +405,24 @@ public:
*/
bool fetchVirtualReferences() const;
+ /**
+ * Fetch relations for items.
+ *
+ * The default is @c false.
+ *
+ * @param fetchTags whether or not to load relations.
+ * @since 4.15
+ */
+ void setFetchRelations(bool fetchRelations);
+
+ /**
+ * Returns whether relations should be retrieved.
+ *
+ * @see setFetchRelations()
+ * @since 4.15
+ */
+ bool fetchRelations() const;
+
private:
//@cond PRIVATE
QSharedDataPointer<ItemFetchScopePrivate> d;
diff --git a/akonadi/itemfetchscope_p.h b/akonadi/itemfetchscope_p.h
index 3dda8bf..a7eff17 100644
--- a/akonadi/itemfetchscope_p.h
+++ b/akonadi/itemfetchscope_p.h
@@ -46,6 +46,7 @@ public:
, mFetchGid(false)
, mFetchTags(false)
, mFetchVRefs(false)
+ , mFetchRelations(false)
{
mTagFetchScope.setFetchIdOnly(true);
}
@@ -68,6 +69,7 @@ public:
mFetchTags = other.mFetchTags;
mTagFetchScope = other.mTagFetchScope;
mFetchVRefs = other.mFetchVRefs;
+ mFetchRelations = other.mFetchRelations;
}
public:
@@ -86,6 +88,7 @@ public:
bool mFetchTags;
TagFetchScope mTagFetchScope;
bool mFetchVRefs;
+ bool mFetchRelations;
};
}
diff --git a/akonadi/monitor.h b/akonadi/monitor.h
index 2fb6bc2..e737a42 100644
--- a/akonadi/monitor.h
+++ b/akonadi/monitor.h
@@ -23,6 +23,7 @@
#include <akonadi/tag.h>
#include <akonadi/collection.h>
#include <akonadi/item.h>
+#include <akonadi/relation.h>
#include <QtCore/QObject>
@@ -82,7 +83,8 @@ public:
*/
Collections = 1,
Items,
- Tags
+ Tags,
+ Relations
};
/**
@@ -427,6 +429,17 @@ Q_SIGNALS:
const QSet<Akonadi::Tag> &removedTags);
/**
+ * This signal is emitted if relations of monitored items have changed.
+ *
+ * @param items Items that were changed
+ * @param addedRelations Relations that have been added to each item in @p items.
+ * @param removedRelations Relations that have been removed from each item in @p items
+ * @since 4.15
+ */
+ void itemsRelationsChanged(const Akonadi::Item::List &items, const Akonadi::Relation::List &addedRelations,
+ const Akonadi::Relation::List &removedRelations);
+
+ /**
* This signal is emitted if a monitored item has been moved between two collections
*
* @param item The moved item.
@@ -617,6 +630,28 @@ Q_SIGNALS:
void tagRemoved(const Akonadi::Tag &tag);
/**
+ * This signal is emitted if a relation has been added to Akonadi storage.
+ *
+ * The monitor will also emit itemRelationsChanged() signal for all monitored items
+ * hat are affected by @p relation.
+ *
+ * @param relation The added relation
+ * @since 4.13
+ */
+ void relationAdded(const Akonadi::Relation &relation);
+
+ /**
+ * This signal is emitted if a monitored relation is removed from the server storage.
+ *
+ * The monitor will also emit itemRelationsChanged() signal for all monitored items
+ * that were affected by @p relation.
+ *
+ * @param relation The removed relation.
+ * @since 4.13
+ */
+ void relationRemoved(const Akonadi::Relation &relation);
+
+ /**
* This signal is emitted if the Monitor starts or stops monitoring @p collection explicitly.
* @param collection The collection
* @param monitored Whether the collection is now being monitored or not.
diff --git a/akonadi/monitor_p.cpp b/akonadi/monitor_p.cpp
index b514efd..c439611 100644
--- a/akonadi/monitor_p.cpp
+++ b/akonadi/monitor_p.cpp
@@ -232,6 +232,11 @@ void MonitorPrivate::checkBatchSupport(const NotificationMessageV3 &msg, bool &n
batchSupported = true;
needsSplit = false;
return;
+ case NotificationMessageV2::ModifyRelations:
+ // Relations were added after batch notifications, so they are always supported
+ batchSupported = true;
+ needsSplit = false;
+ return;
case NotificationMessageV2::Move:
needsSplit = isBatch && q_ptr->receivers(SIGNAL(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection))) > 0;
batchSupported = q_ptr->receivers(SIGNAL(itemsMoved(Akonadi::Item::List,Akonadi::Collection,Akonadi::Collection))) > 0;
@@ -260,6 +265,9 @@ void MonitorPrivate::checkBatchSupport(const NotificationMessageV3 &msg, bool &n
} else if (msg.type() == NotificationMessageV2::Tags) {
needsSplit = isBatch;
batchSupported = false;
+ } else if (msg.type() == NotificationMessageV2::Relations) {
+ needsSplit = isBatch;
+ batchSupported = false;
}
}
@@ -303,7 +311,7 @@ bool MonitorPrivate::acceptNotification(const Akonadi::NotificationMessageV3 &ms
return false;
}
- if (msg.entities().count() == 0) {
+ if (msg.entities().count() == 0 && msg.type() != NotificationMessageV2::Relations) {
return false;
}
@@ -378,6 +386,8 @@ bool MonitorPrivate::acceptNotification(const Akonadi::NotificationMessageV3 &ms
return false;
}
return true;
+ case NotificationMessageV2::Relations:
+ return true;
}
Q_ASSERT(false);
return false;
@@ -418,6 +428,9 @@ bool MonitorPrivate::ensureDataAvailable(const NotificationMessageV3 &msg)
}
return true;
}
+ if (msg.type() == NotificationMessageV2::Relations) {
+ return true;
+ }
bool allCached = true;
if (fetchCollection) {
@@ -490,6 +503,22 @@ bool MonitorPrivate::emitNotification(const NotificationMessageV3 &msg)
//In case of a Remove notification this will return a list of invalid entities (we'll deal later with them)
const Tag::List tags = tagCache->retrieve(msg.uids());
someoneWasListening = emitTagsNotification(msg, tags);
+ } else if (msg.type() == NotificationMessageV2::Relations) {
+ Relation rel;
+ Q_FOREACH (const QByteArray & part, msg.itemParts()) {
+ QList<QByteArray> splitPart = part.split(' ');
+ Q_ASSERT(splitPart.size() == 2);
+ if (splitPart.first() == "LEFT") {
+ rel.setLeft(Akonadi::Item(splitPart.at(1).toLongLong()));
+ } else if (splitPart.first() == "RIGHT") {
+ rel.setRight(Akonadi::Item(splitPart.at(1).toLongLong()));
+ } else if (splitPart.first() == "TYPE") {
+ rel.setType(splitPart.at(1));
+ } else if (splitPart.first() == "RID") {
+ rel.setRemoteId(splitPart.at(1));
+ }
+ }
+ someoneWasListening = emitRelationsNotification(msg, Relation::List() << rel);
} else {
const Collection parent = collectionCache->retrieve(msg.parentCollection());
Collection destParent;
@@ -741,8 +770,23 @@ void MonitorPrivate::dispatchNotifications()
}
}
-bool MonitorPrivate::emitItemsNotification(const NotificationMessageV3 &msg, const Item::List &items, const Collection &collection, const Collection &collectionDest)
+static Relation::List extractRelations(QSet<QByteArray> &flags)
{
+ Relation::List relations;
+ Q_FOREACH (const QByteArray &flag, flags) {
+ if (flag.startsWith("RELATION")) {
+ flags.remove(flag);
+ const QList<QByteArray> parts = flag.split(' ');
+ Q_ASSERT(parts.size() == 4);
+ relations << Relation(parts[1], Item(parts[2].toLongLong()), Item(parts[3].toLongLong()));
+ }
+ }
+ return relations;
+}
+
+bool MonitorPrivate::emitItemsNotification(const NotificationMessageV3 &msg_, const Item::List &items, const Collection &collection, const Collection &collectionDest)
+{
+ NotificationMessageV3 msg = msg_;
Q_ASSERT(msg.type() == NotificationMessageV2::Items);
Collection col = collection;
Collection colDest = collectionDest;
@@ -758,6 +802,17 @@ bool MonitorPrivate::emitItemsNotification(const NotificationMessageV3 &msg, con
}
}
+ Relation::List addedRelations, removedRelations;
+ if (msg.operation() == NotificationMessageV2::ModifyRelations) {
+ QSet<QByteArray> addedFlags = msg.addedFlags();
+ addedRelations = extractRelations(addedFlags);
+ msg.setAddedFlags(addedFlags);
+
+ QSet<QByteArray> removedFlags = msg.removedFlags();
+ removedRelations = extractRelations(removedFlags);
+ msg.setRemovedFlags(removedFlags);
+ }
+
Tag::List addedTags, removedTags;
if (msg.operation() == NotificationMessageV2::ModifyTags) {
addedTags = tagCache->retrieve(msg.addedTags().toList());
@@ -895,6 +950,12 @@ bool MonitorPrivate::emitItemsNotification(const NotificationMessageV3 &msg, con
return true;
}
return false;
+ case NotificationMessageV2::ModifyRelations:
+ if (q_ptr->receivers(SIGNAL(itemsRelationsChanged(Akonadi::Item::List,QSet<Akonadi::Relation>,QSet<Akonadi::Relation>))) > 0) {
+ emit q_ptr->itemsRelationsChanged(its, addedRelations, removedRelations);
+ return true;
+ }
+ return false;
default:
kDebug() << "Unknown operation type" << msg.operation() << "in item change notification";
}
@@ -1026,6 +1087,34 @@ bool MonitorPrivate::emitTagsNotification(const NotificationMessageV3 &msg, cons
return false;
}
+bool MonitorPrivate::emitRelationsNotification(const NotificationMessageV3 &msg, const Relation::List &relations)
+{
+ Q_ASSERT(msg.type() == NotificationMessageV2::Relations);
+
+ switch (msg.operation()) {
+ case NotificationMessageV2::Add:
+ if (q_ptr->receivers(SIGNAL(relationAdded(Akonadi::Relation))) == 0) {
+ return false;
+ }
+ Q_FOREACH (const Relation &relation, relations) {
+ Q_EMIT q_ptr->relationAdded(relation);
+ }
+ return true;
+ case NotificationMessageV2::Remove:
+ if (q_ptr->receivers(SIGNAL(relationRemoved(Akonadi::Relation))) == 0) {
+ return false;
+ }
+ Q_FOREACH (const Relation &relation, relations) {
+ Q_EMIT q_ptr->relationRemoved(relation);
+ }
+ return true;
+ default:
+ kDebug() << "Unknown operation type" << msg.operation() << "in tag change notification";
+ }
+
+ return false;
+}
+
void MonitorPrivate::invalidateCaches(const NotificationMessageV3 &msg)
{
// remove invalidates
diff --git a/akonadi/monitor_p.h b/akonadi/monitor_p.h
index fb9f4d2..03e1aee 100644
--- a/akonadi/monitor_p.h
+++ b/akonadi/monitor_p.h
@@ -158,6 +158,8 @@ public:
bool emitTagsNotification(const NotificationMessageV3 &msg, const Tag::List &tags);
+ bool emitRelationsNotification(const NotificationMessageV3 &msg, const Relation::List &relations);
+
void serverStateChanged(Akonadi::ServerManager::State state);
/**
diff --git a/akonadi/protocolhelper.cpp b/akonadi/protocolhelper.cpp
index 60e2440..c0027c7 100644
--- a/akonadi/protocolhelper.cpp
+++ b/akonadi/protocolhelper.cpp
@@ -22,6 +22,7 @@
#include "attributefactory.h"
#include "collectionstatistics.h"
#include "entity_p.h"
+#include "item_p.h"
#include "exception.h"
#include "itemserializer_p.h"
#include "itemserializerplugin.h"
@@ -109,7 +110,6 @@ void ProtocolHelper::parseAncestors( const QByteArray &data, Entity *entity, int
static const Collection::Id rootCollectionId = Collection::root().id();
QVarLengthArray<QByteArray, 16> ancestors;
- // QVarLengthArray<QByteArray, 16> parentIds;
QList<QByteArray> parentIds;
ImapParser::parseParenthesizedList( data, ancestors );
@@ -465,6 +465,8 @@ QByteArray ProtocolHelper::itemFetchScopeToByteArray( const ItemFetchScope &fetc
command += " VIRTREF";
if ( fetchScope.fetchModificationTime() )
command += " DATETIME";
+ if ( fetchScope.fetchRelations() )
+ command += " RELATIONS";
foreach ( const QByteArray &part, fetchScope.payloadParts() )
command += ' ' + ProtocolHelper::encodePartIdentifier( ProtocolHelper::PartPayload, part );
foreach ( const QByteArray &part, fetchScope.attributes() )
@@ -586,6 +588,18 @@ void ProtocolHelper::parseItemFetchResult( const QList<QByteArray> &lineTokens,
}
}
item.setTags( tags );
+ } else if ( key == "RELATIONS" ) {
+ Relation::List relations;
+ QList<QByteArray> data;
+ ImapParser::parseParenthesizedList( lineTokens[i + 1], data );
+ Q_FOREACH (const QByteArray &d, data) {
+ QList<QByteArray> parts;
+ ImapParser::parseParenthesizedList( d, parts );
+ Relation relation;
+ parseRelationFetchResult(parts, relation);
+ relations << relation;
+ }
+ item.d_func()->mRelations = relations;
} else if ( key == "VIRTREF" ) {
ImapSet set;
ImapParser::parseSequenceSet( lineTokens[i + 1], set );
@@ -695,6 +709,26 @@ void ProtocolHelper::parseTagFetchResult( const QList<QByteArray> &lineTokens, T
}
}
+void ProtocolHelper::parseRelationFetchResult( const QList<QByteArray> &lineTokens, Relation &relation )
+{
+ for (int i = 0; i < lineTokens.count() - 1; i += 2) {
+ const QByteArray key = lineTokens.value(i);
+ const QByteArray value = lineTokens.value(i + 1);
+
+ if (key == "LEFT") {
+ relation.setLeft(Akonadi::Item(value.toLongLong()));
+ } else if (key == "RIGHT") {
+ relation.setRight(Akonadi::Item(value.toLongLong()));
+ } else if (key == "REMOTEID") {
+ relation.setRemoteId(value);
+ } else if ( key == "TYPE" ) {
+ relation.setType(value);
+ } else {
+ kWarning() << "Unknown relation attribute " << key;
+ }
+ }
+}
+
QString ProtocolHelper::akonadiStoragePath()
{
QString fullRelPath = QLatin1String("akonadi");
diff --git a/akonadi/protocolhelper_p.h b/akonadi/protocolhelper_p.h
index f3612f6..150965f 100644
--- a/akonadi/protocolhelper_p.h
+++ b/akonadi/protocolhelper_p.h
@@ -234,6 +234,7 @@ public:
*/
static void parseItemFetchResult(const QList<QByteArray> &lineTokens, Item &item, ProtocolHelperValuePool *valuePool = 0);
static void parseTagFetchResult(const QList<QByteArray> &lineTokens, Tag &tag);
+ static void parseRelationFetchResult(const QList<QByteArray> &lineTokens, Relation &tag);
static QString akonadiStoragePath();
static QString absolutePayloadFilePath(const QString &fileName);
diff --git a/akonadi/relation.cpp b/akonadi/relation.cpp
new file mode 100644
index 0000000..566b4f3
--- /dev/null
+++ b/akonadi/relation.cpp
@@ -0,0 +1,136 @@
+/*
+ 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 "relation.h"
+
+#include <akonadi/item.h>
+
+using namespace Akonadi;
+
+const char *Akonadi::Relation::GENERIC = "GENERIC";
+
+struct Relation::Private {
+ Item left;
+ Item right;
+ QByteArray type;
+ QByteArray remoteId;
+};
+
+Relation::Relation()
+ : d(new Private)
+{
+
+}
+
+Relation::Relation(const QByteArray &type, const Item &left, const Item &right)
+ : d(new Private)
+{
+ d->left = left;
+ d->right = right;
+ d->type = type;
+}
+
+Relation::Relation(const Relation &other)
+ : d(new Private)
+{
+ operator=(other);
+}
+
+Relation::~Relation()
+{
+}
+
+Relation &Relation::operator=(const Relation &other)
+{
+ d->left = other.d->left;
+ d->right = other.d->right;
+ d->type = other.d->type;
+ return *this;
+}
+
+bool Relation::operator==(const Relation &other) const
+{
+ if (isValid() && other.isValid()) {
+ return d->left == other.d->left
+ && d->right == other.d->right
+ && d->type == other.d->type;
+ }
+ return false;
+}
+
+bool Relation::operator!=(const Relation &other) const
+{
+ return !operator==(other);
+}
+
+void Relation::setLeft(const Item &left)
+{
+ d->left = left;
+}
+
+Item Relation::left() const
+{
+ return d->left;
+}
+
+void Relation::setRight(const Item &right)
+{
+ d->right = right;
+}
+
+Item Relation::right() const
+{
+ return d->right;
+}
+
+void Relation::setType(const QByteArray &type) const
+{
+ d->type = type;
+}
+
+QByteArray Relation::type() const
+{
+ return d->type;
+}
+
+void Relation::setRemoteId(const QByteArray &remoteId) const
+{
+ d->remoteId = remoteId;
+}
+
+QByteArray Relation::remoteId() const
+{
+ return d->remoteId;
+}
+
+bool Relation::isValid() const
+{
+ return (d->left.isValid() || !d->left.remoteId().isEmpty()) && (d->right.isValid() || !d->left.remoteId().isEmpty()) && !d->type.isEmpty();
+}
+
+uint qHash(const Relation &relation)
+{
+ return (3*qHash(relation.left())+qHash(relation.right())+qHash(relation.type()));
+}
+
+QDebug &operator<<(QDebug &debug, const Relation &relation)
+{
+ debug << "Akonadi::Relation( TYPE " << relation.type() << ", LEFT " << relation.left().id() << ", RIGHT " << relation.right().id() << ")";
+ return debug;
+}
diff --git a/akonadi/relation.h b/akonadi/relation.h
new file mode 100644
index 0000000..7d2a78a
--- /dev/null
+++ b/akonadi/relation.h
@@ -0,0 +1,133 @@
+/*
+ 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 AKONADI_RELATION_H
+#define AKONADI_RELATION_H
+
+#include "akonadi_export.h"
+
+namespace Akonadi {
+class Relation;
+}
+
+AKONADI_EXPORT unsigned int qHash(const Akonadi::Relation &);
+
+#include <QSharedPointer>
+#include <QByteArray>
+#include <QList>
+#include <QDebug>
+
+namespace Akonadi {
+class Item;
+
+/**
+ * An Akonadi Relation.
+ *
+ * A Relation object represents an relation between two Akonadi items.
+ *
+ * An example usecase could be a association of a note with an email. The note (that for instance contains personal notes for the email),
+ * can be stored independently but is easily retrieved by asking for relations the email.
+ *
+ * The relation type allows to distinguish various types of relations that could for instance be bidirectional or not.
+ *
+ * @since 4.15
+ */
+class AKONADI_EXPORT Relation
+{
+public:
+ typedef QList<Relation> List;
+
+ /**
+ * The GENERIC type represents a generic relation between two items.
+ */
+ static const char *GENERIC;
+
+ /**
+ * Creates an invalid relation.
+ */
+ Relation();
+
+ /**
+ * Creates a relation
+ */
+ explicit Relation(const QByteArray &type, const Item &left, const Item &right);
+
+ Relation(const Relation &other);
+ ~Relation();
+
+ Relation &operator=(const Relation &);
+ bool operator==(const Relation &) const;
+ bool operator!=(const Relation &) const;
+
+ /**
+ * Sets the @p item of the left side of the relation.
+ */
+ void setLeft(const Item &item);
+
+ /**
+ * Returns the identifier of the left side of the relation.
+ */
+ Item left() const;
+
+ /**
+ * Sets the @p item of the right side of the relation.
+ */
+ void setRight(const Akonadi::Item &item);
+
+ /**
+ * Returns the identifier of the right side of the relation.
+ */
+ Item right() const;
+
+ /**
+ * Sets the type of the relation.
+ */
+ void setType(const QByteArray &type) const;
+
+ /**
+ * Returns the type of the relation.
+ */
+ QByteArray type() const;
+
+ /**
+ * Sets the type of the relation.
+ */
+ void setRemoteId(const QByteArray &type) const;
+
+ /**
+ * Returns the type of the relation.
+ */
+ QByteArray remoteId() const;
+
+ bool isValid() const;
+
+private:
+ class Private;
+ QSharedPointer<Private> d;
+};
+
+}
+
+AKONADI_EXPORT QDebug &operator<<(QDebug &debug, const Akonadi::Relation &tag);
+
+Q_DECLARE_METATYPE(Akonadi::Relation)
+Q_DECLARE_METATYPE(Akonadi::Relation::List)
+Q_DECLARE_METATYPE(QSet<Akonadi::Relation>)
+
+#endif
diff --git a/akonadi/relationcreatejob.cpp b/akonadi/relationcreatejob.cpp
new file mode 100644
index 0000000..4cc8a9a
--- /dev/null
+++ b/akonadi/relationcreatejob.cpp
@@ -0,0 +1,86 @@
+/*
+ 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 "relationcreatejob.h"
+#include "job_p.h"
+#include "relation.h"
+#include "protocolhelper_p.h"
+#include <KLocalizedString>
+
+using namespace Akonadi;
+
+struct Akonadi::RelationCreateJobPrivate : public JobPrivate
+{
+ RelationCreateJobPrivate(RelationCreateJob *parent)
+ : JobPrivate(parent)
+ {
+ }
+
+ Relation mRelation;
+};
+
+RelationCreateJob::RelationCreateJob(const Akonadi::Relation &relation, QObject *parent)
+ : Job(new RelationCreateJobPrivate(this), parent)
+{
+ Q_D(RelationCreateJob);
+ d->mRelation = relation;
+}
+
+void RelationCreateJob::doStart()
+{
+ Q_D(RelationCreateJob);
+
+ if (!d->mRelation.isValid()) {
+ kWarning() << "The relation is invalid";
+ setError(Job::Unknown);
+ setErrorText(i18n("Failed to create relation."));
+ emitResult();
+ return;
+ }
+
+ QByteArray command = d->newTag() + " UID RELATIONSTORE ";
+
+ QList<QByteArray> list;
+ list << "LEFT";
+ list << QByteArray::number(d->mRelation.left().id());
+ list << "RIGHT";
+ list << QByteArray::number(d->mRelation.right().id());
+ list << "TYPE";
+ list << ImapParser::quote(d->mRelation.type());
+ if (!d->mRelation.remoteId().isEmpty()) {
+ list << "REMOTEID";
+ list << d->mRelation.remoteId();
+ }
+
+ command += ImapParser::join(list, " ") + "\n";
+
+ d->writeData(command);
+}
+
+void RelationCreateJob::doHandleResponse(const QByteArray &tag, const QByteArray &data)
+{
+ Q_D(RelationCreateJob);
+ kWarning() << "Unhandled response: " << tag << data;
+}
+
+Relation RelationCreateJob::relation() const
+{
+ Q_D(const RelationCreateJob);
+ return d->mRelation;
+}
diff --git a/akonadi/relationcreatejob.h b/akonadi/relationcreatejob.h
new file mode 100644
index 0000000..5feac15
--- /dev/null
+++ b/akonadi/relationcreatejob.h
@@ -0,0 +1,62 @@
+/*
+ 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 AKONADI_RELATIONCREATEJOB_H
+#define AKONADI_RELATIONCREATEJOB_H
+
+#include <akonadi/job.h>
+
+namespace Akonadi {
+
+class Relation;
+class RelationCreateJobPrivate;
+
+/**
+ * @short Job that creates a new relation in the Akonadi storage.
+ * @since 4.15
+ */
+class AKONADI_EXPORT RelationCreateJob : public Job
+{
+ Q_OBJECT
+
+public:
+ /**
+ * Creates a new relation create job.
+ *
+ * @param relation The relation to create.
+ * @param parent The parent object.
+ */
+ explicit RelationCreateJob(const Relation &relation, QObject *parent = 0);
+
+ /**
+ * Returns the relation.
+ */
+ Relation relation() const;
+
+protected:
+ virtual void doStart();
+ virtual void doHandleResponse(const QByteArray &tag, const QByteArray &data);
+
+private:
+ Q_DECLARE_PRIVATE(RelationCreateJob)
+};
+
+}
+
+#endif
diff --git a/akonadi/relationdeletejob.cpp b/akonadi/relationdeletejob.cpp
new file mode 100644
index 0000000..8c65d41
--- /dev/null
+++ b/akonadi/relationdeletejob.cpp
@@ -0,0 +1,84 @@
+/*
+ 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 "relationdeletejob.h"
+#include "job_p.h"
+#include "relation.h"
+#include "protocolhelper_p.h"
+#include <KLocalizedString>
+
+using namespace Akonadi;
+
+struct Akonadi::RelationDeleteJobPrivate : public JobPrivate
+{
+ RelationDeleteJobPrivate(RelationDeleteJob *parent)
+ : JobPrivate(parent)
+ {
+ }
+
+ Relation mRelation;
+};
+
+RelationDeleteJob::RelationDeleteJob(const Akonadi::Relation &relation, QObject *parent)
+ : Job(new RelationDeleteJobPrivate(this), parent)
+{
+ Q_D(RelationDeleteJob);
+ d->mRelation = relation;
+}
+
+void RelationDeleteJob::doStart()
+{
+ Q_D(RelationDeleteJob);
+
+ if (!d->mRelation.isValid()) {
+ kWarning() << "The relation is invalid";
+ setError(Job::Unknown);
+ setErrorText(i18n("Failed to create relation."));
+ emitResult();
+ return;
+ }
+
+ QByteArray command = d->newTag() + " UID RELATIONREMOVE ";
+
+ QList<QByteArray> list;
+ list << "LEFT";
+ list << QByteArray::number(d->mRelation.left().id());
+ list << "RIGHT";
+ list << QByteArray::number(d->mRelation.right().id());
+ if (!d->mRelation.type().isEmpty()) {
+ list << "TYPE";
+ list << ImapParser::quote(d->mRelation.type());
+ }
+
+ command += ImapParser::join(list, " ") + "\n";
+
+ d->writeData(command);
+}
+
+void RelationDeleteJob::doHandleResponse(const QByteArray &tag, const QByteArray &data)
+{
+ Q_D(RelationDeleteJob);
+ kWarning() << "Unhandled response: " << tag << data;
+}
+
+Relation RelationDeleteJob::relation() const
+{
+ Q_D(const RelationDeleteJob);
+ return d->mRelation;
+}
diff --git a/akonadi/relationdeletejob.h b/akonadi/relationdeletejob.h
new file mode 100644
index 0000000..6dbd210
--- /dev/null
+++ b/akonadi/relationdeletejob.h
@@ -0,0 +1,62 @@
+/*
+ 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 AKONADI_RELATIONDELETEJOB_H
+#define AKONADI_RELATIONDELETEJOB_H
+
+#include <akonadi/job.h>
+
+namespace Akonadi {
+
+class Relation;
+class RelationDeleteJobPrivate;
+
+/**
+ * @short Job that deletes a relation in the Akonadi storage.
+ * @since 4.15
+ */
+class AKONADI_EXPORT RelationDeleteJob : public Job
+{
+ Q_OBJECT
+
+public:
+ /**
+ * Creates a new relation delete job.
+ *
+ * @param relation The relation to delete.
+ * @param parent The parent object.
+ */
+ explicit RelationDeleteJob(const Relation &relation, QObject *parent = 0);
+
+ /**
+ * Returns the relation.
+ */
+ Relation relation() const;
+
+protected:
+ virtual void doStart();
+ virtual void doHandleResponse(const QByteArray &tag, const QByteArray &data);
+
+private:
+ Q_DECLARE_PRIVATE(RelationDeleteJob)
+};
+
+}
+
+#endif
diff --git a/akonadi/relationfetchjob.cpp b/akonadi/relationfetchjob.cpp
new file mode 100644
index 0000000..e248afb
--- /dev/null
+++ b/akonadi/relationfetchjob.cpp
@@ -0,0 +1,164 @@
+/*
+ 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 "relationfetchjob.h"
+#include "job_p.h"
+#include "relation.h"
+#include "protocolhelper_p.h"
+#include <QTimer>
+
+using namespace Akonadi;
+
+class Akonadi::RelationFetchJobPrivate : public JobPrivate
+{
+public:
+ RelationFetchJobPrivate(RelationFetchJob *parent)
+ : JobPrivate(parent)
+ , mEmitTimer(0)
+ {
+ }
+
+ void init()
+ {
+ Q_Q(RelationFetchJob);
+ mEmitTimer = new QTimer(q);
+ mEmitTimer->setSingleShot(true);
+ mEmitTimer->setInterval(100);
+ q->connect(mEmitTimer, SIGNAL(timeout()), q, SLOT(timeout()));
+ }
+
+ void aboutToFinish()
+ {
+ timeout();
+ }
+
+ void timeout()
+ {
+ Q_Q(RelationFetchJob);
+ mEmitTimer->stop(); // in case we are called by result()
+ if (!mPendingRelations.isEmpty()) {
+ if (!q->error()) {
+ emit q->relationsReceived(mPendingRelations);
+ }
+ mPendingRelations.clear();
+ }
+ }
+
+ Q_DECLARE_PUBLIC(RelationFetchJob)
+
+ Relation::List mResultRelations;
+ Relation::List mPendingRelations; // relation pending for emitting itemsReceived()
+ QTimer *mEmitTimer;
+ QStringList mTypes;
+ QString mResource;
+ Relation mRequestedRelation;
+};
+
+RelationFetchJob::RelationFetchJob(const Relation &relation, QObject *parent)
+ : Job(new RelationFetchJobPrivate(this), parent)
+{
+ Q_D(RelationFetchJob);
+ d->init();
+ d->mRequestedRelation = relation;
+}
+
+RelationFetchJob::RelationFetchJob(const QStringList &types, QObject *parent)
+ : Job(new RelationFetchJobPrivate(this), parent)
+{
+ Q_D(RelationFetchJob);
+ d->init();
+ d->mTypes = types;
+}
+
+void RelationFetchJob::doStart()
+{
+ Q_D(RelationFetchJob);
+
+ QByteArray command = d->newTag();
+ command += " UID RELATIONFETCH ";
+
+ QList<QByteArray> filter;
+ if (!d->mResource.isEmpty()) {
+ filter.append("RESOURCE");
+ filter.append(d->mResource.toUtf8());
+ }
+ if (!d->mTypes.isEmpty()) {
+ filter.append("TYPE");
+ QList<QByteArray> types;
+ foreach (const QString &t, d->mTypes) {
+ types.append(t.toUtf8());
+ }
+ filter.append('(' + ImapParser::join(types, " ") + ')');
+ } else if (!d->mRequestedRelation.type().isEmpty()) {
+ filter.append("TYPE");
+ filter.append('(' + d->mRequestedRelation.type() + ')');
+ }
+ if (d->mRequestedRelation.left().id() >= 0) {
+ filter << "LEFT" << QByteArray::number(d->mRequestedRelation.left().id());
+ }
+ if (d->mRequestedRelation.right().id() >= 0) {
+ filter << "RIGHT" << QByteArray::number(d->mRequestedRelation.right().id());
+ }
+
+ command += "(" + ImapParser::join(filter, " ") + ")\n";
+
+ kDebug() << command;
+ d->writeData(command);
+}
+
+void RelationFetchJob::doHandleResponse(const QByteArray &tag, const QByteArray &data)
+{
+ Q_D(RelationFetchJob);
+
+ if (tag == "*") {
+ int begin = data.indexOf("RELATIONFETCH");
+ if (begin >= 0) {
+ // split fetch response into key/value pairs
+ QList<QByteArray> fetchResponse;
+ ImapParser::parseParenthesizedList(data, fetchResponse, begin + 8);
+
+ Relation rel;
+ ProtocolHelper::parseRelationFetchResult(fetchResponse, rel);
+
+ if (rel.isValid()) {
+ d->mResultRelations.append(rel);
+ d->mPendingRelations.append(rel);
+ if (!d->mEmitTimer->isActive()) {
+ d->mEmitTimer->start();
+ }
+ }
+ return;
+ }
+ }
+ kDebug() << "Unhandled response: " << tag << data;
+}
+
+Relation::List RelationFetchJob::relations() const
+{
+ Q_D(const RelationFetchJob);
+ return d->mResultRelations;
+}
+
+void RelationFetchJob::setResource(const QString &identifier)
+{
+ Q_D(RelationFetchJob);
+ d->mResource = identifier;
+}
+
+#include "moc_relationfetchjob.cpp"
diff --git a/akonadi/relationfetchjob.h b/akonadi/relationfetchjob.h
new file mode 100644
index 0000000..27c60ba
--- /dev/null
+++ b/akonadi/relationfetchjob.h
@@ -0,0 +1,79 @@
+/*
+ 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 AKONADI_RELATIONFETCHJOB_H
+#define AKONADI_RELATIONFETCHJOB_H
+
+#include <akonadi/job.h>
+#include <akonadi/relation.h>
+
+namespace Akonadi {
+
+class Relation;
+class RelationFetchJobPrivate;
+
+/**
+ * @short Job that to fetch relations from Akonadi storage.
+ * @since 4.15
+ */
+class AKONADI_EXPORT RelationFetchJob : public Job
+{
+ Q_OBJECT
+
+public:
+ /**
+ * Creates a new relation fetch job.
+ *
+ * @param relation The relation to fetch.
+ * @param parent The parent object.
+ */
+ explicit RelationFetchJob(const Relation &relation, QObject *parent = 0);
+
+ explicit RelationFetchJob(const QStringList &types, QObject *parent = 0);
+
+ void setResource(const QString &identifier);
+
+ /**
+ * Returns the relations.
+ */
+ Relation::List relations() const;
+
+Q_SIGNALS:
+ /**
+ * This signal is emitted whenever new relations have been fetched completely.
+ *
+ * @param relations The fetched relations.
+ */
+ void relationsReceived(const Akonadi::Relation::List &relations);
+
+protected:
+ virtual void doStart();
+ virtual void doHandleResponse(const QByteArray &tag, const QByteArray &data);
+
+private:
+ Q_DECLARE_PRIVATE(RelationFetchJob)
+
+ //@cond PRIVATE
+ Q_PRIVATE_SLOT(d_func(), void timeout())
+ //@endcond
+};
+
+}
+
+#endif
diff --git a/akonadi/tests/CMakeLists.txt b/akonadi/tests/CMakeLists.txt
index a67b635..7d66e4a 100644
--- a/akonadi/tests/CMakeLists.txt
+++ b/akonadi/tests/CMakeLists.txt
@@ -152,4 +152,5 @@ add_akonadi_isolated_test(lazypopulationtest.cpp)
add_akonadi_isolated_test(favoriteproxytest.cpp)
add_akonadi_isolated_test_advanced(itemsearchjobtest.cpp testsearchplugin/testsearchplugin.cpp "")
add_akonadi_isolated_test(tagtest.cpp)
+add_akonadi_isolated_test(relationtest.cpp)
add_akonadi_isolated_test(etmpopulationtest.cpp)
diff --git a/akonadi/tests/relationtest.cpp b/akonadi/tests/relationtest.cpp
new file mode 100644
index 0000000..5deeb6a
--- /dev/null
+++ b/akonadi/tests/relationtest.cpp
@@ -0,0 +1,165 @@
+/*
+ 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 <QObject>
+
+#include "test_utils.h"
+
+#include <akonadi/control.h>
+#include <akonadi/relationcreatejob.h>
+#include <akonadi/relationfetchjob.h>
+#include <akonadi/relationdeletejob.h>
+#include <tagmodifyjob.h>
+#include <resourceselectjob_p.h>
+#include <akonadi/qtest_akonadi.h>
+#include <akonadi/item.h>
+#include <akonadi/itemcreatejob.h>
+#include <akonadi/itemmodifyjob.h>
+#include <akonadi/itemfetchjob.h>
+#include <akonadi/itemfetchscope.h>
+#include <akonadi/monitor.h>
+#include <akonadi/attributefactory.h>
+
+using namespace Akonadi;
+
+class RelationTest : public QObject
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void initTestCase();
+
+ void testCreateFetch();
+ void testMonitor();
+};
+
+void RelationTest::initTestCase()
+{
+ AkonadiTest::checkTestIsIsolated();
+ AkonadiTest::setAllResourcesOffline();
+ qRegisterMetaType<Akonadi::Relation>();
+ qRegisterMetaType<QSet<Akonadi::Relation> >();
+ qRegisterMetaType<Akonadi::Item::List>();
+}
+
+void RelationTest::testCreateFetch()
+{
+ const Collection res3 = Collection( collectionIdFromPath( "res3" ) );
+ Item item1;
+ {
+ item1.setMimeType( "application/octet-stream" );
+ ItemCreateJob *append = new ItemCreateJob(item1, res3, this);
+ AKVERIFYEXEC(append);
+ item1 = append->item();
+ }
+ Item item2;
+ {
+ item2.setMimeType( "application/octet-stream" );
+ ItemCreateJob *append = new ItemCreateJob(item2, res3, this);
+ AKVERIFYEXEC(append);
+ item2 = append->item();
+ }
+
+ Relation rel(Relation::GENERIC, item1, item2);
+ RelationCreateJob *createjob = new RelationCreateJob(rel, this);
+ AKVERIFYEXEC(createjob);
+
+ //Test fetch & create
+ {
+ RelationFetchJob *fetchJob = new RelationFetchJob(QStringList(), this);
+ AKVERIFYEXEC(fetchJob);
+ QCOMPARE(fetchJob->relations().size(), 1);
+ QCOMPARE(fetchJob->relations().first().type(), QByteArray(Relation::GENERIC));
+ }
+
+ //Test item fetch
+ {
+ ItemFetchJob *fetchJob = new ItemFetchJob(item1);
+ fetchJob->fetchScope().setFetchRelations(true);
+ AKVERIFYEXEC(fetchJob);
+ QCOMPARE(fetchJob->items().first().relations().size(), 1);
+ }
+
+ {
+ ItemFetchJob *fetchJob = new ItemFetchJob(item2);
+ fetchJob->fetchScope().setFetchRelations(true);
+ AKVERIFYEXEC(fetchJob);
+ QCOMPARE(fetchJob->items().first().relations().size(), 1);
+ }
+
+ //Test delete
+ {
+ RelationDeleteJob *deleteJob = new RelationDeleteJob(rel, this);
+ AKVERIFYEXEC(deleteJob);
+
+ RelationFetchJob *fetchJob = new RelationFetchJob(QStringList(), this);
+ AKVERIFYEXEC(fetchJob);
+ QCOMPARE(fetchJob->relations().size(), 0);
+ }
+}
+
+void RelationTest::testMonitor()
+{
+ Akonadi::Monitor monitor;
+ monitor.setTypeMonitored(Akonadi::Monitor::Relations);
+
+ const Collection res3 = Collection( collectionIdFromPath( "res3" ) );
+ Item item1;
+ {
+ item1.setMimeType( "application/octet-stream" );
+ ItemCreateJob *append = new ItemCreateJob(item1, res3, this);
+ AKVERIFYEXEC(append);
+ item1 = append->item();
+ }
+ Item item2;
+ {
+ item2.setMimeType( "application/octet-stream" );
+ ItemCreateJob *append = new ItemCreateJob(item2, res3, this);
+ AKVERIFYEXEC(append);
+ item2 = append->item();
+ }
+
+ Relation rel(Relation::GENERIC, item1, item2);
+
+ {
+ QSignalSpy addedSpy(&monitor, SIGNAL(relationAdded(Akonadi::Relation)));
+ QVERIFY(addedSpy.isValid());
+
+ RelationCreateJob *createjob = new RelationCreateJob(rel, this);
+ AKVERIFYEXEC(createjob);
+
+ //We usually pick up signals from the previous tests as well (due to server-side notification caching)
+ QTRY_VERIFY(addedSpy.count() >= 1);
+ QTRY_COMPARE(addedSpy.last().first().value<Akonadi::Relation>(), rel);
+ }
+
+ {
+ QSignalSpy removedSpy(&monitor, SIGNAL(relationRemoved(Akonadi::Relation)));
+ QVERIFY(removedSpy.isValid());
+ RelationDeleteJob *deleteJob = new RelationDeleteJob(rel, this);
+ AKVERIFYEXEC(deleteJob);
+ QTRY_VERIFY(removedSpy.count() >= 1);
+ QTRY_COMPARE(removedSpy.last().first().value<Akonadi::Relation>(), rel);
+ }
+}
+
+
+#include "relationtest.moc"
+
+QTEST_AKONADIMAIN(RelationTest, NoGUI)
More information about the commits
mailing list