Commit 5535d6e5 authored by Daniel Vrátil's avatar Daniel Vrátil 🤖

Generate Protocol from an XML specification (ABI break)

Instead of maintaining 12k lines of hand-written protocol code, we
specify the protocol in an XML and use a custom-written generator
that generates the code for us.

It's not only much easier to modify the protocol - we only need to
change a single thing in the XML instead of touching several places
of the implementation - but it's also much safer, as there's less
risk of accidentally introducing a bug in the code.

The major difference between the original hand-written code and the
generated code is that we no longer use QSharedDataPointer and virtual
methods in the Private classes, but instead all members are directly
in the command clas with most getters and setters inlined. This means
that copying commands is quite costly, so we pass them around as
QSharedPointers or const references. This should give us a tiny little
bit more performance.
parent 5da66a91
......@@ -24,8 +24,8 @@ macro(add_akonadi_isolated_test_advanced _source _additionalsources _linklibrari
add_executable( ${_name} ${_test} ${_additionalsources})
ecm_mark_as_test(${_name})
target_link_libraries(${_name}
Qt5::Test Qt5::Gui Qt5::Widgets Qt5::Network KF5::KIOCore KF5::AkonadiCore KF5::DBusAddons
${_linklibraries})
Qt5::Test Qt5::Gui Qt5::Widgets Qt5::Network KF5::KIOCore KF5::AkonadiCore
KF5::AkonadiPrivate KF5::DBusAddons ${_linklibraries})
if (NOT DEFINED _testrunner)
if (${PROJECT_NAME} STREQUAL Akonadi AND TARGET akonaditest)
......
......@@ -133,7 +133,7 @@ public:
virtual ~FakeNotificationConnection()
{}
void emitNotify(const Akonadi::Protocol::ChangeNotification &ntf)
void emitNotify(const Akonadi::Protocol::ChangeNotificationPtr &ntf)
{
Q_EMIT commandReceived(3, ntf);
}
......
......@@ -38,7 +38,7 @@ public:
{
}
bool emitNotification(const Akonadi::Protocol::ChangeNotification &msg) Q_DECL_OVERRIDE {
bool emitNotification(const Akonadi::Protocol::ChangeNotificationPtr &msg) Q_DECL_OVERRIDE {
// TODO: Check/Log
return Akonadi::ChangeRecorderPrivate::emitNotification(msg);
}
......@@ -55,11 +55,11 @@ public:
return qobject_cast<FakeNotificationConnection *>(d_ptr->ntfConnection);
}
QQueue<Akonadi::Protocol::ChangeNotification> pendingNotifications() const
QQueue<Akonadi::Protocol::ChangeNotificationPtr> pendingNotifications() const
{
return d_ptr->pendingNotifications;
}
QQueue<Akonadi::Protocol::ChangeNotification> pipeline() const
QQueue<Akonadi::Protocol::ChangeNotificationPtr> pipeline() const
{
return d_ptr->pipeline;
}
......
......@@ -38,7 +38,7 @@ public:
{
}
bool emitNotification(const Akonadi::Protocol::ChangeNotification &msg) Q_DECL_OVERRIDE {
bool emitNotification(const Akonadi::Protocol::ChangeNotificationPtr &msg) Q_DECL_OVERRIDE {
// TODO: Check/Log
return Akonadi::MonitorPrivate::emitNotification(msg);
}
......@@ -55,11 +55,11 @@ public:
return qobject_cast<FakeNotificationConnection *>(d_ptr->ntfConnection);
}
QQueue<Akonadi::Protocol::ChangeNotification> pendingNotifications() const
QQueue<Akonadi::Protocol::ChangeNotificationPtr> pendingNotifications() const
{
return d_ptr->pendingNotifications;
}
QQueue<Akonadi::Protocol::ChangeNotification> pipeline() const
QQueue<Akonadi::Protocol::ChangeNotificationPtr> pipeline() const
{
return d_ptr->pipeline;
}
......
......@@ -349,6 +349,7 @@ void ItemAppendTest::testItemMerge()
QCOMPARE(merge->item().remoteRevision(), mergedItem.remoteRevision());
QCOMPARE(merge->item().payloadData(), mergedItem.payloadData());
QCOMPARE(merge->item().size(), mergedItem.size());
qDebug() << merge->item().flags() << mergedItem.flags();
QCOMPARE(merge->item().flags(), mergedItem.flags());
}
......
......@@ -99,15 +99,15 @@ void MonitorNotificationTest::testSingleMessage_impl(MonitorImpl *monitor, FakeC
monitor->setSession(m_fakeSession);
monitor->fetchCollection(true);
Protocol::ChangeNotification::List list;
Protocol::ChangeNotificationList list;
Collection parent(1);
Collection added(2);
Protocol::CollectionChangeNotification msg;
msg.setParentCollection(parent.id());
msg.setOperation(Protocol::CollectionChangeNotification::Add);
msg.setId(added.id());
auto msg = Protocol::CollectionChangeNotificationPtr::create();
msg->setParentCollection(parent.id());
msg->setOperation(Protocol::CollectionChangeNotification::Add);
msg->setId(added.id());
QHash<Collection::Id, Collection> data;
data.insert(parent.id(), parent);
......@@ -163,7 +163,7 @@ void MonitorNotificationTest::testFillPipeline_impl(MonitorImpl *monitor, FakeCo
monitor->setSession(m_fakeSession);
monitor->fetchCollection(true);
Protocol::ChangeNotification::List list;
Protocol::ChangeNotificationList list;
QHash<Collection::Id, Collection> data;
int i = 1;
......@@ -171,10 +171,10 @@ void MonitorNotificationTest::testFillPipeline_impl(MonitorImpl *monitor, FakeCo
Collection parent(i++);
Collection added(i++);
Protocol::CollectionChangeNotification msg;
msg.setParentCollection(parent.id());
msg.setOperation(Protocol::CollectionChangeNotification::Add);
msg.setId(added.id());
auto msg = Protocol::CollectionChangeNotificationPtr::create();
msg->setParentCollection(parent.id());
msg->setOperation(Protocol::CollectionChangeNotification::Add);
msg->setId(added.id());
data.insert(parent.id(), parent);
data.insert(added.id(), added);
......@@ -185,7 +185,7 @@ void MonitorNotificationTest::testFillPipeline_impl(MonitorImpl *monitor, FakeCo
QVERIFY(monitor->pipeline().isEmpty());
QVERIFY(monitor->pendingNotifications().isEmpty());
Q_FOREACH (const Protocol::ChangeNotification &ntf, list) {
Q_FOREACH (const Protocol::ChangeNotificationPtr &ntf, list) {
monitor->notificationConnection()->emitNotify(ntf);
}
......@@ -232,7 +232,7 @@ void MonitorNotificationTest::testMonitor_impl(MonitorImpl *monitor, FakeCollect
monitor->setSession(m_fakeSession);
monitor->fetchCollection(true);
Protocol::ChangeNotification::List list;
Protocol::ChangeNotificationList list;
Collection col2(2);
col2.setParentCollection(Collection::root());
......@@ -244,10 +244,10 @@ void MonitorNotificationTest::testMonitor_impl(MonitorImpl *monitor, FakeCollect
while (i < 8) {
Collection added(i++);
Protocol::CollectionChangeNotification msg;
msg.setParentCollection(i % 2 ? 2 : added.id() - 1);
msg.setOperation(Protocol::CollectionChangeNotification::Add);
msg.setId(added.id());
auto msg = Protocol::CollectionChangeNotificationPtr::create();
msg->setParentCollection(i % 2 ? 2 : added.id() - 1);
msg->setOperation(Protocol::CollectionChangeNotification::Add);
msg->setId(added.id());
list << msg;
}
......@@ -271,7 +271,7 @@ void MonitorNotificationTest::testMonitor_impl(MonitorImpl *monitor, FakeCollect
QVERIFY(monitor->pipeline().isEmpty());
QVERIFY(monitor->pendingNotifications().isEmpty());
Q_FOREACH (const Protocol::ChangeNotification &ntf, list) {
Q_FOREACH (const Protocol::ChangeNotificationPtr &ntf, list) {
monitor->notificationConnection()->emitNotify(ntf);
}
......
......@@ -24,9 +24,7 @@ using namespace Akonadi;
Q_DECLARE_METATYPE(Scope)
Q_DECLARE_METATYPE(QVector<Protocol::Ancestor>)
Q_DECLARE_METATYPE(Protocol::FetchCollectionsResponse)
Q_DECLARE_METATYPE(Protocol::FetchScope)
Q_DECLARE_METATYPE(Protocol::FetchTagsResponse)
class ProtocolHelperTest : public QObject
{
......@@ -257,7 +255,7 @@ private Q_SLOTS:
Protocol::FetchScope::RemoteRevision |
Protocol::FetchScope::MTime |
Protocol::FetchScope::IgnoreErrors);
fs.setAncestorDepth(Akonadi::Protocol::Ancestor::AllAncestors);
fs.setAncestorDepth(Protocol::FetchScope::AllAncestors);
QTest::newRow("full") << scope << fs;
}
......
......@@ -30,64 +30,73 @@ using namespace Akonadi::Protocol;
void NotificationMessageTest::testCompress()
{
ChangeNotification::List list;
ChangeNotificationList list;
CollectionChangeNotification msg;
msg.setOperation(CollectionChangeNotification::Add);
CollectionChangeNotification::appendAndCompress(list, msg);
QVERIFY(CollectionChangeNotification::appendAndCompress(
list, CollectionChangeNotificationPtr::create(msg)));
QCOMPARE(list.count(), 1);
msg.setOperation(CollectionChangeNotification::Modify);
CollectionChangeNotification::appendAndCompress(list, msg);
QVERIFY(!CollectionChangeNotification::appendAndCompress(
list, CollectionChangeNotificationPtr::create(msg)));
QCOMPARE(list.count(), 1);
QCOMPARE(static_cast<CollectionChangeNotification&>(list.first()).operation(), CollectionChangeNotification::Add);
QCOMPARE(list.first().staticCast<CollectionChangeNotification>()->operation(), CollectionChangeNotification::Add);
msg.setOperation(CollectionChangeNotification::Remove);
CollectionChangeNotification::appendAndCompress(list, msg);
QVERIFY(CollectionChangeNotification::appendAndCompress(
list, CollectionChangeNotificationPtr::create(msg)));
QCOMPARE(list.count(), 2);
}
void NotificationMessageTest::testCompress2()
{
ChangeNotification::List list;
ChangeNotificationList list;
CollectionChangeNotification msg;
msg.setOperation(CollectionChangeNotification::Modify);
CollectionChangeNotification::appendAndCompress(list, msg);
QVERIFY(CollectionChangeNotification::appendAndCompress(
list, CollectionChangeNotificationPtr::create(msg)));
QCOMPARE(list.count(), 1);
msg.setOperation(CollectionChangeNotification::Remove);
CollectionChangeNotification::appendAndCompress(list, msg);
QVERIFY(CollectionChangeNotification::appendAndCompress(
list, CollectionChangeNotificationPtr::create(msg)));
QCOMPARE(list.count(), 2);
QCOMPARE(static_cast<CollectionChangeNotification&>(list.first()).operation(), CollectionChangeNotification::Modify);
QCOMPARE(static_cast<CollectionChangeNotification&>(list.last()).operation(), CollectionChangeNotification::Remove);
QCOMPARE(list.first().staticCast<CollectionChangeNotification>()->operation(), CollectionChangeNotification::Modify);
QCOMPARE(list.last().staticCast<CollectionChangeNotification>()->operation(), CollectionChangeNotification::Remove);
}
void NotificationMessageTest::testCompress3()
{
ChangeNotification::List list;
ChangeNotificationList list;
CollectionChangeNotification msg;
msg.setOperation(CollectionChangeNotification::Modify);
CollectionChangeNotification::appendAndCompress(list, msg);
QVERIFY(CollectionChangeNotification::appendAndCompress(
list, CollectionChangeNotificationPtr::create(msg)));
QCOMPARE(list.count(), 1);
CollectionChangeNotification::appendAndCompress(list, msg);
QVERIFY(!CollectionChangeNotification::appendAndCompress(
list, CollectionChangeNotificationPtr::create(msg)));
QCOMPARE(list.count(), 1);
}
void NotificationMessageTest::testPartModificationMerge()
{
ChangeNotification::List list;
ChangeNotificationList list;
CollectionChangeNotification msg;
msg.setOperation(CollectionChangeNotification::Modify);
msg.setChangedParts(QSet<QByteArray>() << "PART1");
CollectionChangeNotification::appendAndCompress(list, msg);
QVERIFY(CollectionChangeNotification::appendAndCompress(
list, CollectionChangeNotificationPtr::create(msg)));
QCOMPARE(list.count(), 1);
msg.setChangedParts(QSet<QByteArray>() << "PART2");
CollectionChangeNotification::appendAndCompress(list, msg);
QVERIFY(!CollectionChangeNotification::appendAndCompress(
list, CollectionChangeNotificationPtr::create(msg)));
QCOMPARE(list.count(), 1);
QCOMPARE(static_cast<CollectionChangeNotification&>(list.first()).changedParts(), (QSet<QByteArray>() << "PART1" << "PART2"));
QCOMPARE(list.first().staticCast<CollectionChangeNotification>()->changedParts(), (QSet<QByteArray>() << "PART1" << "PART2"));
}
This diff is collapsed.
......@@ -63,15 +63,15 @@ private Q_SLOTS:
private:
template<typename T>
typename std::enable_if<std::is_base_of<Akonadi::Protocol::Command, T>::value, T>::type
serializeAndDeserialize(const T &in)
typename std::enable_if<std::is_base_of<Akonadi::Protocol::Command, T>::value, QSharedPointer<T>>::type
serializeAndDeserialize(const QSharedPointer<T> &in)
{
QBuffer buf;
buf.open(QIODevice::ReadWrite);
Akonadi::Protocol::serialize(&buf, in);
buf.seek(0);
return T(Akonadi::Protocol::deserialize(&buf));
return Akonadi::Protocol::deserialize(&buf).staticCast<T>();
}
......
This diff is collapsed.
/*
Copyright (c) 2017 Daniel Vrátil <dvratil@kde.org>
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 "dbinitializer.h"
#include "storage/collectiontreecache.h"
#include "storage/selectquerybuilder.h"
#include "private/scope_p.h"
#include "aktest.h"
#include "fakeakonadiserver.h"
#include <QObject>
#include <QSignalSpy>
#include <QTest>
using namespace Akonadi;
using namespace Akonadi::Server;
class InspectableCollectionTreeCache : public CollectionTreeCache
{
Q_OBJECT
public:
InspectableCollectionTreeCache()
: CollectionTreeCache()
, mCachePopulated(0)
{}
bool waitForCachePopulated()
{
QSignalSpy spy(this, &InspectableCollectionTreeCache::cachePopulated);
return mCachePopulated == 1 || spy.wait(5000);
}
Q_SIGNALS:
void cachePopulated();
protected:
void init() Q_DECL_OVERRIDE
{
CollectionTreeCache::init();
mCachePopulated = 1;
Q_EMIT cachePopulated();
}
void quit() Q_DECL_OVERRIDE
{
}
private:
QAtomicInt mCachePopulated;
};
class CollectionTreeCacheTest : public QObject
{
Q_OBJECT
public:
CollectionTreeCacheTest()
{
try {
FakeAkonadiServer::instance()->init();
} catch (const FakeAkonadiServerException &e) {
qWarning() << "Server exception: " << e.what();
qFatal("Fake Akonadi Server failed to start up, aborting test");
}
}
~CollectionTreeCacheTest()
{
FakeAkonadiServer::instance()->quit();
}
private:
void populateDb(DbInitializer &db)
{
// ResA
// |- Col A1
// |- Col A2
// | |- Col A3
// | |- Col A7
// | |- Col A5
// | |- Col A8
// |- Col A6
// | |- Col A10
// |- Col A9
auto res = db.createResource("TestResource");
auto resA = db.createCollection("Res A", Collection());
auto colA1 = db.createCollection("Col A1", resA);
auto colA2 = db.createCollection("Col A2", resA);
auto colA3 = db.createCollection("Col A3", colA2);
auto colA5 = db.createCollection("Col A5", colA2);
auto colA6 = db.createCollection("Col A6", resA);
auto colA7 = db.createCollection("Col A7", colA2);
auto colA8 = db.createCollection("Col A8", colA7);
auto colA9 = db.createCollection("Col A9", resA);
auto colA10 = db.createCollection("Col A10", colA6);
// Move the collection to the final parent
colA5.setParent(colA7);
colA5.update();
}
private Q_SLOTS:
void populateTest()
{
DbInitializer db;
populateDb(db);
InspectableCollectionTreeCache treeCache;
QVERIFY(treeCache.waitForCachePopulated());
auto allCols = treeCache.retrieveCollections(Scope(), std::numeric_limits<int>::max(), 1);
SelectQueryBuilder<Collection> qb;
QVERIFY(qb.exec());
auto expCols = qb.result();
const auto sort = [](const Collection &l, const Collection &r) { return l.id() < r.id(); };
qSort(allCols.begin(), allCols.end(), sort);
qSort(expCols.begin(), expCols.end(), sort);
QCOMPARE(allCols.size(), expCols.size());
QCOMPARE(allCols, expCols);
}
};
AKTEST_FAKESERVER_MAIN(CollectionTreeCacheTest)
#include "collectiontreecachetest.moc"
This diff is collapsed.
......@@ -116,29 +116,29 @@ QByteArray DbInitializer::toByteArray(Collection::Tristate tristate)
return "DEFAULT";
}
Akonadi::Protocol::FetchCollectionsResponse DbInitializer::listResponse(const Collection &col,
bool ancestors,
bool mimetypes,
const QStringList &ancestorFetchScope)
Akonadi::Protocol::FetchCollectionsResponsePtr DbInitializer::listResponse(const Collection &col,
bool ancestors,
bool mimetypes,
const QStringList &ancestorFetchScope)
{
Akonadi::Protocol::FetchCollectionsResponse resp(col.id());
resp.setParentId(col.parentId());
resp.setName(col.name());
auto resp = Akonadi::Protocol::FetchCollectionsResponsePtr::create(col.id());
resp->setParentId(col.parentId());
resp->setName(col.name());
if (mimetypes) {
QStringList mts;
for (const Akonadi::Server::MimeType &mt : col.mimeTypes()) {
mts << mt.name();
}
resp.setMimeTypes(mts);
resp->setMimeTypes(mts);
}
resp.setRemoteId(col.remoteId());
resp.setRemoteRevision(col.remoteRevision());
resp.setResource(col.resource().name());
resp.setIsVirtual(col.isVirtual());
resp->setRemoteId(col.remoteId());
resp->setRemoteRevision(col.remoteRevision());
resp->setResource(col.resource().name());
resp->setIsVirtual(col.isVirtual());
Akonadi::Protocol::CachePolicy cp;
cp.setInherit(true);
cp.setLocalParts({ QLatin1String("ALL") });
resp.setCachePolicy(cp);
resp->setCachePolicy(cp);
if (ancestors) {
QVector<Akonadi::Protocol::Ancestor> ancs;
Collection parent = col.parent();
......@@ -163,19 +163,19 @@ Akonadi::Protocol::FetchCollectionsResponse DbInitializer::listResponse(const Co
}
// Root
ancs.push_back(Akonadi::Protocol::Ancestor(0));
resp.setAncestors(ancs);
resp->setAncestors(ancs);
}
resp.setReferenced(col.referenced());
resp.setEnabled(col.enabled());
resp.setDisplayPref(static_cast<Tristate>(col.displayPref()));
resp.setSyncPref(static_cast<Tristate>(col.syncPref()));
resp.setIndexPref(static_cast<Tristate>(col.indexPref()));
resp->setReferenced(col.referenced());
resp->setEnabled(col.enabled());
resp->setDisplayPref(static_cast<Tristate>(col.displayPref()));
resp->setSyncPref(static_cast<Tristate>(col.syncPref()));
resp->setIndexPref(static_cast<Tristate>(col.indexPref()));
Akonadi::Protocol::Attributes attrs;
Q_FOREACH(const CollectionAttribute &attr, col.attributes()) {
attrs.insert(attr.type(), attr.value());
}
resp.setAttributes(attrs);
resp->setAttributes(attrs);
return resp;
}
......
......@@ -33,10 +33,10 @@ public:
Akonadi::Server::Part createPart(qint64 pimitemId, const QByteArray &partname, const QByteArray &data);
QByteArray toByteArray(bool enabled);
QByteArray toByteArray(Akonadi::Server::Collection::Tristate tristate);
Akonadi::Protocol::FetchCollectionsResponse listResponse(const Akonadi::Server::Collection &col,
bool ancestors = false,
bool mimetypes = true,
const QStringList &ancestorFetchScope = QStringList());
Akonadi::Protocol::FetchCollectionsResponsePtr listResponse(const Akonadi::Server::Collection &col,
bool ancestors = false,
bool mimetypes = true,
const QStringList &ancestorFetchScope = QStringList());
Akonadi::Server::Collection collection(const char *name);
void cleanup();
......
......@@ -51,7 +51,7 @@ using namespace Akonadi::Server;
Q_DECLARE_METATYPE(Akonadi::Server::NotificationCollector*)
TestScenario TestScenario::create(qint64 tag, TestScenario::Action action,
const Protocol::Command &response)
const Protocol::CommandPtr &response)
{
TestScenario sc;
sc.action = action;
......@@ -70,15 +70,15 @@ TestScenario TestScenario::create(qint64 tag, TestScenario::Action action,
qint64 cmpTag;
os >> cmpTag;
Q_ASSERT(cmpTag == tag);
Protocol::Command cmpResp = Protocol::deserialize(os.device());
Protocol::CommandPtr cmpResp = Protocol::deserialize(os.device());
bool ok = false;
[cmpTag, tag, cmpResp, response, &ok]() {
QCOMPARE(cmpTag, tag);
QCOMPARE(cmpResp.type(), response.type());
QCOMPARE(cmpResp.isResponse(), response.isResponse());